diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..9f07da83 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1482 @@ +## 24.1.2 2024-12-05 +### PaymentSheet +* [Fixed] Fixed an issue where FlowController returned incorrect `PaymentOptionDisplayData` for Link card brand transactions. + +## 24.1.1 2024-12-02 +### PaymentSheet +* [Fixed] Fixed an animation glitch when dismissing PaymentSheet in React Native. +* [Fixed] Fixed an issue in Instant Bank Payments that occurred when using a connected account. + +## 24.1.0 2024-11-25 +### Payments +* [Added] Support for Crypto bindings. + +### PaymentSheet +* [Fixed] US Bank Account now shows the correct mandate when using the `instant_or_skip` verification method. + +## 24.0.2 2024-11-21 +### PaymentSheet +* [Fixed] A bug where PaymentSheet would cause layout issues when nested within certain navigation stacks. + +## 24.0.1 2024-11-18 +### PaymentSheet +* [Added] Instant Bank Payments are now available when using deferred intents. +* [Fixed] Fixed an issue with the vertical list with 3 or more saved payment methods where tapping outside the screen sometimes drops changes that were made (e.g. removal or update of PMs). +* [Fixed] Fixed an issue where the dialog when removing a co-branded card may show the incorrect card brand. +* [Fixed] Fixed issue preventing users to enter in 4 digit account numbers for AU Becs. + +## 24.0.0 2024-11-04 +### PaymentSheet +* [Changed] The default value of `PaymentSheet.Configuration.paymentMethodLayout` has changed from `.horizontal` to `.automatic`. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for more details. +* [Fixed] Fixed an animation glitch when dismissing PaymentSheet in React Native. +* [Fixed] Fixed an issue with FlowController in vertical layout where the payment method could incorrectly be preserved across a call to `update` when it's no longer valid. +* [Fixed] Fixed a potential deadlock when `paymentOption` is accessed from Swift concurrency. +* [Fixed] Fixed deferred intent validation to handle cloned payment methods ([#4195](https://github.com/stripe/stripe-ios/issues/4195) + +### Basic Integration +* [Removed] Basic Integration has been removed. [Please use Mobile Payment Element instead](https://docs.stripe.com/payments/mobile/migrating-to-mobile-payment-element-from-basic-integration). + +## 23.32.0 2024-10-21 +### PaymentSheet +* [Added] Added `PaymentSheet.Configuration.paymentMethodLayout`. Configure the layout of payment methods in the sheet using `paymentMethodLayout` to display them either horizontally, vertically, or let Stripe optimize the layout automatically. + +## 23.31.1 2024-10-08 +### PaymentSheet +* [Fixed] Fixed an issue where ISK was not correctly formatted as a zero-decimal currency when using PaymentSheet or Apple Pay. (Thanks [@Thithip](https://github.com/Thithip)!) +* [Fixed] Fixed an issue where US Bank Account forms would drop form field input when `FlowController.update` is called. + +## 23.31.0 2024-09-23 +### PaymentSheet +* [Added] The ability to customize the disabled colors of the primary button with `PaymentSheetAppearance.primaryButton.disabledBackgroundColor` and `PaymentSheetAppearance.primaryButton.disabledTextColor`. +* [Added] CVC Recollection is now in GA. For more information see our docs for [here](https://docs.stripe.com/payments/accept-a-payment?platform=ios#ios-cvc-recollection) for intent first integrations or [here](https://docs.stripe.com/payments/accept-a-payment-deferred?platform=ios&type=payment#ios-cvc-recollection) for deferred intent integrations. +* [Fixed] Fixed an issue where checkboxes were not visible when `appearance.colors.componentBorder` was transparent. + +### CardScan +* [Fixed] The 0.5x lens is now used when scanning cards, if available. (Thanks [@akhmedovgg](https://github.com/akhmedovgg)!) + +## 23.30.0 2024-09-09 +### PaymentSheet +* [Added] CustomerSessions is now in private beta. +* [Fixed] PaymentSheet now uses a border width of 1.5 instead of 0 when `PaymentSheet.Appearance.borderWidth' is 0. +* [Fixed] The 0.5x lens is now used when scanning cards, if available. (Thanks [@akhmedovgg](https://github.com/akhmedovgg)!) + +## 23.29.2 2024-08-19 +### PaymentSheet +* [Fixed] Avoid multiple calls to CVC Recollection callback for deferred intent integrations +* [Fixed] Fixed an issue in SwiftUI where setting `isPresented=false` wouldn't dismiss the sheet. + +## 23.29.1 2024-08-12 +### PaymentSheet +* [Fixed] Fixed an issue where signing up with Link and paying would vend an empty `STPPaymentMethod` object to an `IntentConfiguration` confirmHandler callback. +* [Fixed] Fixed PaymentSheet.FlowController returning unlocalized labels for certain payment methods e.g. "AfterPay ClearPay" instead of "Afterpay" or "Clearpay" depending on locale. +* [Added] `PaymentSheet.IntentConfiguration` now validates that its `amount` is non-zero. + +### PaymentsUI +* [Fixed] Fixed an issue where STPPaymentCardTextField wouldn't call its delegate `paymentCardTextFieldDidChange` method when the preferred card network changed. + +## 23.29.0 2024-08-05 +### PaymentSheet +* [Fixed] Fixed a scroll issue with native 3DS2 authentication screen when the keyboard appears. +* [Added] When a card is saved (ie you're using a PaymentIntent + setup_future_usage or SetupIntent), legal disclaimer text now appears below the form indicating the card can be charged for future payments. +* [Fixed] iOS 18 Compatibility with removing multiple saved payment methods +* [Fixed] Fixed an issue where the keyboard could focus on a hidden phone number field. +* [Added] Support for Sunbit (Private Beta) with PaymentIntents. +* [Added] Support for Billie (Private Beta) with PaymentIntents. +* [Fixed] Fixed an issue where saved payment method UI wouldn't respect `PaymentSheet.Configuration.style` when selected. +* [Added] Support for Satispay (Private Beta) with PaymentIntents. + +### Payments +* [Added] Support for Sunbit (Private Beta) bindings. +* [Added] Support for Billie (Private Beta) bindings. +* [Added] Support for Satispay (Private Beta) bindings. + +## 23.28.3 2024-09-03 +This release was made in error, and contains changes from 23.29.0, 23.29.1, and 23.29.2. + +## 23.28.1 2024-07-16 +### Payments +* [Fixed] Improved reliability when paying or setting up with Cash App Pay. +* [Fixed] Pass stripeAccount context when presenting PayWithLinkWebController for connected accounts + +## 23.28.0 2024-07-08 + +### Payments +* [Fixed] An issue where the correct card brand was not being displayed for card brand choice in STPPaymentOptionsViewController and STPPaymentContext. +* [Added] Adds coupon support to STPApplePayContext with a new `didChangeCouponCode` delegate method (h/t @JoeyLeeMEA). +* [Fixed] Fixed an issue where successful TWINT payments were sometimes incorrectly considered 'canceled'. + +### PaymentSheet +* [Fixed] Fixed an issue where certain cobranded cards showed a generic card icon instead of using the other card brand. +* [Fixed] Fixed an issue where amounts with currency=IDR were displayed as-is, instead of dropping the last two digits. +* [Fixed] Fixed an issue where some payment method images in the horizontal scrollview could briefly flash. + +## 23.27.6 2024-06-25 +### All +* [Fixed] Improved reliability when paying with Swish. + +## 23.27.5 2024-06-20 +### PaymentSheet +* [Fixed] An issue that was preventing users from completing checkout with SetupIntents and PaymentIntents using `setup_future_usage` for the following payment method types: Amazon Pay, Cash App Pay, PayPal, and Revolut Pay. + +## 23.27.4 2024-06-18 +### PaymentSheet +* [Fixed] Fixed an issue where when displaying an LPM with no input fields, the sheet would take up the entire height of the screen. + +## 23.27.3 2024-06-14 +### PaymentSheet +* [Fixed] Fixed an issue where changing the country of a phone number would not update the UI when the phone number's validity changed. +* [Changed] The "save this card" checkbox is now unchecked by default. To change this behavior, set your PaymentSheet.Configuration.savePaymentMethodOptInBehavior to `.requiresOptOut`. +* [Fixed] Fixed an issue where PaymentSheet would not present in the iOS 18 beta when using SwiftUI. +* [Fixed] Fixed an issue in PaymentSheet.FlowController that could lead to the CVC recollection form being shown on presentPaymentOptions() + +### CustomerSheet +* [Fixed] Fixed an issue where CustomerSheet would not present in the iOS 18 beta when using SwiftUI. + +### Payments +* [Added] Updated support for MobilePay bindings. +* [Changed] Some Payment Methods (including Klarna and PayPal) may now authenticate using ASWebAuthenticationSession, enabling these payment methods to share session storage across apps. +* [Fixed] Fixed printing spurious STPAssertionFailure warnings. + +## 23.27.2 2024-05-06 +### CardScan +* [Changed] ScannedCard to allow access for expiryMonth, expiryYear and name. + +### PaymentSheet +* [Added] Support for Multibanco with PaymentIntents. +* [Fixed] Fixed an issue where STPPaymentHandler sometimes reported errors using `unexpectedErrorCode` instead of a more specific error when customers fail a next action. +* [Changed] PaymentSheet displays Apple Pay as a button when there are saved payment methods and Link isn't available instead of within the list of saved payment methods. +* [Fixed] Expiration dates more than 50 years in the past (e.g. `95`) are now blocked. + +### Payments +* [Added] Support for Multibanco bindings. +* [Fixed] Expiration dates more than 50 years in the past (e.g. `95`) are now blocked. + +## 23.27.1 2024-04-22 +### Payments +* [Fixed] An issue where the PrivacyInfo.xcprivacy was not bundled with StripePayments when installing with Cocoapods. + +### Apple Pay +* [Changed] Apple Pay additionalEnabledApplePayNetworks are now in front of the supported network list. + +### PaymentsUI +* [Added] Added support for `onBehalfOf` to STPPaymentCardTextField and STPCardFormView. This parameter may be required when setting a connected account as the merchant of record for a payment. For more information, see the [Connect docs](https://docs.stripe.com/connect/charges#on_behalf_of). + +## 23.27.0 2024-04-08 +### Payments +* [Added] Support for Alma bindings. +* [Fixed] STPBankAccountCollector errors now use "STPBankAccountCollectorErrorDomain" instead of "STPPaymentHandlerErrorDomain". + +### All +* [Fixed] Fixed an issue with generating App Privacy reports. + +## 23.26.0 2024-03-25 +### PaymentSheet +* [Fixed] When confirming a SetupIntent with Link, "Set up" will be shown as the confirm button text instead of "Pay". + +### CustomerSheet +* [Fixed] Fixed an issue dismissing the sheet when Link is the default payment method. + +### Financial Connections +* [Fixed] Improved the UX of an edge case in Financial Connections authentication flow. + +### All +* Added a [Privacy Manifest](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files). + +## 23.25.1 2024-03-18 +### All +* Xcode 14 is [no longer supported by Apple](https://developer.apple.com/news/upcoming-requirements/). Please upgrade to Xcode 15 or later. + +### PaymentSheet +* [Fixed] A bug where `PaymentSheet.FlowController` was not respecting `PaymentSheet.Configuration.primaryButtonLabel`. +* [Added] Support for Klarna with SetupIntents and PaymentIntents with `setup_future_usage`. + +### Financial Connections +* [Changed] Updated the design of Financial Connections authentication flow. + +## 23.25.0 2024-03-11 +### CustomerSheet +* [Added] Added `paymentMethodTypes` in `CustomerAdapter` to control what payment methods are displayed. + +### PaymentSheet +* [Fixed] The rotating [card brand view](https://docs.stripe.com/co-badged-cards-compliance) is now shown when card brand choice is enabled if the card number is empty. + +## 23.24.1 2024-03-05 +### PaymentSheet +* [Fixed] Fixed an assertionFailure that happens when using FlowController and switching between saved payment methods + +## 23.24.0 2024-03-04 +### PaymentSheet +* [Added] Added support for [Link](https://docs.stripe.com/payments/link/mobile-payment-element-link) in PaymentSheet. Enabling Link in your [payment method settings](https://dashboard.stripe.com/settings/payment_methods) will enable Link in PaymentSheet. To choose different Link availability settings on web and mobile, use a custom [payment method configuration](https://docs.stripe.com/payments/multiple-payment-method-configs). +* [Fixed] Fixed an issue where some 3DS2 payments may fail to complete successfully. + +### Payments +* [Added] Support for Amazon Pay bindings. + +## 23.23.0 2024-02-26 +### PaymentSheet +* [Added] Added support for [payment method configurations](https://docs.stripe.com/payments/multiple-payment-method-configs) when using the deferred intent integration path. + +### CustomerSheet +* [Fixed] Fixed a bug where if an exception is thrown in detachPaymentMethod(), the payment method was removed in the UI [#3309](https://github.com/stripe/stripe-ios/pull/3309) + +## 23.22.0 2024-02-12 +### PaymentSheet +* [Changed] The separator text under the Apple Pay button from "Or pay with a card" to "Or use a card" when using a SetupIntent. +* [Fixed] Fixed a bug where deleting the last saved payment method in PaymentSheet wouldn't automatically transition to the "Add a payment method" screen. +* [Added] Support for CVC recollection in PaymentSheet and PaymentSheet.FlowController (client-side confirmation) + +* [Changed] Make STPPinManagementService still usable from Swift. + +## 23.21.2 2024-02-05 +### Payments +* [Changed] We now auto append `mandate_data` when using Klarna with a SetupIntent. If you are interested in using Klarna with SetupIntents you sign up for the beta [here](https://stripe.com/docs/payments/klarna/accept-a-payment). + +## 23.21.1 2024-01-22 +### Payments +* [Changed] Increased the maximum number of status update retries when waiting for an intent to update to a terminal state. This impacts Cash App Pay and 3DS2. + +## 23.21.0 2024-01-16 +### PaymentSheet +* [Fixed] Fixed a few design issues on visionOS. +* [Added] Added billing details and type properties to [`PaymentSheet.FlowController.PaymentOptionDisplayData`](https://stripe.dev/stripe-ios/stripepaymentsheet/documentation/stripepaymentsheet/paymentsheet/flowcontroller/paymentoptiondisplaydata). + +## 23.20.0 2023-12-18 +### PaymentSheet +* [Added] Support for [card brand choice](https://stripe.com/docs/card-brand-choice). To set default preferred networks, use the new configuration option `PaymentSheet.Configuration.preferredNetworks`. +* [Fixed] Fixed visionOS support in Swift Package Manager and Cocoapods. + +### CustomerSheet +* [Added] Support for [card brand choice](https://stripe.com/docs/card-brand-choice). To set default preferred networks, use the new configuration option `PaymentSheet.Configuration.preferredNetworks`. + +### PaymentsUI +* [Added] Adds support for [card brand choice](https://stripe.com/docs/card-brand-choice) to STPPaymentCardTextField and STPCardFormView. To set a default preferred network for these UI elements, use the new `preferredNetworks` parameter. + +* [Changed] Mark STPPinManagementService deprecated & suggest alternative. + +## 23.19.0 2023-12-11 +### Apple Pay +* [Fixed] STPApplePayContext initializer returns nil in more cases where the request is invalid. +* [Fixed] STPApplePayContext now allows Apple Pay when the customer doesn’t have saved cards but can set them up in the Apple Pay sheet (iOS 15+). + +### PaymentSheet +* [Fixed] PaymentSheet sets newly saved payment methods as the default so that they're pre-selected the next time the customer pays. +* [Added] PaymentSheet now supports external payment methods. See https://stripe.com/docs/payments/external-payment-methods?platform=ios + +### CustomerSheet +* [Added] Saved SEPA payment methods are now displayed to the customer for reuse, similar to saved cards. + + +## 23.18.3 2023-11-28 +### PaymentSheet +* [Fixed] Visual bug where re-presenting PaymentSheet wouldn't show a spinner while it reloads. +* [Added] If PaymentSheet fails to load a deferred intent configuration, we fall back to displaying cards (or the intent configuration payment method types) instead of failing. +* [Fixed] Fixed an issue where PaymentSheet wouldn't accept valid Mexican phone numbers. +* [Added] The ability to customize the success colors of the primary button with `PaymentSheetAppearance.primaryButton.successBackgroundColor` and `PaymentSheetAppearance.primaryButton.successTextColor`. + +## 23.18.2 2023-11-06 +### CustomerSheet +* [Fixed] CustomerSheet no longer displays saved cards that originated from Apple Pay or Google Pay. + +## 23.18.1 2023-10-30 +### PaymentSheet +* [Fixed] Added a public initializer for `PaymentSheet.BillingDetails`. +* [Fixed] Fixed some payment method icons not updating to use the latest assets. +* [Fixed] PaymentSheet no longer displays saved cards that originated from Apple Pay or Google Pay. + +### PaymentsUI +* [Fixed] Fixed an issue with `STPPaymentCardTextField` where the `paymentCardTextFieldDidEndEditing` delegate method was not called. + +### PaymentSheet +* [Fixed] Fixed some payment method icons not updating to use the latest assets. + +## 23.18.0 2023-10-23 +### PaymentSheet +* [Added] Saved SEPA payment methods are now displayed to the customer for reuse, similar to saved cards. + +### PaymentsUI +* [Fixed] Fixed an issue where the unknown card icon would sometimes pick up the view's tint color. + +## 23.17.2 2023-10-16 +### PaymentsUI +* [Fixed] An issue with `STPPaymentCardTextField`, where the card params were not updated after deleting an empty sub field. +* [Fixed] Switched to Asset Catalogs and updated to the latest card brand logos. + +### Payments +* [Added] Support for MobilePay bindings. + +## 23.17.1 2023-10-09 +### PaymentSheet +* [Fixed] Fixed an issue when advancing from the country dropdown that prevented user's' from typing in their postal code. ([#2936](https://github.com/stripe/stripe-ios/issues/2936)) + +### PaymentsUI +* [Fixed] An issue with `STPPaymentCardTextField`, where the `paymentCardTextFieldDidChange` delegate method wasn't being called after deleting an empty sub field. + +## 23.17.0 2023-10-02 +### PaymentSheet +* [Fixed] Fixed an issue with selecting from lists on macOS Catalyst. Note that only macOS 11 or later is supported: We do not recommend releasing a Catalyst app targeting macOS 10.15. +* [Fixed] Fixed an issue with scanning card expiration dates. +* [Fixed] Fixed an issue where billing address collection configuration was not passed to Apple Pay. +* [Added] Support for Swish with PaymentIntents. +* [Added] Support for Bacs Direct Debit with PaymentIntents. + +### Basic Integration +* [Fixed] Fixed an issue with scanning card expiration dates. + +### Payments +* [Fixed] Fixed an issue where amounts in Serbian Dinar were displayed incorrectly. +* [Fixed] Fixed an issue where the SDK could hang in macOS Catalyst. +* [Added] Support for Swish bindings. + +## 23.16.0 2023-09-18 +### Payments +* [Added] Properties of STPConnectAccountParams are now mutable. +* [Fixed] Fixed STPConnectAccountCompanyParams.address being force unwrapped. It's now optional. +* [Added] Support for RevolutPay bindings + +### PaymentSheet +* [Added] Support for Alipay with PaymentIntents. +* [Added] Support for Cash App Pay with SetupIntents and PaymentIntents with `setup_future_usage`. +* [Added] Support for AU BECS Debit with SetupIntents. +* [Added] Support for OXXO with PaymentIntents. +* [Added] Support for Konbini with PaymentIntents. +* [Added] Support for PayNow with PaymentIntents. +* [Added] Support for PromptPay with PaymentIntents. +* [Added] Support for Boleto with PaymentIntents and SetupIntets. +* [Added] Support for External Payment Method as an invite-only private beta. +* [Added] Support for RevolutPay with SetupIntents and PaymentIntents with setup_future_usage (private beta). Note: PaymentSheet doesn't display this as a saved payment method yet. +* [Added] Support for Alma (Private Beta) with PaymentIntents. + +## 23.15.0 2023-08-28 +### PaymentSheet +* [Added] Support for AmazonPay (private beta), BLIK, and FPX with PaymentIntents. +* [Fixed] A bug where payment amounts were not displayed correctly for LAK currency. + +### StripeApplePay +* Fixed a compile-time issue with using StripeApplePay in an App Extension. ([#2853](https://github.com/stripe/stripe-ios/issues/2853)) + +### CustomerSheet +* [Added] `CustomerSheet`(https://stripe.com/docs/elements/customer-sheet?platform=ios) API, a prebuilt UI component that lets your customers manage their saved payment methods. + +## 23.14.0 2023-08-21 +### All +* Improved redirect UX when using Cash App Pay. + +### PaymentSheet +* [Added] Support for GrabPay with PaymentIntents. + +### Payments +* [Added] You can now create an STPConnectAccountParams without specifying a business type. + +### Basic Integration +* [Added] Adds `applePayLaterAvailability` to `STPPaymentContext`, a property that mirrors `PKPaymentRequest.applePayLaterAvailability`. This is useful if you need to disable Apple Pay Later. Note: iOS 17+. + + +## 23.13.0 2023-08-07 +### All +* [Fixed] Fixed compatibility with Xcode 15 beta 3. visionOS is now supported in iPadOS compatibility mode. +### PaymentSheet +* [Added] Enable bancontact and sofort for SetupIntents and PaymentIntents with setup_future_usage. Note: PaymentSheet doesn't display saved SEPA Debit payment methods yet. +### CustomerSheet +* [Added] `us_bank_account` PaymentMethod is now available in CustomerSheet + +## 23.12.0 2023-07-31 +### PaymentSheet +* [Added] Enable SEPA Debit and iDEAL for SetupIntents and PaymentIntents with setup_future_usage. Note: PaymentSheet doesn't display saved SEPA Debit payment methods yet. +* [Added] Add removeSavedPaymentMethodMessage to PaymentSheet.Configuration and CustomerSheet.Configuration. + +### Identity +* [Added] Supports [phone verification](https://stripe.com/docs/identity/phone) in Identity mobile SDK. + + +## 23.11.2 2023-07-24 +### PaymentSheet +* [Fixed] Update stp_icon_add@3x.png to 8bit color depth (Thanks @jszumski) + +### CustomerSheet +* [Fixed] Ability to removing payment method immediately after adding it. +* [Fixed] Re-init addPaymentMethodViewController after adding payment method to allow for adding another payment method + +## 23.11.1 2023-07-18 +### PaymentSheet +* [Fixed] Fixed various bugs in Link private beta. + +## 23.11.0 2023-07-17 +### CustomerSheet +* [Changed] Breaking interface change for `CustomerSheetResult`. `CustomerSheetResult.canceled` now has a nullable associated value signifying that there is no selected payment method. Please use both `.canceled(StripeOptionSelection?)` and `.selected(PaymentOptionSelection?)` to update your UI to show the latest selected payment method. + +## 23.10.0 2023-07-10 +### Payments +* [Fixed] A bug where `mandate_data` was not being properly attached to PayPal SetupIntent's. +### PaymentSheet +* [Added] You can now collect payment details before creating a PaymentIntent or SetupIntent. See [our docs](https://stripe.com/docs/payments/accept-a-payment-deferred) for more info. This integration also allows you to [confirm the Intent on the server](https://stripe.com/docs/payments/finalize-payments-on-the-server). + +## 23.9.4 2023-07-05 +### PaymentSheet +* [Added] US bank accounts are now supported when initializing with an IntentConfiguration. + +## 23.9.3 2023-06-26 +### PaymentSheet +* [Fixed] Affirm no longer requires shipping details. + +### CustomerSheet +* [Added] Added `billingDetailsCollectionConfiguration` to configure how you want to collect billing details (private beta). + +## 23.9.2 2023-06-20 +### Payments +* [Fixed] Fixed a bug causing Cash App Pay SetupIntents to incorrectly state they were canceled when they succeeded. + +### AddressElement +* [Fixed] A bug that was causing `addressViewControllerDidFinish` to return a non-nil `AddressDetails` when the user cancels out of the AddressElement when default values are provided. +* [Fixed] A bug that prevented the auto complete view from being presented when the AddressElement was created with default values. + +## 23.9.1 2023-06-12 +### PaymentSheet +* [Fixed] Fixed validating the IntentConfiguration matches the PaymentIntent/SetupIntent when it was already confirmed on the server. Note: server-side confirmation is in private beta. +### CustomerSheet +* [Fixed] Fixed bug with removing multiple saved payment methods + +## 23.9.0 2023-05-30 +### PaymentSheet +* [Changed] The private beta API for https://stripe.com/docs/payments/finalize-payments-on-the-server has changed: + * If you use `IntentConfiguration(..., confirmHandler:)`, the confirm handler now has an additional `shouldSavePaymentMethod: Bool` parameter that you should ignore. + * If you use `IntentConfiguration(..., confirmHandlerForServerSideConfirmation:)`, use `IntentConfiguration(..., confirmHandler:)` instead. Additionally, the confirm handler's first parameter is now an `STPPaymentMethod` object instead of a String id. Use `paymentMethod.stripeId` to get its id and send it to your server. +* [Fixed] Fixed PKR currency formatting. + +### CustomerSheet +* [Added] [CustomerSheet](https://stripe.com/docs/elements/customer-sheet?platform=ios) is now available (private beta) + +## 23.8.0 2023-05-08 +### Identity +* [Added] Added test mode M1 for the SDK. + +## 23.7.1 2023-05-02 +### Payments +* [Fixed] STPPaymentHandler.handleNextAction allows payment methods that are delayed or require further customer action like like SEPA Debit or OXXO. + +## 23.7.0 2023-04-24 +### PaymentSheet +* [Fixed] Fixed disabled text color, using a lower opacity version of the original color instead of the previous `.tertiaryLabel`. + +### Identity +* [Added] Added test mode for the SDK. + +## 23.6.2 2023-04-20 + +### Payments +* [Fixed] Fixed UnionPay cards appearing as invalid in some cases. + +### PaymentSheet +* [Fixed] Fixed a bug that prevents users from using SEPA Debit w/ PaymentIntents or SetupIntents and Paypal in PaymentIntent+setup_future_usage or SetupIntent. + +## 23.6.1 2023-04-17 +### All +* Xcode 13 is [no longer supported by Apple](https://developer.apple.com/news/upcoming-requirements/). Please upgrade to Xcode 14.1 or later. +### PaymentSheet +* [Fixed] Visual bug of the delete icon when deleting saved payment methods reported in [#2461](https://github.com/stripe/stripe-ios/issues/2461). + +## 23.6.0 2023-03-27 +### PaymentSheet +* [Added] Added `billingDetailsCollectionConfiguration` to configure how you want to collect billing details. See the docs [here](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet#billing-details-collection). + +## 23.5.1 2023-03-20 +### Payments +* [Fixed] Fixed amounts in COP being formatted incorrectly. +* [Fixed] Fixed BLIK payment bindings not handling next actions correctly. +* [Changed] Removed usage of `UIDevice.currentDevice.name`. + +### Identity +* [Added] Added a retake photo button on selfie scanning screen. + +## 23.5.0 2023-03-13 +### Payments +* [Added] API bindings support for Cash App Pay. See the docs [here](https://stripe.com/docs/payments/cash-app-pay/accept-a-payment?platform=mobile). +* [Added] Added `STPCardValidator.possibleBrands(forCard:completion:)`, which returns the list of available networks for a card. + +### PaymentSheet +* [Added] Support for Cash App Pay in PaymentSheet. + +## 23.4.2 2023-03-06 +### Identity +* [Added] ID/Address verification. + +## 23.4.1 2023-02-27 +### PaymentSheet +* [Added] Debug logging to help identify why specific payment methods are not showing up in PaymentSheet. + +### Basic Integration +* [Fixed] Race condition reported in #2302 + +## 23.4.0 2023-02-21 +### PaymentSheet +* [Added] Adds support for setting up PayPal using a SetupIntent or a PaymentIntent w/ setup_future_usage=off_session. Note: PayPal is in beta. + +## 23.3.4 2023-02-13 +### Financial Connections +* [Changed] Polished Financial Connections UI. + +## 23.3.3 2023-01-30 +### Payments +* [Changed] Updated image asset for AFFIN bank. + +### Financial Connections +* [Fixed] Double encoding of GET parameters. + +## 23.3.2 2023-01-09 +* [Changed] Using [Tuist](https://tuist.io) to generate Xcode projects. From now on, only release versions of the SDK will include Xcode project files, in case you want to build a non release revision from source, you can follow [these instructions](https://docs.tuist.io/tutorial/get-started) to generate the project files. For Carthage users, this also means that you will only be able to depend on release versions. + +### PaymentSheet +* [Added] `PaymentSheetError` now conforms to `CustomDebugStringConvertible` and has a more useful description when no payment method types are available. +* [Changed] Customers can now re-enter the autocomplete flow of `AddressViewController` by tapping an icon in the line 1 text field. + +## 23.3.1 2022-12-12 +* [Fixed] Fixed a bug where 3 decimal place currencies were not being formatted properly. + +### PaymentSheet +* [Fixed] Fixed an issue that caused animations of the card logos in the Card input field to glitch. +* [Fixed] Fixed a layout issue in the "Save my info" checkbox. + +### CardScan +* [Fixed] Fixed UX model loading from the wrong bundle. [#2078](https://github.com/stripe/stripe-ios/issues/2078) (Thanks [nickm01](https://github.com/nickm01)) + +## 23.3.0 2022-12-05 +### PaymentSheet +* [Added] Added logos of accepted card brands on Card input field. +* [Fixed] Fixed erroneously displaying the card scan button when card scanning is not available. + +### Financial Connections +* [Changed] FinancialConnectionsSheet methods now require to be called from non-extensions. +* [Changed] BankAccountToken.bankAccount was changed to an optional. + +## 23.2.0 2022-11-14 +### PaymentSheet +* [Added] Added `AddressViewController`, a customizable view controller that collects local and international addresses for your customers. See https://stripe.com/docs/elements/address-element?platform=ios. +* [Added] Added `PaymentSheet.Configuration.allowsPaymentMethodsRequiringShippingAddress`. Previously, to allow payment methods that require a shipping address (e.g. Afterpay and Affirm) in PaymentSheet, you attached a shipping address to the PaymentIntent before initializing PaymentSheet. Now, you can instead set this property to `true` and set `PaymentSheet.Configuration.shippingDetails` to a closure that returns your customers' shipping address. The shipping address will be attached to the PaymentIntent when the customer completes the checkout. +* [Fixed] Fixed user facing error messages for card related errors. +* [Fixed] Fixed `setup_future_usage` value being set when there's no customer. + +## 23.1.1 2022-11-07 +### Payments +* [Fixed] Fixed an issue with linking the StripePayments SDK in certain configurations. + +## 23.1.0 2022-10-31 +### CardScan +* [Added] Added a README.md for the `CardScanSheet` integration. + +### PaymentSheet +* [Added] Added parameters to customize the primary button and Apple Pay button labels. They can be found under `PaymentSheet.Configuration.primaryButtonLabel` and `PaymentSheet.ApplePayConfiguration.buttonType` respectively. + +## 23.0.0 2022-10-24 +### Payments +* [Changed] Reduced the size of the SDK by splitting the `Stripe` module into `StripePaymentSheet`, `StripePayments`, and `StripePaymentsUI`. Some manual changes may be required. Migration instructions are available at [https://stripe.com/docs/mobile/ios/sdk-23-migration](https://stripe.com/docs/mobile/ios/sdk-23-migration). + +|Module|Description|Compressed|Uncompressed| +|------|-----------|----------|------------| +|StripePaymentSheet|Stripe's [prebuilt payment UI](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet).|2.7MB|6.3MB| +|Stripe|Contains all the below frameworks, plus [Issuing](https://stripe.com/docs/issuing/cards/digital-wallets?platform=iOS) and [Basic Integration](/docs/mobile/ios/basic).|2.3MB|5.1MB| +|StripeApplePay|[Apple Pay support](/docs/apple-pay), including `STPApplePayContext`.|0.4MB|1.0MB| +|StripePayments|Bindings for the Stripe Payments API.|1.0MB|2.6MB| +|StripePaymentsUI|Bindings for the Stripe Payments API, [STPPaymentCardTextField](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom), STPCardFormView, and other UI elements.|1.7MB|3.9MB| + +* [Changed] The minimum iOS version is now 13.0. If you'd like to deploy for iOS 12.0, please use Stripe SDK 22.8.4. +* [Changed] STPPaymentCardTextField's `cardParams` parameter has been deprecated in favor of `paymentMethodParams`, making it easier to include the postal code from the card field. If you need to access the `STPPaymentMethodCardParams`, use `.paymentMethodParams.card`. + +### PaymentSheet +* [Fixed] Fixed a validation issue where cards expiring at the end of the current month were incorrectly treated as expired. +* [Fixed] Fixed a visual bug in iOS 16 where advancing between text fields would momentarily dismiss the keyboard. + +## 22.8.4 2022-10-12 +### PaymentSheet +* [Fixed] Use `.formSheet` modal presentation in Mac Catalyst. [#2023](https://github.com/stripe/stripe-ios/issues/2023) (Thanks [sergiocampama](https://github.com/sergiocampama)!) + +## 22.8.3 2022-10-03 +### CardScan +* [Fixed] [Garbled privacy link text in Card Scan UI](https://github.com/stripe/stripe-ios/issues/2015) + +## 22.8.2 2022-09-19 +### Identity +* [Changed] Support uploading single side documents. +* [Fixed] Fixed Xcode 14 support. +### Financial Connections +* [Fixed] Fixes an issue of returning canceled result from FinancialConnections if user taps cancel on the manual entry success screen. +### CardScan +* [Added] Added a new parameter to CardScanSheet.present() to specify if the presentation should be done animated or not. Defaults to true. +* [Changed] Changed card scan ML model loading to be async. +* [Changed] Changed minimum deployment target for card scan to iOS 13. + +## 22.8.1 2022-09-12 +### PaymentSheet +* [Fixed] Fixed potential crash when using Link in Mac Catalyst. +* [Fixed] Fixed Right-to-Left (RTL) layout issues. + +### Apple Pay +* [Fixed] Fixed an issue where `applePayContext:willCompleteWithResult:authorizationResult:handler:` may not be called in Objective-C implementations of `STPApplePayContextDelegate`. + +## 22.8.0 2022-09-06 +### PaymentSheet +* [Changed] Renamed `PaymentSheet.reset()` to `PaymentSheet.resetCustomer()`. See `MIGRATING.md` for more info. +* [Added] You can now set closures in `PaymentSheet.ApplePayConfiguration.customHandlers` to configure the PKPaymentRequest and PKPaymentAuthorizationResult during a transaction. This enables you to build support for [Merchant Tokens](https://developer.apple.com/documentation/passkit/pkpaymentrequest/3916053-recurringpaymentrequest) and [Order Tracking](https://developer.apple.com/documentation/passkit/pkpaymentorderdetails) in iOS 16. + +### Apple Pay +* [Added] You can now implement the `applePayContext(_:willCompleteWithResult:handler:)` function in your `ApplePayContextDelegate` to configure the PKPaymentAuthorizationResult during a transaction. This enables you to build support for [Order Tracking](https://developer.apple.com/documentation/passkit/pkpaymentorderdetails) in iOS 16. + +## 22.7.1 2022-08-31 +* [Fixed] Fixed Mac Catalyst support in Xcode 14. [#2001](https://github.com/stripe/stripe-ios/issues/2001) + +### PaymentSheet +* [Fixed] PaymentSheet now uses configuration.apiClient for Apple Pay instead of always using STPAPIClient.shared. +* [Fixed] Fixed a layout issue with PaymentSheet in landscape. + +## 22.7.0 2022-08-15 +### PaymentSheet +* [Fixed] Fixed a layout issue on iPad. +* [Changed] Improved Link support in custom flow (`PaymentSheet.FlowController`). + +## 22.6.0 2022-07-05 +### PaymentSheet +* [Added] PaymentSheet now supports Link payment method. +* [Changed] Change behavior of Afterpay/Clearpay: Charge in 3 for GB, FR, and ES + +### STPCardFormView +* [Changed] Postal code is no longer collected for billing addresses in Japan. + +### Identity +* [Added] The ability to capture Selfie images in the native component flow. +* [Fixed] Fixed an issue where the welcome and confirmation screens were not correctly decoding non-ascii characters. +* [Fixed] Fixed an issue where, if a manually uploaded document could not be decoded on the server, there was no way to select a new image to upload. +* [Fixed] Fixed an issue where the IdentityVerificationSheet completion block was called early when manually uploading a document image instead of using auto-capture. + +## 22.5.1 2022-06-21 +* [Fixed] Fixed an issue with `STPPaymentHandler` where returning an app redirect could cause a crash. + +## 22.5.0 2022-06-13 +### PaymentSheet +* [Added] You can now use `PaymentSheet.ApplePayConfiguration.paymentSummaryItems` to directly configure the payment summary items displayed in the Apple Pay sheet. This is useful for recurring payments. + +## 22.4.0 2022-05-23 +### PaymentSheet +* [Added] The ability to customize the appearance of the PaymentSheet using `PaymentSheet.Appearance`. +* [Added] Support for collecting payments from customers in 54 additional countries within PaymentSheet. Most of these countries are located in Africa and the Middle East. +* [Added] `affirm` and `AUBECSDebit` payment methods are now available in PaymentSheet + +## 22.3.2 2022-05-18 +### CardScan +* [Added] Added privacy text to the CardImageVerification Sheet UI + +## 22.3.1 2022-05-16 +* [Fixed] Fixed an issue where ApplePayContext failed to parse an API response if the funding source was unknown. +* [Fixed] Fixed an issue where PaymentIntent confirmation could fail when the user closes the challenge window immediately after successfully completing a challenge + +### Identity +* [Fixed] Fixed an issue where the verification flow would get stuck in a document upload loop when verifying with a passport and uploading an image manually. + +## 22.3.0 2022-05-03 + +### PaymentSheet +* [Added] `us_bank_account` PaymentMethod is now available in payment sheet + +## 22.2.0 2022-04-25 + +### Connections +* [Changed] `StripeConnections` SDK has been renamed to `StripeFinancialConnections`. See `MIGRATING.md` for more info. + +### PaymentSheet +* [Fixed] Fixed an issue where `source_cancel` API requests were being made for non-3DS payment method types. +* [Fixed] Fixed an issue where certain error messages were not being localized. +* [Added] `us_bank_account` PaymentMethod is now available in PaymentSheet. + +### Identity +* [Fixed] Minor UI fixes when using `IdentityVerificationSheet` with native components +* [Changed] Improvements to native component `IdentityVerificationSheet` document detection + +## 22.1.1 2022-04-11 + +### Identity +* [Fixed] Fixes VerificationClientSecret (Thanks [Masataka-n](https://github.com/Masataka-n)!) + +## 22.1.0 2022-04-04 +* [Changed] Localization improvements. +### Identity +* [Added] `IdentityVerificationSheet` can now be used with native iOS components. + +## 22.0.0 2022-03-28 +* [Changed] The minimum iOS version is now 12.0. If you'd like to deploy for iOS 11.0, please use Stripe SDK 21.12.0. +* [Added] `us_bank_account` PaymentMethod is now available for ACH Direct Debit payments, including APIs to collect customer bank information (requires `StripeConnections`) and verify microdeposits. +* [Added] `StripeConnections` SDK can be optionally included to support ACH Direct Debit payments. + +### PaymentSheet +* [Changed] PaymentSheet now uses light and dark mode agnostic icons for payment method types. +* [Changed] Link payment method (private beta) UX improvements. + +### Identity +* [Changed] `IdentityVerificationSheet` now has an availability requirement of iOS 14.3 on its initializer instead of the `present` method. + +## 21.13.0 2022-03-15 +* [Changed] Binary framework distribution now requires Xcode 13. Carthage users using Xcode 12 need to add the `--no-use-binaries` flag. + +### PaymentSheet +* [Fixed] Fixed potential crash when using PaymentSheet custom flow with SwiftUI. +* [Fixed] Fixed being unable to cancel native 3DS2 in PaymentSheet. +* [Fixed] The payment method icons will now use the correct colors when PaymentSheet is configured with `alwaysLight` or `alwaysDark`. +* [Fixed] A race condition when setting the `primaryButtonColor` on `PaymentSheet.Configuration`. +* [Added] PaymentSheet now supports Link (private beta). + +### CardScan +* [Added] The `CardImageVerificationSheet` initializer can now take an additional `Configuration` object. + +## 21.12.0 2022-02-14 +* [Added] We now offer a 1MB Apple Pay SDK module intended for use in an App Clip. Visit [our App Clips docs](https://stripe.com/docs/apple-pay#app-clips) for details. +* `Stripe` now requires `StripeApplePay`. See `MIGRATING.md` for more info. +* [Added] Added a convenience initializer to create an STPCardParams from an STPPaymentMethodParams. + +### PaymentSheet +* [Changed] The "save this card" checkbox in PaymentSheet is now unchecked by default in non-US countries. +* [Fixed] Fixes issue that could cause symbol name collisions when using Objective-C +* [Fixed] Fixes potential crash when using PaymentSheet with SwiftUI + +## 21.11.1 2022-01-10 +* Fixes a build warning in SPM caused by an invalid Package.swift file. + +## 21.11.0 2022-01-04 +* [Changed] The maximum `identity_document` file upload size has been increased, improving the quality of compressed images. See https://stripe.com/docs/file-upload +* [Fixed] The maximum `dispute_evidence` file upload size has been decreased to match server requirements, preventing the server from rejecting uploads that exceeded 5MB. See https://stripe.com/docs/file-upload +* [Added] PaymentSheet now supports Afterpay / Clearpay, EPS, Giropay, Klarna, Paypal (private beta), and P24. + +## 21.10.0 2021-12-14 +* Added API bindings for Klarna +* `StripeIdentity` now requires `StripeCameraCore`. See `MIGRATING.md` for more info. +* Releasing `StripeCardScan` Beta iOS SDK +* Fixes a bug where the text field would cause a crash when typing a space (U+0020) followed by pressing the backspace key on iPad. [#1907](https://github.com/stripe/stripe-ios/issues/1907) (Thanks [buhikon](https://github.com/buhikon)!) + +## 21.9.1 2021-12-02 +* Fixes a build warning caused by a duplicate NSURLComponents+Stripe.swift file. + +## 21.9.0 2021-10-18 +### PaymentSheet +This release adds several new features to PaymentSheet, our drop-in UI integration: + +#### More supported payment methods +The list of supported payment methods depends on your integration. +If you’re using a PaymentIntent, we support: +- Card +- SEPA Debit, bancontact, iDEAL, sofort + +If you’re using a PaymentIntent with `setup_future_usage` or a SetupIntent, we support: +- Card +- Apple/GooglePay + +Note: To enable SEPA Debit and sofort, set `PaymentSheet.configuration.allowsDelayedPaymentMethods` to `true` on the client. +These payment methods can't guarantee you will receive funds from your customer at the end of the checkout because they take time to settle. Don't enable these if your business requires immediate payment (e.g., an on-demand service). See https://stripe.com/payments/payment-methods-guide + +#### Pre-fill billing details +PaymentSheet collects billing details like name and email for certain payment methods. Pre-fill these fields to save customers time by setting `PaymentSheet.Configuration.defaultBillingDetails`. + +#### Save payment methods on payment +> This is currently only available for cards + Apple/Google Pay. + +PaymentSheet supports PaymentIntents with `setup_future_usage` set. This property tells us to save the payment method for future use (e.g., taking initial payment of a recurring subscription). +When set, PaymentSheet hides the 'Save this card for future use' checkbox and always saves. + +#### SetupIntent support +> This is currently only available for cards + Apple/Google Pay. + +Initialize PaymentSheet with a SetupIntent to set up cards for future use without charging. + +#### Smart payment method ordering +When a customer is adding a new payment method, PaymentSheet uses information like the customers region to show the most relevant payment methods first. + +#### Other changes +* Postal code collection for cards is now limited to US, CA, UK +* Fixed SwiftUI memory leaks [Issue #1881](https://github.com/stripe/stripe-ios/issues/1881) +* Added "hint" for error messages +* Adds many new localizations. The SDK now localizes in the following languages: bg-BG,ca-ES,cs-CZ,da,de,el-GR,en-GB,es-419,es,et-EE,fi,fil,fr-CA,fr,hr,hu,id,it,ja,ko,lt-LT,lv-LV,ms-MY,mt,nb,nl,nn-NO,pl-PL,pt-BR,pt-PT,ro-RO,ru,sk-SK,sl-SI,sv,tk,tr,vi,zh-Hans,zh-Hant,zh-HK +* `Stripe` and `StripeIdentity` now require `StripeUICore`. See `MIGRATING.md` for more info. + +## 21.8.1 2021-08-10 +* Fixes an issue with image loading when using Swift Package Manager. +* Temporarily disabled WeChat Pay support in PaymentMethods. +* The `Stripe` module now requires `StripeCore`. See `MIGRATING.md` for more info. + +## 21.8.0 2021-08-04 +* Fixes broken card scanning links. (Thanks [ricsantos](https://github.com/ricsantos)) +* Fixes accessibilityLabel for postal code field. (Thanks [romanilchyshyndepop](https://github.com/romanilchyshyndepop)) +* Improves compile time by 30% [#1846](https://github.com/stripe/stripe-ios/pull/1846) (Thanks [JonathanDowning](https://github.com/JonathanDowning)!) +* Releasing `StripeIdentity` iOS SDK for use with [Stripe Identity](https://stripe.com/identity). + +## 21.7.0 2021-07-07 +* Fixes an issue with `additionaDocument` field typo [#1833](https://github.com/stripe/stripe-ios/issues/1833) +* Adds support for WeChat Pay to PaymentMethods +* Weak-links SwiftUI [#1828](https://github.com/stripe/stripe-ios/issues/1828) +* Adds 3DS2 support for Cartes Bancaires +* Fixes an issue with camera rotation during card scanning on iPad +* Fixes an issue where PaymentSheet could cause conflicts when included in an app that also includes PanModal [#1818](https://github.com/stripe/stripe-ios/issues/1818) +* Fixes an issue with building on Xcode 13 [#1822](https://github.com/stripe/stripe-ios/issues/1822) +* Fixes an issue where overriding STPPaymentCardTextField's `brandImage()` func had no effect [#1827](https://github.com/stripe/stripe-ios/issues/1827) +* Fixes documentation typo. (Thanks [iAugux](https://github.com/iAugux)) + +## 21.6.0 2021-05-27 +* Adds `STPCardFormView`, a UI component that collects card details +* Adds 'STPRadarSession'. Note this requires additional Stripe permissions to use. + +## 21.5.1 2021-05-07 +* Fixes the `PaymentSheet` API not being public. +* Fixes an issue with missing headers. (Thanks [jctrouble](https://github.com/jctrouble)!) + +## 21.5.0 2021-05-06 +* Adds the `PaymentSheet`(https://stripe.dev/stripe-ios/docs/Classes/PaymentSheet.html) API, a prebuilt payment UI. +* Fixes Mac Catalyst support in Xcode 12.5 [#1797](https://github.com/stripe/stripe-ios/issues/1797) +* Fixes `STPPaymentCardTextField` not being open [#1768](https://github.com/stripe/stripe-ios/issues/1797) + +## 21.4.0 2021-04-08 +* Fixed warnings in Xcode 12.5. [#1772](https://github.com/stripe/stripe-ios/issues/1772) +* Fixes a layout issue when confirming payments in SwiftUI. [#1761](https://github.com/stripe/stripe-ios/issues/1761) (Thanks [mvarie](https://github.com/mvarie)!) +* Fixes a potential race condition when finalizing 3DS2 confirmations. +* Fixes an issue where a 3DS2 transaction could result in an incorrect error message when the card number is incorrect. [#1778](https://github.com/stripe/stripe-ios/issues/1778) +* Fixes an issue where `STPPaymentHandler.shared().handleNextAction` sometimes didn't return a `handleActionError`. [#1769](https://github.com/stripe/stripe-ios/issues/1769) +* Fixes a layout issue when confirming payments in SwiftUI. [#1761](https://github.com/stripe/stripe-ios/issues/1761) (Thanks [mvarie](https://github.com/mvarie)!) +* Fixes an issue with opening URLs on Mac Catalyst +* Fixes an issue where OXXO next action is mistaken for a cancel in STPPaymentHandler +* SetupIntents for iDEAL, Bancontact, EPS, and Sofort will now send the required mandate information. +* Adds support for BLIK. +* Adds `decline_code` information to STPError. [#1755](https://github.com/stripe/stripe-ios/issues/1755) +* Adds support for SetupIntents to STPApplePayContext +* Allows STPPaymentCardTextField to be subclassed. [#1768](https://github.com/stripe/stripe-ios/issues/1768) + +## 21.3.1 2021-03-25 +* Adds support for Maestro in Apple Pay on iOS 12 or later. + +## 21.3.0 2021-02-18 +* Adds support for SwiftUI in custom integration using the `STPPaymentCardTextField.Representable` View and the `.paymentConfirmationSheet()` ViewModifier. See `IntegrationTester` for usage examples. +* Removes the UIViewController requirement from STPApplePayContext, allowing it to be used in SwiftUI. +* Fixes an issue where `STPPaymentOptionsViewController` could fail to register a card. [#1758](https://github.com/stripe/stripe-ios/issues/1758) +* Fixes an issue where some UnionPay test cards were marked as invalid. [#1759](https://github.com/stripe/stripe-ios/issues/1759) +* Updates tests to run on Carthage 0.37 with .xcframeworks. + + +## 21.2.1 2021-01-29 +* Fixed an issue where a payment card text field could resize incorrectly on smaller devices or with certain languages. [#1600](https://github.com/stripe/stripe-ios/issues/1600) +* Fixed an issue where the SDK could always return English strings in certain situations. [#1677](https://github.com/stripe/stripe-ios/pull/1677) (Thanks [glaures-ioki](https://github.com/glaures-ioki)!) +* Fixed an issue where an STPTheme had no effect on the navigation bar. [#1753](https://github.com/stripe/stripe-ios/pull/1753) (Thanks [@rbenna](https://github.com/rbenna)!) +* Fixed handling of nil region codes. [#1752](https://github.com/stripe/stripe-ios/issues/1752) +* Fixed an issue preventing card scanning from being disabled. [#1751](https://github.com/stripe/stripe-ios/issues/1751) +* Fixed an issue with enabling card scanning in an app with a localized Info.plist.[#1745](https://github.com/stripe/stripe-ios/issues/1745) +* Added a missing additionalDocument parameter to STPConnectAccountIndividualVerification. +* Added support for Afterpay/Clearpay. + +## 21.2.0 2021-01-06 +* Stripe3DS2 is now open source software under the MIT License. +* Fixed various issues with bundling Stripe3DS2 in Cocoapods and Swift Package Manager. All binary dependencies have been removed. +* Fixed an infinite loop during layout on small screen sizes. [#1731](https://github.com/stripe/stripe-ios/issues/1731) +* Fixed issues with missing image assets when using Cocoapods. [#1655](https://github.com/stripe/stripe-ios/issues/1655) [#1722](https://github.com/stripe/stripe-ios/issues/1722) +* Fixed an issue which resulted in unnecessary queries to the BIN information service. +* Adds the ability to `attach` and `detach` PaymentMethod IDs to/from a CustomerContext. [#1729](https://github.com/stripe/stripe-ios/issues/1729) +* Adds support for NetBanking. + +## 21.1.0 2020-12-07 +* Fixes a crash during manual confirmation of a 3DS2 payment. [#1725](https://github.com/stripe/stripe-ios/issues/1725) +* Fixes an issue that could cause some image assets to be missing in certain configurations. [#1722](https://github.com/stripe/stripe-ios/issues/1722) +* Fixes an issue with confirming Alipay transactions. +* Re-exposes `cardNumber` parameter in `STPPaymentCardTextField`. +* Adds support for UPI. + +## 21.0.1 2020-11-19 +* Fixes an issue with some initializers not being exposed publicly following the [conversion to Swift](https://stripe.com/docs/mobile/ios/sdk-21-migration). +* Updates GrabPay integration to support synchronous updates. + +## 21.0.0 2020-11-18 +* The SDK is now written in Swift, and some manual changes are required. Migration instructions are available at [https://stripe.com/docs/mobile/ios/sdk-21-migration](https://stripe.com/docs/mobile/ios/sdk-21-migration). +* Adds full support for Apple silicon. +* Xcode 12.2 is now required. + +## 20.1.1 2020-10-23 +* Fixes an issue when using Cocoapods 1.10 and Xcode 12. [#1683](https://github.com/stripe/stripe-ios/pull/1683) +* Fixes a warning when using Swift Package Manager. [#1675](https://github.com/stripe/stripe-ios/pull/1675) + +## 20.1.0 2020-10-15 +* Adds support for OXXO. [#1592](https://github.com/stripe/stripe-ios/pull/1592) +* Applies a workaround for various bugs in Swift Package Manager. [#1671](https://github.com/stripe/stripe-ios/pull/1671) Please see [#1673](https://github.com/stripe/stripe-ios/issues/1673) for additional notes when using Xcode 12.0. +* Card scanning now works when the device's orientation is unknown. [#1659](https://github.com/stripe/stripe-ios/issues/1659) +* The expiration date field's Simplified Chinese localization has been corrected. (Thanks [cythb](https://github.com/cythb)!) [#1654](https://github.com/stripe/stripe-ios/pull/1654) + +## 20.0.0 2020-09-14 +* [Card scanning](https://github.com/stripe/stripe-ios#card-scanning) is now built into STPAddCardViewController. Card.io support has been removed. [#1629](https://github.com/stripe/stripe-ios/pull/1629) +* Shrunk the SDK from 1.3MB when compressed & thinned to 0.7MB, allowing for easier App Clips integration. [#1643](https://github.com/stripe/stripe-ios/pull/1643) +* Swift Package Manager, Apple Silicon, and Catalyst are now fully supported on Xcode 12. [#1644](https://github.com/stripe/stripe-ios/pull/1644) +* Adds support for 19-digit cards. [#1608](https://github.com/stripe/stripe-ios/pull/1608) +* Adds GrabPay and Sofort as PaymentMethod. [#1627](https://github.com/stripe/stripe-ios/pull/1627) +* Drops support for iOS 10. [#1643](https://github.com/stripe/stripe-ios/pull/1643) + +## 19.4.0 2020-08-13 +* `pkPaymentErrorForStripeError` no longer returns PKPaymentUnknownErrors. Instead, it returns the original NSError back, resulting in dismissal of the Apple Pay sheet. This means ApplePayContext dismisses the Apple Pay sheet for all errors that aren't specifically PKPaymentError types. +* `metadata` fields are no longer populated on retrieved Stripe API objects and must be fetched on your server using your secret key. If this is causing issues with your deployed app versions please reach out to [Stripe Support](https://support.stripe.com/?contact=true). These fields have been marked as deprecated and will be removed in a future SDK version. + +## 19.3.0 2020-05-28 +* Adds giropay PaymentMethod bindings [#1569](https://github.com/stripe/stripe-ios/pull/1569) +* Adds Przelewy24 (P24) PaymentMethod bindings [#1556](https://github.com/stripe/stripe-ios/pull/1556) +* Adds Bancontact PaymentMethod bindings [#1565](https://github.com/stripe/stripe-ios/pull/1565) +* Adds EPS PaymentMethod bindings [#1578](https://github.com/stripe/stripe-ios/pull/1578) +* Replaces es-AR localization with es-419 for full Latin American Spanish support and updates multiple localizations [#1549](https://github.com/stripe/stripe-ios/pull/1549) [#1570](https://github.com/stripe/stripe-ios/pull/1570) +* Fixes missing custom number placeholder in `STPPaymentCardTextField` [#1576](https://github.com/stripe/stripe-ios/pull/1576) +* Adds tabbing on external keyboard support to `STPAUBECSFormView` and correctly types it as a `UIView` instead of `UIControl` [#1580](https://github.com/stripe/stripe-ios/pull/1580) + +## 19.2.0 2020-05-01 +* Adds ability to attach shipping details when confirming PaymentIntents [#1558](https://github.com/stripe/stripe-ios/pull/1558) +* `STPApplePayContext` now provides shipping details in the `applePayContext:didCreatePaymentMethod:paymentInformation:completion:` delegate method and automatically attaches shipping details to PaymentIntents (unless manual confirmation)[#1561](https://github.com/stripe/stripe-ios/pull/1561) +* Adds support for the BECS Direct Debit payment method for Stripe users in Australia [#1547](https://github.com/stripe/stripe-ios/pull/1547) + +## 19.1.1 2020-04-28 +* Add advancedFraudSignalsEnabled property [#1560](https://github.com/stripe/stripe-ios/pull/1560) + +## 19.1.0 2020-04-15 +* Relaxes need for dob for full name connect account (`STPConnectAccountIndividualParams`). [#1539](https://github.com/stripe/stripe-ios/pull/1539) +* Adds Chinese (Traditional) and Chinese (Hong Kong) localizations [#1536](https://github.com/stripe/stripe-ios/pull/1536) +* Adds `STPApplePayContext`, a helper class for Apple Pay. [#1499](https://github.com/stripe/stripe-ios/pull/1499) +* Improves accessibility [#1513](https://github.com/stripe/stripe-ios/pull/1513), [#1504](https://github.com/stripe/stripe-ios/pull/1504) +* Adds support for the Bacs Direct Debit payment method [#1487](https://github.com/stripe/stripe-ios/pull/1487) +* Adds support for 16 digit Diners Club cards [#1498](https://github.com/stripe/stripe-ios/pull/1498) + +## 19.0.1 2020-03-24 +* Fixes an issue building with Xcode 11.4 [#1526](https://github.com/stripe/stripe-ios/pull/1526) + +## 19.0.0 2020-02-12 +* Deprecates the `STPAPIClient` `initWithConfiguration:` method. Set the `configuration` property on the `STPAPIClient` instance instead. [#1474](https://github.com/stripe/stripe-ios/pull/1474) +* Deprecates `publishableKey` and `stripeAccount` properties of `STPPaymentConfiguration`. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for more details. [#1474](https://github.com/stripe/stripe-ios/pull/1474) +* Adds explicit STPAPIClient properties on all SDK components that make API requests. These default to `[STPAPIClient sharedClient]`. This is a breaking change for some users of `stripeAccount`. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for more details. [#1469](https://github.com/stripe/stripe-ios/pull/1469) +* The user's postal code is now collected by default in countries that support postal codes. We always recommend collecting a postal code to increase card acceptance rates and reduce fraud. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for more details. [#1479](https://github.com/stripe/stripe-ios/pull/1479) + +## 18.4.0 2020-01-15 +* Adds support for Klarna Pay on Sources API [#1444](https://github.com/stripe/stripe-ios/pull/1444) +* Compresses images using `pngcrush` to reduce SDK size [#1471](https://github.com/stripe/stripe-ios/pull/1471) +* Adds support for CVC recollection in PaymentIntent confirm [#1473](https://github.com/stripe/stripe-ios/pull/1473) +* Fixes a race condition when setting `defaultPaymentMethod` on `STPPaymentOptionsViewController` [#1476](https://github.com/stripe/stripe-ios/pull/1476) + +## 18.3.0 2019-12-3 +* STPAddCardViewControllerDelegate methods previously removed in v16.0.0 are now marked as deprecated, to help migrating users [#1439](https://github.com/stripe/stripe-ios/pull/1439) +* Fixes an issue where canceling 3DS authentication could leave PaymentIntents in an inaccurate `requires_action` state [#1443](https://github.com/stripe/stripe-ios/pull/1443) +* Fixes text color for large titles [#1446](https://github.com/stripe/stripe-ios/pull/1446) +* Re-adds support for pre-selecting the last selected payment method in STPPaymentContext and STPPaymentOptionsViewController. [#1445](https://github.com/stripe/stripe-ios/pull/1445) +* Fix crash when adding/removing postal code cells [#1450](https://github.com/stripe/stripe-ios/pull/1450) + +## 18.2.0 2019-10-31 +* Adds support for creating tokens with the last 4 digits of an SSN [#1432](https://github.com/stripe/stripe-ios/pull/1432) +* Renames Standard Integration to Basic Integration + +## 18.1.0 2019-10-29 +* Adds localizations for English (Great Britain), Korean, Russian, and Turkish [#1373](https://github.com/stripe/stripe-ios/pull/1373) +* Adds support for SEPA Debit as a PaymentMethod [#1415](https://github.com/stripe/stripe-ios/pull/1415) +* Adds support for custom SEPA Debit Mandate params with PaymentMethod [#1420](https://github.com/stripe/stripe-ios/pull/1420) +* Improves postal code UI for users with mismatched regions [#1302](https://github.com/stripe/stripe-ios/issues/1302) +* Fixes a potential crash when presenting the add card view controller [#1426](https://github.com/stripe/stripe-ios/issues/1426) +* Adds offline status checking to FPX payment flows [#1422](https://github.com/stripe/stripe-ios/pull/1422) +* Adds support for push provisions for Issuing users [#1396](https://github.com/stripe/stripe-ios/pull/1396) + +## 18.0.0 2019-10-04 +* Adds support for building on macOS 10.15 with Catalyst. Use the .xcframework file attached to the release in GitHub. Cocoapods support is coming soon. [#1364](https://github.com/stripe/stripe-ios/issues/1364) +* Errors from the Payment Intents API are now localized by default. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for details. +* Adds support for FPX in Standard Integration. [#1390](https://github.com/stripe/stripe-ios/pull/1390) +* Simplified Apple Pay integration when using 3DS2. [#1386](https://github.com/stripe/stripe-ios/pull/1386) +* Improved autocomplete behavior for some STPPaymentHandler blocks. [#1403](https://github.com/stripe/stripe-ios/pull/1403) +* Fixed spurious `keyboardWillAppear` messages triggered by STPPaymentTextCard. [#1393](https://github.com/stripe/stripe-ios/pull/1393) +* Fixed an issue with non-numeric placeholders in STPPaymentTextCard. [#1394](https://github.com/stripe/stripe-ios/pull/1394) +* Dropped support for iOS 9. Please continue to use 17.0.2 if you need to support iOS 9. + +## 17.0.2 2019-09-24 +* Fixes an error that could prevent a 3D Secure 2 challenge dialog from appearing in certain situations. +* Improved VoiceOver support. [#1384](https://github.com/stripe/stripe-ios/pull/1384) +* Updated Apple Pay and Mastercard branding. [#1374](https://github.com/stripe/stripe-ios/pull/1374) +* Updated the Standard Integration example app to use automatic confirmation. [#1363](https://github.com/stripe/stripe-ios/pull/1363) +* Added support for collecting email addresses and phone numbers from Apple Pay. [#1372](https://github.com/stripe/stripe-ios/pull/1372) +* Introduced support for FPX payments. (Invite-only Beta) [#1375](https://github.com/stripe/stripe-ios/pull/1375) + +## 17.0.1 2019-09-09 +* Cancellation during the 3DS2 flow will no longer cause an unexpected error. [#1353](https://github.com/stripe/stripe-ios/pull/1353) +* Large Title UIViewControllers will no longer have a transparent background in iOS 13. [#1362](https://github.com/stripe/stripe-ios/pull/1362) +* Adds an `availableCountries` option to STPPaymentConfiguration, allowing one to limit the list of countries in the address entry view. [#1327](https://github.com/stripe/stripe-ios/pull/1327) +* Fixes a crash when using card.io. [#1357](https://github.com/stripe/stripe-ios/pull/1357) +* Fixes an issue with birthdates when creating a Connect account. [#1361](https://github.com/stripe/stripe-ios/pull/1361) +* Updates example code to Swift 5. [#1354](https://github.com/stripe/stripe-ios/pull/1354) +* The default value of `[STPTheme translucentNavigationBar]` is now `YES`. [#1367](https://github.com/stripe/stripe-ios/pull/1367) + +## 17.0.0 2019-09-04 +* Adds support for iOS 13, including Dark Mode and minor bug fixes. [#1307](https://github.com/stripe/stripe-ios/pull/1307) +* Updates API version from 2015-10-12 to 2019-05-16 [#1254](https://github.com/stripe/stripe-ios/pull/1254) + * Adds `STPSourceRedirectStatusNotRequired` to `STPSourceRedirectStatus`. Previously, optional redirects were marked as `STPSourceRedirectStatusSucceeded`. + * Adds `STPSourceCard3DSecureStatusRecommended` to `STPSourceCard3DSecureStatus`. + * Removes `STPLegalEntityParams`. Initialize an `STPConnectAccountParams` with an `individual` or `company` dictionary instead. See https://stripe.com/docs/api/tokens/create_account#create_account_token-account +* Changes the `STPPaymentContextDelegate paymentContext:didCreatePaymentResult:completion:` completion block type to `STPPaymentStatusBlock`, to let you inform the context that the user canceled. +* Adds initial support for WeChat Pay. [#1326](https://github.com/stripe/stripe-ios/pull/1326) +* The user's billing address will now be included when creating a PaymentIntent from an Apple Pay token. [#1334](https://github.com/stripe/stripe-ios/pull/1334) + + +## 16.0.7 2019-08-23 +* Fixes STPThreeDSUICustomization not initializing defaults correctly. [#1303](https://github.com/stripe/stripe-ios/pull/1303) +* Fixes STPPaymentHandler treating post-authentication errors as authentication errors [#1291](https://github.com/stripe/stripe-ios/pull/1291) +* Removes preferredStatusBarStyle from STPThreeDSUICustomization, see STPThreeDSNavigationBarCustomization.barStyle instead [#1308](https://github.com/stripe/stripe-ios/pull/1308) + +## 16.0.6 2019-08-13 +* Adds a method to STPAuthenticationContext allowing you to configure the SFSafariViewController presented for web-based authentication. +* Adds STPAddress initializer that takes STPPaymentMethodBillingDetails. [#1278](https://github.com/stripe/stripe-ios/pull/1278) +* Adds convenience method to populate STPUserInformation with STPPaymentMethodBillingDetails. [#1278](https://github.com/stripe/stripe-ios/pull/1278) +* STPShippingAddressViewController prefills billing address for PaymentMethods too now, not just Card. [#1278](https://github.com/stripe/stripe-ios/pull/1278) +* Update libStripe3DS2.a to avoid a conflict with Firebase. [#1293](https://github.com/stripe/stripe-ios/issues/1293) + +## 16.0.5 2019-08-09 +* Fixed an compatibility issue when building with certain Cocoapods configurations. [#1288](https://github.com/stripe/stripe-ios/issues/1288) + +## 16.0.4 2019-08-08 +* Improved compatibility with other OpenSSL-using libraries. [#1265](https://github.com/stripe/stripe-ios/issues/1265) +* Fixed compatibility with Xcode 10.1. [#1273](https://github.com/stripe/stripe-ios/issues/1273) +* Fixed an issue where STPPaymentContext could be left in a bad state when cancelled. [#1284](https://github.com/stripe/stripe-ios/pull/1284) + +## 16.0.3 2019-08-01 +* Changes to code obfuscation, resolving an issue with App Store review [#1269](https://github.com/stripe/stripe-ios/pull/1269) +* Adds Apple Pay support to STPPaymentHandler [#1264](https://github.com/stripe/stripe-ios/pull/1264) + +## 16.0.2 2019-07-29 +* Adds API to let users set a default payment option for Standard Integration [#1252](https://github.com/stripe/stripe-ios/pull/1252) +* Removes querying the Advertising Identifier (IDFA). +* Adds customizable UIStatusBarStyle to STDSUICustomization. + +## 16.0.1 2019-07-25 +* Migrates Stripe3DS2.framework to libStripe3DS2.a, resolving an issue with App Store validation. [#1246](https://github.com/stripe/stripe-ios/pull/1246) +* Fixes a crash in STPPaymentHandler. [#1244](https://github.com/stripe/stripe-ios/pull/1244) + +## 16.0.0 2019-07-18 +* Migrates STPPaymentCardTextField.cardParams property type from STPCardParams to STPPaymentMethodCardParams +* STPAddCardViewController: + * Migrates addCardViewController:didCreateSource:completion: and addCardViewController:didCreateToken:completion: to addCardViewController:didCreatePaymentMethod:completion + * Removes managedAccountCurrency property - there’s no equivalent parameter necessary for PaymentMethods. +* STPPaymentOptionViewController now shows, adds, removes PaymentMethods instead of Source/Tokens. +* STPCustomerContext, STPBackendAPIAdapter: + * Removes selectDefaultCustomerSource:completion: - Users must explicitly select their Payment Method of choice. + * Migrates detachSourceFromCustomer:completion:, attachSourceToCustomer:completion to detachPaymentMethodFromCustomer:completion:, attachPaymentMethodToCustomer:completion: + * Adds listPaymentMethodsForCustomerWithCompletion: - the Customer object doesn’t contain attached Payment Methods; you must fetch it from the Payment Methods API. +* STPPaymentContext now uses the new Payment Method APIs listed above instead of Source/Token, and returns the reworked STPPaymentResult containing a PaymentMethod. +* Migrates STPPaymentResult.source to paymentMethod of type STPPaymentMethod +* Deprecates STPPaymentIntentAction* types, replaced by STPIntentAction*. [#1208](https://github.com/stripe/stripe-ios/pull/1208) + * Deprecates `STPPaymentIntentAction`, replaced by `STPIntentAction` + * Deprecates `STPPaymentIntentActionType`, replaced by `STPIntentActionType` + * Deprecates `STPPaymentIntentActionRedirectToURL`, replaced by `STPIntentActionTypeRedirectToURL` +* Adds support for SetupIntents. See https://stripe.com/docs/payments/cards/saving-cards#saving-card-without-payment +* Adds support for 3DS2 authentication. See https://stripe.com/docs/mobile/ios/authentication + +## 15.0.1 2019-04-16 +* Adds configurable support for JCB (Apple Pay). [#1158](https://github.com/stripe/stripe-ios/pull/1158) +* Updates sample apps to use `PaymentIntents` and `PaymentMethods` where available. [#1159](https://github.com/stripe/stripe-ios/pull/1159) +* Changes `STPPaymentMethodCardParams` `expMonth` and `expYear` property types to `NSNumber *` to fix a bug using Apple Pay. [#1161](https://github.com/stripe/stripe-ios/pull/1161) + +## 15.0.0 2019-3-19 +* Renames all former references to 'PaymentMethod' to 'PaymentOption'. See [MIGRATING.md](/MIGRATING.md) for more details. [#1139](https://github.com/stripe/stripe-ios/pull/1139) + * Renames `STPPaymentMethod` to `STPPaymentOption` + * Renames `STPPaymentMethodType` to `STPPaymentOptionType` + * Renames `STPApplePaymentMethod` to `STPApplePayPaymentOption` + * Renames `STPPaymentMethodTuple` to `STPPaymentOptionTuple` + * Renames `STPPaymentMethodsViewController` to `STPPaymentOptionsViewController` + * Renames all properties, methods, comments referencing 'PaymentMethod' to 'PaymentOption' +* Rewrites `STPaymentMethod` and `STPPaymentMethodType` to match the [Stripe API](https://stripe.com/docs/api/payment_methods/object). [#1140](https://github.com/stripe/stripe-ios/pull/1140). +* Adds `[STPAPI createPaymentMethodWithParams:completion:]`, which creates a PaymentMethod. [#1141](https://github.com/stripe/stripe-ios/pull/1141) +* Adds `paymentMethodParams` and `paymentMethodId` to `STPPaymentIntentParams`. You can now confirm a PaymentIntent with a PaymentMethod. [#1142](https://github.com/stripe/stripe-ios/pull/1142) +* Adds `paymentMethodTypes` to `STPPaymentIntent`. +* Deprecates several Source-named properties, based on changes to the [Stripe API](https://stripe.com/docs/upgrades#2019-02-11). [#1146](https://github.com/stripe/stripe-ios/pull/1146) + * Deprecates `STPPaymentIntentParams.saveSourceToCustomer`, replaced by `savePaymentMethod` + * Deprecates `STPPaymentIntentsStatusRequiresSource`, replaced by `STPPaymentIntentsStatusRequiresPaymentMethod` + * Deprecates `STPPaymentIntentsStatusRequiresSourceAction`, replaced by `STPPaymentIntentsStatusRequiresAction` + * Deprecates `STPPaymentIntentSourceAction`, replaced by `STPPaymentIntentAction` + * Deprecates `STPPaymentSourceActionAuthorizeWithURL`, replaced by `STPPaymentActionRedirectToURL` + * Deprecates `STPPaymentIntent.nextSourceAction`, replaced by `nextAction` +* Added new localizations for the following languages [#1050](https://github.com/stripe/stripe-ios/pull/1050) + * Danish + * Spanish (Argentina/Latin America) + * French (Canada) + * Norwegian + * Portuguese (Brazil) + * Portuguese (Portugal) + * Swedish +* Deprecates `STPEphemeralKeyProvider`, replaced by `STPCustomerEphemeralKeyProvider`. We now allow for ephemeral keys that are not customer [#1131](https://github.com/stripe/stripe-ios/pull/1131) +* Adds CVC image for Amex cards [#1046](https://github.com/stripe/stripe-ios/pull/1046) +* Fixed `STPPaymentCardTextField.nextFirstResponderField` to never return nil [#1059](https://github.com/stripe/stripe-ios/pull/1059) +* Improves return key functionality for `STPPaymentCardTextField`, `STPAddCardViewController` [#1059](https://github.com/stripe/stripe-ios/pull/1059) +* Add postal code support for Saudi Arabia [#1127](https://github.com/stripe/stripe-ios/pull/1127) +* CVC field updates validity if card number/brand change [#1128](https://github.com/stripe/stripe-ios/pull/1128) + +## 14.0.0 2018-11-14 +* Changes `STPPaymentCardTextField`, which now copies the `cardParams` property. See [MIGRATING.md](/MIGRATING.md) for more details. [#1031](https://github.com/stripe/stripe-ios/pull/1031) +* Renames `STPPaymentIntentParams.returnUrl` to `STPPaymentIntentParams.returnURL`. [#1037](https://github.com/stripe/stripe-ios/pull/1037) +* Removes `STPPaymentIntent.returnUrl` and adds `STPPaymentIntent.nextSourceAction`, based on changes to the [Stripe API](https://stripe.com/docs/upgrades#2018-11-08). [#1038](https://github.com/stripe/stripe-ios/pull/1038) +* Adds `STPVerificationParams.document_back` property. [#1017](https://github.com/stripe/stripe-ios/pull/1017) +* Fixes bug in `STPPaymentMethodsViewController` where selected payment method changes back if it wasn't dismissed in the `didFinish` delegate method. [#1020](https://github.com/stripe/stripe-ios/pull/1020) + +## 13.2.0 2018-08-14 +* Adds `STPPaymentMethod` protocol implementation for `STPSource`. You can now call `image`/`templatedImage`/`label` on a source. [#976](https://github.com/stripe/stripe-ios/pull/976) +* Fixes crash in `STPAddCardViewController` with some prefilled billing addresses [#1004](https://github.com/stripe/stripe-ios/pull/1004) +* Fixes `STPPaymentCardTextField` layout issues on small screens [#1009](https://github.com/stripe/stripe-ios/pull/1009) +* Fixes hidden text fields in `STPPaymentCardTextField` from being read by VoiceOver [#1012](https://github.com/stripe/stripe-ios/pull/1012) +* Updates example app to add client-side metadata `charge_request_id` to requests to `example-ios-backend` [#1008](https://github.com/stripe/stripe-ios/pull/1008) + +## 13.1.0 2018-07-13 +* Adds `STPPaymentIntent` to support PaymentIntents. [#985](https://github.com/stripe/stripe-ios/pull/985), [#986](https://github.com/stripe/stripe-ios/pull/986), [#987](https://github.com/stripe/stripe-ios/pull/987), [#988](https://github.com/stripe/stripe-ios/pull/988) +* Reduce `NSURLSession` memory footprint. [#969](https://github.com/stripe/stripe-ios/pull/969) +* Fixes invalid JSON error when deleting `Card` from a `Customer`. [#992](https://github.com/stripe/stripe-ios/pull/992) + +## 13.0.3 2018-06-11 +* Fixes payment method label overlapping the checkmark, for Amex on small devices [#952](https://github.com/stripe/stripe-ios/pull/952) +* Adds EPS and Multibanco support to `STPSourceParams` [#961](https://github.com/stripe/stripe-ios/pull/961) +* Adds `STPBillingAddressFieldsName` option to `STPBillingAddressFields` [#964](https://github.com/stripe/stripe-ios/pull/964) +* Fixes crash in `STPColorUtils.perceivedBrightnessForColor` [#954](https://github.com/stripe/stripe-ios/pull/954) +* Applies recommended project changes for Xcode 9.4 [#963](https://github.com/stripe/stripe-ios/pull/963) +* Fixes `[Stripe handleStripeURLCallbackWithURL:url]` incorrectly returning `NO` [#962](https://github.com/stripe/stripe-ios/pull/962) + +## 13.0.2 2018-05-24 +* Makes iDEAL `name` parameter optional, also accepts empty string as `nil` [#940](https://github.com/stripe/stripe-ios/pull/940) +* Adjusts scroll view content offset behavior when focusing on a text field [#943](https://github.com/stripe/stripe-ios/pull/943) + +## 13.0.1 2018-05-17 +* Fixes an issue in `STPRedirectContext` causing some redirecting sources to fail in live mode due to prematurely dismissing the `SFSafariViewController` during the initial redirects. [#937](https://github.com/stripe/stripe-ios/pull/937) + +## 13.0.0 2018-04-26 +* Removes Bitcoin source support. See MIGRATING.md. [#931](https://github.com/stripe/stripe-ios/pull/931) +* Adds Masterpass support to `STPSourceParams` [#928](https://github.com/stripe/stripe-ios/pull/928) +* Adds community submitted Norwegian (nb) translation. Thank @Nailer! +* Fixes example app usage of localization files (they were not able to be tested in Finnish and Norwegian before) +* Silences STPAddress deprecation warnings we ignore to stay compatible with older iOS versions +* Fixes "Card IO" link in full SDK reference [#913](https://github.com/stripe/stripe-ios/pull/913) + +## 12.1.2 2018-03-16 +* Updated the "62..." credit card number BIN range to show a UnionPay icon + +## 12.1.1 2018-02-22 +* Fix issue with apple pay token creation in PaymentContext, introduced by 12.1.0. [#899](https://github.com/stripe/stripe-ios/pull/899) +* Now matches clang static analyzer settings with Cocoapods, so you won't see any more analyzer issues. [#897](https://github.com/stripe/stripe-ios/pull/897) + +## 12.1.0 2018-02-05 +* Adds `createCardSources` to `STPPaymentConfiguration`. If you enable this option, when your user adds a card in the SDK's UI, a card source will be created and attached to their Stripe Customer. If this option is disabled (the default), a card token is created. For more information on card sources, see https://stripe.com/docs/sources/cards + +## 12.0.1 2018-01-31 +* Adding Visa Checkout support to `STPSourceParams` [#889](https://github.com/stripe/stripe-ios/pull/889) + +## 12.0.0 2018-01-16 +* Minimum supported iOS version is now 9.0. + * If you need to support iOS 8, the last supported version is [11.5.0](https://github.com/stripe/stripe-ios/releases/tag/v11.5.0) +* Minimum supported Xcode version is now 9.0 +* `AddressBook` framework support has been removed. +* `STPRedirectContext` will no longer retain itself for the duration of the redirect, you must explicitly maintain a reference to it yourself. [#846](https://github.com/stripe/stripe-ios/pull/846) +* `STPPaymentConfiguration.requiredShippingAddress` now is a set of `STPContactField` objects instead of a `PKAddressField` bitmask. [#848](https://github.com/stripe/stripe-ios/pull/848) +* See MIGRATING.md for more information on any of the previously mentioned breaking API changes. +* Pre-built view controllers now layout properly on iPhone X in landscape orientation, respecting `safeAreaInsets`. [#854](https://github.com/stripe/stripe-ios/pull/854) +* Fixes a bug in `STPAddCardViewController` that prevented users in countries without postal codes from adding a card when `requiredBillingFields = .Zip`. [#853](https://github.com/stripe/stripe-ios/pull/853) +* Fixes a bug in `STPPaymentCardTextField`. When completely filled out, it ignored calls to `becomeFirstResponder`. [#855](https://github.com/stripe/stripe-ios/pull/855) +* `STPPaymentContext` now has a `largeTitleDisplayMode` property, which you can use to control the title display mode in the navigation bar of our pre-built view controllers. [#849](https://github.com/stripe/stripe-ios/pull/849) +* Fixes a bug where `STPPaymentContext`'s `retryLoading` method would not re-retrieve the customer object, even after calling `STPCustomerContext`'s `clearCachedCustomer` method. [#863](https://github.com/stripe/stripe-ios/pull/863) +* `STPPaymentContext`'s `retryLoading` method will now always attempt to retrieve a new customer object, regardless of whether a cached customer object is available. Previously, this method was only intended for recovery from a loading error; if a customer had already been retrieved, `retryLoading` would do nothing. [#863](https://github.com/stripe/stripe-ios/pull/863) +* `STPCustomerContext` has a new property: `includeApplePaySources`. It is turned off by default. [#864](https://github.com/stripe/stripe-ios/pull/864) +* Adds `UITextContentType` support. This turns on QuickType suggestions for the name, email, and address fields; and uses a better keyboard for Payment Card fields. [#870](https://github.com/stripe/stripe-ios/pull/870) +* Fixes a bug that prevented redirects to the 3D Secure authentication flow when it was optional. [#878](https://github.com/stripe/stripe-ios/pull/878) +* `STPPaymentConfiguration` now has a `stripeAccount` property, which can be used to make API requests on behalf of a Connected account. [#875](https://github.com/stripe/stripe-ios/pull/875) +* Adds `- [STPAPIClient createTokenWithConnectAccount:completion:]`, which creates Tokens for Connect Accounts: (optionally) accepting the Terms of Service, and sending information about the legal entity. [#876](https://github.com/stripe/stripe-ios/pull/876) +* Fixes an iOS 11 bug in `STPPaymentCardTextField` that blocked tapping on the number field while editing the expiration or CVC on narrow devices (4" screens). [#883](https://github.com/stripe/stripe-ios/pull/883) + +## 11.5.0 2017-11-09 +* Adds a new helper method to `STPSourceParams` for creating reusable Alipay sources. [#811](https://github.com/stripe/stripe-ios/pull/811) +* Silences spurious availability warnings when using Xcode9 [#823](https://github.com/stripe/stripe-ios/pull/823) +* Auto capitalizes currency code when using `paymentRequestWithMerchantIdentifier ` to improve compatibility with iOS 11 `PKPaymentAuthorizationViewController` [#829](https://github.com/stripe/stripe-ios/pull/829) +* Fixes a bug in `STPRedirectContext` which caused `SFSafariViewController`-based redirects to incorrectly dismiss when switching apps. [#833](https://github.com/stripe/stripe-ios/pull/833) +* Fixes a bug that incorrectly offered users the option to "Use Billing Address" on the shipping address screen when there was no existing billing address to fill in. [#834](https://github.com/stripe/stripe-ios/pull/834) + +## 11.4.0 2017-10-20 +* Restores `[STPCard brandFromString:]` method which was marked as deprecated in a recent version [#801](https://github.com/stripe/stripe-ios/pull/801) +* Adds `[STPBankAccount metadata]` and `[STPCard metadata]` read-only accessors and improves annotation for `[STPSource metadata]` [#808](https://github.com/stripe/stripe-ios/pull/808) +* Un-deprecates `STPBackendAPIAdapter` and all associated methods. [#813](https://github.com/stripe/stripe-ios/pull/813) +* The `STPBackendAPIAdapter` protocol now includes two optional methods, `detachSourceFromCustomer` and `updateCustomerWithShipping`. If you've implemented a class conforming to `STPBackendAPIAdapter`, you may add implementations of these methods to support deleting cards from a customer and saving shipping info to a customer. [#813](https://github.com/stripe/stripe-ios/pull/813) +* Adds the ability to set custom footers on view controllers managed by the SDK. [#792](https://github.com/stripe/stripe-ios/pull/792) +* `STPPaymentMethodsViewController` will now display saved card sources in addition to saved card tokens. [#810](https://github.com/stripe/stripe-ios/pull/810) +* Fixes a bug where certain requests would return a generic failed to parse response error instead of the actual API error. [#809](https://github.com/stripe/stripe-ios/pull/809) + +## 11.3.0 2017-09-13 +* Adds support for creating `STPSourceParams` for P24 source [#779](https://github.com/stripe/stripe-ios/pull/779) +* Adds support for native app-to-app Alipay redirects [#783](https://github.com/stripe/stripe-ios/pull/783) +* Fixes crash when `paymentContext.hostViewController` is set to a `UINavigationController` [#786](https://github.com/stripe/stripe-ios/pull/786) +* Improves support and compatibility with iOS 11 + * Explicitly disable code coverage generation for compatibility with Carthage in Xcode 9 [#795](https://github.com/stripe/stripe-ios/pull/795) + * Restore use of native "Back" buttons [#789](https://github.com/stripe/stripe-ios/pull/789) +* Changes and fixes methods on `STPCard`, `STPCardParams`, `STPBankAccount`, and `STPBankAccountParams` to bring card objects more in line with the rest of the API. See MIGRATING for further details. + * `STPCard` and `STPCardParams` [#760](https://github.com/stripe/stripe-ios/pull/760) + * `STPBankAccount` and `STPBankAccountParams` [#761](https://github.com/stripe/stripe-ios/pull/761) +* Adds nullability annotations to `STPPaymentMethod` protocol [#753](https://github.com/stripe/stripe-ios/pull/753) +* Improves the `[STPAPIResponseDecodable allResponseFields]` by removing all instances of `[NSNull null]` including ones that are nested. See MIGRATING.md. [#747](https://github.com/stripe/stripe-ios/pull/747) + +## 11.2.0 2017-07-27 +* Adds an option to allow users to delete payment methods from the `STPPaymentMethodsViewController`. Enabled by default but can disabled using the `canDeletePaymentMethods` property of `STPPaymentConfiguration`. + * Screenshots: https://user-images.githubusercontent.com/28276156/28131357-7a353474-66ee-11e7-846c-b38277d111fd.png +* Adds a postal code field to `STPPaymentCardTextField`, configurable with `postalCodeEntryEnabled` and `postalCodePlaceholder`. Disabled by default. +* `STPCustomer`'s `shippingAddress` property is now correctly annotated as nullable. +* Removed `STPCheckoutUnknownError`, `STPCheckoutTooManyAttemptsError`, and `STPCustomerContextMissingKeyProviderError`. These errors will no longer occur. + +## 11.1.0 2017-07-12 +* Adds stripeAccount property to `STPAPIClient`, set this to perform API requests on behalf of a connected account +* Fixes the `routingNumber` property of `STPBankAccount` so that it is populated when the information is available +* Adds iOS Objective-C Style Guide + +## 11.0.0 2017-06-27 +* We've greatly simplified the integration for `STPPaymentContext`. See MIGRATING.md. +* As part of this new integration, we've added a new class, `STPCustomerContext`, which will automatically prefetch your customer and cache it for a brief interval. We recommend initializing your `STPCustomerContext` before your user enters your checkout flow so their payment methods are loaded in advance. If in addition to using `STPPaymentContext`, you create a separate `STPPaymentMethodsViewController` to let your customer manage their payment methods outside of your checkout flow, you can use the same instance of `STPCustomerContext` for both. +* We've added a `shippingAddress` property to `STPUserInformation`, which you can use to pre-fill your user's shipping information. +* `STPPaymentContext` will now save your user's shipping information to their Stripe customer object. Shipping information will automatically be pre-filled from the customer object for subsequent checkouts. +* Fixes nullability annotation for `[STPFile stringFromPurpose:]`. See MIGRATING.md. +* Adds description implementations to all public models, for easier logging and debugging. +* The card autofill via SMS feature of `STPPaymentContext` has been removed. See MIGRATING.md. + +## 10.2.0 2017-06-19 +* We've added a `paymentCountry` property to `STPPaymentContext`. This affects the countryCode of Apple Pay payments, and defaults to "US". You should set this to the country your Stripe account is in. +* `paymentRequestWithMerchantIdentifier:` has been deprecated. See MIGRATING.md +* If the card.io framework is present in your app, `STPPaymentContext` and `STPAddCardViewController` will show a "scan card" button. +* `STPAddCardViewController` will now attempt to auto-fill the users city and state from their entered Zip code (United States only) +* Polling for source object updates is deprecated. Check https://stripe.com/docs for the latest best practices on how to integrate with the sources API using webhooks. +* Fixes a crash in `STPCustomerDeserializer` when both data and error are nil. +* `paymentMethodsViewController:didSelectPaymentMethod:` is now optional. +* Updates the example apps to use Alamofire. + +## 10.1.0 2017-05-05 +* Adds STPRedirectContext, a helper class for handling redirect sources. +* STPAPIClient now supports tokenizing a PII number and uploading images. +* Updates STPPaymentCardTextField's icons to match Elements on the web. When the card number is invalid, the field will now display an error icon. +* The alignment of the new brand icons has changed to match the new CVC and error icons. If you use these icons via `STPImageLibrary`, you may need to adjust your layout. +* STPPaymentCardTextField's isValid property is now KVO-observable. +* When creating STPSourceParams for a SEPA debit source, address fields are now optional. +* `STPPaymentMethodsViewControllerDelegate` now has a separate `paymentMethodsViewControllerDidCancel:` callback, differentiating from successful method selections. You should make sure to also dismiss the view controller in that callback +* Because collecting some basic data on tokenization helps us detect fraud, we've removed the ability to disable analytics collection using `[Stripe disableAnalytics]`. + +## 10.0.1 2017-03-16 +* Fixes a bug where card sources didn't include the card owner's name. +* Fixes an issue where STPPaymentMethodsViewController didn't reload after adding a new payment method. + +## 10.0.0 2017-03-06 +* Adds support for creating, retrieving, and polling Sources. You can enable any payment methods available to you in the Dashboard. + * https://stripe.com/docs/mobile/ios/sources + * https://dashboard.stripe.com/account/payments/settings +* Updates the Objective-C example app to include example integrations using several different payment methods. +* Updates `STPCustomer` to include `STPSource` objects in its `sources` array if a customer has attached sources. +* Removes methods deprecated in Version 6.0. +* Fixes property declarations missing strong/nullable identifiers. + +## 9.4.0 2017-02-03 +* Adds button to billing/shipping entry screens to fill address information from the other one. +* Fixes and unifies view controller behavior around theming and nav bars. +* Adds month validity check to `validationStateForExpirationYear` +* Changes some Apple Pay images to better conform to official guidelines. +* Changes STPPaymentCardTextField's card number placeholder to "4242..." +* Updates STPPaymentCardTextField's CVC placeholder so that it changes to "CVV" for Amex cards + +## 9.3.0 2017-01-05 +* Fixes a regression introduced in v9.0.0 in which color in STPTheme is used as the background color for UINavigationBar + * Note: This will cause navigation bar theming to work properly as described in the Stripe iOS docs, but you may need to audit your custom theme settings if you based them on the actual behavior of 9.0-9.2 +* If the navigation bar has a theme different than the view controller's theme, STP view controllers will use the bar's theme to style it's UIBarButtonItems +* Adds a fallback to using main bundle for localized strings lookup if locale is set to a language the SDK doesn't support +* Adds method to get a string of a card brand from `STPCardBrand` +* Updated description of how to run tests in README +* Fixes crash when user cancels payment before STPBackendAPIAdapter methods finish +* Fixes bug where country picker wouldn't update when first selected. + + +## 9.2.0 2016-11-14 +* Moves FBSnapshotTestCase dependency to Cartfile.private. No changes if you are not using Carthage. +* Adds prebuilt UI for collecting shipping information. + +## 9.1.0 2016-11-01 +* Adds localized strings for 7 languages: de, es, fr, it, ja, nl, zh-Hans. +* Slight redesign to card/billing address entry screen. +* Improved internationalization for State/Province/County address field. +* Adds new Mastercard 2-series BIN ranges. +* Fixes an issue where callbacks may be run on the wrong thread. +* Fixes UIAppearance compatibility in STPPaymentCardTextField. +* Fixes a crash when changing application language via an Xcode scheme. + +## 9.0.0 2016-10-04 +* Change minimum requirements to iOS 8 and Xcode 8 +* Adds "app extension API only" support. +* Updates Swift example app to Swift 3 +* Various fixes to ObjC example app + +## 8.0.7 2016-09-15 +* Add ability to set currency for managed accounts when adding card +* Fix broken links for Privacy Policy/Terms of Service for Remember Me feature +* Sort countries in picker alphabetically by name instead of ISO code +* Make "County" field optional on billing address screen. +* PKPayment-related methods are now annotated as available in iOS8+ only +* Optimized speed of input sanitation methods (thanks @kballard!) + +## 8.0.6 2016-09-01 +* Improved internationalization on billing address forms + * Users in countries that don't use postal codes will no longer see that field. + * The country field is now auto filled in with the phone's region + * Changing the selected country will now live update other fields on the form (such as State/County or Zip/Postal Code). +* Fixed an issue where certain Cocoapods configurations could result in Stripe resource files being used in place of other frameworks' or the app's resources. +* Fixed an issue where when using Apple Pay, STPPaymentContext would fire two `didFinishWithStatus` messages. +* Fixed the `deviceSupportsApplePay` method to also check for Discover cards. +* Removed keys from Stripe.bundle's Info.plist that were causing iTunes Connect to sometimes error on app submission. + +## 8.0.5 2016-08-26 +* You can now optionally use an array of PKPaymentSummaryItems to set your payment amount, if you would like more control over how Apple Pay forms are rendered. +* Updated credit card and Apple Pay icons. +* Fixed some images not being included in the resources bundle target. +* Non-US locales now have an alphanumeric keyboard for postal code entry. +* Modals now use UIModalPresentationStyleFormSheet. +* Added more accessibility labels. +* STPPaymentCardTextField now conforms to UIKeyInput (thanks @theill). + +## 8.0.4 2016-08-01 +* Fixed an issue with Apple Pay payments not using the correct currency. +* View controllers now update their status bar and scroll view indicator styles based on their theme. +* SMS code screen now offers to paste copied codes. + +## 8.0.3 2016-07-25 +* Fixed an issue with some Cocoapods installations + +## 8.0.2 2016-07-09 +* Fixed an issue with custom theming of Stripe UI + +## 8.0.1 2016-07-06 +* Fixed error handling in STPAddCardViewController + +## 8.0.0 2016-06-30 +* Added prebuilt UI for collecting and managing card information. + +## 7.0.2 2016-05-24 +* Fixed an issue with validating certain Visa cards. + +## 7.0.1 2016-04-29 +* Added Discover support for Apple Pay +* Add the now-required `accountHolderName` and `accountHolderType` properties to STPBankAccountParams +* We now record performance metrics for the /v1/tokens API - to disable this behavior, call [Stripe disableAnalytics]. +* You can now demo the SDK more easily by running `pod try stripe`. +* This release also removes the deprecated Checkout functionality from the SDK. + +## 6.2.0 2016-02-05 +* Added an `additionalAPIParameters` field to STPCardParams and STPBankAccountParams for sending additional values to the API - useful for beta features. Similarly, added an `allResponseFields` property to STPToken, STPCard, and STPBankAccount for accessing fields in the response that are not yet reflected in those classes' @properties. + +## 6.1.0 2016-01-21 +* Renamed card on STPPaymentCardTextField to cardParams. +* You can now set an STPPaymentCardTextField's contents programmatically by setting cardParams to an STPCardParams object. +* Added delegate methods for responding to didBeginEditing events in STPPaymentCardTextField. +* Added a UIImage category for accessing our card icon images +* Fixed deprecation warnings for deployment targets >= iOS 9.0 + +## 6.0.0 2015-10-19 +* Splits logic in STPCard into 2 classes - STPCard and STPCardParams. STPCardParams is for making requests to the Stripe API, while STPCard represents the response (you'll almost certainly want just to replace any usage of STPCard in your app with STPCardParams). This also applies to STPBankAccount and the newly-created STPBankAccountParams. +* Version 6.0.1 fixes a minor Cocoapods issue. + +## 5.1.0 2015-08-17 +* Adds STPPaymentCardTextField, a new version of github.com/stripe/PaymentKit featuring many bugfixes. It's useful if you need a pre-built credit card entry form. +* Adds the currency param to STPCard for those using managed accounts & debit card payouts. +* Versions 5.1.1 and 5.1.2 fix minor issues with CocoaPods installation +* Version 5.1.3 contains bug fixes for STPPaymentCardTextField. +* Version 5.1.4 improves compatibility with iOS 9. + +## 5.0.0 2015-08-06 +* Fix an issue with Carthage installation +* Fix an issue with CocoaPods frameworks +* Deprecate native Stripe Checkout + +## 4.0.1 2015-05-06 +* Fix a compiler warning +* Versions 4.0.1 and 4.0.2 fix minor issues with CocoaPods and Carthage installation. + +## 4.0.0 2015-05-06 +* Remove STPPaymentPresenter +* Support for latest ApplePayStubs +* Add nullability annotations to improve Swift support (note: this now requires Swift 1.2) +* Bug fixes + +## 3.1.0 2015-01-19 +* Add support for native Stripe Checkout, as well as STPPaymentPresenter for automatically using Checkout as a fallback for Apple Pay +* Add OSX support, including Checkout +* Add framework targets and Carthage support +* It's safe to remove the STRIPE_ENABLE_APPLEPAY compiler flag after this release. + +## 3.0.0 2015-01-05 +* Migrate code into STPAPIClient +* Add 'brand' and 'funding' properties to STPCard + +## 2.2.2 2014-11-17 +* Add bank account tokenization methods + +## 2.2.1 2014-10-27 +* Add billing address fields to our Apple Pay API +* Various bug fixes and code improvements + +## 2.2.0 2014-10-08 +* Move Apple Pay testing functionality into a separate project, ApplePayStubs. For more info, see github.com/stripe/ApplePayStubs. +* Improve the provided example app + +## 2.1.0 2014-10-07 +* Remove token retrieval API method +* Refactor functional tests to use new XCTestCase functionality + +## 2.0.3 2014-09-24 +* Group ApplePay code in a CocoaPods subspec + +## 2.0.2 2014-09-24 +* Move ApplePay code behind a compiler flag to avoid warnings from Apple when accidentally including it + +## 2.0.1 2014-09-18 +* Fix some small bugs related to ApplePay and iOS8 + +## 2.0 2014-09-09 +* Add support for native payments via Pay + +## 1.2 2014-08-21 +* Removed PaymentKit as a dependency. If you'd like to use it, you may still do so by including it separately. +* Removed STPView. PaymentKit provides a near-identical version of this functionality if you need to migrate. +* Improve example project +* Various code fixes + +## 1.1.4 2014-05-22 +* Fixed an issue where tokenization requests would fail under iOS 6 due to SSL certificate verification + +## 1.1.3 2014-05-12 +* Send some basic version and device details with requests for debugging. +* Added -description to STPToken +* Fixed some minor code nits +* Modernized code + +## 1.1.2 2014-04-21 +* Added test suite for SSL certificate expiry/revocation +* You can now set STPView's delegate from Interface Builder + +## 1.1.1 2014-04-14 +* API methods now verify the server's SSL certificate against a preset blacklist. +* Fixed some bugs with SSL verification. +* Note: This version now requires the `Security` framework. You will need to add this to your app if you're not using CocoaPods. + +## 1.0.4 2014-03-24 + +* Upgraded tests from OCUnit to XCTest +* Fixed an issue with the SenTestingKit dependency +* Removed some dead code + +## 1.0.3 2014-03-21 + +* Fixed: Some example files had target memberships set for StripeiOS and iOSTest. +* Fixed: The example publishable key was expired. +* Fixed: Podspec did not pass linting. +* Some fixes for 64-bit. +* Many improvements to the README. +* Fixed example under iOS 7 +* Some source code cleaning and modernization. + +## 1.0.2 2013-09-09 + +* Add exceptions for null successHandler and errorHandler. +* Added the ability to POST the created token to a URL. +* Made STPCard properties nonatomic. +* Moved PaymentKit to be a submodule; added to Podfile as a dependency. +* Fixed some warnings caught by the static analyzer (thanks to jcjimenez!) + +## 1.0.1 2012-11-16 + +* Add CocoaPods support +* Change directory structure of bindings to make it easier to install + +## 1.0.0 2012-11-16 + +* Initial release + +Special thanks to: Todd Heasley, jcjimenez. diff --git a/MIGRATING.md b/MIGRATING.md new file mode 100644 index 00000000..77a249ac --- /dev/null +++ b/MIGRATING.md @@ -0,0 +1,324 @@ +## Migration Guides +### Migrating from versions < 24.0.0 + +#### Basic integration +- Basic Integration has been removed. [Please use Mobile Payment Element instead](https://docs.stripe.com/payments/mobile/migrating-to-mobile-payment-element-from-basic-integration). + +#### PaymentSheet +PaymentSheet displays payment methods in either a vertical or horizontal layout. Prior to this major version, PaymentSheet defaulted to a horizontal layout. Now, Stripe optimizes the layout automatically. To set a specific layout instead, set the `PaymentSheet.Configuration.paymentMethodLayout` property to either `.horizontal` or `.vertical`. + +``` +var configuration = PaymentSheet.Configuration() +configuration.paymentMethodLayout = .horizontal, .vertical, or .automatic +``` + +![image](https://github.com/user-attachments/assets/d4592a2b-9af7-4fb5-813f-6a3afdb4ec28) + + +### Migrating from versions < 23.0.0 +* The `Stripe` module is now split between `StripePaymentSheet`, `StripePayments`, and `StripePaymentsUI`. Some manual changes may be required. Migration instructions are available at [https://stripe.com/docs/mobile/ios/sdk-23-migration](https://stripe.com/docs/mobile/ios/sdk-23-migration). +* [Changed] If you use PaymentSheet, you must now `import StripePaymentSheet`. PaymentSheet users no longer need to import the `Stripe` module. +* [Changed] The minimum iOS version is now 13.0. If you'd like to deploy for iOS 12.0, please use Stripe SDK 22.8.4. +* [Changed] STPPaymentCardTextField's `cardParams` parameter has been deprecated in favor of `paymentMethodParams`, making it easier to include the postal code from the card field. If you need to access the `STPPaymentMethodCardParams`, use `.paymentMethodParams.card`. + * Note that `.paymentMethodParams` returns a copy, so `paymentMethodParams.card` should not be set directly. If you need to set the card information, set `.paymentMethodParams` to a new STPPaymentMethodParams: +``` +cardField.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) +``` + +### Migrating from versions < 22.8.0 +* `PaymentSheet.reset()` has been renamed to `PaymentSheet.resetCustomer()`. If calling the former method, follow the warning in Xcode and apply the suggested fix-it. + +### Migrating from versions < 22.2.0 +* `StripeConnections` SDK has been renamed to `StripeFinancialConnections`. If you included `StripeConnections` to support ACH Direct Debit payments, you will need to rename the dependency to `StripeFinancialConnections`. If you are manually installing `StripeConnections`, you will need to remove the old `StripeConnections.xcframework` and include the new `StripeFinancialConnections.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/22.2.0) for version 22.2.0 of the SDK. + +### Migrating from versions < 22.0.0 +* The minimum iOS version is now 12.0. If you'd like to deploy for iOS 11.0, please use Stripe SDK 21.12.0. +* `IdentityVerificationSheet` now has an availability requirement of iOS 14.3 on its initializer instead of the `present` method. If your app supports iOS versions < 14.3, you will need to add an availability check for iOS 14.3 before initializing the sheet. + +### Migrating from versions < 21.12.0 +* `Stripe` now requires `StripeApplePay`. If you are manually installing `Stripe`, you will need to include `StripeApplePay.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/21.12.0) for version 21.12.0 of the SDK. If you are using CocoaPods or Swift Package Manager, these dependencies will be imported automatically. + +### Migrating from versions < 21.10.0 +* `StripeIdentity` now requires `StripeCameraCore`. If you are manually installing `StripeIdentity`, you will need to include `StripeCameraCore.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/21.10.0) for version 21.10.0 of the SDK. If you are using CocoaPods or Swift Package Manager, these dependencies will be imported automatically. + +### Migrating from versions < 21.9.0 +* `Stripe` and `StripeIdentity` now require `StripeUICore`. If you are manually installing `Stripe` or `StripeIdentity`, you will need to include `StripeUICore.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/21.9.0) for version 21.9.0 of the SDK. If you are using CocoaPods or Swift Package Manager, these dependencies will be imported automatically. + +### Migrating from versions < 21.8.1 +* The `Stripe` module now requires `StripeCore`. If you are manually installing the Stripe SDK, you will need to include `StripeCore.xcframework`, which can be found in the [release assets](https://github.com/stripe/stripe-ios/releases/tag/21.8.1) for version 21.8.1 of the SDK. If you are using CocoaPods or Swift Package Manager, these dependencies will be imported automatically. + +### Migrating from versions < 21.4.0 +* STPPaymentHandler now presents its SFSafariViewController using the `.overFullScreen` presentation style by default. To select a different style, implement the `STPAuthenticationContext.configureSafariViewController(_:)` function in your `STPAuthenticationContext`. + +### Migrating from versions < 21.2.0 +* Stripe3DS2 is now a separate component for Carthage users. You must embed both Stripe.xcframework and Stripe3DS2.xcframework in your app. + +### Migrating from versions < 21.0.0 +* The SDK is now written in Swift, and some manual changes are required. Migration instructions are available at [https://stripe.com/docs/mobile/ios/sdk-21-migration](https://stripe.com/docs/mobile/ios/sdk-21-migration). + +### Migrating from versions < 20.1.0 +* Swift Package Manager users may need to remove and re-add Stripe from the `Frameworks, Libraries, and Embedded Content` section of your target's settings after updating. +* Swift Package Manager users with Xcode 12.0 may need to use a [workaround](https://github.com/stripe/stripe-ios/issues/1673) for a code signing issue. This is fixed in Xcode 12.2. + +### Migrating from versions < 20.0.0 +* The minimum iOS version is now 11.0. If you'd like to deploy for iOS 10.0, please use Stripe SDK 19.4.0. +* Card.io is no longer supported. To enable our built-in [card scanning](https://github.com/stripe/stripe-ios#card-scanning) beta, set the `cardScanningEnabled` flag on STPPaymentConfiguration. +* Catalyst support is out of beta, and now requires Swift Package Manager with Xcode 12 or Cocoapods 1.10. + +### Migrating from versions < 19.4.0 +* `metadata` fields are no longer populated on retrieved Stripe API objects and must be fetched on your server using your secret key. If this is causing issues with your deployed app versions please reach out to [Stripe Support](https://support.stripe.com/?contact=true). These fields have been marked as deprecated and will be removed in a future SDK version. + +### Migrating from versions < 19.3.0 +* `STPAUBECSFormView` now inherits from `UIView` instead of `UIControl` + +### Migrating from versions < 19.2.0 +* The `STPApplePayContext` 'applePayContext:didCreatePaymentMethod:completion:` delegate method now includes paymentInformation: 'applePayContext:didCreatePaymentMethod:paymentInformation:completion:`. + + +### Migrating from versions < 19.0.0 +#### `publishableKey` and `stripeAccount` changes +* Deprecates `publishableKey` and `stripeAccount` properties of `STPPaymentConfiguration`. + * If you used `STPPaymentConfiguration.sharedConfiguration` to set `publishableKey` and/or `stripeAccount`, use `STPAPIClient.sharedClient` instead. + * If you passed a STPPaymentConfiguration instance to an SDK component, you should instead create an STPAPIClient, set publishableKey on it, and set the SDK component's APIClient property. +* The SDK now uses `STPAPIClient.sharedClient` to make API requests by default. + +This changes the behavior of the following classes, which previously used API client instances configured from `STPPaymentConfiguration.shared`: `STPCustomerContext`, `STPPaymentOptionsViewController`, `STPAddCardViewController`, `STPPaymentContext`, `STPPinManagementService`, `STPPushProvisioningContext`. + +You are affected by this change if: + +1. You use `stripeAccount` to work with your Connected accounts +2. You use one of the above affected classes +3. You set different `stripeAccount` values on `STPPaymentConfiguration` and `STPAPIClient`, i.e. `STPPaymentConfiguration.shared.stripeAccount != STPAPIClient.shared.stripeAccount` + +If all three of the above conditions are true, you must update your integration! The SDK used to send `STPPaymentConfiguration.shared.stripeAccount`, and will now send `STPAPIClient.shared.stripeAccount`. + +For example, if you are a Connect user who stores Payment Methods on your platform, but clones PaymentMethods to a connected account and creates direct charges on that connected account i.e. if: + +1. You never set `STPPaymentConfiguration.shared.stripeAccount` +2. You set `STPAPIClient.shared.stripeAccount` + +We recommend you do the following: + +``` + // By default, you don't want the SDK to pass stripeAccount + STPAPIClient.shared().publishableKey = "pk_platform" + STPAPIClient.shared().stripeAccount = nil + + // You do want the SDK to pass stripeAccount when it makes payments directly on your connected account, so + // you create a separate APIClient instance... + let connectedAccountAPIClient = STPAPIClient(publishableKey: "pk_platform") + + // ...set stripeAccount on it... + connectedAccountAPIClient.stripeAccount = "your connected account's id" + + // ...and either set the relevant SDK components' apiClient property to your custom APIClient instance: + STPPaymentHandler.shared().apiClient = connectedAccountAPIClient // e.g. if you are using PaymentIntents + + // ...or use it directly to make API requests with `stripeAccount` set: + connectedAccountAPIClient.createToken(withCard:...) // e.g. if you are using Tokens + Charges +``` +#### Postal code changes +* The user's postal code is now collected by default in countries that support postal codes. We always recommend collecting a postal code to increase card acceptance rates and reduce fraud. To disable this behavior: + * For STPPaymentContext and other full-screen UI, set your `STPPaymentConfiguration`'s `.requiredBillingAddressFields` to `STPBillingAddressFieldsNone`. + * For a PKPaymentRequest, set `.requiredBillingContactFields` to an empty set. If your app supports iOS 10, also set `.requiredBillingAddressFields` to `PKAddressFieldNone`. + * For STPPaymentCardView, set `.postalCodeEntryEnabled` to `NO`. +* Users may now enter spaces, dashes, and uppercase letters into the postal code field in situations where the user has not explicitly selected a country. This allows users with non-US addreses to enter their postal code. +* `STPBillingAddressFieldsZip` has been renamed to `STPBillingAddressFieldsPostalCode`. +#### Localization changes +* All [Stripe Error messages](https://stripe.com/docs/api/errors#errors-message) are now localized + based on the device locale. + + For example, when retrieving a SetupIntent with a nonexistent `id` + when the device locale is set to `Locale.JAPAN`, the error message will now be localized. + ``` + // before - English + "No such setupintent: seti_invalid123" + + // after - Japanese + "そのような setupintent はありません : seti_invalid123" + ``` + +### Migrating from versions < 18.0.0 +* Some error messages from the Payment Intents API are now localized to the user's display language. If your application's logic depends on specific `message` strings from the Stripe API, please use the error [`code`](https://stripe.com/docs/error-codes) instead. +* `STPPaymentResult` may contain a `paymentMethodParams` instead of a `paymentMethod` when using single-use payment methods such as FPX. Because of this, `STPPaymentResult.paymentMethod` is now nullable. Instead of setting the `paymentMethodId` manually on your `paymentIntentParams`, you may now call `paymentIntentParams.configure(with result: STPPaymentResult)`: +``` +// 17.0.0 +paymentIntentParams.paymentMethodId = paymentResult.paymentMethod.stripeId + +// 18.0.0 +paymentIntentParams.configure(with: paymentResult) +``` +* `STPPaymentOptionTypeAll` has been renamed to `STPPaymentOptionTypeDefault`. This option will not include FPX or future optional payment methods. +* The minimum iOS version is now 10.0. If you'd like to deploy for iOS 9.0, please use Stripe SDK 17.0.2. + +### Migrating from versions < 17.0.0 +* The API version has been updated from 2015-10-12 to 2019-05-16. CHANGELOG.md has details on the changes made, which includes breaking changes for `STPConnectAccountParams` users. Your backend Stripe API version should be sufficiently decoupled from the SDK's so that keeping their versions in sync is not required, and no further action is required to migrate to this version of the SDK. +* For STPPaymentContext users: the completion block type in `paymentContext:didCreatePaymentResult:completion:` has changed to `STPPaymentStatusBlock`, to let you inform the context that the user has cancelled. + +### Migrating from versions < 16.0.0 +* The following have been migrated from Source/Token to PaymentMethod. If you have integrated with any of these things, you must also migrate to PaymentMethod and the Payment Intent API. See https://stripe.com/docs/payments/payment-intents/migration. See CHANGELOG.md for more details. + * UI components + * STPPaymentCardTextField + * STPAddCardViewController + * STPPaymentOptionsViewController + * PaymentContext + * STPPaymentContext + * STPCustomerContext + * STPBackendAPIAdapter + * STPPaymentResult + * Standard Integration example project +* `STPPaymentIntentAction*` types have been renamed to `STPIntentAction*`. Xcode should offer a deprecation warning & fix-it to help you migrate. +* `STPPaymentHandler` supports 3DS2 authentication, and is recommended instead of `STPRedirectContext`. See https://stripe.com/docs/mobile/ios/authentication + +### Migrating from versions < 15.0.0 +* "PaymentMethod" has a new meaning: https://stripe.com/docs/api/payment_methods/object. All things referring to "PaymentMethod" have been renamed to "PaymentOption" (see CHANGELOG.md for the full list). `STPPaymentMethod` and `STPPaymentMethodType` have been rewritten to match this new API object. +* PaymentMethod succeeds Source as the recommended way to charge customers. In this vein, several 'Source'-named things have been deprecated, and replaced with 'PaymentMethod' equivalents. For example, `STPPaymentIntentsStatusRequiresSource` is replaced by `STPPaymentIntentsStatusRequiresPaymentMethod` (see CHANGELOG.md for the full list). Following the deprecation warnings & fix-its will be enough to migrate your code - they've simply been renamed, and will continue to work for Source-based flows. + +### Migrating from versions < 14.0.0 +* `STPPaymentCardTextField` now copies the `STPCardParams` object when setting/getting the `cardParams` property, instead of sharing the object with the caller. + * Changes to the `STPCardParams` object after setting `cardParams` no longer mutate the object held by the `STPPaymentCardTextField` + * Changes to the object returned by `STPPaymentCardTextField.cardParams` no longer mutate the object held by the `STPPaymentCardTextField` + * This is a breaking change for code like: `paymentCardTextField.cardParams.name = @"Jane Doe";` +* `STPPaymentIntentParams.returnUrl` has been renamed to `STPPaymentIntentParams.returnURL`. Xcode should offer a deprecation warning & fix-it to help you migrate. +* `STPPaymentIntent.returnUrl` has been removed, because it's no longer a property of the PaymentIntent. When the PaymentIntent status is `.requiresSourceAction`, and the `nextSourceAction.type` is `.authorizeWithURL`, you can find the return URL at `nextSourceAction.authorizeWithURL.returnURL`. + +### Migrating from versions < 13.1.0 + * The SDK now supports PaymentIntents with `STPPaymentIntent`, which use `STPRedirectContext` in the same way that `STPSource` does + * `STPRedirectContextCompletionBlock` has been renamed to `STPRedirectContextSourceCompletionBlock`. It has the same signature, and Xcode should offer a deprecation warning & fix-it to help you migrate. + +### Migrating from versions < 13.0.0 +* Remove Bitcoin source support because Stripe no longer processes Bitcoin payments: https://stripe.com/blog/ending-bitcoin-support + * Sources can no longer have a "STPSourceTypeBitcoin" source type. These sources will now be interpreted as "STPSourceTypeUnknown". + * You can no longer `createBitcoinParams`. Please use a different payment method. + +### Migrating from versions < 12.0.0 +* The SDK now requires iOS 9+ and Xcode version 9+. If you need to support iOS 8 or Xcode 8, the last supported version is [11.5.0](https://github.com/stripe/stripe-ios/releases/tag/v11.5.0) +* `STPPaymentConfiguration.requiredShippingAddress` now is a set of `STPContactField` objects instead of a `PKAddressField` bitmask. + * Most of the previous `PKAddressField` constants have matching `STPContactField` constants. To convert your code, switch to passing in a set of the matching constants + * Example: `(PKAddressField)(PKAddressFieldName|PKAddressFieldPostalAddress)` becomes `[NSSet setwithArray:@[STPContactFieldName, STPContactFieldPostalAddress]]`) + * Anywhere you were using `PKAddressFieldNone` you can now simply pass in `nil` + * If you were using `PKAddressFieldAll`, you must switch to manually listing all the fields that you want. + * The new constants also correspond to and work similarly to Apple's new `PKContactField` values. +* `AddressBook` framework support has been removed. If you were using AddressBook related functionality, you must switch over to using the `Contacts` framework. +* `STPRedirectContext` will no longer retain itself for the duration of the redirect. If you were relying on this functionality, you must change your code to explicitly maintain a reference to it. + +### Migrating from versions < 11.4.0 +* The `STPBackendAPIAdapter` protocol and all associated methods are no longer deprecated. We still recommend using `STPCustomerContext` to update a Stripe customer object on your behalf, rather than using your own implementation of `STPBackendAPIAdapter`. + +### Migrating from versions < 11.3.0 +* Changes to `STPCard`, `STPCardParams`, `STPBankAccount`, and `STPBankAccountParams` + * `STPCard` no longer subclasses from `STPCardParams`. You must now specifically create `STPCardParams` objects to create new tokens. + * `STPBankAccount` no longer subclasses from `STPBankAccountParams`. + * You can no longer directly create `STPCard` objects, you should only use ones that have been decoded from Stripe API responses via `STPAPIClient`. + * All `STPCard` and `STPBankAccount` properties have been made readonly. + * Broken out individual address properties on `STPCard` and `STPCardParams` have been deprecated in favor of the grouped `address` property. +* The value of `[STPAPIResponseDecodable allResponseFields]` is now completely (deeply) filtered to not contain any instances of `[NSNull null]`. Previously, only `[NSNull null]` one level deep (shallow) were removed. + +### Migrating from versions < 11.2.0 +* `STPCustomer`'s `shippingAddress` property is now correctly annotated as nullable. Its type is an optional (`STPAddress?`) in Swift. + +### Migrating from versions < 11.0.0 +- We've greatly simplified the integration for `STPPaymentContext`. In order to migrate to the new `STPPaymentContext` integration using ephemeral keys, you'll need to: + 1. On your backend, add a new endpoint that creates an ephemeral key for the Stripe customer associated with your user, and returns its raw JSON. Note that you should _not_ remove the 3 endpoints you added for your initial PaymentContext integration until you're ready to drop support for previous versions of your app. + 2. In your app, make your API client class conform to `STPEphemeralKeyProvider` by adding a method that requests an ephemeral key from the endpoint you added in (1). + 3. In your app, remove any references to `STPBackendAPIAdapter`. Your API client class will no longer need to conform to `STPBackendAPIAdapter`, and you can delete the `retrieveCustomer`, `attachSourceToCustomer`, and `selectDefaultCustomerSource` methods. + 4. Instead of using the initializers for `STPPaymentContext` or `STPPaymentMethodsViewController` that take an `STPBackendAPIAdapter` parameter, you should use the new initializers that take an `STPCustomerContext` parameter. You'll need to set up your instance of `STPCustomerContext` using the key provider you set up in (2). +- For a more detailed overview of the new integration, you can refer to our tutorial at https://stripe.com/docs/mobile/ios/standard +- `[STPFile stringFromPurpose:]` now returns `nil` for `STPFilePurposeUnknown`. Will return a non-nil value for all other `STPFilePurpose`. +- We've removed the `email` and `phone` properties in `STPUserInformation`. You can pre-fill this information in the shipping form using the new `shippingAddress` property. +- The SMS card fill feature has been removed from `STPPaymentContext`, as well as the associated `smsAutofillDisabled` configuration option (ie it will now always behave as if it is disabled). + +### Migrating from versions < 10.2.0 +- `paymentRequestWithMerchantIdentifier:` has been deprecated. You should instead use `paymentRequestWithMerchantIdentifier:country:currency:`. Apple Pay is now available in many countries and currencies, and you should use the appropriate values for your business. +- We've added a `paymentCountry` property to `STPPaymentContext`. This affects the countryCode of Apple Pay payments, and defaults to "US". You should set this to the country your Stripe account is in. +- Polling for source object updates is deprecated. Check https://stripe.com/docs for the latest best practices on how to integrate with the sources API using webhooks. +- `paymentMethodsViewController:didSelectPaymentMethod:` is now optional. If you have an empty implementation of this method, you can remove it. + +### Migrating from versions < 10.1.0 + +- STPPaymentMethodsViewControllerDelegate now has a separate `paymentMethodsViewControllerDidCancel:` callback, differentiating from successful method selections. You should make sure to also dismiss the view controller in that callback. + +### Migrating from versions < 10.0 + +- Methods deprecated in Version 6.0 have now been removed. +- The `STPSource` protocol has been renamed `STPSourceProtocol`. +- `STPSource` is now a model object representing a source from the Stripe API. https://stripe.com/docs/sources +- `STPCustomer` will now include `STPSource` objects in its `sources` array if a customer has attached sources. +- `STPErrorCode` and `STPCardErrorCode` are now first class Swift enums (before, their types were `Int` and `String`, respectively) + +### Migrating from versions < 9.0 + +Version 9.0 drops support for iOS 7.x and Xcode 7.x. If you need to support iOS or Xcode versions below 8.0, the last compatible Stripe SDK release is version 8.0.7. + +### Migrating from versions < 6.0 + +6.0 moves most of the contents of `STPCard` into a new class, `STPCardParams`, which represents a request to the Stripe API. `STPCard` now only refers to responses from the Stripe API. Most apps should be able to simply replace all usage of `STPCard` with `STPCardParams` - you should only use `STPCard` if you're dealing with an API response, e.g. a card attached to an `STPToken`. This renaming has been done in a way that will avoid breaking changes, although using `STPCard`s to make requests to the Stripe API will produce deprecation warnings. + +### Migrating from versions < 5.0 + +5.0 deprecates our native Stripe Checkout adapters. If you were using these, we recommend building your own credit card form instead. If you need help with this, please contact support@stripe.com. + +### Migrating from versions < 3.0 + +Before version 3.0, most token-creation methods were class methods on the `Stripe` class. These are now all instance methods on the `STPAPIClient` class. Where previously you might write +```objective-c +[Stripe createTokenWithCard:card publishableKey:myPublishableKey completion:completion]; +``` +you would now instead write +```objective-c +STPAPIClient *client = [[STPAPIClient alloc] initWithPublishableKey:myPublishableKey]; +[client createTokenWithCard:card completion:completion]; +``` +This version also made several helper classes, including `STPAPIConnection` and `STPUtils`, private. You should remove any references to them from your code (most apps shouldn't have any). + +## Migrating from versions < 1.2 + +Versions of Stripe-iOS prior to 1.2 included a class called `STPView`, which provided a pre-built credit card form. This functionality has been moved from Stripe-iOS to PaymentKit, a separate project. If you were using `STPView` prior to version 1.2, migrating is simple: + +1. Add PaymentKit to your project, as explained on its [project page](https://github.com/stripe/PaymentKit). +2. Replace any references to `STPView` with a `PTKView` instead. Similarly, any classes that implement `STPViewDelegate` should now instead implement the equivalent `PTKViewDelegate` methods. Note that unlike `STPView`, `PTKView` does not take a Stripe API key in its constructor. +3. To submit the credit card details from your `PTKView` instance, where you would previously call `createToken` on your `STPView`, replace that with the following code (assuming `self.paymentView` is your `PTKView` instance): + +```objective-c +if (![self.paymentView isValid]) { + return; +} +STPCard *card = [[STPCard alloc] init]; +card.number = self.paymentView.card.number; +card.expMonth = self.paymentView.card.expMonth; +card.expYear = self.paymentView.card.expYear; +card.cvc = self.paymentView.card.cvc; +STPAPIClient *client = [[STPAPIClient alloc] initWithPublishableKey:publishableKey]; +[client createTokenWithCard:card completion:^(STPToken *token, NSError *error) { + if (error) { + // handle the error as you did previously + } else { + // submit the token to your payment backend as you did previously + } +}]; +``` + +## Misc. notes + +### Handling errors + +See [our documentation](https://docs.stripe.com/error-codes) for a list of error codes that may be returned from the Stripe API. + +### Validating STPCards + +You have a few options for handling validation of credit card data on the client, depending on what your application does. Client-side validation of credit card data is not required since our API will correctly reject invalid card information, but can be useful to validate information as soon as a user enters it, or simply to save a network request. + +The simplest thing you can do is to populate an `STPCard` object and, before sending the request, call `- (BOOL)validateCardReturningError:` on the card. This validates the entire card object, but is not useful for validating card properties one at a time. + +To validate `STPCard` properties individually, you should use the following: + +```objective-c + - (BOOL)validateNumber:error: + - (BOOL)validateCvc:error: + - (BOOL)validateExpMonth:error: + - (BOOL)validateExpYear:error: +``` + +These methods follow the validation method convention used by [key-value validation](http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/KeyValueCoding/Articles/Validation.html). So, you can use these methods by invoking them directly, or by calling `[card validateValue:forKey:error]` for a property on the `STPCard` object. + +When using these validation methods, you will want to set the property on your card object when a property does validate before validating the next property. This allows the methods to use existing properties on the card correctly to validate a new property. For example, validating `5` for the `expMonth` property will return YES if no `expYear` is set. But if `expYear` is set and you try to set `expMonth` to 5 and the combination of `expMonth` and `expYear` is in the past, `5` will not validate. The order in which you call the validate methods does not matter for this though. diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..d351d0ff --- /dev/null +++ b/Package.swift @@ -0,0 +1,157 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "Stripe", + defaultLocalization: "en", + platforms: [ + .iOS(.v13) + ], + products: [ + .library( + name: "Stripe", + targets: ["Stripe"] + ), + .library( + name: "StripePayments", + targets: ["StripePayments"] + ), + .library( + name: "StripePaymentsUI", + targets: ["StripePaymentsUI"] + ), + .library( + name: "StripePaymentSheet", + targets: ["StripePaymentSheet"] + ), + .library( + name: "StripeApplePay", + targets: ["StripeApplePay"] + ), + .library( + name: "StripeIdentity", + targets: ["StripeIdentity"] + ), + .library( + name: "StripeCardScan", + targets: ["StripeCardScan"] + ), + .library( + name: "StripeFinancialConnections", + targets: ["StripeFinancialConnections"] + ) + ], + targets: [ + .target( + name: "Stripe", + dependencies: ["Stripe3DS2", "StripeCore", "StripeApplePay", "StripeUICore", "StripePayments", "StripePaymentsUI"], + path: "Stripe/StripeiOS", + exclude: ["Info.plist"], + resources: [ + .process("Resources/StripeiOS.xcassets"), + .process("PrivacyInfo.xcprivacy") + ] + ), + .target( + name: "Stripe3DS2", + path: "Stripe3DS2/Stripe3DS2", + exclude: ["Info.plist", "Resources/CertificateFiles", "include/Stripe3DS2-Prefix.pch"], + resources: [ + .process("Resources"), + .process("PrivacyInfo.xcprivacy") + ], + cSettings: [ + .headerSearchPath(".") + ] + ), + .target( + name: "StripeCameraCore", + dependencies: ["StripeCore"], + path: "StripeCameraCore/StripeCameraCore", + exclude: ["Info.plist"] + ), + .target( + name: "StripeCore", + path: "StripeCore/StripeCore", + exclude: ["Info.plist"], + resources: [ + .process("PrivacyInfo.xcprivacy") + ] + ), + .target( + name: "StripeApplePay", + dependencies: ["StripeCore"], + path: "StripeApplePay/StripeApplePay", + exclude: ["Info.plist"] + ), + .target( + name: "StripeIdentity", + dependencies: ["StripeCore", "StripeUICore", "StripeCameraCore"], + path: "StripeIdentity/StripeIdentity", + exclude: ["Info.plist"], + resources: [ + .process("Resources/Images") + ] + ), + .target( + name: "StripeCardScan", + dependencies: ["StripeCore"], + path: "StripeCardScan/StripeCardScan", + exclude: ["Info.plist"], + resources: [ + .copy("Resources/CompiledModels/UxModel.mlmodelc"), + .copy("Resources/CompiledModels/SSDOcr.mlmodelc") + ] + ), + .target( + name: "StripeUICore", + dependencies: ["StripeCore"], + path: "StripeUICore/StripeUICore", + exclude: ["Info.plist"], + resources: [ + .process("Resources/StripeUICore.xcassets"), + .process("Resources/JSON") + ] + ), + .target( + name: "StripePayments", + dependencies: ["StripeCore", "Stripe3DS2"], + path: "StripePayments/StripePayments", + exclude: ["Info.plist"], + resources: [ + .process("Resources") + ] + ), + .target( + name: "StripePaymentsUI", + dependencies: ["StripeCore", "Stripe3DS2", "StripePayments", "StripeUICore"], + path: "StripePaymentsUI/StripePaymentsUI", + exclude: ["Info.plist"], + resources: [ + .process("Resources/StripePaymentsUI.xcassets"), + .process("Resources/JSON") + ] + ), + .target( + name: "StripePaymentSheet", + dependencies: ["StripePaymentsUI", "StripeApplePay", "StripePayments", "StripeCore", "StripeUICore"], + path: "StripePaymentSheet/StripePaymentSheet", + exclude: ["Info.plist"], + resources: [ + .process("Resources/StripePaymentSheet.xcassets"), + .process("Resources/JSON"), + .process("PrivacyInfo.xcprivacy") + ] + ), + .target( + name: "StripeFinancialConnections", + dependencies: ["StripeCore", "StripeUICore"], + path: "StripeFinancialConnections/StripeFinancialConnections", + exclude: ["Info.plist"], + resources: [ + .process("Resources/Images"), + .process("PrivacyInfo.xcprivacy") + ] + ) + ] +) diff --git a/README.md b/README.md index 4fea3738..624f700d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,156 @@ -# stripe-ios-spm -This is a lightweight Swift Package Manager mirror for the [Stripe iOS SDK](https://github.com/stripe/stripe-ios). It offers [tagged source releases](https://github.com/stripe/stripe-ios-spm/releases) for each version of the SDK. +# Stripe iOS SDK -Please file issues on the [`stripe-ios` issues page](https://github.com/stripe/stripe-ios/issues). +[![CocoaPods](https://img.shields.io/cocoapods/v/Stripe.svg?style=flat)](http://cocoapods.org/?q=author%3Astripe%20name%3Astripe) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![License](https://img.shields.io/cocoapods/l/Stripe.svg?style=flat)](https://github.com/stripe/stripe-ios/blob/master/LICENSE) +[![Platform](https://img.shields.io/cocoapods/p/Stripe.svg?style=flat)](https://github.com/stripe/stripe-ios#) + +> [!TIP] +> Want to chat live with Stripe engineers? Join us on our [Discord server](https://stripe.com/go/developer-chat). + +The Stripe iOS SDK makes it quick and easy to build an excellent payment experience in your iOS app. We provide powerful and customizable UI screens and elements that can be used out-of-the-box to collect your users' payment details. We also expose the low-level APIs that power those UIs so that you can build fully custom experiences. + +Get started with our [📚 integration guides](https://stripe.com/docs/payments/accept-a-payment?platform=ios) and [example projects](#examples), or [📘 browse the SDK reference](https://stripe.dev/stripe-ios/docs/index.html). + +> Updating to a newer version of the SDK? See our [migration guide](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) and [changelog](https://github.com/stripe/stripe-ios/blob/master/CHANGELOG.md). + +Table of contents +================= + + + * [Features](#features) + * [Releases](#releases) + * [Requirements](#requirements) + * [Getting started](#getting-started) + * [Integration](#integration) + * [Examples](#examples) + * [Building from source](#building-from-source) + * [Card scanning](#card-scanning) + * [Contributing](#contributing) + * [Migrating](#migrating-from-older-versions) + * [Code Stye](#code-style) + * [Licenses](#licenses) + + + +## Features + +**Simplified security**: We make it simple for you to collect sensitive data such as credit card numbers and remain [PCI compliant](https://stripe.com/docs/security#pci-dss-guidelines). This means the sensitive data is sent directly to Stripe instead of passing through your server. For more information, see our [integration security guide](https://stripe.com/docs/security). + +**Apple Pay**: [StripeApplePay](StripeApplePay/README.md) provides a [seamless integration with Apple Pay](https://stripe.com/docs/apple-pay). + +**SCA-ready**: The SDK automatically performs native [3D Secure authentication](https://stripe.com/docs/payments/3d-secure) if needed to comply with [Strong Customer Authentication](https://stripe.com/docs/strong-customer-authentication) regulation in Europe. + +**Native UI**: We provide native screens and elements to collect payment details. For example, [PaymentSheet](https://stripe.com/docs/payments/accept-a-payment?platform=ios) is a prebuilt UI that combines all the steps required to pay - collecting payment details, billing details, and confirming the payment - into a single sheet that displays on top of your app. + +PaymentSheet + +**Stripe API**: [StripePayments](StripePayments/README.md) provides [low-level APIs](https://stripe.dev/stripe-ios/docs/Classes/STPAPIClient.html) that correspond to objects and methods in the Stripe API. You can build your own entirely custom UI on top of this layer, while still taking advantage of utilities like [STPCardValidator](https://stripe.dev/stripe-ios/docs/Classes/STPCardValidator.html) to validate your user’s input. + +**Card scanning**: We support card scanning on iOS 13 and higher. See our [Card scanning](#card-scanning) section. + +**App Clips**: The `StripeApplePay` module provides a [lightweight SDK for offering Apple Pay in an App Clip](https://stripe.com/docs/apple-pay#app-clips). + +**Localized**: We support the following localizations: Bulgarian, Catalan, Chinese (Hong Kong), Chinese (Simplified), Chinese (Traditional), Croatian, Czech, Danish, Dutch, English (US), English (United Kingdom), Estonian, Filipino, Finnish, French, French (Canada), German, Greek, Hungarian, Indonesian, Italian, Japanese, Korean, Latvian, Lithuanian, Malay, Maltese, Norwegian Bokmål, Norwegian Nynorsk (Norway), Polish, Portuguese, Portuguese (Brazil), Romanian, Russian, Slovak, Slovenian, Spanish, Spanish (Latin America), Swedish, Turkish, Thai and Vietnamese. + +**Identity**: Learn about our [Stripe Identity iOS SDK](StripeIdentity/README.md) to verify the identity of your users. + +#### Recommended usage + +If you're selling digital products or services that will be consumed within your app, (e.g. subscriptions, in-game currencies, game levels, access to premium content, or unlocking a full version), you must use Apple's in-app purchase APIs. See the [App Store review guidelines](https://developer.apple.com/app-store/review/guidelines/#payments) for more information. For all other scenarios you can use this SDK to process payments via Stripe. + +#### Privacy + +The Stripe iOS SDK collects data to help us improve our products and prevent fraud. This data is never used for advertising and is not rented, sold, or given to advertisers. Our full privacy policy is available at [https://stripe.com/privacy](https://stripe.com/privacy). + +For help with Apple's App Privacy Details form in App Store Connect, visit [Stripe iOS SDK Privacy Details](https://support.stripe.com/questions/stripe-ios-sdk-privacy-details). + +## Modules +|Module|Description|Compressed|Uncompressed| +|------|-----------|----------|------------| +|StripePaymentSheet|Stripe's [prebuilt payment UI](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet).|2.7MB|6.3MB| +|Stripe|Contains all the below frameworks, plus [Issuing](https://stripe.com/docs/issuing/cards/digital-wallets?platform=iOS).|2.3MB|5.1MB| +|StripeApplePay|[Apple Pay support](/docs/apple-pay), including `STPApplePayContext`.|0.4MB|1.0MB| +|StripePayments|Bindings for the Stripe Payments API.|1.0MB|2.6MB| +|StripePaymentsUI|Bindings for the Stripe Payments API, [STPPaymentCardTextField](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom), STPCardFormView, and other UI elements.|1.7MB|3.9MB| + +## Releases + +We support Cocoapods, Carthage, and Swift Package Manager. + +If you link the library manually, use a version from our [releases](https://github.com/stripe/stripe-ios/releases) page and make sure to embed all of the required frameworks. + +For the `Stripe` module, link the following frameworks: +- `Stripe.xcframework` +- `Stripe3DS2.xcframework` +- `StripeApplePay.xcframework` +- `StripePayments.xcframework` +- `StripePaymentsUI.xcframework` +- `StripeCore.xcframework` +- `StripeUICore.xcframework` + +For other modules, follow the instructions below: +- [StripePaymentSheet](StripePaymentSheet/README.md#manual-linking) +- [StripePayments](StripePayments/README.md#manual-linking) +- [StripePaymentsUI](StripePaymentsUI/README.md#manual-linking) +- [StripeApplePay](StripeApplePay/README.md#manual-linking) +- [StripeIdentity](StripeIdentity/README.md#manual-linking) + +If you're reading this on GitHub.com, please make sure you are looking at the [tagged version](https://github.com/stripe/stripe-ios/tags) that corresponds to the release you have installed. Otherwise, the instructions and example code may be mismatched with your copy. + +## Requirements + +The Stripe iOS SDK requires Xcode 15 or later and is compatible with apps targeting iOS 13 or above. We support Catalyst on macOS 11 or later. + +For iOS 12 support, please use [v22.8.4](https://github.com/stripe/stripe-ios/tree/v22.8.4). For iOS 11 support, please use [v21.13.0](https://github.com/stripe/stripe-ios/tree/v21.13.0). For iOS 10, please use [v19.4.0](https://github.com/stripe/stripe-ios/tree/v19.4.0). If you need to support iOS 9, use [v17.0.2](https://github.com/stripe/stripe-ios/tree/v17.0.2). + +## Getting started + +### Integration + +Get started with our [📚 integration guides](https://stripe.com/docs/payments/accept-a-payment?platform=ios) and [example projects](/Example), or [📘 browse the SDK reference](https://stripe.dev/stripe-ios/docs/index.html) for fine-grained documentation of all the classes and methods in the SDK. + +### Examples + +- [Prebuilt UI](Example/PaymentSheet%20Example) (Recommended) + - This example demonstrates how to build a payment flow using [`PaymentSheet`](https://stripe.com/docs/payments/accept-a-payment?platform=ios), an embeddable native UI component that lets you accept [10+ payment methods](https://stripe.com/docs/payments/payment-methods/integration-options#payment-method-product-support) with a single integration. + +- [Non-Card Payment Examples](Example/Non-Card%20Payment%20Examples) + - This example demonstrates how to manually accept various payment methods using the Stripe API. + +## Card scanning + +[PaymentSheet](https://stripe.com/docs/payments/accept-a-payment?platform=ios) offers built-in card scanning. To enable card scanning, you'll need to set `NSCameraUsageDescription` in your application's plist, and provide a reason for accessing the camera (e.g. "To scan cards"). Card scanning is supported on devices with iOS 13 or higher. + +You can demo this feature in our [PaymentSheet example app](Example/PaymentSheet%20Example). When you run the example app on a device, you'll see a "Scan Card" button when adding a new card. + +## Contributing + +We welcome contributions of any kind including new features, bug fixes, and documentation improvements. Please first open an issue describing what you want to build if it is a major change so that we can discuss how to move forward. Otherwise, go ahead and open a pull request for minor changes such as typo fixes and one liners. + +### Running tests + +1. Install Carthage 0.37 or later (if you have homebrew installed, `brew install carthage`) +2. From the root of the repo, run `bundle install && bundle exec fastlane stripeios_tests`. This will install the test dependencies and run the tests. +3. Once you have run this once, you can also run the tests in Xcode from the `StripeiOS` target in `Stripe.xcworkspace`. + +To re-record snapshot tests, use the `bundle exec ruby ci_scripts/snapshots.rb --record`. + +## Migrating from older versions + +See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) + +## Code style +We use [swiftlint](https://github.com/realm/SwiftLint) to enforce code style. + +To install it, run `brew install swiftlint` + +To lint your code before pushing you can run `ci_scripts/lint_modified_files.sh` + +You can also add this script as a pre-push hook by running `ln -s "$(pwd)/ci_scripts/lint_modified_files.sh" .git/hooks/pre-push && chmod +x .git/hooks/pre-push` + +To format modified files automatically, you can use `ci_scripts/format_modified_files.sh` and you can add it as a pre-commit hook using `ln -s "$(pwd)/ci_scripts/format_modified_files.sh" .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit` + +## Licenses + +- [Stripe iOS SDK License](LICENSE) diff --git a/Stripe/BuildConfigurations/Stripe Tests-Debug.xcconfig b/Stripe/BuildConfigurations/Stripe Tests-Debug.xcconfig new file mode 100644 index 00000000..6d153e27 --- /dev/null +++ b/Stripe/BuildConfigurations/Stripe Tests-Debug.xcconfig @@ -0,0 +1,8 @@ +// +// Stripe Tests-Debug.xcconfig +// + +#include "../../BuildConfigurations/StripeiOS Tests-Debug.xcconfig" + +SWIFT_OBJC_BRIDGING_HEADER = StripeiOSTests/StripeiOS Tests-Bridging-Header.h +GCC_PREFIX_HEADER = StripeiOSTests/StripeTests-Prefix.pch diff --git a/Stripe/BuildConfigurations/Stripe Tests-Release.xcconfig b/Stripe/BuildConfigurations/Stripe Tests-Release.xcconfig new file mode 100644 index 00000000..b72a89b2 --- /dev/null +++ b/Stripe/BuildConfigurations/Stripe Tests-Release.xcconfig @@ -0,0 +1,8 @@ +// +// Stripe Tests-Release.xcconfig +// + +#include "../../BuildConfigurations/StripeiOS Tests-Release.xcconfig" + +SWIFT_OBJC_BRIDGING_HEADER = StripeiOSTests/StripeiOS Tests-Bridging-Header.h +GCC_PREFIX_HEADER = StripeiOSTests/StripeTests-Prefix.pch diff --git a/Stripe/BuildConfigurations/Stripe-Debug.xcconfig b/Stripe/BuildConfigurations/Stripe-Debug.xcconfig new file mode 100644 index 00000000..c5831ee6 --- /dev/null +++ b/Stripe/BuildConfigurations/Stripe-Debug.xcconfig @@ -0,0 +1,7 @@ +// +// Stripe-Debug.xcconfig +// + +#include "../../BuildConfigurations/StripeiOS-Debug.xcconfig" + +MODULEMAP_FILE = StripeiOS/Stripe.modulemap diff --git a/Stripe/BuildConfigurations/Stripe-Release.xcconfig b/Stripe/BuildConfigurations/Stripe-Release.xcconfig new file mode 100644 index 00000000..662db163 --- /dev/null +++ b/Stripe/BuildConfigurations/Stripe-Release.xcconfig @@ -0,0 +1,7 @@ +// +// Stripe-Release.xcconfig +// + +#include "../../BuildConfigurations/StripeiOS-Release.xcconfig" + +MODULEMAP_FILE = StripeiOS/Stripe.modulemap diff --git a/Stripe/Stripe.xcodeproj/project.pbxproj b/Stripe/Stripe.xcodeproj/project.pbxproj new file mode 100644 index 00000000..67864069 --- /dev/null +++ b/Stripe/Stripe.xcodeproj/project.pbxproj @@ -0,0 +1,1951 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXAggregateTarget section */ + E652E5F22CB20B7200081F83 /* ReleaseFrameworksTarget */ = { + isa = PBXAggregateTarget; + buildConfigurationList = E652E5F52CB20B7200081F83 /* Build configuration list for PBXAggregateTarget "ReleaseFrameworksTarget" */; + buildPhases = ( + E652E5F62CB20BA200081F83 /* ShellScript */, + ); + dependencies = ( + ); + name = ReleaseFrameworksTarget; + packageProductDependencies = ( + ); + productName = ReleaseFrameworksTarget; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 013F991AB34E38BDBA6E4521 /* STPFormViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44327A2B2C9483F52EE343B /* STPFormViewSnapshotTests.swift */; }; + 0185AC6B123CD73E877D4FCE /* STPPaymentMethodCashAppParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABB2CA7E96BE249CE8C0566 /* STPPaymentMethodCashAppParamsTests.swift */; }; + 0305C1689C25B57C43640173 /* STPPaymentMethodBacsDebitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1CF8FB0100664A02468FBC /* STPPaymentMethodBacsDebitTest.swift */; }; + 03E60F9EF24C975AF90E2447 /* StripePaymentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E136A967522048B313E3C62F /* StripePaymentsUI.framework */; }; + 044B7BECFBDB1F6C8CA08514 /* STPSetupIntentConfirmParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CC0B1FC92A573AAEA4F4E94 /* STPSetupIntentConfirmParamsTest.swift */; }; + 0684E2ABDA4566356143CC14 /* STPPaymentMethodSofortParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207677E2A0DBC04C88139372 /* STPPaymentMethodSofortParamsTests.swift */; }; + 07A5CDBFDF2340BAD99D6EB3 /* STPCardFormViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D983E089196152DA1C69469 /* STPCardFormViewSnapshotTests.swift */; }; + 07BF3CF1656AF5F5A0678873 /* STPPhoneNumberValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 924E878428D15506711CA628 /* STPPhoneNumberValidatorTest.swift */; }; + 08111F4AD3CA0755420E05F7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 147D2DC1FFDFC99269039377 /* LaunchScreen.storyboard */; }; + 0B9C0E9A7A750607413C9E53 /* STPFakeAddPaymentPassViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0669B4CA326CE74D125C789C /* STPFakeAddPaymentPassViewController.swift */; }; + 0DFA17378D894C70D72C9F62 /* Error+PaymentSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CE85C770AEEDBE4AEC93EAA /* Error+PaymentSheetTests.swift */; }; + 0FA3C1494BA57884B5DE3B20 /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4259421D2CD26E37B96F97B2 /* Stripe.framework */; }; + 10342D659764A88A695EF38B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DFA7A75BA785EBBE4C05DAA3 /* Images.xcassets */; }; + 14656D177E67594B8C75A9FE /* STPConnectAccountParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195DEC752CC82CC4BA1E2351 /* STPConnectAccountParamsTest.swift */; }; + 162C101E57D66F0051164C4A /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4259421D2CD26E37B96F97B2 /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 17BD7C0391F3182E32A63D6B /* STPAUBECSDebitFormViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCBEA9E4823F08C1F5057B5A /* STPAUBECSDebitFormViewSnapshotTests.swift */; }; + 194154708E1A9E013DCE2C72 /* STPPaymentHandlerStubbedMockedFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF48EC440E1ED5D6BAA567FF /* STPPaymentHandlerStubbedMockedFilesTests.swift */; }; + 1948544E75A2E16E46CBA00E /* STPRedirectContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF9EB77A45F3E9E83F5D8B /* STPRedirectContextTest.swift */; }; + 1A058C42C4703458CA1CA522 /* STPCardNumberInputTextFieldValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C4773C2D193BEDF1CBB530 /* STPCardNumberInputTextFieldValidatorTests.swift */; }; + 1BC4044802EE7D3E2643DC84 /* STPPaymentIntentEnumsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C31CAD0FA74B58BA2B8530 /* STPPaymentIntentEnumsTest.swift */; }; + 1CCFC43F7FCD273E2100D321 /* STPPaymentMethodBancontactTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E5416F6AE8BED88980D6F8 /* STPPaymentMethodBancontactTests.swift */; }; + 1CD3AB315580606AF87A7B1F /* STPPaymentCardTextFieldKVOTest.m in Sources */ = {isa = PBXBuildFile; fileRef = BB08D2AC882B21C8ADD76B92 /* STPPaymentCardTextFieldKVOTest.m */; }; + 1E8D8E2494062262A332879C /* STPCardValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1644AA33E81233EF33022BA /* STPCardValidatorTest.swift */; }; + 225140E0BD9C0630116DDE4A /* STPPaymentMethodUSBankAccountTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B112FFF3FCA82094281493F /* STPPaymentMethodUSBankAccountTest.swift */; }; + 234C71F480318E9062075924 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BBCE3A905041A709E8F279A /* AppDelegate.swift */; }; + 23CF725CFAB2ABED416BF416 /* STPApplePayContextFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CE268457D21A7209862E004 /* STPApplePayContextFunctionalTest.swift */; }; + 240993144289CD0DEC2C73C7 /* STPConfirmPaymentMethodOptionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05FE1BA89B80336F16924FA2 /* STPConfirmPaymentMethodOptionsTest.swift */; }; + 246920234EE8382FB4E56516 /* STPCardFormViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5E08A1651D9DFE502DA021 /* STPCardFormViewTests.swift */; }; + 27F1783CBFEC06BFD6C114F6 /* STPPaymentMethodKlarnaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD94FF270165D699DA89B24 /* STPPaymentMethodKlarnaTests.swift */; }; + 28538CD5885636DC523E8751 /* STPSourceRedirectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0436A8574E7D0730641407A /* STPSourceRedirectTest.swift */; }; + 29428CDB658E6F504402D844 /* STPPaymentMethodBillingDetailsTests+Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC0A18C441FCA394BEF6A3D /* STPPaymentMethodBillingDetailsTests+Link.swift */; }; + 2A528B7B2579E5F977797822 /* STPPaymentHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC30E6129279F14506219E98 /* STPPaymentHandlerTests.swift */; }; + 2AC91F23CF3949ADC60D27F7 /* STPThreeDSTextFieldCustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A583966A33DCDCF04322A592 /* STPThreeDSTextFieldCustomizationTest.swift */; }; + 2AE9ABA774B430E174279FEA /* stp_test_upload_image.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = FD3398E2352CEA0264F20AEA /* stp_test_upload_image.jpeg */; }; + 2C6DC246DD12FE0D87156A4D /* STPPaymentMethodPayPalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A63AC868755CB4745E7458E /* STPPaymentMethodPayPalTests.swift */; }; + 2C9F69E4A384C5743F4EAF69 /* STPPaymentMethodSwishParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9631915F03F157A1CC3FEFFE /* STPPaymentMethodSwishParamsTests.swift */; }; + 2CD7968DA48F7129E16EA0CB /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 911CA85A1610303FA0AF0643 /* OHHTTPStubsSwift */; }; + 2E35B0FB60FCBE7608080642 /* STPPushProvisioningContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6887F19BB9804BF45FD703FF /* STPPushProvisioningContext.swift */; }; + 2EB68A59660A4D1E14799DA4 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 62887B4538E4E41E735685E1 /* OHHTTPStubs */; }; + 2F0FC4E67BE577AD66CD1475 /* StripePaymentSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDC55CC034022DFAC9366E2E /* StripePaymentSheet.framework */; }; + 2F9FA9CBCA3C0CE52FAC9B6B /* ConfirmButtonSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 806124200E77795DCFC8418E /* ConfirmButtonSnapshotTests.swift */; }; + 30D48C62B2FA6B28EC23A5BB /* Stripe-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = A8598727045C6268B57A5FC7 /* Stripe-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 313F5F792B0BE59100BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F782B0BE59000BD98A9 /* Docs.docc */; }; + 3172C789DF2CE133ECA359D7 /* STPPushProvisioningDetailsFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8A6B88797870BC71CCB3AF /* STPPushProvisioningDetailsFunctionalTest.swift */; }; + 319899DEC91B3F88D380DB47 /* STPPaymentMethodGrabPayParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8FF64CA314F909D7EC82FE /* STPPaymentMethodGrabPayParamsTest.swift */; }; + 31CDFC2E2BA3708000B3DD91 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 31CDFC2D2BA3708000B3DD91 /* PrivacyInfo.xcprivacy */; }; + 325694E4284BEAE787A5ECB6 /* NSLocale+STPSwizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = C641A744CCEA67C07E9BFE05 /* NSLocale+STPSwizzling.swift */; }; + 32874C6147344A9CB2EF4DAD /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33FDC634FD5D79E824240DDC /* Stripe3DS2.framework */; }; + 331924F0801287BAD413FDCB /* STPMandateCustomerAcceptanceParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C713F58BC61A962C720AE0AE /* STPMandateCustomerAcceptanceParamsTest.swift */; }; + 33DF66640B5ABBCB12B46AFE /* STPPaymentMethodCardWalletVisaCheckoutTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E46B18CDDC191934F3D4BE /* STPPaymentMethodCardWalletVisaCheckoutTest.swift */; }; + 35C1CF73701EECC7DB6AB722 /* FormSpecProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C04AFC9CDE50D09D38A3232 /* FormSpecProviderTest.swift */; }; + 35E05040EA813C3B9C8EF054 /* STPViewWithSeparatorSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86983B09A1712944EC012AD4 /* STPViewWithSeparatorSnapshotTests.swift */; }; + 360EEE8B706D2A4A49666F7A /* StripePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22D1C6EB5826E2D7C80B6CF3 /* StripePayments.framework */; }; + 37E9160706C9EEEFEF133617 /* STPPaymentIntentFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDEAB86BE4711841D426F3B /* STPPaymentIntentFunctionalTest.swift */; }; + 37FBCED5F71F03483EA73F27 /* STPPaymentMethodOXXOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80BBCC4D386EE44E809A591C /* STPPaymentMethodOXXOTests.swift */; }; + 385CAC4D2FF119D2E925916B /* STPPostalCodeInputTextFieldFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917154477796779ECFA1334A /* STPPostalCodeInputTextFieldFormatterTests.swift */; }; + 39B1B88B8506BE4574E6B376 /* STPBankAccountFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 989411FA3CD0CCC38BC227F4 /* STPBankAccountFunctionalTest.swift */; }; + 3AD22E0BD44B02D968C6569A /* STPImageLibraryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F546088BA4F763334CFD3D34 /* STPImageLibraryTest.swift */; }; + 3B237145902E3DB07E747E32 /* TextFieldElement+IBANTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BD2CE41E4F0CF648F44E4A /* TextFieldElement+IBANTest.swift */; }; + 3C1A7B9810B038177FF1CF52 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F29C15B47C7CB0941CD4C9E /* ViewController.swift */; }; + 3C1E9069CD03ED9981D7F3E2 /* ConfirmButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFFE40AD9D875709F643D2E5 /* ConfirmButtonTests.swift */; }; + 3EA9D509E59DA65EE4EDF98D /* STPAddressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A39580123A4F1EA96F91768A /* STPAddressTests.swift */; }; + 3EB3745F556EA12AB27A8545 /* APIRequestTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AAB72218409F85FE29E69E /* APIRequestTest.swift */; }; + 3FD5ABC45AF3A03F4EFE196F /* STPSourceFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17013F78CE3F9662029FEF5B /* STPSourceFunctionalTest.swift */; }; + 4059301B0365BD4220E591FB /* STPPaymentMethodSwishTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE9F0BAC35EA14579775033 /* STPPaymentMethodSwishTests.swift */; }; + 420F8CAB4FAD6D9AF4AF25C0 /* StripePaymentSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDC55CC034022DFAC9366E2E /* StripePaymentSheet.framework */; }; + 42395EF962DB8AD6A094630B /* StripePaymentSheet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DDC55CC034022DFAC9366E2E /* StripePaymentSheet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 42F18560F3DC6980408AF051 /* STPPaymentMethodPayPalParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2A766FB355DD9C461939C1 /* STPPaymentMethodPayPalParamsTests.swift */; }; + 43FFF2881D4EFA7B57A60E09 /* STPPaymentMethodCardTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD02D8298877F10F2EF2A9D /* STPPaymentMethodCardTest.swift */; }; + 450FAE41FB4538462D05F2E4 /* LinkSignupViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7C7D85A7FAAFDF4F59BA85E /* LinkSignupViewModelTests.swift */; }; + 45FA9B8CC2D18E29BE81CF8F /* STPIntentActionAlipayHandleRedirectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD06AED0AF8A9A7FB4A2E66F /* STPIntentActionAlipayHandleRedirectTest.swift */; }; + 460B31EDB22BD6B912567363 /* STPSourceVerificationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89551702F1F9A3AFF1ED676 /* STPSourceVerificationTest.swift */; }; + 46FF3CC61200F2C27D4F3369 /* STPSourceReceiverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7A947152A728EB2CBC4DB2 /* STPSourceReceiverTest.swift */; }; + 492F7C4DABB4CE8EBE34EEF2 /* STPConnectAccountAddressTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967DDC94B687B14E07842CC8 /* STPConnectAccountAddressTest.swift */; }; + 4935C8B3ECFBAD947E694934 /* STPIntentActionTypeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F205F920E971DEA59E3C31 /* STPIntentActionTypeTest.swift */; }; + 4993037E5386D0AF87B24871 /* STPPaymentMethodAffirmParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D87817A1D3D213AA4ADF6A4C /* STPPaymentMethodAffirmParamsTest.swift */; }; + 4A61DC36F10B9C9C24345613 /* STPRadarSessionFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D878F923A1F69B58D6B2812 /* STPRadarSessionFunctionalTest.swift */; }; + 4AAA2CD5AEF1F913395B3B95 /* STPPaymentMethodUSBankAccountParamsStubbedTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF13BAEF86594C9CABD4F42A /* STPPaymentMethodUSBankAccountParamsStubbedTest.swift */; }; + 4B0917FC15BF56D0100E0ED1 /* STPGenericInputTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A53F005EA8FDDAA66126BA /* STPGenericInputTextFieldSnapshotTests.swift */; }; + 4E09E54E7FEC35C49C59A379 /* STPPushProvisioningDetailsParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B28B8A547CD846277ECD578 /* STPPushProvisioningDetailsParams.swift */; }; + 4E31B1864DA407598FB1BBC6 /* STPPostalCodeInputTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6618739767139C25C05B3631 /* STPPostalCodeInputTextFieldTests.swift */; }; + 4FB67F10A0B7106A8142B842 /* STPEphemeralKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588C260880FFC584A00A89F5 /* STPEphemeralKeyManager.swift */; }; + 51044B947A7FDB99451466D8 /* STPGenericInputPickerFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 284C67269D2606DA147AE01D /* STPGenericInputPickerFieldSnapshotTests.swift */; }; + 5170651536332C4842E9D009 /* STPPaymentMethodBoletoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E371E9B3B2E343FE954531C /* STPPaymentMethodBoletoTests.swift */; }; + 51D515315F02D4C03BA12366 /* UserDefaults+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B0E131538728BC4802627B1 /* UserDefaults+StripeTest.swift */; }; + 5212C7875C07F9BF16AFD98D /* STPAPIClient+PushProvisioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 807FF966F1DE05F3496B817B /* STPAPIClient+PushProvisioning.swift */; }; + 524AE1978E0A4490D1C390C5 /* CustomerAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C866064B3482878A69892F /* CustomerAdapterTests.swift */; }; + 5302F9246A4A6381CB4FB874 /* StripePaymentsTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77247622AB08FEF48CA0DC26 /* StripePaymentsTestUtils.framework */; }; + 5370700ED1F630E8261507D3 /* STPBankAccountParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967C784618A074FF021B3089 /* STPBankAccountParamsTest.swift */; }; + 583DE9869C885BA02E0A071E /* STPAUBECSFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAE5EE11611F9F7762B64C6 /* STPAUBECSFormViewModelTests.swift */; }; + 58A8B3F57FE98C22D8F90C77 /* STPThreeDSButtonCustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F3B966470A530E0DC53F8C /* STPThreeDSButtonCustomizationTest.swift */; }; + 590DB84AC15709E3C6F1FC3B /* STPThreeDSNavigationBarCustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51408DE266D0345784ADD4FA /* STPThreeDSNavigationBarCustomizationTest.swift */; }; + 5910FCB9822259D5EC7E4051 /* AutoCompleteViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFD2F6A5A046A620BAB75B41 /* AutoCompleteViewControllerSnapshotTests.swift */; }; + 5B6F1BF973FC2D8DD6127B7F /* StripePaymentsUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E136A967522048B313E3C62F /* StripePaymentsUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5C51167CC14F653E7117BA61 /* OCMock in Frameworks */ = {isa = PBXBuildFile; productRef = E804AA8C4156CC85FFD9595F /* OCMock */; }; + 5C5E1CE53D89DE8F0B867115 /* STPPaymentMethodSofortTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8063E8073A32E0B081A1DFA /* STPPaymentMethodSofortTests.swift */; }; + 5D7F632025C261B88F0C2016 /* STPFPXBankBrandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C29AE44D809CD677B5E52B /* STPFPXBankBrandTest.swift */; }; + 5D9EB3E2725C38D7098B9965 /* STPPaymentMethodParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4002981AC12687681616D21E /* STPPaymentMethodParamsTest.swift */; }; + 5E498CDA0115CF9F8463C566 /* STPPaymentMethodAUBECSDebitParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FF2B9FF57E100301B5C38DB /* STPPaymentMethodAUBECSDebitParamsTests.swift */; }; + 609C2C8F10AFAA2711639CD0 /* NSArray+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17799DC7FA54E758EED31A6 /* NSArray+StripeTest.swift */; }; + 610DF5DC2B33597500DA6AAA /* HostedSurfaceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610DF5DB2B33597500DA6AAA /* HostedSurfaceTest.swift */; }; + 61152B4F2B866827003B69A0 /* STPPaymentMethodAmazonPayParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61152B4E2B866827003B69A0 /* STPPaymentMethodAmazonPayParamsTests.swift */; }; + 617C1C882BB4992400B10AC5 /* STPPaymentMethodAlmaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617C1C872BB4992400B10AC5 /* STPPaymentMethodAlmaTests.swift */; }; + 617C1C8A2BB4998C00B10AC5 /* STPPaymentMethodAlmaParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617C1C892BB4998C00B10AC5 /* STPPaymentMethodAlmaParamsTests.swift */; }; + 61951FB92B866BA1005F90BE /* STPPaymentMethodAmazonPayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61951FB82B866BA1005F90BE /* STPPaymentMethodAmazonPayTests.swift */; }; + 61E0A0C52BF31D3C00C89786 /* STPPaymentHandlerRefreshTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E0A0C42BF31D3C00C89786 /* STPPaymentHandlerRefreshTests.swift */; }; + 61E1CA1F2BD6B72800A421AE /* STPPaymentMethodMultibancoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E1CA1E2BD6B72800A421AE /* STPPaymentMethodMultibancoTests.swift */; }; + 61E1CA212BD6B78500A421AE /* STPPaymentMethodMultibancoParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E1CA202BD6B78500A421AE /* STPPaymentMethodMultibancoParamsTests.swift */; }; + 61E1CA272BD6BED600A421AE /* STPIntentActionMultibancoDisplayDetailsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E1CA262BD6BED600A421AE /* STPIntentActionMultibancoDisplayDetailsTest.swift */; }; + 62B91808A088C4F9FDB62C53 /* STPEphemeralKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890660C21E3666CE7B82695B /* STPEphemeralKey.swift */; }; + 66B7EF2DC1CBF813707C767C /* STPBSBNumberValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C732C25FD961631BD44FDD /* STPBSBNumberValidatorTests.swift */; }; + 68318DB86DFCD19505FC47BA /* NSURLComponents_StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CD20E00EAD41091B71ABD5 /* NSURLComponents_StripeTest.swift */; }; + 687517E7FE02FFB96DCE2328 /* STPEphemeralKeyManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C645F78B3EFFAA083B6FD3E9 /* STPEphemeralKeyManagerTest.swift */; }; + 6BA4B91A2BF433B200D1F21D /* STPPaymentMethodMobilePayParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA4B9192BF433B200D1F21D /* STPPaymentMethodMobilePayParamsTests.swift */; }; + 6BA4B91C2BF4343B00D1F21D /* STPPaymentMethodMobilePayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BA4B91B2BF4343B00D1F21D /* STPPaymentMethodMobilePayTests.swift */; }; + 6BC5EC2D2B4609FF00CC75E8 /* LinkInlineSignupElementSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC5EC2C2B4609FF00CC75E8 /* LinkInlineSignupElementSnapshotTests.swift */; }; + 6E7AD3CCC966A7F34922B172 /* NSDictionary+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07275F94914B7E7937D24FE /* NSDictionary+StripeTest.swift */; }; + 6EDFC83541EED9E361B71C02 /* STPCustomerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB817ED5B5DB87AE1290894 /* STPCustomerTest.swift */; }; + 6EF3F611E6EA3CB479D62450 /* AfterpayPriceBreakdownViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C742844915B96CFD25BFFF9 /* AfterpayPriceBreakdownViewSnapshotTests.swift */; }; + 6F4FBB4F10B5DB2CF8BB3460 /* STPBinRangeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC0560A312147C37CFE6CF9 /* STPBinRangeTest.swift */; }; + 6F9525063D76A9F86A10CCBF /* STPApplePayContextFunctionalTestExtras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1272F2E05A0E294DD9ECA26 /* STPApplePayContextFunctionalTestExtras.swift */; }; + 6FCA954C32AB351F902BA876 /* STPPostalCodeValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF7394DDD552EDE996EAD8E /* STPPostalCodeValidatorTest.swift */; }; + 701C464523173C6809544935 /* STPThreeDSUICustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED44491EB0AC72B1B1A773C /* STPThreeDSUICustomizationTest.swift */; }; + 71116C2D5831E271E12DB059 /* ServerErrorMapperTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20879436DCFB1F03BE1608B3 /* ServerErrorMapperTest.swift */; }; + 73AFE2A8839EFAB8330F6CF0 /* STPPaymentIntentTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACE8998EAF997A78759E49B5 /* STPPaymentIntentTest.swift */; }; + 7435E6BB6971012A9B0DB52E /* STPErrorBridgeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9EAE9A2AE65771403CE57C11 /* STPErrorBridgeTest.m */; }; + 7589E37795D21AB818B0C333 /* STPAnalyticsClient+Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD89580A3E41D7167C30B287 /* STPAnalyticsClient+Payments.swift */; }; + 7623057AC6AC5369DCD94E84 /* STPPaymentMethodCardWalletTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC4B62C336EAA05A33FC384 /* STPPaymentMethodCardWalletTest.swift */; }; + 76BC927BC7A591601C1DAB18 /* StripeiOS.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 77E846CD56018D8417A3AB95 /* StripeiOS.xcassets */; }; + 77C0FD1BCDA7BBFB88559B44 /* STPPaymentCardTextFieldTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939360978872BBE4334215B1 /* STPPaymentCardTextFieldTest.swift */; }; + 781EC0163AC001C6A66045B6 /* STPMandateDataParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F7AA0B7B86BA5BB2FE92CE /* STPMandateDataParamsTest.swift */; }; + 7844BB705AEB002965EF82B0 /* STPPaymentMethodKlarnaParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E12A0CBFA259A032F7AF0C /* STPPaymentMethodKlarnaParamsTests.swift */; }; + 78641CE4011A1C1EE6E35DC5 /* STPIntentActionPromptPayDisplayQrCodeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9466F23BA8712EA2EDA48BBD /* STPIntentActionPromptPayDisplayQrCodeTest.swift */; }; + 786C30837EAD918EDE52284E /* STPCardBrandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 901BE31021AF27DB5D326327 /* STPCardBrandTest.swift */; }; + 78B70C2EE8334F0FA91439CA /* Stripe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4259421D2CD26E37B96F97B2 /* Stripe.framework */; }; + 795F3783D62AB8E2A00DCD05 /* ConsumerSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6312182B5BCAB940D216650 /* ConsumerSessionTests.swift */; }; + 7A9D7D156B5053638F9B21E1 /* STPPaymentMethodThreeDSecureUsageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916DB8789F65D3C1BCB510C0 /* STPPaymentMethodThreeDSecureUsageTest.swift */; }; + 7D251ABF1EBF65ACA8A4BDD4 /* STPPaymentMethodUSBankAccountParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74FDEF9F687C63BADFB96480 /* STPPaymentMethodUSBankAccountParamsTest.swift */; }; + 7D2C0D1BF455625997CBC33B /* STPAPIClientNetworkBridgeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BA8D8467218C7E691C9FAE /* STPAPIClientNetworkBridgeTest.swift */; }; + 7EAA7334372DBC38DF8FA0AA /* STPPinManagementService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EBB07171F6FDCE6E20C454A /* STPPinManagementService.swift */; }; + 801F417CE53689B95C4A098B /* STPBankAccountTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D103BC590F1E0EC0C31C7B5F /* STPBankAccountTest.swift */; }; + 8378F2A4B0796819BB1C6C54 /* STPPaymentMethodCardParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1671EC46C713D51013AD7D8B /* STPPaymentMethodCardParamsTest.swift */; }; + 8520A27C204A068C43592024 /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52F8AEC50D4623F80F04A533 /* StripeApplePay.framework */; }; + 8532FEBF4F2E0EB282D466CE /* STPGenericInputPickerFieldValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D63B73C5773432CA134D1FC /* STPGenericInputPickerFieldValidatorTest.swift */; }; + 86BAF121184D71F5F4FFAD7B /* STPPaymentCardTextFieldTestsSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F16C36797D978E72E612100 /* STPPaymentCardTextFieldTestsSwift.swift */; }; + 8B80FB6FC88D411A90E9D487 /* WalletHeaderViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB03E83746FE78361831546 /* WalletHeaderViewSnapshotTests.swift */; }; + 8C977F8D224A7360AE8E15A7 /* STPPaymentMethodBoletoParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C67AC5D415615E9F27D3E3 /* STPPaymentMethodBoletoParamsTests.swift */; }; + 8E423294AB602BF25DB11D8E /* OperationDebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61763BA2CCA86F9B8FD4F1F /* OperationDebouncerTests.swift */; }; + 8EC1820299152F8565D30A40 /* STPCardCVCInputTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1859673CAD068B345F5DD7D /* STPCardCVCInputTextFieldSnapshotTests.swift */; }; + 8F0326E98C74EB62E34B9FEA /* STPIntentActionWeChatPayRedirectToAppTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5D11C977A95B8E936E907 /* STPIntentActionWeChatPayRedirectToAppTest.swift */; }; + 8F5AF9D3566B8DBCA5AB5188 /* STPPaymentHandlerFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF450AD17FF7BCE5916DDF1 /* STPPaymentHandlerFunctionalTest.swift */; }; + 903FFB756C6ED520BE38EF6F /* STPInputTextFieldValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE395C4DD0E0112AF3720C /* STPInputTextFieldValidatorTests.swift */; }; + 91558F51B87C72E745244958 /* STPPostalCodeInputTextFieldValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9975553E669AF69F3CE437 /* STPPostalCodeInputTextFieldValidatorTests.swift */; }; + 91A839DEDA7D1EAF6FC66BE0 /* STPFormEncoderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30964128998473CAA9F2DD7E /* STPFormEncoderTest.swift */; }; + 922C0DF37F5AAA29375A5454 /* STPSourceOwnerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095EBF095BA2BC8D299547DB /* STPSourceOwnerTest.swift */; }; + 9291A08CCB34504FCA4B7481 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 005650A59D692F820EF20F5F /* XCTest.framework */; }; + 9363F8F389C04C19B37D0F0A /* StripePaymentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E136A967522048B313E3C62F /* StripePaymentsUI.framework */; }; + 951344464ACF84F0F6D43D10 /* OneTimeCodeTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F9CB667BC68767DFB5FACD /* OneTimeCodeTextFieldTests.swift */; }; + 9535CADFFBC9E1FA291E947E /* STPPIIFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D9793463E266501B74FD /* STPPIIFunctionalTest.swift */; }; + 96098727EFA6A72087A35A52 /* STPFormTextFieldTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E1862744F23286D1FB9D4AE /* STPFormTextFieldTest.swift */; }; + 9A24970C5FB6D3F7314AE550 /* STPAPIClientStubbedTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4194E605BB5F31E9CBB8F96 /* STPAPIClientStubbedTest.swift */; }; + 9B149DA42FB38C3542E0CB4B /* STPApplePayFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C77C7BC4BA57EC296CF2F1C /* STPApplePayFunctionalTest.swift */; }; + 9B1AC278FDCDABF26C5E468C /* STPPostalCodeInputTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090EF7D598B8DE779C275395 /* STPPostalCodeInputTextFieldSnapshotTests.swift */; }; + 9D464A252FBD0D4E2A0A7398 /* STPCountryPickerInputFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D49257A97E71A475A9F6E08 /* STPCountryPickerInputFieldSnapshotTests.swift */; }; + 9D8354BDB04CEC5D1EFCF54F /* STPSwiftFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E315168EF07F52B733EA77F8 /* STPSwiftFixtures.swift */; }; + 9FB20E559379F468070C7B50 /* STPLabeledFormTextFieldViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D1EEABBAE5BD5615486B0F /* STPLabeledFormTextFieldViewSnapshotTests.swift */; }; + A01BB7F09134F7081679F9C4 /* STPPaymentMethodUPIParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47E5E1173A37AABB07FB68AB /* STPPaymentMethodUPIParamsTest.swift */; }; + A08C2F0E7F642515B1D263ED /* STPPaymentMethodTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148C1D7D1BBBC6B74894A869 /* STPPaymentMethodTest.swift */; }; + A12CFA90DAE8BBB39A8C7AA1 /* STPCardFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D38184A7CD27B978DFA30E69 /* STPCardFunctionalTest.swift */; }; + A22D548084E7DE1FE5ABE8E7 /* STPLabeledMultiFormTextFieldViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F68637E75142DCD46710796 /* STPLabeledMultiFormTextFieldViewSnapshotTests.swift */; }; + A30CD9DA2CCC2DFE00EA22D3 /* STPAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30CD9D92CCC2DFE00EA22D3 /* STPAPIClientTest.swift */; }; + A66C279957B6AC8F72DE05C7 /* STPCardExpiryInputTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C75157665428685C7A4FD20 /* STPCardExpiryInputTextFieldSnapshotTests.swift */; }; + A77C5769B20D7884FC8FC4FB /* STPNumericDigitInputTextFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6223E57D3A198F956A37ED89 /* STPNumericDigitInputTextFormatterTests.swift */; }; + A781FB0F586B26655FAEC3C0 /* STPCertTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D51B04D83D4FEF7F90DF16A /* STPCertTest.swift */; }; + A8B0DB753CAA2223C8BED099 /* StripeErrorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3AD586DDED620B9E68F461 /* StripeErrorTest.swift */; }; + AC35943F1EAD50E9D5D509B3 /* STPCardExpiryInputTextFieldFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BB87E28719FE0C6B946BB5 /* STPCardExpiryInputTextFieldFormatterTests.swift */; }; + AC7C127B11A60222465F4696 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 005650A59D692F820EF20F5F /* XCTest.framework */; }; + ACC1B91FC687AFD0DFD27CD4 /* STPIntentActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33B9D01D037909D1C9C0B617 /* STPIntentActionTest.swift */; }; + ACF6CFE0F8B88FDBBB16968C /* FraudDetectionDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45FF7A07CFC3B9B7AD6B49EE /* FraudDetectionDataTest.swift */; }; + AE747ADA2841AA06F32558D8 /* STPSourceCardDetailsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63114D0EAAE2606732DF5AA0 /* STPSourceCardDetailsTest.swift */; }; + AF18D569B296BFC1EB5A7338 /* ImageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF368BCD5990EE5DC17D299 /* ImageTest.swift */; }; + AF44725558E654548FED2A2B /* STPPaymentMethodUPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940209E5D30E86E856016906 /* STPPaymentMethodUPITests.swift */; }; + B359F6DCB31EAD0814AD9AFD /* STPPaymentMethodSEPADebitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A41F721AEBB942BB81408A59 /* STPPaymentMethodSEPADebitTest.swift */; }; + B44E4CF6C65522F80C946775 /* STPPaymentMethodFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8192839B0F1AE9D9F2A94504 /* STPPaymentMethodFunctionalTest.swift */; }; + B4719234E4BBDAD260E31373 /* STPPaymentCardTextFieldViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6DEE912364C9F4B51B374D0 /* STPPaymentCardTextFieldViewModelTest.swift */; }; + B6656829DEC006DBEED2AA0E /* STPEphemeralKeyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BEAA15B53AC5662A33D0E1 /* STPEphemeralKeyTest.swift */; }; + B6784B7F4B9B04617C0EE510 /* PKAddPaymentPassRequest+Stripe_Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 273EE407039913F0B644172B /* PKAddPaymentPassRequest+Stripe_Error.swift */; }; + B71F04D02538FA1723558C48 /* STPPaymentMethodiDEALTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B83930B631CF8EADFB606D6 /* STPPaymentMethodiDEALTest.swift */; }; + B795A5EB8FDECA1060A9655C /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; productRef = C55551F29B99CF6D6DD9EE2F /* iOSSnapshotTestCase */; }; + B82859A4444B9F735720F232 /* STPMandateOnlineParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB58AC5E2E0A68221260FD44 /* STPMandateOnlineParamsTest.swift */; }; + B8385576DC25BDEEB92D812F /* STPEphemeralKeyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485E747DA1F72F091986787B /* STPEphemeralKeyProvider.swift */; }; + B86EE8C85E6AB6B0A34C1887 /* STPTokenTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F090B8D315E7FD12A5F9C09 /* STPTokenTest.swift */; }; + B8ED1F697519A6FCD3D79431 /* STPPaymentMethodGiropayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583DB466066B47C0F716E474 /* STPPaymentMethodGiropayTests.swift */; }; + B917BF282C84507292112B9D /* STPCardBINMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1C5E08678292561255B1C5 /* STPCardBINMetadataTests.swift */; }; + B98D71ED9ACC2E1B47372F53 /* NSDecimalNumber+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20552E792B8E7BA15821AB5D /* NSDecimalNumber+StripeTest.swift */; }; + BBB734F006FAD749678B87D1 /* STPPaymentMethodRevolutPayParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE7BEADD3824A06C2994854 /* STPPaymentMethodRevolutPayParamsTests.swift */; }; + BC6912C0DE15008C8D8C303C /* STPFloatingPlaceholderTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7385193226663A5B79E69ED /* STPFloatingPlaceholderTextFieldSnapshotTests.swift */; }; + BC694A1642DC30D530B60635 /* RotatingCardBrandsViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA32D0C9E8A7A69F4899EDC /* RotatingCardBrandsViewSnapshotTests.swift */; }; + BEC0435570B9199B918ED4DA /* STPAPIClient+LinkAccountSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E956CFA6317CAA8B41E217CA /* STPAPIClient+LinkAccountSessionTest.swift */; }; + BEC5B2ACC54FB72DEBFB70AB /* STPPaymentMethodBancontactParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 655209238C85F466F9F14F14 /* STPPaymentMethodBancontactParamsTests.swift */; }; + BF4A92064926FD7B0E3E92F7 /* StripePayments.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 22D1C6EB5826E2D7C80B6CF3 /* StripePayments.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C035D82D7096E3005858848C /* StripeCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43B4E4B85C598D7A9AFCB4D4 /* StripeCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C0688E067AE4FFDFFDDC03BB /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5D9F97ABC88302478220267 /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift */; }; + C0B59D0A7025A55ECD948D47 /* STPPaymentMethodNetBankingParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41081066DC4465734F7FCD7 /* STPPaymentMethodNetBankingParamsTest.swift */; }; + C1E70FD29BBE36D76A7E6929 /* CardExpiryDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153071C69A0BEE033E035DCF /* CardExpiryDateTests.swift */; }; + C32D7ACEBC852CBC295BBEF2 /* STPSetupIntentFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AA313E068FB99CEAA5F7D3 /* STPSetupIntentFunctionalTest.swift */; }; + C34D0BBDF6553ACF85204ACD /* STPPaymentMethodAfterpayClearpayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDD1E823223F450193E8746 /* STPPaymentMethodAfterpayClearpayTest.swift */; }; + C35CF837D67AE8DB7CBDAD98 /* STPThreeDSLabelCustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FDD4956E0A04D33F0856F31 /* STPThreeDSLabelCustomizationTest.swift */; }; + C3AAA4AFEE274B27D3483876 /* STPPaymentMethodFPXTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAEE347A6372AAE2735FAD6F /* STPPaymentMethodFPXTest.swift */; }; + C4C1295E7DA618DFB944A534 /* NSString+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63F5F35DB97D8A176FB6ED24 /* NSString+StripeTest.swift */; }; + C4DC3F4FA93A3BAF6EE782A0 /* PKPayment+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D12508C2F1056A7EAFEC86 /* PKPayment+StripeTest.swift */; }; + C57BDA835AED735321906977 /* STPCardExpiryInputTextFieldValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4418164D75002AE6A0273176 /* STPCardExpiryInputTextFieldValidatorTests.swift */; }; + C5D295FE9988CA80ABA57801 /* RotatingCardBrandsViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12632E6710DE8861CAF1BAA4 /* RotatingCardBrandsViewTests.swift */; }; + C7EB8FB325BF491FDE25FE66 /* STPPaymentMethodEPSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C44B4366D6C4FD4B11662C8 /* STPPaymentMethodEPSTests.swift */; }; + C8226E24CA51133091131391 /* STPConfirmCardOptionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD77D4B1C5B64E45F9DA09B5 /* STPConfirmCardOptionsTest.swift */; }; + C8490E55B1F2EB836144F91C /* STPConnectAccountFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97018A201D01A8FA59999C2 /* STPConnectAccountFunctionalTest.swift */; }; + C861BB9EAAD04949E338D7FF /* PaymentTypeCellSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9A4A2B0FB9F8C743BBED48 /* PaymentTypeCellSnapshotTests.swift */; }; + C9E66A22494C02050AE34A9B /* FBSnapshotTestCase+STPViewControllerLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180CF848E3ABF0236C494D8B /* FBSnapshotTestCase+STPViewControllerLoading.swift */; }; + CA189278AD606BEAC62D545F /* STPPaymentIntentParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF902DC49DD90860BD0E5E80 /* STPPaymentIntentParamsTest.swift */; }; + CA4F392070740C56FE2BB461 /* STPStringUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6AE83989B0596F0C111E13 /* STPStringUtilsTest.swift */; }; + CAC3A0342C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC3A0332C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift */; }; + CAC3A03E2C2F2CD8007BC888 /* STPPaymentMethodBillieTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC3A03D2C2F2CD8007BC888 /* STPPaymentMethodBillieTests.swift */; }; + CAC80EBF2C33339D001E3D0D /* STPPaymentMethodSatispayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC80EBE2C33339D001E3D0D /* STPPaymentMethodSatispayTests.swift */; }; + CB5AADE45B7B7A40514C054B /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52F8AEC50D4623F80F04A533 /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CBAF9C6F87F746F17495ADC2 /* STPPaymentMethodCashAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDE50CBC86AD77084C877B6 /* STPPaymentMethodCashAppTests.swift */; }; + CBCA59D39B30D869B4FDC04B /* STPE2ETest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C1548BA518F7AC2A9ECF9D5 /* STPE2ETest.swift */; }; + CF2E17AC77EB08393B8A3F98 /* STPCardNumberInputTextFieldFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3803D0DED98501AA26B2EAC /* STPCardNumberInputTextFieldFormatterTests.swift */; }; + CFC1F2B8D48FFF7B0F81B5A0 /* STPPaymentMethodAddressTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB3F9F0228008BE213706DF /* STPPaymentMethodAddressTest.swift */; }; + CFDD431A9A8A82BAA11AE5BF /* Stripe3DS2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 33FDC634FD5D79E824240DDC /* Stripe3DS2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D0342D50F9AC319919D93D59 /* STPBECSDebitAccountNumberValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23997D61DF41CA84BFC33080 /* STPBECSDebitAccountNumberValidatorTests.swift */; }; + D0C81317E0AA8EB0370B1BA1 /* LinkLegalTermsViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835CB781FBC19773ACC20676 /* LinkLegalTermsViewSnapshotTests.swift */; }; + D15160C0F0763078DBB434E4 /* STPCardTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D575C31524E596E9C1A8E9B /* STPCardTest.swift */; }; + D151C8724925DCBA4BA4F46A /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 512A0E7C246D5F044245E069 /* StripeCoreTestUtils.framework */; }; + D2C062CE4E54094B1AC33E78 /* STPCardCVCInputTextFieldValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42F83F785EAF24F5DC7ED1A /* STPCardCVCInputTextFieldValidatorTests.swift */; }; + D375ADBD1F4B48380D5347D1 /* CircularButtonSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46AC0B5EC7433E081825D31B /* CircularButtonSnapshotTests.swift */; }; + D3D654D8376AAA634466D31D /* STPSourceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AA6A1B2A462F1CE2F537C5 /* STPSourceTest.swift */; }; + D4602454AC17D3584BA88217 /* STPPaymentMethodEPSParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF93C4B7A5F8FA4E7919794F /* STPPaymentMethodEPSParamsTests.swift */; }; + D53C04A27B6B8EFB70E236A7 /* STPCardParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3F2B714DB3D1DED561A7EF /* STPCardParamsTest.swift */; }; + D54508ED433792AD8AA6610F /* STPPaymentMethodRevolutPayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7963CC618A1A1346EC20C7 /* STPPaymentMethodRevolutPayTests.swift */; }; + D567569568C0D8F2D7B179B3 /* STPPaymentMethodAUBECSDebitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54426CBF6F77ABEFBDFDA8C4 /* STPPaymentMethodAUBECSDebitTests.swift */; }; + D73B7A0C24EDCA415FFBBB18 /* StripeUICore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D794C5E6396B4A19DC4F6921 /* StripeUICore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D76D24F6A94108853BB08712 /* STPFileTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3E0CA28591EB0748C64D1FA /* STPFileTest.swift */; }; + D776B91F0E8E6CCB6C09AC4F /* STPFileFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1264C4DCB32B1FA5CE19201 /* STPFileFunctionalTest.swift */; }; + D7956073A8FD3785193E0577 /* STPStackViewWithSeparatorSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E62BB62EA9B782778CA880 /* STPStackViewWithSeparatorSnapshotTests.swift */; }; + D7C555B36C282B99E22B8D45 /* STPInputTextFieldFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A22E5B87755C1F05C3DB438C /* STPInputTextFieldFormatterTests.swift */; }; + D7D24DCC9402153965AF7F1B /* STPPaymentMethodPrzelewy24Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6558C62376C2397030BD4A6 /* STPPaymentMethodPrzelewy24Tests.swift */; }; + D83F76F584BC345CFBA71CF8 /* OneTimeCodeTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D85FA7B714BDD8D1FD83B75 /* OneTimeCodeTextFieldSnapshotTests.swift */; }; + D8BECFB70834CC42BA6706D8 /* STPSourceParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F6CB4B8FAD14B4D70A63595 /* STPSourceParamsTest.swift */; }; + DC57D2DC40C6BA0C9CF7EC92 /* PayWithLinkButtonSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF342CBC167F9CAB5B49CC32 /* PayWithLinkButtonSnapshotTests.swift */; }; + DCF615643A22D0A7B739547C /* Stripe+Exports.swift in Sources */ = {isa = PBXBuildFile; fileRef = B70DF0B659009041F485EE0F /* Stripe+Exports.swift */; }; + DD16FC7ABCA7817794ECC407 /* STPThreeDSSelectionCustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23D612FD5AD7772E1B30DCC /* STPThreeDSSelectionCustomizationTest.swift */; }; + DD8E2B99BAE917F83258DC35 /* STPPaymentMethodOptionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2638F7AA0906914117C2D5 /* STPPaymentMethodOptionsTest.swift */; }; + DE23FEF74E860620A334FDF5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 884C01B087B4D820395BD374 /* Main.storyboard */; }; + DF85F5EC6E16CAD21491891A /* AnalyticsHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61AF6E95FE0DD913204CAB32 /* AnalyticsHelperTests.swift */; }; + E0F011E9C6CA368EF87F8E28 /* STPPaymentMethodBillingDetailsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E24B5689732EB9106DA232 /* STPPaymentMethodBillingDetailsTest.swift */; }; + E2790AB17C8C65CDE1E81532 /* STPPaymentMethodPrzelewy24ParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6269E77F81C32A5EC8BE412 /* STPPaymentMethodPrzelewy24ParamsTests.swift */; }; + E3E916EB10E19727D6B33081 /* STPPaymentMethodOXXOParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC9D4B0158266B01840AD9A /* STPPaymentMethodOXXOParamsTests.swift */; }; + E3F1BAD22CC6E90B761B0502 /* STPTextFieldDelegateProxyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8A2CD759D465290066EF65 /* STPTextFieldDelegateProxyTests.swift */; }; + E699508F4DB4D9D4666BAA08 /* STPSetupIntentLastSetupErrorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B057D99A14E5BA6019C349 /* STPSetupIntentLastSetupErrorTest.swift */; }; + E6F428CFAD64979A8874B00B /* STPAnalyticsClientPaymentsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7ACB4FAFAD33296DE34D036 /* STPAnalyticsClientPaymentsTest.swift */; }; + E97168F37D769524B58461B6 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43B4E4B85C598D7A9AFCB4D4 /* StripeCore.framework */; }; + E9A2C6E153CB480891846705 /* STPPaymentMethodGiropayParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F372EDF9C2C45E1CA2C76866 /* STPPaymentMethodGiropayParamsTests.swift */; }; + E9C690F3629C0AC3CD0260AF /* StripePaymentsObjcTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D237177A7F99EB0F5F4F5E4 /* StripePaymentsObjcTestUtils.framework */; }; + EA34719659CB9F1A269FECC7 /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D794C5E6396B4A19DC4F6921 /* StripeUICore.framework */; }; + EA571ECEFDDF10AF87CE2B74 /* STPThreeDSFooterCustomizationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483B243268646AE65B06E98C /* STPThreeDSFooterCustomizationTest.swift */; }; + EA80A8DB806DEF4F519059CB /* STPSourceSEPADebitDetailsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21780C410D22264B7C299520 /* STPSourceSEPADebitDetailsTest.swift */; }; + EBD436689635CC28A24DECD4 /* STPPinManagementServiceFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336CC555B845DED30208D39D /* STPPinManagementServiceFunctionalTest.swift */; }; + EC4DC8E386544959E1AA9355 /* STPSetupIntentTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA8B8F540CD05B3DC2C5EEA6 /* STPSetupIntentTest.swift */; }; + EEA502DF8809B8FD0D00785E /* StripePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22D1C6EB5826E2D7C80B6CF3 /* StripePayments.framework */; }; + EEB5E5E9C4E06B148A91C7BD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6955B3A3353F8442E4FBBBF6 /* Assets.xcassets */; }; + EEBA9A95E8057A06E5E7C103 /* STPCardNumberInputTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD289E1EA9F0CE1C848AC0BB /* STPCardNumberInputTextFieldSnapshotTests.swift */; }; + F06EAD0F48302B061ED29E61 /* STPPaymentMethodCardWalletMasterpassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533538E3EB92E326CCB95506 /* STPPaymentMethodCardWalletMasterpassTest.swift */; }; + F2655328479314A9C8718DE4 /* STPApplePayContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F7AB40A5A10C2D267323ABE /* STPApplePayContextTest.swift */; }; + F35E090A607EB5F86FFC3D31 /* STPCardCVCInputTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A7104C1C470515616E4D2B /* STPCardCVCInputTextFieldTests.swift */; }; + F49D9C4030829D13A6EB45BE /* MockFiles in Resources */ = {isa = PBXBuildFile; fileRef = FE2DED6ABA7407C17C1391B6 /* MockFiles */; }; + F550D4EB3DCFE03D6FC8F023 /* STPIntentActionPayNowDisplayQrCodeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A517686D4D12691351311CA /* STPIntentActionPayNowDisplayQrCodeTest.swift */; }; + F5CC4F320D09A06F0B21ABE6 /* STPPaymentMethodAffirmTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0DF2ED9232A7CC51F5FCB1 /* STPPaymentMethodAffirmTests.swift */; }; + F60858F62CEF058200D62278 /* STPPaymentMethodCryptoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60858F52CEF058200D62278 /* STPPaymentMethodCryptoTests.swift */; }; + F60858F82CEF058F00D62278 /* STPPaymentMethodCryptoParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60858F72CEF058F00D62278 /* STPPaymentMethodCryptoParamsTests.swift */; }; + F729E784CFFC1F79EF5F2ABE /* STPPaymentIntentLastPaymentErrorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7B8DACB0A7294BC235E3BC /* STPPaymentIntentLastPaymentErrorTest.swift */; }; + F86F2DF6E46EFABE23AD5D27 /* STPApplePayContextDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E452877E5D11120B1E28A6E7 /* STPApplePayContextDelegate.swift */; }; + F975CE029DF30419B8DB0D8F /* STPNumericStringValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA36705DED9164663A98B6A /* STPNumericStringValidatorTests.swift */; }; + FBBA3B39598BBECB664C5E7F /* STPApplePayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC4BC1AB047ED88C4D13C89 /* STPApplePayTest.swift */; }; + FDADE3E36804A8AD82301BF3 /* STPPaymentMethodAfterpayClearpayParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBA5B09A3FD875C93218573 /* STPPaymentMethodAfterpayClearpayParamsTest.swift */; }; + FDD1858CAEFCEBB22BEC9BBC /* MKPlacemark+PaymentSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DCCA0E8A02B4F4B23837FB4 /* MKPlacemark+PaymentSheetTests.swift */; }; + FE6647242714D9BEA1EBC055 /* STPCardCVCInputTextFieldFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1F6514E7530C2A3478B2F5 /* STPCardCVCInputTextFieldFormatterTests.swift */; }; + FE7C38B95B3B7E028AB21238 /* STPPaymentMethodCardChecksTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A15BBFFA401852A8719E3DDD /* STPPaymentMethodCardChecksTest.swift */; }; + FEE74744B657F86873EA2F3D /* STPPushProvisioningDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E5DA3029F141B5111A5B2C /* STPPushProvisioningDetails.swift */; }; + FEF2E0DAC862FF42B814AFCA /* STPPaymentHandlerFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 683F7735569D22CBEC9CA2E6 /* STPPaymentHandlerFunctionalTest.m */; }; + FF0F9BA6FE4B88297A434EA7 /* STPPaymentMethodNetBankingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739934737B9A09775CD278C9 /* STPPaymentMethodNetBankingTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 32221E5BA07FB5AA4EBFE81C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E63832AA5BB4225708B7C838 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1628E8B14F1F7C63CF8C9962; + remoteInfo = StripeiOSTestHostApp; + }; + 8F25D3DFD6D65DAE4B581911 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E63832AA5BB4225708B7C838 /* Project object */; + proxyType = 1; + remoteGlobalIDString = ADF894AA8F6022D9BED17346; + remoteInfo = StripeiOS; + }; + D90CE98566BBDA0E7340E1D7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E63832AA5BB4225708B7C838 /* Project object */; + proxyType = 1; + remoteGlobalIDString = ADF894AA8F6022D9BED17346; + remoteInfo = StripeiOS; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 16FB342935F756BD2EA7CE4C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + CFDD431A9A8A82BAA11AE5BF /* Stripe3DS2.framework in Embed Frameworks */, + CB5AADE45B7B7A40514C054B /* StripeApplePay.framework in Embed Frameworks */, + C035D82D7096E3005858848C /* StripeCore.framework in Embed Frameworks */, + 42395EF962DB8AD6A094630B /* StripePaymentSheet.framework in Embed Frameworks */, + BF4A92064926FD7B0E3E92F7 /* StripePayments.framework in Embed Frameworks */, + 5B6F1BF973FC2D8DD6127B7F /* StripePaymentsUI.framework in Embed Frameworks */, + D73B7A0C24EDCA415FFBBB18 /* StripeUICore.framework in Embed Frameworks */, + 162C101E57D66F0051164C4A /* Stripe.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 5789BE2CD297329ED86678C0 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9F30C6D40956861F588C2CD0 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + CAAA5D4C8E87896995960E8C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 005650A59D692F820EF20F5F /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 01B057D99A14E5BA6019C349 /* STPSetupIntentLastSetupErrorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSetupIntentLastSetupErrorTest.swift; sourceTree = ""; }; + 05AA6A1B2A462F1CE2F537C5 /* STPSourceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceTest.swift; sourceTree = ""; }; + 05FE1BA89B80336F16924FA2 /* STPConfirmPaymentMethodOptionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPConfirmPaymentMethodOptionsTest.swift; sourceTree = ""; }; + 0669B4CA326CE74D125C789C /* STPFakeAddPaymentPassViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFakeAddPaymentPassViewController.swift; sourceTree = ""; }; + 090EF7D598B8DE779C275395 /* STPPostalCodeInputTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextFieldSnapshotTests.swift; sourceTree = ""; }; + 095EBF095BA2BC8D299547DB /* STPSourceOwnerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceOwnerTest.swift; sourceTree = ""; }; + 0A8FF64CA314F909D7EC82FE /* STPPaymentMethodGrabPayParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodGrabPayParamsTest.swift; sourceTree = ""; }; + 0ABB2CA7E96BE249CE8C0566 /* STPPaymentMethodCashAppParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCashAppParamsTests.swift; sourceTree = ""; }; + 0C44B4366D6C4FD4B11662C8 /* STPPaymentMethodEPSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodEPSTests.swift; sourceTree = ""; }; + 0C75157665428685C7A4FD20 /* STPCardExpiryInputTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardExpiryInputTextFieldSnapshotTests.swift; sourceTree = ""; }; + 0DB03E83746FE78361831546 /* WalletHeaderViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletHeaderViewSnapshotTests.swift; sourceTree = ""; }; + 0E10C4D64477638398251FFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 11C866064B3482878A69892F /* CustomerAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerAdapterTests.swift; sourceTree = ""; }; + 12632E6710DE8861CAF1BAA4 /* RotatingCardBrandsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotatingCardBrandsViewTests.swift; sourceTree = ""; }; + 148C1D7D1BBBC6B74894A869 /* STPPaymentMethodTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodTest.swift; sourceTree = ""; }; + 153071C69A0BEE033E035DCF /* CardExpiryDateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardExpiryDateTests.swift; sourceTree = ""; }; + 1645D9793463E266501B74FD /* STPPIIFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPIIFunctionalTest.swift; sourceTree = ""; }; + 1671EC46C713D51013AD7D8B /* STPPaymentMethodCardParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardParamsTest.swift; sourceTree = ""; }; + 17013F78CE3F9662029FEF5B /* STPSourceFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceFunctionalTest.swift; sourceTree = ""; }; + 180CF848E3ABF0236C494D8B /* FBSnapshotTestCase+STPViewControllerLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FBSnapshotTestCase+STPViewControllerLoading.swift"; sourceTree = ""; }; + 195DEC752CC82CC4BA1E2351 /* STPConnectAccountParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPConnectAccountParamsTest.swift; sourceTree = ""; }; + 1A8A6B88797870BC71CCB3AF /* STPPushProvisioningDetailsFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPushProvisioningDetailsFunctionalTest.swift; sourceTree = ""; }; + 1C1548BA518F7AC2A9ECF9D5 /* STPE2ETest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPE2ETest.swift; sourceTree = ""; }; + 1CE268457D21A7209862E004 /* STPApplePayContextFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayContextFunctionalTest.swift; sourceTree = ""; }; + 1D23EB567F573612E0794B3A /* Stripe Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe Tests-Debug.xcconfig"; sourceTree = ""; }; + 1D51B04D83D4FEF7F90DF16A /* STPCertTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCertTest.swift; sourceTree = ""; }; + 1D575C31524E596E9C1A8E9B /* STPCardTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardTest.swift; sourceTree = ""; }; + 1D983E089196152DA1C69469 /* STPCardFormViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardFormViewSnapshotTests.swift; sourceTree = ""; }; + 1DD6897858F46976A946394E /* StripeiOSAppHostedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeiOSAppHostedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1E2638F7AA0906914117C2D5 /* STPPaymentMethodOptionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodOptionsTest.swift; sourceTree = ""; }; + 1F0DF2ED9232A7CC51F5FCB1 /* STPPaymentMethodAffirmTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAffirmTests.swift; sourceTree = ""; }; + 1F16C36797D978E72E612100 /* STPPaymentCardTextFieldTestsSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextFieldTestsSwift.swift; sourceTree = ""; }; + 1F29C15B47C7CB0941CD4C9E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 1F6CB4B8FAD14B4D70A63595 /* STPSourceParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceParamsTest.swift; sourceTree = ""; }; + 20552E792B8E7BA15821AB5D /* NSDecimalNumber+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDecimalNumber+StripeTest.swift"; sourceTree = ""; }; + 207677E2A0DBC04C88139372 /* STPPaymentMethodSofortParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSofortParamsTests.swift; sourceTree = ""; }; + 20879436DCFB1F03BE1608B3 /* ServerErrorMapperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerErrorMapperTest.swift; sourceTree = ""; }; + 21780C410D22264B7C299520 /* STPSourceSEPADebitDetailsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceSEPADebitDetailsTest.swift; sourceTree = ""; }; + 22D1C6EB5826E2D7C80B6CF3 /* StripePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 23997D61DF41CA84BFC33080 /* STPBECSDebitAccountNumberValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBECSDebitAccountNumberValidatorTests.swift; sourceTree = ""; }; + 273EE407039913F0B644172B /* PKAddPaymentPassRequest+Stripe_Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKAddPaymentPassRequest+Stripe_Error.swift"; sourceTree = ""; }; + 284C67269D2606DA147AE01D /* STPGenericInputPickerFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerFieldSnapshotTests.swift; sourceTree = ""; }; + 294CD46E24BB2743042872D7 /* StripeiOSTestHostApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StripeiOSTestHostApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A63AC868755CB4745E7458E /* STPPaymentMethodPayPalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodPayPalTests.swift; sourceTree = ""; }; + 2A8A2CD759D465290066EF65 /* STPTextFieldDelegateProxyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTextFieldDelegateProxyTests.swift; sourceTree = ""; }; + 2B28B8A547CD846277ECD578 /* STPPushProvisioningDetailsParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPushProvisioningDetailsParams.swift; sourceTree = ""; }; + 2D63B73C5773432CA134D1FC /* STPGenericInputPickerFieldValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerFieldValidatorTest.swift; sourceTree = ""; }; + 2D878F923A1F69B58D6B2812 /* STPRadarSessionFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPRadarSessionFunctionalTest.swift; sourceTree = ""; }; + 2DC4B62C336EAA05A33FC384 /* STPPaymentMethodCardWalletTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardWalletTest.swift; sourceTree = ""; }; + 2E1862744F23286D1FB9D4AE /* STPFormTextFieldTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFormTextFieldTest.swift; sourceTree = ""; }; + 30964128998473CAA9F2DD7E /* STPFormEncoderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFormEncoderTest.swift; sourceTree = ""; }; + 313F5F782B0BE59000BD98A9 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; + 31CDFC2D2BA3708000B3DD91 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 336CC555B845DED30208D39D /* STPPinManagementServiceFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPinManagementServiceFunctionalTest.swift; sourceTree = ""; }; + 33B9D01D037909D1C9C0B617 /* STPIntentActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionTest.swift; sourceTree = ""; }; + 33FDC634FD5D79E824240DDC /* Stripe3DS2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stripe3DS2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AE7BEADD3824A06C2994854 /* STPPaymentMethodRevolutPayParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodRevolutPayParamsTests.swift; sourceTree = ""; }; + 3B0E131538728BC4802627B1 /* UserDefaults+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+StripeTest.swift"; sourceTree = ""; }; + 3B112FFF3FCA82094281493F /* STPPaymentMethodUSBankAccountTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodUSBankAccountTest.swift; sourceTree = ""; }; + 3C742844915B96CFD25BFFF9 /* AfterpayPriceBreakdownViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AfterpayPriceBreakdownViewSnapshotTests.swift; sourceTree = ""; }; + 3C77C7BC4BA57EC296CF2F1C /* STPApplePayFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayFunctionalTest.swift; sourceTree = ""; }; + 3CDD1E823223F450193E8746 /* STPPaymentMethodAfterpayClearpayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAfterpayClearpayTest.swift; sourceTree = ""; }; + 3E1C5E08678292561255B1C5 /* STPCardBINMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardBINMetadataTests.swift; sourceTree = ""; }; + 3EBB07171F6FDCE6E20C454A /* STPPinManagementService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPinManagementService.swift; sourceTree = ""; }; + 3ED44491EB0AC72B1B1A773C /* STPThreeDSUICustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSUICustomizationTest.swift; sourceTree = ""; }; + 3FC0560A312147C37CFE6CF9 /* STPBinRangeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBinRangeTest.swift; sourceTree = ""; }; + 4002981AC12687681616D21E /* STPPaymentMethodParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodParamsTest.swift; sourceTree = ""; }; + 40BB87E28719FE0C6B946BB5 /* STPCardExpiryInputTextFieldFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardExpiryInputTextFieldFormatterTests.swift; sourceTree = ""; }; + 4259421D2CD26E37B96F97B2 /* Stripe.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stripe.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43B4E4B85C598D7A9AFCB4D4 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4418164D75002AE6A0273176 /* STPCardExpiryInputTextFieldValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardExpiryInputTextFieldValidatorTests.swift; sourceTree = ""; }; + 45FF7A07CFC3B9B7AD6B49EE /* FraudDetectionDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FraudDetectionDataTest.swift; sourceTree = ""; }; + 46AC0B5EC7433E081825D31B /* CircularButtonSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularButtonSnapshotTests.swift; sourceTree = ""; }; + 46C31CAD0FA74B58BA2B8530 /* STPPaymentIntentEnumsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentEnumsTest.swift; sourceTree = ""; }; + 47E12A0CBFA259A032F7AF0C /* STPPaymentMethodKlarnaParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodKlarnaParamsTests.swift; sourceTree = ""; }; + 47E5E1173A37AABB07FB68AB /* STPPaymentMethodUPIParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodUPIParamsTest.swift; sourceTree = ""; }; + 483B243268646AE65B06E98C /* STPThreeDSFooterCustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSFooterCustomizationTest.swift; sourceTree = ""; }; + 485E747DA1F72F091986787B /* STPEphemeralKeyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEphemeralKeyProvider.swift; sourceTree = ""; }; + 49AA313E068FB99CEAA5F7D3 /* STPSetupIntentFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSetupIntentFunctionalTest.swift; sourceTree = ""; }; + 4AA36705DED9164663A98B6A /* STPNumericStringValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPNumericStringValidatorTests.swift; sourceTree = ""; }; + 4AAE5EE11611F9F7762B64C6 /* STPAUBECSFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAUBECSFormViewModelTests.swift; sourceTree = ""; }; + 4CA5D11C977A95B8E936E907 /* STPIntentActionWeChatPayRedirectToAppTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionWeChatPayRedirectToAppTest.swift; sourceTree = ""; }; + 4E29B46F2C940E0A21734E09 /* StripeiOSTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = StripeiOSTests.xctestplan; sourceTree = ""; }; + 4E371E9B3B2E343FE954531C /* STPPaymentMethodBoletoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBoletoTests.swift; sourceTree = ""; }; + 4FD94FF270165D699DA89B24 /* STPPaymentMethodKlarnaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodKlarnaTests.swift; sourceTree = ""; }; + 4FFA8B446217CDE678D7287F /* STPBlocks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STPBlocks.h; sourceTree = ""; }; + 512A0E7C246D5F044245E069 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 51408DE266D0345784ADD4FA /* STPThreeDSNavigationBarCustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSNavigationBarCustomizationTest.swift; sourceTree = ""; }; + 51BD2CE41E4F0CF648F44E4A /* TextFieldElement+IBANTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+IBANTest.swift"; sourceTree = ""; }; + 51E62BB62EA9B782778CA880 /* STPStackViewWithSeparatorSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPStackViewWithSeparatorSnapshotTests.swift; sourceTree = ""; }; + 52F8AEC50D4623F80F04A533 /* StripeApplePay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeApplePay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 533538E3EB92E326CCB95506 /* STPPaymentMethodCardWalletMasterpassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardWalletMasterpassTest.swift; sourceTree = ""; }; + 54426CBF6F77ABEFBDFDA8C4 /* STPPaymentMethodAUBECSDebitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAUBECSDebitTests.swift; sourceTree = ""; }; + 583DB466066B47C0F716E474 /* STPPaymentMethodGiropayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodGiropayTests.swift; sourceTree = ""; }; + 588C260880FFC584A00A89F5 /* STPEphemeralKeyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEphemeralKeyManager.swift; sourceTree = ""; }; + 58A53F005EA8FDDAA66126BA /* STPGenericInputTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputTextFieldSnapshotTests.swift; sourceTree = ""; }; + 5D237177A7F99EB0F5F4F5E4 /* StripePaymentsObjcTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentsObjcTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5DE9F0BAC35EA14579775033 /* STPPaymentMethodSwishTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSwishTests.swift; sourceTree = ""; }; + 5E4EA6394497D1BD57ED0032 /* Stripe Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe Tests-Release.xcconfig"; sourceTree = ""; }; + 5F7AB40A5A10C2D267323ABE /* STPApplePayContextTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayContextTest.swift; sourceTree = ""; }; + 5FDD4956E0A04D33F0856F31 /* STPThreeDSLabelCustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSLabelCustomizationTest.swift; sourceTree = ""; }; + 610DF5DB2B33597500DA6AAA /* HostedSurfaceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostedSurfaceTest.swift; sourceTree = ""; }; + 61152B4E2B866827003B69A0 /* STPPaymentMethodAmazonPayParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAmazonPayParamsTests.swift; sourceTree = ""; }; + 617C1C872BB4992400B10AC5 /* STPPaymentMethodAlmaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAlmaTests.swift; sourceTree = ""; }; + 617C1C892BB4998C00B10AC5 /* STPPaymentMethodAlmaParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAlmaParamsTests.swift; sourceTree = ""; }; + 61951FB82B866BA1005F90BE /* STPPaymentMethodAmazonPayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAmazonPayTests.swift; sourceTree = ""; }; + 61AF6E95FE0DD913204CAB32 /* AnalyticsHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsHelperTests.swift; sourceTree = ""; }; + 61E0A0C42BF31D3C00C89786 /* STPPaymentHandlerRefreshTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentHandlerRefreshTests.swift; sourceTree = ""; }; + 61E1CA1E2BD6B72800A421AE /* STPPaymentMethodMultibancoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodMultibancoTests.swift; sourceTree = ""; }; + 61E1CA202BD6B78500A421AE /* STPPaymentMethodMultibancoParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodMultibancoParamsTests.swift; sourceTree = ""; }; + 61E1CA262BD6BED600A421AE /* STPIntentActionMultibancoDisplayDetailsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionMultibancoDisplayDetailsTest.swift; sourceTree = ""; }; + 6223E57D3A198F956A37ED89 /* STPNumericDigitInputTextFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPNumericDigitInputTextFormatterTests.swift; sourceTree = ""; }; + 63114D0EAAE2606732DF5AA0 /* STPSourceCardDetailsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceCardDetailsTest.swift; sourceTree = ""; }; + 63F5F35DB97D8A176FB6ED24 /* NSString+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSString+StripeTest.swift"; sourceTree = ""; }; + 655209238C85F466F9F14F14 /* STPPaymentMethodBancontactParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBancontactParamsTests.swift; sourceTree = ""; }; + 65DCC9BDA647E58D3882C698 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 6618739767139C25C05B3631 /* STPPostalCodeInputTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextFieldTests.swift; sourceTree = ""; }; + 683F7735569D22CBEC9CA2E6 /* STPPaymentHandlerFunctionalTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentHandlerFunctionalTest.m; sourceTree = ""; }; + 6887F19BB9804BF45FD703FF /* STPPushProvisioningContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPushProvisioningContext.swift; sourceTree = ""; }; + 6955B3A3353F8442E4FBBBF6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6B7A947152A728EB2CBC4DB2 /* STPSourceReceiverTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceReceiverTest.swift; sourceTree = ""; }; + 6BA4B9192BF433B200D1F21D /* STPPaymentMethodMobilePayParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodMobilePayParamsTests.swift; sourceTree = ""; }; + 6BA4B91B2BF4343B00D1F21D /* STPPaymentMethodMobilePayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodMobilePayTests.swift; sourceTree = ""; }; + 6BC5EC2C2B4609FF00CC75E8 /* LinkInlineSignupElementSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkInlineSignupElementSnapshotTests.swift; sourceTree = ""; }; + 6C7B8DACB0A7294BC235E3BC /* STPPaymentIntentLastPaymentErrorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentLastPaymentErrorTest.swift; sourceTree = ""; }; + 6CC0B1FC92A573AAEA4F4E94 /* STPSetupIntentConfirmParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSetupIntentConfirmParamsTest.swift; sourceTree = ""; }; + 6E1F6514E7530C2A3478B2F5 /* STPCardCVCInputTextFieldFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextFieldFormatterTests.swift; sourceTree = ""; }; + 739934737B9A09775CD278C9 /* STPPaymentMethodNetBankingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodNetBankingTests.swift; sourceTree = ""; }; + 74FDEF9F687C63BADFB96480 /* STPPaymentMethodUSBankAccountParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodUSBankAccountParamsTest.swift; sourceTree = ""; }; + 77247622AB08FEF48CA0DC26 /* StripePaymentsTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentsTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 77E846CD56018D8417A3AB95 /* StripeiOS.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StripeiOS.xcassets; sourceTree = ""; }; + 78D1EEABBAE5BD5615486B0F /* STPLabeledFormTextFieldViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLabeledFormTextFieldViewSnapshotTests.swift; sourceTree = ""; }; + 7A517686D4D12691351311CA /* STPIntentActionPayNowDisplayQrCodeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionPayNowDisplayQrCodeTest.swift; sourceTree = ""; }; + 7A7963CC618A1A1346EC20C7 /* STPPaymentMethodRevolutPayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodRevolutPayTests.swift; sourceTree = ""; }; + 7AC0A18C441FCA394BEF6A3D /* STPPaymentMethodBillingDetailsTests+Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPPaymentMethodBillingDetailsTests+Link.swift"; sourceTree = ""; }; + 7B9A4A2B0FB9F8C743BBED48 /* PaymentTypeCellSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentTypeCellSnapshotTests.swift; sourceTree = ""; }; + 7C04AFC9CDE50D09D38A3232 /* FormSpecProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormSpecProviderTest.swift; sourceTree = ""; }; + 7DDE50CBC86AD77084C877B6 /* STPPaymentMethodCashAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCashAppTests.swift; sourceTree = ""; }; + 7F090B8D315E7FD12A5F9C09 /* STPTokenTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTokenTest.swift; sourceTree = ""; }; + 7F68637E75142DCD46710796 /* STPLabeledMultiFormTextFieldViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLabeledMultiFormTextFieldViewSnapshotTests.swift; sourceTree = ""; }; + 7FF2B9FF57E100301B5C38DB /* STPPaymentMethodAUBECSDebitParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAUBECSDebitParamsTests.swift; sourceTree = ""; }; + 806124200E77795DCFC8418E /* ConfirmButtonSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonSnapshotTests.swift; sourceTree = ""; }; + 807FF966F1DE05F3496B817B /* STPAPIClient+PushProvisioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+PushProvisioning.swift"; sourceTree = ""; }; + 80BBCC4D386EE44E809A591C /* STPPaymentMethodOXXOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodOXXOTests.swift; sourceTree = ""; }; + 8192839B0F1AE9D9F2A94504 /* STPPaymentMethodFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodFunctionalTest.swift; sourceTree = ""; }; + 82E46B18CDDC191934F3D4BE /* STPPaymentMethodCardWalletVisaCheckoutTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardWalletVisaCheckoutTest.swift; sourceTree = ""; }; + 835CB781FBC19773ACC20676 /* LinkLegalTermsViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkLegalTermsViewSnapshotTests.swift; sourceTree = ""; }; + 85AAB72218409F85FE29E69E /* APIRequestTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIRequestTest.swift; sourceTree = ""; }; + 85C29AE44D809CD677B5E52B /* STPFPXBankBrandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFPXBankBrandTest.swift; sourceTree = ""; }; + 86983B09A1712944EC012AD4 /* STPViewWithSeparatorSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPViewWithSeparatorSnapshotTests.swift; sourceTree = ""; }; + 890660C21E3666CE7B82695B /* STPEphemeralKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEphemeralKey.swift; sourceTree = ""; }; + 89E5DA3029F141B5111A5B2C /* STPPushProvisioningDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPushProvisioningDetails.swift; sourceTree = ""; }; + 8A3F2B714DB3D1DED561A7EF /* STPCardParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardParamsTest.swift; sourceTree = ""; }; + 8BD02D8298877F10F2EF2A9D /* STPPaymentMethodCardTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardTest.swift; sourceTree = ""; }; + 8CE85C770AEEDBE4AEC93EAA /* Error+PaymentSheetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+PaymentSheetTests.swift"; sourceTree = ""; }; + 8D49257A97E71A475A9F6E08 /* STPCountryPickerInputFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCountryPickerInputFieldSnapshotTests.swift; sourceTree = ""; }; + 901BE31021AF27DB5D326327 /* STPCardBrandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardBrandTest.swift; sourceTree = ""; }; + 916DB8789F65D3C1BCB510C0 /* STPPaymentMethodThreeDSecureUsageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodThreeDSecureUsageTest.swift; sourceTree = ""; }; + 917154477796779ECFA1334A /* STPPostalCodeInputTextFieldFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextFieldFormatterTests.swift; sourceTree = ""; }; + 924E878428D15506711CA628 /* STPPhoneNumberValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPhoneNumberValidatorTest.swift; sourceTree = ""; }; + 939360978872BBE4334215B1 /* STPPaymentCardTextFieldTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextFieldTest.swift; sourceTree = ""; }; + 940209E5D30E86E856016906 /* STPPaymentMethodUPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodUPITests.swift; sourceTree = ""; }; + 9466F23BA8712EA2EDA48BBD /* STPIntentActionPromptPayDisplayQrCodeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionPromptPayDisplayQrCodeTest.swift; sourceTree = ""; }; + 94A7104C1C470515616E4D2B /* STPCardCVCInputTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextFieldTests.swift; sourceTree = ""; }; + 9631915F03F157A1CC3FEFFE /* STPPaymentMethodSwishParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSwishParamsTests.swift; sourceTree = ""; }; + 967C784618A074FF021B3089 /* STPBankAccountParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBankAccountParamsTest.swift; sourceTree = ""; }; + 967DDC94B687B14E07842CC8 /* STPConnectAccountAddressTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPConnectAccountAddressTest.swift; sourceTree = ""; }; + 969E196AB597EEF68C38103E /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + 989411FA3CD0CCC38BC227F4 /* STPBankAccountFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBankAccountFunctionalTest.swift; sourceTree = ""; }; + 98F9CB667BC68767DFB5FACD /* OneTimeCodeTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneTimeCodeTextFieldTests.swift; sourceTree = ""; }; + 9B83930B631CF8EADFB606D6 /* STPPaymentMethodiDEALTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodiDEALTest.swift; sourceTree = ""; }; + 9BBCE3A905041A709E8F279A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 9D85FA7B714BDD8D1FD83B75 /* OneTimeCodeTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneTimeCodeTextFieldSnapshotTests.swift; sourceTree = ""; }; + 9DCCA0E8A02B4F4B23837FB4 /* MKPlacemark+PaymentSheetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MKPlacemark+PaymentSheetTests.swift"; sourceTree = ""; }; + 9EAE9A2AE65771403CE57C11 /* STPErrorBridgeTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPErrorBridgeTest.m; sourceTree = ""; }; + 9FEE395C4DD0E0112AF3720C /* STPInputTextFieldValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPInputTextFieldValidatorTests.swift; sourceTree = ""; }; + A0E24B5689732EB9106DA232 /* STPPaymentMethodBillingDetailsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBillingDetailsTest.swift; sourceTree = ""; }; + A1272F2E05A0E294DD9ECA26 /* STPApplePayContextFunctionalTestExtras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayContextFunctionalTestExtras.swift; sourceTree = ""; }; + A15BBFFA401852A8719E3DDD /* STPPaymentMethodCardChecksTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCardChecksTest.swift; sourceTree = ""; }; + A1C67AC5D415615E9F27D3E3 /* STPPaymentMethodBoletoParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBoletoParamsTests.swift; sourceTree = ""; }; + A22E5B87755C1F05C3DB438C /* STPInputTextFieldFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPInputTextFieldFormatterTests.swift; sourceTree = ""; }; + A30CD9D92CCC2DFE00EA22D3 /* STPAPIClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPAPIClientTest.swift; sourceTree = ""; }; + A39580123A4F1EA96F91768A /* STPAddressTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAddressTests.swift; sourceTree = ""; }; + A41F721AEBB942BB81408A59 /* STPPaymentMethodSEPADebitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSEPADebitTest.swift; sourceTree = ""; }; + A583966A33DCDCF04322A592 /* STPThreeDSTextFieldCustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSTextFieldCustomizationTest.swift; sourceTree = ""; }; + A61763BA2CCA86F9B8FD4F1F /* OperationDebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationDebouncerTests.swift; sourceTree = ""; }; + A6269E77F81C32A5EC8BE412 /* STPPaymentMethodPrzelewy24ParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodPrzelewy24ParamsTests.swift; sourceTree = ""; }; + A8598727045C6268B57A5FC7 /* Stripe-umbrella.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Stripe-umbrella.h"; sourceTree = ""; }; + AAF368BCD5990EE5DC17D299 /* ImageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTest.swift; sourceTree = ""; }; + ACE8998EAF997A78759E49B5 /* STPPaymentIntentTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentTest.swift; sourceTree = ""; }; + ACF450AD17FF7BCE5916DDF1 /* STPPaymentHandlerFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentHandlerFunctionalTest.swift; sourceTree = ""; }; + AD06AED0AF8A9A7FB4A2E66F /* STPIntentActionAlipayHandleRedirectTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionAlipayHandleRedirectTest.swift; sourceTree = ""; }; + AD8BED2A6066514B51693172 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + AF342CBC167F9CAB5B49CC32 /* PayWithLinkButtonSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayWithLinkButtonSnapshotTests.swift; sourceTree = ""; }; + B3E0745D13EB19BAA24F3BA3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + B407FE2D39775902A95B1118 /* StripeiOS Tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StripeiOS Tests-Bridging-Header.h"; sourceTree = ""; }; + B4D12508C2F1056A7EAFEC86 /* PKPayment+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKPayment+StripeTest.swift"; sourceTree = ""; }; + B6F3B966470A530E0DC53F8C /* STPThreeDSButtonCustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSButtonCustomizationTest.swift; sourceTree = ""; }; + B70DF0B659009041F485EE0F /* Stripe+Exports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stripe+Exports.swift"; sourceTree = ""; }; + B7F7AA0B7B86BA5BB2FE92CE /* STPMandateDataParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMandateDataParamsTest.swift; sourceTree = ""; }; + B9BA8D8467218C7E691C9FAE /* STPAPIClientNetworkBridgeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAPIClientNetworkBridgeTest.swift; sourceTree = ""; }; + BB08D2AC882B21C8ADD76B92 /* STPPaymentCardTextFieldKVOTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentCardTextFieldKVOTest.m; sourceTree = ""; }; + BCBEA9E4823F08C1F5057B5A /* STPAUBECSDebitFormViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAUBECSDebitFormViewSnapshotTests.swift; sourceTree = ""; }; + BD77D4B1C5B64E45F9DA09B5 /* STPConfirmCardOptionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPConfirmCardOptionsTest.swift; sourceTree = ""; }; + BD89580A3E41D7167C30B287 /* STPAnalyticsClient+Payments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+Payments.swift"; sourceTree = ""; }; + BEB3F9F0228008BE213706DF /* STPPaymentMethodAddressTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAddressTest.swift; sourceTree = ""; }; + BF1CF8FB0100664A02468FBC /* STPPaymentMethodBacsDebitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBacsDebitTest.swift; sourceTree = ""; }; + C0436A8574E7D0730641407A /* STPSourceRedirectTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceRedirectTest.swift; sourceTree = ""; }; + C17799DC7FA54E758EED31A6 /* NSArray+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSArray+StripeTest.swift"; sourceTree = ""; }; + C23D612FD5AD7772E1B30DCC /* STPThreeDSSelectionCustomizationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPThreeDSSelectionCustomizationTest.swift; sourceTree = ""; }; + C5923A4DD3CD39CB64B8A8C9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + C5BEAA15B53AC5662A33D0E1 /* STPEphemeralKeyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEphemeralKeyTest.swift; sourceTree = ""; }; + C641A744CCEA67C07E9BFE05 /* NSLocale+STPSwizzling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocale+STPSwizzling.swift"; sourceTree = ""; }; + C645F78B3EFFAA083B6FD3E9 /* STPEphemeralKeyManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEphemeralKeyManagerTest.swift; sourceTree = ""; }; + C713F58BC61A962C720AE0AE /* STPMandateCustomerAcceptanceParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMandateCustomerAcceptanceParamsTest.swift; sourceTree = ""; }; + C980D24DDC884FECCE39139F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + CA8B8F540CD05B3DC2C5EEA6 /* STPSetupIntentTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSetupIntentTest.swift; sourceTree = ""; }; + CAC3A0332C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSunbitTests.swift; sourceTree = ""; }; + CAC3A03D2C2F2CD8007BC888 /* STPPaymentMethodBillieTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBillieTests.swift; sourceTree = ""; }; + CAC80EBE2C33339D001E3D0D /* STPPaymentMethodSatispayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSatispayTests.swift; sourceTree = ""; }; + CBC9D4B0158266B01840AD9A /* STPPaymentMethodOXXOParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodOXXOParamsTests.swift; sourceTree = ""; }; + CD5AC2BFBC8141F98C00CF9F /* StripeiOS_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeiOS_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CDDEAB86BE4711841D426F3B /* STPPaymentIntentFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentFunctionalTest.swift; sourceTree = ""; }; + CF13BAEF86594C9CABD4F42A /* STPPaymentMethodUSBankAccountParamsStubbedTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodUSBankAccountParamsStubbedTest.swift; sourceTree = ""; }; + CF902DC49DD90860BD0E5E80 /* STPPaymentIntentParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentParamsTest.swift; sourceTree = ""; }; + CFC4BC1AB047ED88C4D13C89 /* STPApplePayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayTest.swift; sourceTree = ""; }; + CFFE40AD9D875709F643D2E5 /* ConfirmButtonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonTests.swift; sourceTree = ""; }; + D07275F94914B7E7937D24FE /* NSDictionary+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDictionary+StripeTest.swift"; sourceTree = ""; }; + D103BC590F1E0EC0C31C7B5F /* STPBankAccountTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBankAccountTest.swift; sourceTree = ""; }; + D1859673CAD068B345F5DD7D /* STPCardCVCInputTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextFieldSnapshotTests.swift; sourceTree = ""; }; + D2F205F920E971DEA59E3C31 /* STPIntentActionTypeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPIntentActionTypeTest.swift; sourceTree = ""; }; + D3803D0DED98501AA26B2EAC /* STPCardNumberInputTextFieldFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardNumberInputTextFieldFormatterTests.swift; sourceTree = ""; }; + D38184A7CD27B978DFA30E69 /* STPCardFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardFunctionalTest.swift; sourceTree = ""; }; + D3E0CA28591EB0748C64D1FA /* STPFileTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFileTest.swift; sourceTree = ""; }; + D41081066DC4465734F7FCD7 /* STPPaymentMethodNetBankingParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodNetBankingParamsTest.swift; sourceTree = ""; }; + D42F83F785EAF24F5DC7ED1A /* STPCardCVCInputTextFieldValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextFieldValidatorTests.swift; sourceTree = ""; }; + D609FFD051FC01BF566665A0 /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + D794C5E6396B4A19DC4F6921 /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D7C4773C2D193BEDF1CBB530 /* STPCardNumberInputTextFieldValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardNumberInputTextFieldValidatorTests.swift; sourceTree = ""; }; + D87817A1D3D213AA4ADF6A4C /* STPPaymentMethodAffirmParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAffirmParamsTest.swift; sourceTree = ""; }; + DA82BB67D434E76B9ABA4CEC /* Stripe-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe-Release.xcconfig"; sourceTree = ""; }; + DAB817ED5B5DB87AE1290894 /* STPCustomerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCustomerTest.swift; sourceTree = ""; }; + DB58AC5E2E0A68221260FD44 /* STPMandateOnlineParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMandateOnlineParamsTest.swift; sourceTree = ""; }; + DC3AD586DDED620B9E68F461 /* StripeErrorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeErrorTest.swift; sourceTree = ""; }; + DDC55CC034022DFAC9366E2E /* StripePaymentSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DE2A766FB355DD9C461939C1 /* STPPaymentMethodPayPalParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodPayPalParamsTests.swift; sourceTree = ""; }; + DFA7A75BA785EBBE4C05DAA3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + E1264C4DCB32B1FA5CE19201 /* STPFileFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFileFunctionalTest.swift; sourceTree = ""; }; + E136A967522048B313E3C62F /* StripePaymentsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E1644AA33E81233EF33022BA /* STPCardValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardValidatorTest.swift; sourceTree = ""; }; + E1A2173E0891B7138687D544 /* Stripe-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe-Debug.xcconfig"; sourceTree = ""; }; + E1CD20E00EAD41091B71ABD5 /* NSURLComponents_StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSURLComponents_StripeTest.swift; sourceTree = ""; }; + E315168EF07F52B733EA77F8 /* STPSwiftFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSwiftFixtures.swift; sourceTree = ""; }; + E3C8833E7EBA7A1EBF349D19 /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + E4194E605BB5F31E9CBB8F96 /* STPAPIClientStubbedTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAPIClientStubbedTest.swift; sourceTree = ""; }; + E452877E5D11120B1E28A6E7 /* STPApplePayContextDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayContextDelegate.swift; sourceTree = ""; }; + E5D9F97ABC88302478220267 /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKPaymentAuthorizationViewController+Stripe_Blocks.swift"; sourceTree = ""; }; + E6312182B5BCAB940D216650 /* ConsumerSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsumerSessionTests.swift; sourceTree = ""; }; + E6DEE912364C9F4B51B374D0 /* STPPaymentCardTextFieldViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextFieldViewModelTest.swift; sourceTree = ""; }; + E7ACB4FAFAD33296DE34D036 /* STPAnalyticsClientPaymentsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticsClientPaymentsTest.swift; sourceTree = ""; }; + E7C7D85A7FAAFDF4F59BA85E /* LinkSignupViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkSignupViewModelTests.swift; sourceTree = ""; }; + E8063E8073A32E0B081A1DFA /* STPPaymentMethodSofortTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSofortTests.swift; sourceTree = ""; }; + E89551702F1F9A3AFF1ED676 /* STPSourceVerificationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSourceVerificationTest.swift; sourceTree = ""; }; + E956CFA6317CAA8B41E217CA /* STPAPIClient+LinkAccountSessionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+LinkAccountSessionTest.swift"; sourceTree = ""; }; + E97018A201D01A8FA59999C2 /* STPConnectAccountFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPConnectAccountFunctionalTest.swift; sourceTree = ""; }; + EA9975553E669AF69F3CE437 /* STPPostalCodeInputTextFieldValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextFieldValidatorTests.swift; sourceTree = ""; }; + EAEE347A6372AAE2735FAD6F /* STPPaymentMethodFPXTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodFPXTest.swift; sourceTree = ""; }; + EB6AE83989B0596F0C111E13 /* STPStringUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPStringUtilsTest.swift; sourceTree = ""; }; + EB71A4A2762CF864DB198BCF /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + ECBA5B09A3FD875C93218573 /* STPPaymentMethodAfterpayClearpayParamsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodAfterpayClearpayParamsTest.swift; sourceTree = ""; }; + EF48EC440E1ED5D6BAA567FF /* STPPaymentHandlerStubbedMockedFilesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentHandlerStubbedMockedFilesTests.swift; sourceTree = ""; }; + EFD2F6A5A046A620BAB75B41 /* AutoCompleteViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewControllerSnapshotTests.swift; sourceTree = ""; }; + F372EDF9C2C45E1CA2C76866 /* STPPaymentMethodGiropayParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodGiropayParamsTests.swift; sourceTree = ""; }; + F3C732C25FD961631BD44FDD /* STPBSBNumberValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBSBNumberValidatorTests.swift; sourceTree = ""; }; + F44327A2B2C9483F52EE343B /* STPFormViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFormViewSnapshotTests.swift; sourceTree = ""; }; + F4E5416F6AE8BED88980D6F8 /* STPPaymentMethodBancontactTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodBancontactTests.swift; sourceTree = ""; }; + F546088BA4F763334CFD3D34 /* STPImageLibraryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPImageLibraryTest.swift; sourceTree = ""; }; + F60858F52CEF058200D62278 /* STPPaymentMethodCryptoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCryptoTests.swift; sourceTree = ""; }; + F60858F72CEF058F00D62278 /* STPPaymentMethodCryptoParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodCryptoParamsTests.swift; sourceTree = ""; }; + F6558C62376C2397030BD4A6 /* STPPaymentMethodPrzelewy24Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodPrzelewy24Tests.swift; sourceTree = ""; }; + F7385193226663A5B79E69ED /* STPFloatingPlaceholderTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFloatingPlaceholderTextFieldSnapshotTests.swift; sourceTree = ""; }; + FC30E6129279F14506219E98 /* STPPaymentHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentHandlerTests.swift; sourceTree = ""; }; + FD289E1EA9F0CE1C848AC0BB /* STPCardNumberInputTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardNumberInputTextFieldSnapshotTests.swift; sourceTree = ""; }; + FD3398E2352CEA0264F20AEA /* stp_test_upload_image.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = stp_test_upload_image.jpeg; sourceTree = ""; }; + FDA32D0C9E8A7A69F4899EDC /* RotatingCardBrandsViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotatingCardBrandsViewSnapshotTests.swift; sourceTree = ""; }; + FDF7394DDD552EDE996EAD8E /* STPPostalCodeValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeValidatorTest.swift; sourceTree = ""; }; + FE2DED6ABA7407C17C1391B6 /* MockFiles */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MockFiles; sourceTree = ""; }; + FF5E08A1651D9DFE502DA021 /* STPCardFormViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardFormViewTests.swift; sourceTree = ""; }; + FF93C4B7A5F8FA4E7919794F /* STPPaymentMethodEPSParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodEPSParamsTests.swift; sourceTree = ""; }; + FFCF9EB77A45F3E9E83F5D8B /* STPRedirectContextTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPRedirectContextTest.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1587AD7E52759FBC80315765 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 32874C6147344A9CB2EF4DAD /* Stripe3DS2.framework in Frameworks */, + 8520A27C204A068C43592024 /* StripeApplePay.framework in Frameworks */, + E97168F37D769524B58461B6 /* StripeCore.framework in Frameworks */, + 360EEE8B706D2A4A49666F7A /* StripePayments.framework in Frameworks */, + 9363F8F389C04C19B37D0F0A /* StripePaymentsUI.framework in Frameworks */, + EA34719659CB9F1A269FECC7 /* StripeUICore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6ACBDD66E08F7CEE34A8FFFA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AC7C127B11A60222465F4696 /* XCTest.framework in Frameworks */, + D151C8724925DCBA4BA4F46A /* StripeCoreTestUtils.framework in Frameworks */, + 420F8CAB4FAD6D9AF4AF25C0 /* StripePaymentSheet.framework in Frameworks */, + EEA502DF8809B8FD0D00785E /* StripePayments.framework in Frameworks */, + E9C690F3629C0AC3CD0260AF /* StripePaymentsObjcTestUtils.framework in Frameworks */, + 5302F9246A4A6381CB4FB874 /* StripePaymentsTestUtils.framework in Frameworks */, + 03E60F9EF24C975AF90E2447 /* StripePaymentsUI.framework in Frameworks */, + 0FA3C1494BA57884B5DE3B20 /* Stripe.framework in Frameworks */, + 2EB68A59660A4D1E14799DA4 /* OHHTTPStubs in Frameworks */, + 2CD7968DA48F7129E16EA0CB /* OHHTTPStubsSwift in Frameworks */, + 5C51167CC14F653E7117BA61 /* OCMock in Frameworks */, + B795A5EB8FDECA1060A9655C /* iOSSnapshotTestCase in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7129AA5AD64208D3B1A76AAE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9291A08CCB34504FCA4B7481 /* XCTest.framework in Frameworks */, + 2F0FC4E67BE577AD66CD1475 /* StripePaymentSheet.framework in Frameworks */, + 78B70C2EE8334F0FA91439CA /* Stripe.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C04854A8658964DDD0A15E1C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00AA0CCB7109D2B82EF8EEA0 /* Project */ = { + isa = PBXGroup; + children = ( + D0F67D3A4FA2B67D2AB0A49F /* BuildConfigurations */, + 97BE66955C28DB78DA12F5F6 /* BuildConfigurations */, + B035E857851EAF160C88DC2B /* StripeiOS */, + 9C700DBA02FFEC23B92EF107 /* StripeiOSAppHostedTests */, + CFD249F84390F2310542962A /* StripeiOSTestHostApp */, + EB228B1D404A48918221D9D6 /* StripeiOSTests */, + 4E29B46F2C940E0A21734E09 /* StripeiOSTests.xctestplan */, + ); + name = Project; + sourceTree = ""; + }; + 437EB711F100257495D02BD7 /* Resources */ = { + isa = PBXGroup; + children = ( + 147D2DC1FFDFC99269039377 /* LaunchScreen.storyboard */, + 884C01B087B4D820395BD374 /* Main.storyboard */, + 6955B3A3353F8442E4FBBBF6 /* Assets.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + 7EA3633D452F7A9C017F4D52 /* Source */ = { + isa = PBXGroup; + children = ( + 273EE407039913F0B644172B /* PKAddPaymentPassRequest+Stripe_Error.swift */, + E5D9F97ABC88302478220267 /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift */, + BD89580A3E41D7167C30B287 /* STPAnalyticsClient+Payments.swift */, + 807FF966F1DE05F3496B817B /* STPAPIClient+PushProvisioning.swift */, + E452877E5D11120B1E28A6E7 /* STPApplePayContextDelegate.swift */, + 890660C21E3666CE7B82695B /* STPEphemeralKey.swift */, + 588C260880FFC584A00A89F5 /* STPEphemeralKeyManager.swift */, + 485E747DA1F72F091986787B /* STPEphemeralKeyProvider.swift */, + 0669B4CA326CE74D125C789C /* STPFakeAddPaymentPassViewController.swift */, + 3EBB07171F6FDCE6E20C454A /* STPPinManagementService.swift */, + 6887F19BB9804BF45FD703FF /* STPPushProvisioningContext.swift */, + 89E5DA3029F141B5111A5B2C /* STPPushProvisioningDetails.swift */, + 2B28B8A547CD846277ECD578 /* STPPushProvisioningDetailsParams.swift */, + B70DF0B659009041F485EE0F /* Stripe+Exports.swift */, + ); + path = Source; + sourceTree = ""; + }; + 85B4F3432AB3A8B94C9D2591 /* Products */ = { + isa = PBXGroup; + children = ( + 4259421D2CD26E37B96F97B2 /* Stripe.framework */, + 33FDC634FD5D79E824240DDC /* Stripe3DS2.framework */, + 52F8AEC50D4623F80F04A533 /* StripeApplePay.framework */, + 43B4E4B85C598D7A9AFCB4D4 /* StripeCore.framework */, + 512A0E7C246D5F044245E069 /* StripeCoreTestUtils.framework */, + CD5AC2BFBC8141F98C00CF9F /* StripeiOS_Tests.xctest */, + 1DD6897858F46976A946394E /* StripeiOSAppHostedTests.xctest */, + 294CD46E24BB2743042872D7 /* StripeiOSTestHostApp.app */, + 22D1C6EB5826E2D7C80B6CF3 /* StripePayments.framework */, + DDC55CC034022DFAC9366E2E /* StripePaymentSheet.framework */, + 5D237177A7F99EB0F5F4F5E4 /* StripePaymentsObjcTestUtils.framework */, + 77247622AB08FEF48CA0DC26 /* StripePaymentsTestUtils.framework */, + E136A967522048B313E3C62F /* StripePaymentsUI.framework */, + D794C5E6396B4A19DC4F6921 /* StripeUICore.framework */, + ); + name = Products; + sourceTree = ""; + }; + 97BE66955C28DB78DA12F5F6 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + EB71A4A2762CF864DB198BCF /* Project-Debug.xcconfig */, + 969E196AB597EEF68C38103E /* Project-Release.xcconfig */, + D609FFD051FC01BF566665A0 /* StripeiOS Tests-Debug.xcconfig */, + E3C8833E7EBA7A1EBF349D19 /* StripeiOS Tests-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 9C700DBA02FFEC23B92EF107 /* StripeiOSAppHostedTests */ = { + isa = PBXGroup; + children = ( + 65DCC9BDA647E58D3882C698 /* Info.plist */, + ); + path = StripeiOSAppHostedTests; + sourceTree = ""; + }; + B035E857851EAF160C88DC2B /* StripeiOS */ = { + isa = PBXGroup; + children = ( + 31CDFC2D2BA3708000B3DD91 /* PrivacyInfo.xcprivacy */, + 313F5F782B0BE59000BD98A9 /* Docs.docc */, + FBC7A77342A98B1DE8E416B7 /* Resources */, + 7EA3633D452F7A9C017F4D52 /* Source */, + C5923A4DD3CD39CB64B8A8C9 /* Info.plist */, + A8598727045C6268B57A5FC7 /* Stripe-umbrella.h */, + ); + path = StripeiOS; + sourceTree = ""; + }; + B7A38600F42999DA8F83AD27 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 005650A59D692F820EF20F5F /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + CFD249F84390F2310542962A /* StripeiOSTestHostApp */ = { + isa = PBXGroup; + children = ( + 437EB711F100257495D02BD7 /* Resources */, + 9BBCE3A905041A709E8F279A /* AppDelegate.swift */, + B3E0745D13EB19BAA24F3BA3 /* Info.plist */, + 1F29C15B47C7CB0941CD4C9E /* ViewController.swift */, + ); + path = StripeiOSTestHostApp; + sourceTree = ""; + }; + D0F67D3A4FA2B67D2AB0A49F /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 1D23EB567F573612E0794B3A /* Stripe Tests-Debug.xcconfig */, + 5E4EA6394497D1BD57ED0032 /* Stripe Tests-Release.xcconfig */, + E1A2173E0891B7138687D544 /* Stripe-Debug.xcconfig */, + DA82BB67D434E76B9ABA4CEC /* Stripe-Release.xcconfig */, + ); + path = BuildConfigurations; + sourceTree = ""; + }; + E4802964A9471C082CE01BA9 = { + isa = PBXGroup; + children = ( + 00AA0CCB7109D2B82EF8EEA0 /* Project */, + B7A38600F42999DA8F83AD27 /* Frameworks */, + 85B4F3432AB3A8B94C9D2591 /* Products */, + ); + sourceTree = ""; + }; + EB228B1D404A48918221D9D6 /* StripeiOSTests */ = { + isa = PBXGroup; + children = ( + EB90CE5EA5682921B4C247A0 /* Resources */, + 3C742844915B96CFD25BFFF9 /* AfterpayPriceBreakdownViewSnapshotTests.swift */, + 61AF6E95FE0DD913204CAB32 /* AnalyticsHelperTests.swift */, + 85AAB72218409F85FE29E69E /* APIRequestTest.swift */, + EFD2F6A5A046A620BAB75B41 /* AutoCompleteViewControllerSnapshotTests.swift */, + 153071C69A0BEE033E035DCF /* CardExpiryDateTests.swift */, + 46AC0B5EC7433E081825D31B /* CircularButtonSnapshotTests.swift */, + 806124200E77795DCFC8418E /* ConfirmButtonSnapshotTests.swift */, + CFFE40AD9D875709F643D2E5 /* ConfirmButtonTests.swift */, + E6312182B5BCAB940D216650 /* ConsumerSessionTests.swift */, + 11C866064B3482878A69892F /* CustomerAdapterTests.swift */, + 8CE85C770AEEDBE4AEC93EAA /* Error+PaymentSheetTests.swift */, + 180CF848E3ABF0236C494D8B /* FBSnapshotTestCase+STPViewControllerLoading.swift */, + 7C04AFC9CDE50D09D38A3232 /* FormSpecProviderTest.swift */, + 45FF7A07CFC3B9B7AD6B49EE /* FraudDetectionDataTest.swift */, + AAF368BCD5990EE5DC17D299 /* ImageTest.swift */, + 0E10C4D64477638398251FFB /* Info.plist */, + 610DF5DB2B33597500DA6AAA /* HostedSurfaceTest.swift */, + 6BC5EC2C2B4609FF00CC75E8 /* LinkInlineSignupElementSnapshotTests.swift */, + 835CB781FBC19773ACC20676 /* LinkLegalTermsViewSnapshotTests.swift */, + E7C7D85A7FAAFDF4F59BA85E /* LinkSignupViewModelTests.swift */, + 9DCCA0E8A02B4F4B23837FB4 /* MKPlacemark+PaymentSheetTests.swift */, + C17799DC7FA54E758EED31A6 /* NSArray+StripeTest.swift */, + 20552E792B8E7BA15821AB5D /* NSDecimalNumber+StripeTest.swift */, + D07275F94914B7E7937D24FE /* NSDictionary+StripeTest.swift */, + C641A744CCEA67C07E9BFE05 /* NSLocale+STPSwizzling.swift */, + 63F5F35DB97D8A176FB6ED24 /* NSString+StripeTest.swift */, + E1CD20E00EAD41091B71ABD5 /* NSURLComponents_StripeTest.swift */, + 9D85FA7B714BDD8D1FD83B75 /* OneTimeCodeTextFieldSnapshotTests.swift */, + 98F9CB667BC68767DFB5FACD /* OneTimeCodeTextFieldTests.swift */, + A61763BA2CCA86F9B8FD4F1F /* OperationDebouncerTests.swift */, + 7B9A4A2B0FB9F8C743BBED48 /* PaymentTypeCellSnapshotTests.swift */, + AF342CBC167F9CAB5B49CC32 /* PayWithLinkButtonSnapshotTests.swift */, + B4D12508C2F1056A7EAFEC86 /* PKPayment+StripeTest.swift */, + FDA32D0C9E8A7A69F4899EDC /* RotatingCardBrandsViewSnapshotTests.swift */, + 12632E6710DE8861CAF1BAA4 /* RotatingCardBrandsViewTests.swift */, + 20879436DCFB1F03BE1608B3 /* ServerErrorMapperTest.swift */, + A30CD9D92CCC2DFE00EA22D3 /* STPAPIClientTest.swift */, + A39580123A4F1EA96F91768A /* STPAddressTests.swift */, + E7ACB4FAFAD33296DE34D036 /* STPAnalyticsClientPaymentsTest.swift */, + E956CFA6317CAA8B41E217CA /* STPAPIClient+LinkAccountSessionTest.swift */, + B9BA8D8467218C7E691C9FAE /* STPAPIClientNetworkBridgeTest.swift */, + E4194E605BB5F31E9CBB8F96 /* STPAPIClientStubbedTest.swift */, + 1CE268457D21A7209862E004 /* STPApplePayContextFunctionalTest.swift */, + A1272F2E05A0E294DD9ECA26 /* STPApplePayContextFunctionalTestExtras.swift */, + 5F7AB40A5A10C2D267323ABE /* STPApplePayContextTest.swift */, + 3C77C7BC4BA57EC296CF2F1C /* STPApplePayFunctionalTest.swift */, + CFC4BC1AB047ED88C4D13C89 /* STPApplePayTest.swift */, + BCBEA9E4823F08C1F5057B5A /* STPAUBECSDebitFormViewSnapshotTests.swift */, + 4AAE5EE11611F9F7762B64C6 /* STPAUBECSFormViewModelTests.swift */, + 989411FA3CD0CCC38BC227F4 /* STPBankAccountFunctionalTest.swift */, + 967C784618A074FF021B3089 /* STPBankAccountParamsTest.swift */, + D103BC590F1E0EC0C31C7B5F /* STPBankAccountTest.swift */, + 23997D61DF41CA84BFC33080 /* STPBECSDebitAccountNumberValidatorTests.swift */, + 3FC0560A312147C37CFE6CF9 /* STPBinRangeTest.swift */, + 4FFA8B446217CDE678D7287F /* STPBlocks.h */, + F3C732C25FD961631BD44FDD /* STPBSBNumberValidatorTests.swift */, + 3E1C5E08678292561255B1C5 /* STPCardBINMetadataTests.swift */, + 901BE31021AF27DB5D326327 /* STPCardBrandTest.swift */, + 6E1F6514E7530C2A3478B2F5 /* STPCardCVCInputTextFieldFormatterTests.swift */, + D1859673CAD068B345F5DD7D /* STPCardCVCInputTextFieldSnapshotTests.swift */, + 94A7104C1C470515616E4D2B /* STPCardCVCInputTextFieldTests.swift */, + D42F83F785EAF24F5DC7ED1A /* STPCardCVCInputTextFieldValidatorTests.swift */, + 40BB87E28719FE0C6B946BB5 /* STPCardExpiryInputTextFieldFormatterTests.swift */, + 0C75157665428685C7A4FD20 /* STPCardExpiryInputTextFieldSnapshotTests.swift */, + 4418164D75002AE6A0273176 /* STPCardExpiryInputTextFieldValidatorTests.swift */, + 1D983E089196152DA1C69469 /* STPCardFormViewSnapshotTests.swift */, + FF5E08A1651D9DFE502DA021 /* STPCardFormViewTests.swift */, + D38184A7CD27B978DFA30E69 /* STPCardFunctionalTest.swift */, + D3803D0DED98501AA26B2EAC /* STPCardNumberInputTextFieldFormatterTests.swift */, + FD289E1EA9F0CE1C848AC0BB /* STPCardNumberInputTextFieldSnapshotTests.swift */, + D7C4773C2D193BEDF1CBB530 /* STPCardNumberInputTextFieldValidatorTests.swift */, + 8A3F2B714DB3D1DED561A7EF /* STPCardParamsTest.swift */, + 1D575C31524E596E9C1A8E9B /* STPCardTest.swift */, + E1644AA33E81233EF33022BA /* STPCardValidatorTest.swift */, + 1D51B04D83D4FEF7F90DF16A /* STPCertTest.swift */, + BD77D4B1C5B64E45F9DA09B5 /* STPConfirmCardOptionsTest.swift */, + 05FE1BA89B80336F16924FA2 /* STPConfirmPaymentMethodOptionsTest.swift */, + 967DDC94B687B14E07842CC8 /* STPConnectAccountAddressTest.swift */, + E97018A201D01A8FA59999C2 /* STPConnectAccountFunctionalTest.swift */, + 195DEC752CC82CC4BA1E2351 /* STPConnectAccountParamsTest.swift */, + 8D49257A97E71A475A9F6E08 /* STPCountryPickerInputFieldSnapshotTests.swift */, + DAB817ED5B5DB87AE1290894 /* STPCustomerTest.swift */, + 1C1548BA518F7AC2A9ECF9D5 /* STPE2ETest.swift */, + C645F78B3EFFAA083B6FD3E9 /* STPEphemeralKeyManagerTest.swift */, + C5BEAA15B53AC5662A33D0E1 /* STPEphemeralKeyTest.swift */, + 9EAE9A2AE65771403CE57C11 /* STPErrorBridgeTest.m */, + E1264C4DCB32B1FA5CE19201 /* STPFileFunctionalTest.swift */, + D3E0CA28591EB0748C64D1FA /* STPFileTest.swift */, + F7385193226663A5B79E69ED /* STPFloatingPlaceholderTextFieldSnapshotTests.swift */, + 30964128998473CAA9F2DD7E /* STPFormEncoderTest.swift */, + 2E1862744F23286D1FB9D4AE /* STPFormTextFieldTest.swift */, + F44327A2B2C9483F52EE343B /* STPFormViewSnapshotTests.swift */, + 85C29AE44D809CD677B5E52B /* STPFPXBankBrandTest.swift */, + 284C67269D2606DA147AE01D /* STPGenericInputPickerFieldSnapshotTests.swift */, + 2D63B73C5773432CA134D1FC /* STPGenericInputPickerFieldValidatorTest.swift */, + 58A53F005EA8FDDAA66126BA /* STPGenericInputTextFieldSnapshotTests.swift */, + F546088BA4F763334CFD3D34 /* STPImageLibraryTest.swift */, + A22E5B87755C1F05C3DB438C /* STPInputTextFieldFormatterTests.swift */, + 9FEE395C4DD0E0112AF3720C /* STPInputTextFieldValidatorTests.swift */, + AD06AED0AF8A9A7FB4A2E66F /* STPIntentActionAlipayHandleRedirectTest.swift */, + 7A517686D4D12691351311CA /* STPIntentActionPayNowDisplayQrCodeTest.swift */, + 61E1CA262BD6BED600A421AE /* STPIntentActionMultibancoDisplayDetailsTest.swift */, + 9466F23BA8712EA2EDA48BBD /* STPIntentActionPromptPayDisplayQrCodeTest.swift */, + 33B9D01D037909D1C9C0B617 /* STPIntentActionTest.swift */, + D2F205F920E971DEA59E3C31 /* STPIntentActionTypeTest.swift */, + 4CA5D11C977A95B8E936E907 /* STPIntentActionWeChatPayRedirectToAppTest.swift */, + 78D1EEABBAE5BD5615486B0F /* STPLabeledFormTextFieldViewSnapshotTests.swift */, + 7F68637E75142DCD46710796 /* STPLabeledMultiFormTextFieldViewSnapshotTests.swift */, + C713F58BC61A962C720AE0AE /* STPMandateCustomerAcceptanceParamsTest.swift */, + B7F7AA0B7B86BA5BB2FE92CE /* STPMandateDataParamsTest.swift */, + DB58AC5E2E0A68221260FD44 /* STPMandateOnlineParamsTest.swift */, + 6223E57D3A198F956A37ED89 /* STPNumericDigitInputTextFormatterTests.swift */, + 4AA36705DED9164663A98B6A /* STPNumericStringValidatorTests.swift */, + BB08D2AC882B21C8ADD76B92 /* STPPaymentCardTextFieldKVOTest.m */, + 939360978872BBE4334215B1 /* STPPaymentCardTextFieldTest.swift */, + 1F16C36797D978E72E612100 /* STPPaymentCardTextFieldTestsSwift.swift */, + E6DEE912364C9F4B51B374D0 /* STPPaymentCardTextFieldViewModelTest.swift */, + 683F7735569D22CBEC9CA2E6 /* STPPaymentHandlerFunctionalTest.m */, + ACF450AD17FF7BCE5916DDF1 /* STPPaymentHandlerFunctionalTest.swift */, + EF48EC440E1ED5D6BAA567FF /* STPPaymentHandlerStubbedMockedFilesTests.swift */, + FC30E6129279F14506219E98 /* STPPaymentHandlerTests.swift */, + 61E0A0C42BF31D3C00C89786 /* STPPaymentHandlerRefreshTests.swift */, + 46C31CAD0FA74B58BA2B8530 /* STPPaymentIntentEnumsTest.swift */, + CDDEAB86BE4711841D426F3B /* STPPaymentIntentFunctionalTest.swift */, + 6C7B8DACB0A7294BC235E3BC /* STPPaymentIntentLastPaymentErrorTest.swift */, + CF902DC49DD90860BD0E5E80 /* STPPaymentIntentParamsTest.swift */, + ACE8998EAF997A78759E49B5 /* STPPaymentIntentTest.swift */, + BEB3F9F0228008BE213706DF /* STPPaymentMethodAddressTest.swift */, + D87817A1D3D213AA4ADF6A4C /* STPPaymentMethodAffirmParamsTest.swift */, + 1F0DF2ED9232A7CC51F5FCB1 /* STPPaymentMethodAffirmTests.swift */, + ECBA5B09A3FD875C93218573 /* STPPaymentMethodAfterpayClearpayParamsTest.swift */, + 3CDD1E823223F450193E8746 /* STPPaymentMethodAfterpayClearpayTest.swift */, + 7FF2B9FF57E100301B5C38DB /* STPPaymentMethodAUBECSDebitParamsTests.swift */, + 54426CBF6F77ABEFBDFDA8C4 /* STPPaymentMethodAUBECSDebitTests.swift */, + BF1CF8FB0100664A02468FBC /* STPPaymentMethodBacsDebitTest.swift */, + 655209238C85F466F9F14F14 /* STPPaymentMethodBancontactParamsTests.swift */, + F4E5416F6AE8BED88980D6F8 /* STPPaymentMethodBancontactTests.swift */, + A0E24B5689732EB9106DA232 /* STPPaymentMethodBillingDetailsTest.swift */, + 7AC0A18C441FCA394BEF6A3D /* STPPaymentMethodBillingDetailsTests+Link.swift */, + A1C67AC5D415615E9F27D3E3 /* STPPaymentMethodBoletoParamsTests.swift */, + 4E371E9B3B2E343FE954531C /* STPPaymentMethodBoletoTests.swift */, + A15BBFFA401852A8719E3DDD /* STPPaymentMethodCardChecksTest.swift */, + 1671EC46C713D51013AD7D8B /* STPPaymentMethodCardParamsTest.swift */, + 8BD02D8298877F10F2EF2A9D /* STPPaymentMethodCardTest.swift */, + 533538E3EB92E326CCB95506 /* STPPaymentMethodCardWalletMasterpassTest.swift */, + 2DC4B62C336EAA05A33FC384 /* STPPaymentMethodCardWalletTest.swift */, + 82E46B18CDDC191934F3D4BE /* STPPaymentMethodCardWalletVisaCheckoutTest.swift */, + 0ABB2CA7E96BE249CE8C0566 /* STPPaymentMethodCashAppParamsTests.swift */, + 61152B4E2B866827003B69A0 /* STPPaymentMethodAmazonPayParamsTests.swift */, + 617C1C892BB4998C00B10AC5 /* STPPaymentMethodAlmaParamsTests.swift */, + 61E1CA202BD6B78500A421AE /* STPPaymentMethodMultibancoParamsTests.swift */, + 7DDE50CBC86AD77084C877B6 /* STPPaymentMethodCashAppTests.swift */, + 61951FB82B866BA1005F90BE /* STPPaymentMethodAmazonPayTests.swift */, + 617C1C872BB4992400B10AC5 /* STPPaymentMethodAlmaTests.swift */, + 61E1CA1E2BD6B72800A421AE /* STPPaymentMethodMultibancoTests.swift */, + FF93C4B7A5F8FA4E7919794F /* STPPaymentMethodEPSParamsTests.swift */, + 0C44B4366D6C4FD4B11662C8 /* STPPaymentMethodEPSTests.swift */, + EAEE347A6372AAE2735FAD6F /* STPPaymentMethodFPXTest.swift */, + 8192839B0F1AE9D9F2A94504 /* STPPaymentMethodFunctionalTest.swift */, + F372EDF9C2C45E1CA2C76866 /* STPPaymentMethodGiropayParamsTests.swift */, + 583DB466066B47C0F716E474 /* STPPaymentMethodGiropayTests.swift */, + 0A8FF64CA314F909D7EC82FE /* STPPaymentMethodGrabPayParamsTest.swift */, + 9B83930B631CF8EADFB606D6 /* STPPaymentMethodiDEALTest.swift */, + 47E12A0CBFA259A032F7AF0C /* STPPaymentMethodKlarnaParamsTests.swift */, + 4FD94FF270165D699DA89B24 /* STPPaymentMethodKlarnaTests.swift */, + 6BA4B9192BF433B200D1F21D /* STPPaymentMethodMobilePayParamsTests.swift */, + 6BA4B91B2BF4343B00D1F21D /* STPPaymentMethodMobilePayTests.swift */, + D41081066DC4465734F7FCD7 /* STPPaymentMethodNetBankingParamsTest.swift */, + 739934737B9A09775CD278C9 /* STPPaymentMethodNetBankingTests.swift */, + 1E2638F7AA0906914117C2D5 /* STPPaymentMethodOptionsTest.swift */, + CBC9D4B0158266B01840AD9A /* STPPaymentMethodOXXOParamsTests.swift */, + 80BBCC4D386EE44E809A591C /* STPPaymentMethodOXXOTests.swift */, + 4002981AC12687681616D21E /* STPPaymentMethodParamsTest.swift */, + DE2A766FB355DD9C461939C1 /* STPPaymentMethodPayPalParamsTests.swift */, + 2A63AC868755CB4745E7458E /* STPPaymentMethodPayPalTests.swift */, + A6269E77F81C32A5EC8BE412 /* STPPaymentMethodPrzelewy24ParamsTests.swift */, + F6558C62376C2397030BD4A6 /* STPPaymentMethodPrzelewy24Tests.swift */, + 3AE7BEADD3824A06C2994854 /* STPPaymentMethodRevolutPayParamsTests.swift */, + 7A7963CC618A1A1346EC20C7 /* STPPaymentMethodRevolutPayTests.swift */, + A41F721AEBB942BB81408A59 /* STPPaymentMethodSEPADebitTest.swift */, + 207677E2A0DBC04C88139372 /* STPPaymentMethodSofortParamsTests.swift */, + E8063E8073A32E0B081A1DFA /* STPPaymentMethodSofortTests.swift */, + 9631915F03F157A1CC3FEFFE /* STPPaymentMethodSwishParamsTests.swift */, + 5DE9F0BAC35EA14579775033 /* STPPaymentMethodSwishTests.swift */, + 148C1D7D1BBBC6B74894A869 /* STPPaymentMethodTest.swift */, + 916DB8789F65D3C1BCB510C0 /* STPPaymentMethodThreeDSecureUsageTest.swift */, + 47E5E1173A37AABB07FB68AB /* STPPaymentMethodUPIParamsTest.swift */, + 940209E5D30E86E856016906 /* STPPaymentMethodUPITests.swift */, + CF13BAEF86594C9CABD4F42A /* STPPaymentMethodUSBankAccountParamsStubbedTest.swift */, + 74FDEF9F687C63BADFB96480 /* STPPaymentMethodUSBankAccountParamsTest.swift */, + 3B112FFF3FCA82094281493F /* STPPaymentMethodUSBankAccountTest.swift */, + 924E878428D15506711CA628 /* STPPhoneNumberValidatorTest.swift */, + 1645D9793463E266501B74FD /* STPPIIFunctionalTest.swift */, + 336CC555B845DED30208D39D /* STPPinManagementServiceFunctionalTest.swift */, + 917154477796779ECFA1334A /* STPPostalCodeInputTextFieldFormatterTests.swift */, + 090EF7D598B8DE779C275395 /* STPPostalCodeInputTextFieldSnapshotTests.swift */, + 6618739767139C25C05B3631 /* STPPostalCodeInputTextFieldTests.swift */, + EA9975553E669AF69F3CE437 /* STPPostalCodeInputTextFieldValidatorTests.swift */, + FDF7394DDD552EDE996EAD8E /* STPPostalCodeValidatorTest.swift */, + 1A8A6B88797870BC71CCB3AF /* STPPushProvisioningDetailsFunctionalTest.swift */, + 2D878F923A1F69B58D6B2812 /* STPRadarSessionFunctionalTest.swift */, + FFCF9EB77A45F3E9E83F5D8B /* STPRedirectContextTest.swift */, + 6CC0B1FC92A573AAEA4F4E94 /* STPSetupIntentConfirmParamsTest.swift */, + 49AA313E068FB99CEAA5F7D3 /* STPSetupIntentFunctionalTest.swift */, + 01B057D99A14E5BA6019C349 /* STPSetupIntentLastSetupErrorTest.swift */, + CA8B8F540CD05B3DC2C5EEA6 /* STPSetupIntentTest.swift */, + 63114D0EAAE2606732DF5AA0 /* STPSourceCardDetailsTest.swift */, + 17013F78CE3F9662029FEF5B /* STPSourceFunctionalTest.swift */, + 095EBF095BA2BC8D299547DB /* STPSourceOwnerTest.swift */, + 1F6CB4B8FAD14B4D70A63595 /* STPSourceParamsTest.swift */, + 6B7A947152A728EB2CBC4DB2 /* STPSourceReceiverTest.swift */, + C0436A8574E7D0730641407A /* STPSourceRedirectTest.swift */, + 21780C410D22264B7C299520 /* STPSourceSEPADebitDetailsTest.swift */, + 05AA6A1B2A462F1CE2F537C5 /* STPSourceTest.swift */, + E89551702F1F9A3AFF1ED676 /* STPSourceVerificationTest.swift */, + 51E62BB62EA9B782778CA880 /* STPStackViewWithSeparatorSnapshotTests.swift */, + EB6AE83989B0596F0C111E13 /* STPStringUtilsTest.swift */, + E315168EF07F52B733EA77F8 /* STPSwiftFixtures.swift */, + 2A8A2CD759D465290066EF65 /* STPTextFieldDelegateProxyTests.swift */, + B6F3B966470A530E0DC53F8C /* STPThreeDSButtonCustomizationTest.swift */, + 483B243268646AE65B06E98C /* STPThreeDSFooterCustomizationTest.swift */, + 5FDD4956E0A04D33F0856F31 /* STPThreeDSLabelCustomizationTest.swift */, + 51408DE266D0345784ADD4FA /* STPThreeDSNavigationBarCustomizationTest.swift */, + C23D612FD5AD7772E1B30DCC /* STPThreeDSSelectionCustomizationTest.swift */, + A583966A33DCDCF04322A592 /* STPThreeDSTextFieldCustomizationTest.swift */, + 3ED44491EB0AC72B1B1A773C /* STPThreeDSUICustomizationTest.swift */, + 7F090B8D315E7FD12A5F9C09 /* STPTokenTest.swift */, + 86983B09A1712944EC012AD4 /* STPViewWithSeparatorSnapshotTests.swift */, + DC3AD586DDED620B9E68F461 /* StripeErrorTest.swift */, + B407FE2D39775902A95B1118 /* StripeiOS Tests-Bridging-Header.h */, + 51BD2CE41E4F0CF648F44E4A /* TextFieldElement+IBANTest.swift */, + 3B0E131538728BC4802627B1 /* UserDefaults+StripeTest.swift */, + 0DB03E83746FE78361831546 /* WalletHeaderViewSnapshotTests.swift */, + CAC3A0332C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift */, + CAC3A03D2C2F2CD8007BC888 /* STPPaymentMethodBillieTests.swift */, + CAC80EBE2C33339D001E3D0D /* STPPaymentMethodSatispayTests.swift */, + F60858F52CEF058200D62278 /* STPPaymentMethodCryptoTests.swift */, + F60858F72CEF058F00D62278 /* STPPaymentMethodCryptoParamsTests.swift */, + ); + path = StripeiOSTests; + sourceTree = ""; + }; + EB90CE5EA5682921B4C247A0 /* Resources */ = { + isa = PBXGroup; + children = ( + DFA7A75BA785EBBE4C05DAA3 /* Images.xcassets */, + FE2DED6ABA7407C17C1391B6 /* MockFiles */, + FD3398E2352CEA0264F20AEA /* stp_test_upload_image.jpeg */, + ); + path = Resources; + sourceTree = ""; + }; + FBC7A77342A98B1DE8E416B7 /* Resources */ = { + isa = PBXGroup; + children = ( + 77E846CD56018D8417A3AB95 /* StripeiOS.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8D1F9C232F0DF9D129FE872E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 30D48C62B2FA6B28EC23A5BB /* Stripe-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 1628E8B14F1F7C63CF8C9962 /* StripeiOSTestHostApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = B6D8846CE184BBD9139D6D15 /* Build configuration list for PBXNativeTarget "StripeiOSTestHostApp" */; + buildPhases = ( + C4ED1B5A417CECDB38B21698 /* Sources */, + 9BE857B21623552BA4F3FAA1 /* Resources */, + 9F30C6D40956861F588C2CD0 /* Embed Frameworks */, + C04854A8658964DDD0A15E1C /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeiOSTestHostApp; + productName = StripeiOSTestHostApp; + productReference = 294CD46E24BB2743042872D7 /* StripeiOSTestHostApp.app */; + productType = "com.apple.product-type.application"; + }; + 49A14BBD10B1E97F2B43C448 /* StripeiOSAppHostedTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FCBB942DEBE57423D79373B4 /* Build configuration list for PBXNativeTarget "StripeiOSAppHostedTests" */; + buildPhases = ( + E160E75F2870000E2FA9C5DC /* Sources */, + C501B175E87CBBB0075EB9C5 /* Resources */, + 16FB342935F756BD2EA7CE4C /* Embed Frameworks */, + 7129AA5AD64208D3B1A76AAE /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 4E57D9EF1E91091C57E49111 /* PBXTargetDependency */, + 11BFD72CD1F291E0E86EC784 /* PBXTargetDependency */, + ); + name = StripeiOSAppHostedTests; + productName = StripeiOSAppHostedTests; + productReference = 1DD6897858F46976A946394E /* StripeiOSAppHostedTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 8BE23AD5D9A3D939AF46F31E /* StripeiOSTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 54F4DB1E9C991950FD9EB4B4 /* Build configuration list for PBXNativeTarget "StripeiOSTests" */; + buildPhases = ( + FB03810F22F4E0919BB2EF68 /* Sources */, + BADDFD2C3E190FF24FEB93C7 /* Resources */, + 5789BE2CD297329ED86678C0 /* Embed Frameworks */, + 6ACBDD66E08F7CEE34A8FFFA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + C4F1FB6EFF8D25D54580374A /* PBXTargetDependency */, + ); + name = StripeiOSTests; + packageProductDependencies = ( + 62887B4538E4E41E735685E1 /* OHHTTPStubs */, + 911CA85A1610303FA0AF0643 /* OHHTTPStubsSwift */, + E804AA8C4156CC85FFD9595F /* OCMock */, + C55551F29B99CF6D6DD9EE2F /* iOSSnapshotTestCase */, + ); + productName = StripeiOS_Tests; + productReference = CD5AC2BFBC8141F98C00CF9F /* StripeiOS_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + ADF894AA8F6022D9BED17346 /* StripeiOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = F0FCB32AE130ABA66178DD9B /* Build configuration list for PBXNativeTarget "StripeiOS" */; + buildPhases = ( + 8D1F9C232F0DF9D129FE872E /* Headers */, + 24CDC502E3D468F309116FE1 /* Sources */, + 0F32E5D5E633BAB349B6EB50 /* Resources */, + CAAA5D4C8E87896995960E8C /* Embed Frameworks */, + 1587AD7E52759FBC80315765 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeiOS; + productName = Stripe; + productReference = 4259421D2CD26E37B96F97B2 /* Stripe.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E63832AA5BB4225708B7C838 /* Project object */ = { + isa = PBXProject; + attributes = { + TargetAttributes = { + 49A14BBD10B1E97F2B43C448 = { + TestTargetID = 1628E8B14F1F7C63CF8C9962; + }; + E652E5F22CB20B7200081F83 = { + CreatedOnToolsVersion = 16.0; + }; + }; + }; + buildConfigurationList = 2804CCD7A91C99DF32596147 /* Build configuration list for PBXProject "Stripe" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tk, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = E4802964A9471C082CE01BA9; + packageReferences = ( + D244E8B5402CB2CF89CE7872 /* XCRemoteSwiftPackageReference "ocmock" */, + 44C6210EB47ACF468D255723 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + 71086E1440B890A9DC01C9DF /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + ); + productRefGroup = 85B4F3432AB3A8B94C9D2591 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ADF894AA8F6022D9BED17346 /* StripeiOS */, + 8BE23AD5D9A3D939AF46F31E /* StripeiOSTests */, + 1628E8B14F1F7C63CF8C9962 /* StripeiOSTestHostApp */, + 49A14BBD10B1E97F2B43C448 /* StripeiOSAppHostedTests */, + E652E5F22CB20B7200081F83 /* ReleaseFrameworksTarget */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0F32E5D5E633BAB349B6EB50 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CDFC2E2BA3708000B3DD91 /* PrivacyInfo.xcprivacy in Resources */, + 76BC927BC7A591601C1DAB18 /* StripeiOS.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9BE857B21623552BA4F3FAA1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EEB5E5E9C4E06B148A91C7BD /* Assets.xcassets in Resources */, + 08111F4AD3CA0755420E05F7 /* LaunchScreen.storyboard in Resources */, + DE23FEF74E860620A334FDF5 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BADDFD2C3E190FF24FEB93C7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 10342D659764A88A695EF38B /* Images.xcassets in Resources */, + F49D9C4030829D13A6EB45BE /* MockFiles in Resources */, + 2AE9ABA774B430E174279FEA /* stp_test_upload_image.jpeg in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C501B175E87CBBB0075EB9C5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E652E5F62CB20BA200081F83 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"${PROJECT_DIR}/..\"\nruby ci_scripts/export_builds.rb \ncp \"${PROJECT_DIR}/../build/Stripe.xcframework.zip\" \"${BUILT_PRODUCTS_DIR}\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 24CDC502E3D468F309116FE1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B6784B7F4B9B04617C0EE510 /* PKAddPaymentPassRequest+Stripe_Error.swift in Sources */, + C0688E067AE4FFDFFDDC03BB /* PKPaymentAuthorizationViewController+Stripe_Blocks.swift in Sources */, + 5212C7875C07F9BF16AFD98D /* STPAPIClient+PushProvisioning.swift in Sources */, + 313F5F792B0BE59100BD98A9 /* Docs.docc in Sources */, + 7589E37795D21AB818B0C333 /* STPAnalyticsClient+Payments.swift in Sources */, + F86F2DF6E46EFABE23AD5D27 /* STPApplePayContextDelegate.swift in Sources */, + 62B91808A088C4F9FDB62C53 /* STPEphemeralKey.swift in Sources */, + 4FB67F10A0B7106A8142B842 /* STPEphemeralKeyManager.swift in Sources */, + B8385576DC25BDEEB92D812F /* STPEphemeralKeyProvider.swift in Sources */, + 0B9C0E9A7A750607413C9E53 /* STPFakeAddPaymentPassViewController.swift in Sources */, + 7EAA7334372DBC38DF8FA0AA /* STPPinManagementService.swift in Sources */, + 2E35B0FB60FCBE7608080642 /* STPPushProvisioningContext.swift in Sources */, + FEE74744B657F86873EA2F3D /* STPPushProvisioningDetails.swift in Sources */, + 4E09E54E7FEC35C49C59A379 /* STPPushProvisioningDetailsParams.swift in Sources */, + DCF615643A22D0A7B739547C /* Stripe+Exports.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C4ED1B5A417CECDB38B21698 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 234C71F480318E9062075924 /* AppDelegate.swift in Sources */, + 3C1A7B9810B038177FF1CF52 /* ViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E160E75F2870000E2FA9C5DC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FB03810F22F4E0919BB2EF68 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 617C1C882BB4992400B10AC5 /* STPPaymentMethodAlmaTests.swift in Sources */, + 3EB3745F556EA12AB27A8545 /* APIRequestTest.swift in Sources */, + 6EF3F611E6EA3CB479D62450 /* AfterpayPriceBreakdownViewSnapshotTests.swift in Sources */, + DF85F5EC6E16CAD21491891A /* AnalyticsHelperTests.swift in Sources */, + 5910FCB9822259D5EC7E4051 /* AutoCompleteViewControllerSnapshotTests.swift in Sources */, + C1E70FD29BBE36D76A7E6929 /* CardExpiryDateTests.swift in Sources */, + D375ADBD1F4B48380D5347D1 /* CircularButtonSnapshotTests.swift in Sources */, + 2F9FA9CBCA3C0CE52FAC9B6B /* ConfirmButtonSnapshotTests.swift in Sources */, + 3C1E9069CD03ED9981D7F3E2 /* ConfirmButtonTests.swift in Sources */, + 795F3783D62AB8E2A00DCD05 /* ConsumerSessionTests.swift in Sources */, + 524AE1978E0A4490D1C390C5 /* CustomerAdapterTests.swift in Sources */, + 61E1CA1F2BD6B72800A421AE /* STPPaymentMethodMultibancoTests.swift in Sources */, + 0DFA17378D894C70D72C9F62 /* Error+PaymentSheetTests.swift in Sources */, + C9E66A22494C02050AE34A9B /* FBSnapshotTestCase+STPViewControllerLoading.swift in Sources */, + 35C1CF73701EECC7DB6AB722 /* FormSpecProviderTest.swift in Sources */, + ACF6CFE0F8B88FDBBB16968C /* FraudDetectionDataTest.swift in Sources */, + AF18D569B296BFC1EB5A7338 /* ImageTest.swift in Sources */, + D0C81317E0AA8EB0370B1BA1 /* LinkLegalTermsViewSnapshotTests.swift in Sources */, + 450FAE41FB4538462D05F2E4 /* LinkSignupViewModelTests.swift in Sources */, + FDD1858CAEFCEBB22BEC9BBC /* MKPlacemark+PaymentSheetTests.swift in Sources */, + 609C2C8F10AFAA2711639CD0 /* NSArray+StripeTest.swift in Sources */, + B98D71ED9ACC2E1B47372F53 /* NSDecimalNumber+StripeTest.swift in Sources */, + 6E7AD3CCC966A7F34922B172 /* NSDictionary+StripeTest.swift in Sources */, + 325694E4284BEAE787A5ECB6 /* NSLocale+STPSwizzling.swift in Sources */, + C4C1295E7DA618DFB944A534 /* NSString+StripeTest.swift in Sources */, + 68318DB86DFCD19505FC47BA /* NSURLComponents_StripeTest.swift in Sources */, + D83F76F584BC345CFBA71CF8 /* OneTimeCodeTextFieldSnapshotTests.swift in Sources */, + 951344464ACF84F0F6D43D10 /* OneTimeCodeTextFieldTests.swift in Sources */, + 8E423294AB602BF25DB11D8E /* OperationDebouncerTests.swift in Sources */, + C4DC3F4FA93A3BAF6EE782A0 /* PKPayment+StripeTest.swift in Sources */, + DC57D2DC40C6BA0C9CF7EC92 /* PayWithLinkButtonSnapshotTests.swift in Sources */, + C861BB9EAAD04949E338D7FF /* PaymentTypeCellSnapshotTests.swift in Sources */, + BC694A1642DC30D530B60635 /* RotatingCardBrandsViewSnapshotTests.swift in Sources */, + 6BA4B91C2BF4343B00D1F21D /* STPPaymentMethodMobilePayTests.swift in Sources */, + C5D295FE9988CA80ABA57801 /* RotatingCardBrandsViewTests.swift in Sources */, + BEC0435570B9199B918ED4DA /* STPAPIClient+LinkAccountSessionTest.swift in Sources */, + 7D2C0D1BF455625997CBC33B /* STPAPIClientNetworkBridgeTest.swift in Sources */, + 9A24970C5FB6D3F7314AE550 /* STPAPIClientStubbedTest.swift in Sources */, + 17BD7C0391F3182E32A63D6B /* STPAUBECSDebitFormViewSnapshotTests.swift in Sources */, + 583DE9869C885BA02E0A071E /* STPAUBECSFormViewModelTests.swift in Sources */, + 3EA9D509E59DA65EE4EDF98D /* STPAddressTests.swift in Sources */, + CAC3A0342C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift in Sources */, + E6F428CFAD64979A8874B00B /* STPAnalyticsClientPaymentsTest.swift in Sources */, + 23CF725CFAB2ABED416BF416 /* STPApplePayContextFunctionalTest.swift in Sources */, + 6F9525063D76A9F86A10CCBF /* STPApplePayContextFunctionalTestExtras.swift in Sources */, + F2655328479314A9C8718DE4 /* STPApplePayContextTest.swift in Sources */, + 9B149DA42FB38C3542E0CB4B /* STPApplePayFunctionalTest.swift in Sources */, + FBBA3B39598BBECB664C5E7F /* STPApplePayTest.swift in Sources */, + D0342D50F9AC319919D93D59 /* STPBECSDebitAccountNumberValidatorTests.swift in Sources */, + 66B7EF2DC1CBF813707C767C /* STPBSBNumberValidatorTests.swift in Sources */, + 39B1B88B8506BE4574E6B376 /* STPBankAccountFunctionalTest.swift in Sources */, + 5370700ED1F630E8261507D3 /* STPBankAccountParamsTest.swift in Sources */, + 801F417CE53689B95C4A098B /* STPBankAccountTest.swift in Sources */, + 6F4FBB4F10B5DB2CF8BB3460 /* STPBinRangeTest.swift in Sources */, + B917BF282C84507292112B9D /* STPCardBINMetadataTests.swift in Sources */, + 786C30837EAD918EDE52284E /* STPCardBrandTest.swift in Sources */, + FE6647242714D9BEA1EBC055 /* STPCardCVCInputTextFieldFormatterTests.swift in Sources */, + 8EC1820299152F8565D30A40 /* STPCardCVCInputTextFieldSnapshotTests.swift in Sources */, + F35E090A607EB5F86FFC3D31 /* STPCardCVCInputTextFieldTests.swift in Sources */, + D2C062CE4E54094B1AC33E78 /* STPCardCVCInputTextFieldValidatorTests.swift in Sources */, + AC35943F1EAD50E9D5D509B3 /* STPCardExpiryInputTextFieldFormatterTests.swift in Sources */, + A66C279957B6AC8F72DE05C7 /* STPCardExpiryInputTextFieldSnapshotTests.swift in Sources */, + C57BDA835AED735321906977 /* STPCardExpiryInputTextFieldValidatorTests.swift in Sources */, + 07A5CDBFDF2340BAD99D6EB3 /* STPCardFormViewSnapshotTests.swift in Sources */, + 246920234EE8382FB4E56516 /* STPCardFormViewTests.swift in Sources */, + A12CFA90DAE8BBB39A8C7AA1 /* STPCardFunctionalTest.swift in Sources */, + CF2E17AC77EB08393B8A3F98 /* STPCardNumberInputTextFieldFormatterTests.swift in Sources */, + EEBA9A95E8057A06E5E7C103 /* STPCardNumberInputTextFieldSnapshotTests.swift in Sources */, + 1A058C42C4703458CA1CA522 /* STPCardNumberInputTextFieldValidatorTests.swift in Sources */, + D53C04A27B6B8EFB70E236A7 /* STPCardParamsTest.swift in Sources */, + D15160C0F0763078DBB434E4 /* STPCardTest.swift in Sources */, + 1E8D8E2494062262A332879C /* STPCardValidatorTest.swift in Sources */, + A781FB0F586B26655FAEC3C0 /* STPCertTest.swift in Sources */, + C8226E24CA51133091131391 /* STPConfirmCardOptionsTest.swift in Sources */, + 6BC5EC2D2B4609FF00CC75E8 /* LinkInlineSignupElementSnapshotTests.swift in Sources */, + 240993144289CD0DEC2C73C7 /* STPConfirmPaymentMethodOptionsTest.swift in Sources */, + 492F7C4DABB4CE8EBE34EEF2 /* STPConnectAccountAddressTest.swift in Sources */, + C8490E55B1F2EB836144F91C /* STPConnectAccountFunctionalTest.swift in Sources */, + 14656D177E67594B8C75A9FE /* STPConnectAccountParamsTest.swift in Sources */, + 610DF5DC2B33597500DA6AAA /* HostedSurfaceTest.swift in Sources */, + 9D464A252FBD0D4E2A0A7398 /* STPCountryPickerInputFieldSnapshotTests.swift in Sources */, + 6EDFC83541EED9E361B71C02 /* STPCustomerTest.swift in Sources */, + CBCA59D39B30D869B4FDC04B /* STPE2ETest.swift in Sources */, + 687517E7FE02FFB96DCE2328 /* STPEphemeralKeyManagerTest.swift in Sources */, + B6656829DEC006DBEED2AA0E /* STPEphemeralKeyTest.swift in Sources */, + 7435E6BB6971012A9B0DB52E /* STPErrorBridgeTest.m in Sources */, + 5D7F632025C261B88F0C2016 /* STPFPXBankBrandTest.swift in Sources */, + D776B91F0E8E6CCB6C09AC4F /* STPFileFunctionalTest.swift in Sources */, + D76D24F6A94108853BB08712 /* STPFileTest.swift in Sources */, + BC6912C0DE15008C8D8C303C /* STPFloatingPlaceholderTextFieldSnapshotTests.swift in Sources */, + 91A839DEDA7D1EAF6FC66BE0 /* STPFormEncoderTest.swift in Sources */, + 96098727EFA6A72087A35A52 /* STPFormTextFieldTest.swift in Sources */, + 013F991AB34E38BDBA6E4521 /* STPFormViewSnapshotTests.swift in Sources */, + 51044B947A7FDB99451466D8 /* STPGenericInputPickerFieldSnapshotTests.swift in Sources */, + 8532FEBF4F2E0EB282D466CE /* STPGenericInputPickerFieldValidatorTest.swift in Sources */, + 4B0917FC15BF56D0100E0ED1 /* STPGenericInputTextFieldSnapshotTests.swift in Sources */, + 3AD22E0BD44B02D968C6569A /* STPImageLibraryTest.swift in Sources */, + 61E1CA212BD6B78500A421AE /* STPPaymentMethodMultibancoParamsTests.swift in Sources */, + D7C555B36C282B99E22B8D45 /* STPInputTextFieldFormatterTests.swift in Sources */, + 617C1C8A2BB4998C00B10AC5 /* STPPaymentMethodAlmaParamsTests.swift in Sources */, + 903FFB756C6ED520BE38EF6F /* STPInputTextFieldValidatorTests.swift in Sources */, + 45FA9B8CC2D18E29BE81CF8F /* STPIntentActionAlipayHandleRedirectTest.swift in Sources */, + F550D4EB3DCFE03D6FC8F023 /* STPIntentActionPayNowDisplayQrCodeTest.swift in Sources */, + 78641CE4011A1C1EE6E35DC5 /* STPIntentActionPromptPayDisplayQrCodeTest.swift in Sources */, + ACC1B91FC687AFD0DFD27CD4 /* STPIntentActionTest.swift in Sources */, + 4935C8B3ECFBAD947E694934 /* STPIntentActionTypeTest.swift in Sources */, + 8F0326E98C74EB62E34B9FEA /* STPIntentActionWeChatPayRedirectToAppTest.swift in Sources */, + 9FB20E559379F468070C7B50 /* STPLabeledFormTextFieldViewSnapshotTests.swift in Sources */, + F60858F62CEF058200D62278 /* STPPaymentMethodCryptoTests.swift in Sources */, + A22D548084E7DE1FE5ABE8E7 /* STPLabeledMultiFormTextFieldViewSnapshotTests.swift in Sources */, + 331924F0801287BAD413FDCB /* STPMandateCustomerAcceptanceParamsTest.swift in Sources */, + 781EC0163AC001C6A66045B6 /* STPMandateDataParamsTest.swift in Sources */, + B82859A4444B9F735720F232 /* STPMandateOnlineParamsTest.swift in Sources */, + A77C5769B20D7884FC8FC4FB /* STPNumericDigitInputTextFormatterTests.swift in Sources */, + F975CE029DF30419B8DB0D8F /* STPNumericStringValidatorTests.swift in Sources */, + 9535CADFFBC9E1FA291E947E /* STPPIIFunctionalTest.swift in Sources */, + 61951FB92B866BA1005F90BE /* STPPaymentMethodAmazonPayTests.swift in Sources */, + 1CD3AB315580606AF87A7B1F /* STPPaymentCardTextFieldKVOTest.m in Sources */, + 77C0FD1BCDA7BBFB88559B44 /* STPPaymentCardTextFieldTest.swift in Sources */, + 86BAF121184D71F5F4FFAD7B /* STPPaymentCardTextFieldTestsSwift.swift in Sources */, + B4719234E4BBDAD260E31373 /* STPPaymentCardTextFieldViewModelTest.swift in Sources */, + CAC80EBF2C33339D001E3D0D /* STPPaymentMethodSatispayTests.swift in Sources */, + A30CD9DA2CCC2DFE00EA22D3 /* STPAPIClientTest.swift in Sources */, + FEF2E0DAC862FF42B814AFCA /* STPPaymentHandlerFunctionalTest.m in Sources */, + 8F5AF9D3566B8DBCA5AB5188 /* STPPaymentHandlerFunctionalTest.swift in Sources */, + F60858F82CEF058F00D62278 /* STPPaymentMethodCryptoParamsTests.swift in Sources */, + 194154708E1A9E013DCE2C72 /* STPPaymentHandlerStubbedMockedFilesTests.swift in Sources */, + 2A528B7B2579E5F977797822 /* STPPaymentHandlerTests.swift in Sources */, + 1BC4044802EE7D3E2643DC84 /* STPPaymentIntentEnumsTest.swift in Sources */, + 37E9160706C9EEEFEF133617 /* STPPaymentIntentFunctionalTest.swift in Sources */, + 61E1CA272BD6BED600A421AE /* STPIntentActionMultibancoDisplayDetailsTest.swift in Sources */, + F729E784CFFC1F79EF5F2ABE /* STPPaymentIntentLastPaymentErrorTest.swift in Sources */, + CA189278AD606BEAC62D545F /* STPPaymentIntentParamsTest.swift in Sources */, + 73AFE2A8839EFAB8330F6CF0 /* STPPaymentIntentTest.swift in Sources */, + 5E498CDA0115CF9F8463C566 /* STPPaymentMethodAUBECSDebitParamsTests.swift in Sources */, + D567569568C0D8F2D7B179B3 /* STPPaymentMethodAUBECSDebitTests.swift in Sources */, + CFC1F2B8D48FFF7B0F81B5A0 /* STPPaymentMethodAddressTest.swift in Sources */, + 4993037E5386D0AF87B24871 /* STPPaymentMethodAffirmParamsTest.swift in Sources */, + F5CC4F320D09A06F0B21ABE6 /* STPPaymentMethodAffirmTests.swift in Sources */, + FDADE3E36804A8AD82301BF3 /* STPPaymentMethodAfterpayClearpayParamsTest.swift in Sources */, + C34D0BBDF6553ACF85204ACD /* STPPaymentMethodAfterpayClearpayTest.swift in Sources */, + 0305C1689C25B57C43640173 /* STPPaymentMethodBacsDebitTest.swift in Sources */, + BEC5B2ACC54FB72DEBFB70AB /* STPPaymentMethodBancontactParamsTests.swift in Sources */, + 1CCFC43F7FCD273E2100D321 /* STPPaymentMethodBancontactTests.swift in Sources */, + E0F011E9C6CA368EF87F8E28 /* STPPaymentMethodBillingDetailsTest.swift in Sources */, + 29428CDB658E6F504402D844 /* STPPaymentMethodBillingDetailsTests+Link.swift in Sources */, + 8C977F8D224A7360AE8E15A7 /* STPPaymentMethodBoletoParamsTests.swift in Sources */, + 5170651536332C4842E9D009 /* STPPaymentMethodBoletoTests.swift in Sources */, + FE7C38B95B3B7E028AB21238 /* STPPaymentMethodCardChecksTest.swift in Sources */, + 8378F2A4B0796819BB1C6C54 /* STPPaymentMethodCardParamsTest.swift in Sources */, + 43FFF2881D4EFA7B57A60E09 /* STPPaymentMethodCardTest.swift in Sources */, + F06EAD0F48302B061ED29E61 /* STPPaymentMethodCardWalletMasterpassTest.swift in Sources */, + 7623057AC6AC5369DCD94E84 /* STPPaymentMethodCardWalletTest.swift in Sources */, + 33DF66640B5ABBCB12B46AFE /* STPPaymentMethodCardWalletVisaCheckoutTest.swift in Sources */, + 0185AC6B123CD73E877D4FCE /* STPPaymentMethodCashAppParamsTests.swift in Sources */, + CBAF9C6F87F746F17495ADC2 /* STPPaymentMethodCashAppTests.swift in Sources */, + D4602454AC17D3584BA88217 /* STPPaymentMethodEPSParamsTests.swift in Sources */, + C7EB8FB325BF491FDE25FE66 /* STPPaymentMethodEPSTests.swift in Sources */, + C3AAA4AFEE274B27D3483876 /* STPPaymentMethodFPXTest.swift in Sources */, + B44E4CF6C65522F80C946775 /* STPPaymentMethodFunctionalTest.swift in Sources */, + E9A2C6E153CB480891846705 /* STPPaymentMethodGiropayParamsTests.swift in Sources */, + B8ED1F697519A6FCD3D79431 /* STPPaymentMethodGiropayTests.swift in Sources */, + 319899DEC91B3F88D380DB47 /* STPPaymentMethodGrabPayParamsTest.swift in Sources */, + 7844BB705AEB002965EF82B0 /* STPPaymentMethodKlarnaParamsTests.swift in Sources */, + CAC3A03E2C2F2CD8007BC888 /* STPPaymentMethodBillieTests.swift in Sources */, + 27F1783CBFEC06BFD6C114F6 /* STPPaymentMethodKlarnaTests.swift in Sources */, + C0B59D0A7025A55ECD948D47 /* STPPaymentMethodNetBankingParamsTest.swift in Sources */, + FF0F9BA6FE4B88297A434EA7 /* STPPaymentMethodNetBankingTests.swift in Sources */, + E3E916EB10E19727D6B33081 /* STPPaymentMethodOXXOParamsTests.swift in Sources */, + 37FBCED5F71F03483EA73F27 /* STPPaymentMethodOXXOTests.swift in Sources */, + DD8E2B99BAE917F83258DC35 /* STPPaymentMethodOptionsTest.swift in Sources */, + 5D9EB3E2725C38D7098B9965 /* STPPaymentMethodParamsTest.swift in Sources */, + 42F18560F3DC6980408AF051 /* STPPaymentMethodPayPalParamsTests.swift in Sources */, + 2C6DC246DD12FE0D87156A4D /* STPPaymentMethodPayPalTests.swift in Sources */, + E2790AB17C8C65CDE1E81532 /* STPPaymentMethodPrzelewy24ParamsTests.swift in Sources */, + D7D24DCC9402153965AF7F1B /* STPPaymentMethodPrzelewy24Tests.swift in Sources */, + BBB734F006FAD749678B87D1 /* STPPaymentMethodRevolutPayParamsTests.swift in Sources */, + D54508ED433792AD8AA6610F /* STPPaymentMethodRevolutPayTests.swift in Sources */, + B359F6DCB31EAD0814AD9AFD /* STPPaymentMethodSEPADebitTest.swift in Sources */, + 0684E2ABDA4566356143CC14 /* STPPaymentMethodSofortParamsTests.swift in Sources */, + 5C5E1CE53D89DE8F0B867115 /* STPPaymentMethodSofortTests.swift in Sources */, + 2C9F69E4A384C5743F4EAF69 /* STPPaymentMethodSwishParamsTests.swift in Sources */, + 4059301B0365BD4220E591FB /* STPPaymentMethodSwishTests.swift in Sources */, + A08C2F0E7F642515B1D263ED /* STPPaymentMethodTest.swift in Sources */, + 7A9D7D156B5053638F9B21E1 /* STPPaymentMethodThreeDSecureUsageTest.swift in Sources */, + A01BB7F09134F7081679F9C4 /* STPPaymentMethodUPIParamsTest.swift in Sources */, + AF44725558E654548FED2A2B /* STPPaymentMethodUPITests.swift in Sources */, + 4AAA2CD5AEF1F913395B3B95 /* STPPaymentMethodUSBankAccountParamsStubbedTest.swift in Sources */, + 7D251ABF1EBF65ACA8A4BDD4 /* STPPaymentMethodUSBankAccountParamsTest.swift in Sources */, + 225140E0BD9C0630116DDE4A /* STPPaymentMethodUSBankAccountTest.swift in Sources */, + B71F04D02538FA1723558C48 /* STPPaymentMethodiDEALTest.swift in Sources */, + 07BF3CF1656AF5F5A0678873 /* STPPhoneNumberValidatorTest.swift in Sources */, + EBD436689635CC28A24DECD4 /* STPPinManagementServiceFunctionalTest.swift in Sources */, + 385CAC4D2FF119D2E925916B /* STPPostalCodeInputTextFieldFormatterTests.swift in Sources */, + 9B1AC278FDCDABF26C5E468C /* STPPostalCodeInputTextFieldSnapshotTests.swift in Sources */, + 4E31B1864DA407598FB1BBC6 /* STPPostalCodeInputTextFieldTests.swift in Sources */, + 6BA4B91A2BF433B200D1F21D /* STPPaymentMethodMobilePayParamsTests.swift in Sources */, + 91558F51B87C72E745244958 /* STPPostalCodeInputTextFieldValidatorTests.swift in Sources */, + 6FCA954C32AB351F902BA876 /* STPPostalCodeValidatorTest.swift in Sources */, + 3172C789DF2CE133ECA359D7 /* STPPushProvisioningDetailsFunctionalTest.swift in Sources */, + 4A61DC36F10B9C9C24345613 /* STPRadarSessionFunctionalTest.swift in Sources */, + 1948544E75A2E16E46CBA00E /* STPRedirectContextTest.swift in Sources */, + 044B7BECFBDB1F6C8CA08514 /* STPSetupIntentConfirmParamsTest.swift in Sources */, + C32D7ACEBC852CBC295BBEF2 /* STPSetupIntentFunctionalTest.swift in Sources */, + E699508F4DB4D9D4666BAA08 /* STPSetupIntentLastSetupErrorTest.swift in Sources */, + EC4DC8E386544959E1AA9355 /* STPSetupIntentTest.swift in Sources */, + AE747ADA2841AA06F32558D8 /* STPSourceCardDetailsTest.swift in Sources */, + 3FD5ABC45AF3A03F4EFE196F /* STPSourceFunctionalTest.swift in Sources */, + 922C0DF37F5AAA29375A5454 /* STPSourceOwnerTest.swift in Sources */, + D8BECFB70834CC42BA6706D8 /* STPSourceParamsTest.swift in Sources */, + 46FF3CC61200F2C27D4F3369 /* STPSourceReceiverTest.swift in Sources */, + 28538CD5885636DC523E8751 /* STPSourceRedirectTest.swift in Sources */, + EA80A8DB806DEF4F519059CB /* STPSourceSEPADebitDetailsTest.swift in Sources */, + D3D654D8376AAA634466D31D /* STPSourceTest.swift in Sources */, + 460B31EDB22BD6B912567363 /* STPSourceVerificationTest.swift in Sources */, + D7956073A8FD3785193E0577 /* STPStackViewWithSeparatorSnapshotTests.swift in Sources */, + 61152B4F2B866827003B69A0 /* STPPaymentMethodAmazonPayParamsTests.swift in Sources */, + CA4F392070740C56FE2BB461 /* STPStringUtilsTest.swift in Sources */, + 9D8354BDB04CEC5D1EFCF54F /* STPSwiftFixtures.swift in Sources */, + E3F1BAD22CC6E90B761B0502 /* STPTextFieldDelegateProxyTests.swift in Sources */, + 58A8B3F57FE98C22D8F90C77 /* STPThreeDSButtonCustomizationTest.swift in Sources */, + EA571ECEFDDF10AF87CE2B74 /* STPThreeDSFooterCustomizationTest.swift in Sources */, + C35CF837D67AE8DB7CBDAD98 /* STPThreeDSLabelCustomizationTest.swift in Sources */, + 590DB84AC15709E3C6F1FC3B /* STPThreeDSNavigationBarCustomizationTest.swift in Sources */, + DD16FC7ABCA7817794ECC407 /* STPThreeDSSelectionCustomizationTest.swift in Sources */, + 61E0A0C52BF31D3C00C89786 /* STPPaymentHandlerRefreshTests.swift in Sources */, + 2AC91F23CF3949ADC60D27F7 /* STPThreeDSTextFieldCustomizationTest.swift in Sources */, + 701C464523173C6809544935 /* STPThreeDSUICustomizationTest.swift in Sources */, + B86EE8C85E6AB6B0A34C1887 /* STPTokenTest.swift in Sources */, + 35E05040EA813C3B9C8EF054 /* STPViewWithSeparatorSnapshotTests.swift in Sources */, + 71116C2D5831E271E12DB059 /* ServerErrorMapperTest.swift in Sources */, + A8B0DB753CAA2223C8BED099 /* StripeErrorTest.swift in Sources */, + 3B237145902E3DB07E747E32 /* TextFieldElement+IBANTest.swift in Sources */, + 51D515315F02D4C03BA12366 /* UserDefaults+StripeTest.swift in Sources */, + 8B80FB6FC88D411A90E9D487 /* WalletHeaderViewSnapshotTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 11BFD72CD1F291E0E86EC784 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeiOSTestHostApp; + target = 1628E8B14F1F7C63CF8C9962 /* StripeiOSTestHostApp */; + targetProxy = 32221E5BA07FB5AA4EBFE81C /* PBXContainerItemProxy */; + }; + 4E57D9EF1E91091C57E49111 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeiOS; + target = ADF894AA8F6022D9BED17346 /* StripeiOS */; + targetProxy = D90CE98566BBDA0E7340E1D7 /* PBXContainerItemProxy */; + }; + C4F1FB6EFF8D25D54580374A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeiOS; + target = ADF894AA8F6022D9BED17346 /* StripeiOS */; + targetProxy = 8F25D3DFD6D65DAE4B581911 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 147D2DC1FFDFC99269039377 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + AD8BED2A6066514B51693172 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 884C01B087B4D820395BD374 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + C980D24DDC884FECCE39139F /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 006D8C78BFD35DF18042E040 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1D23EB567F573612E0794B3A /* Stripe Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeiOSTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeiOSTests; + PRODUCT_NAME = StripeiOS_Tests; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 008139FD6C1D3C3903E1FBC4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 969E196AB597EEF68C38103E /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 160D95A170FDDD08C46E4C35 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E4EA6394497D1BD57ED0032 /* Stripe Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeiOSTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeiOSTests; + PRODUCT_NAME = StripeiOS_Tests; + SDKROOT = iphoneos; + }; + name = Release; + }; + 2473A3BC44750A2ADE1CE6A4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E1A2173E0891B7138687D544 /* Stripe-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeiOS/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-ios"; + PRODUCT_NAME = Stripe; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 4CFD88FE3BC957D5059302C0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D609FFD051FC01BF566665A0 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeiOSAppHostedTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeiOSAppHostedTests; + PRODUCT_NAME = StripeiOSAppHostedTests; + SDKROOT = iphoneos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StripeiOSTestHostApp.app/StripeiOSTestHostApp"; + TEST_TARGET_NAME = StripeiOSTestHostApp; + }; + name = Debug; + }; + 50C10A6D37E11392F6B0E7F2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E3C8833E7EBA7A1EBF349D19 /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeiOSAppHostedTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeiOSAppHostedTests; + PRODUCT_NAME = StripeiOSAppHostedTests; + SDKROOT = iphoneos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StripeiOSTestHostApp.app/StripeiOSTestHostApp"; + TEST_TARGET_NAME = StripeiOSTestHostApp; + }; + name = Release; + }; + 610AD56E6CBB9E6781424682 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EB71A4A2762CF864DB198BCF /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + A71DB2DB45060267821F91EF /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E3C8833E7EBA7A1EBF349D19 /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeiOSTestHostApp/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeiOSTestHostApp; + PRODUCT_NAME = StripeiOSTestHostApp; + SDKROOT = iphoneos; + }; + name = Release; + }; + D3B5E5117D5D1C186CC75505 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D609FFD051FC01BF566665A0 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeiOSTestHostApp/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeiOSTestHostApp; + PRODUCT_NAME = StripeiOSTestHostApp; + SDKROOT = iphoneos; + }; + name = Debug; + }; + D88B8DCAA56931FBE4963518 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DA82BB67D434E76B9ABA4CEC /* Stripe-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeiOS/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-ios"; + PRODUCT_NAME = Stripe; + SDKROOT = iphoneos; + }; + name = Release; + }; + E652E5F32CB20B7200081F83 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + E652E5F42CB20B7200081F83 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2804CCD7A91C99DF32596147 /* Build configuration list for PBXProject "Stripe" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 610AD56E6CBB9E6781424682 /* Debug */, + 008139FD6C1D3C3903E1FBC4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 54F4DB1E9C991950FD9EB4B4 /* Build configuration list for PBXNativeTarget "StripeiOSTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 006D8C78BFD35DF18042E040 /* Debug */, + 160D95A170FDDD08C46E4C35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B6D8846CE184BBD9139D6D15 /* Build configuration list for PBXNativeTarget "StripeiOSTestHostApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D3B5E5117D5D1C186CC75505 /* Debug */, + A71DB2DB45060267821F91EF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E652E5F52CB20B7200081F83 /* Build configuration list for PBXAggregateTarget "ReleaseFrameworksTarget" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E652E5F32CB20B7200081F83 /* Debug */, + E652E5F42CB20B7200081F83 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F0FCB32AE130ABA66178DD9B /* Build configuration list for PBXNativeTarget "StripeiOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2473A3BC44750A2ADE1CE6A4 /* Debug */, + D88B8DCAA56931FBE4963518 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FCBB942DEBE57423D79373B4 /* Build configuration list for PBXNativeTarget "StripeiOSAppHostedTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4CFD88FE3BC957D5059302C0 /* Debug */, + 50C10A6D37E11392F6B0E7F2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 44C6210EB47ACF468D255723 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davidme-stripe/OHHTTPStubs"; + requirement = { + branch = "stripe-mock"; + kind = branch; + }; + }; + 71086E1440B890A9DC01C9DF /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; + D244E8B5402CB2CF89CE7872 /* XCRemoteSwiftPackageReference "ocmock" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/erikdoe/ocmock"; + requirement = { + kind = revision; + revision = 2c0bfd373289f4a7716db5d6db471640f91a6507; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 62887B4538E4E41E735685E1 /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; + 911CA85A1610303FA0AF0643 /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; + C55551F29B99CF6D6DD9EE2F /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; + E804AA8C4156CC85FFD9595F /* OCMock */ = { + isa = XCSwiftPackageProductDependency; + productName = OCMock; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = E63832AA5BB4225708B7C838 /* Project object */; +} diff --git a/Stripe/Stripe.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Stripe/Stripe.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Stripe/Stripe.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Stripe/Stripe.xcodeproj/xcshareddata/xcschemes/StripeiOS.xcscheme b/Stripe/Stripe.xcodeproj/xcshareddata/xcschemes/StripeiOS.xcscheme new file mode 100644 index 00000000..c39806ee --- /dev/null +++ b/Stripe/Stripe.xcodeproj/xcshareddata/xcschemes/StripeiOS.xcscheme @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stripe/Stripe.xcodeproj/xcshareddata/xcschemes/StripeiOSTestHostApp.xcscheme b/Stripe/Stripe.xcodeproj/xcshareddata/xcschemes/StripeiOSTestHostApp.xcscheme new file mode 100644 index 00000000..eb6c42eb --- /dev/null +++ b/Stripe/Stripe.xcodeproj/xcshareddata/xcschemes/StripeiOSTestHostApp.xcscheme @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stripe/StripeiOS/Docs.docc/Stripe.md b/Stripe/StripeiOS/Docs.docc/Stripe.md new file mode 100644 index 00000000..54a4e714 --- /dev/null +++ b/Stripe/StripeiOS/Docs.docc/Stripe.md @@ -0,0 +1,3 @@ +# ``Stripe`` + +Placeholder diff --git a/Stripe/StripeiOS/Info.plist b/Stripe/StripeiOS/Info.plist new file mode 100644 index 00000000..abe21473 --- /dev/null +++ b/Stripe/StripeiOS/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + Stripe + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Stripe/StripeiOS/PrivacyInfo.xcprivacy b/Stripe/StripeiOS/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..30919590 --- /dev/null +++ b/Stripe/StripeiOS/PrivacyInfo.xcprivacy @@ -0,0 +1,45 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePaymentInfo + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/Contents.json new file mode 100644 index 00000000..4a264e40 --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_card_form_amex_cvc.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_card_form_amex_cvc@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_card_form_amex_cvc@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc.png new file mode 100644 index 00000000..aabbbb01 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc@2x.png new file mode 100644 index 00000000..599e1fb2 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc@3x.png new file mode 100644 index 00000000..0ef2077b Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_amex_cvc.imageset/stp_card_form_amex_cvc@3x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/Contents.json new file mode 100644 index 00000000..63fd9854 --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_card_form_back.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_card_form_back@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_card_form_back@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back.png new file mode 100644 index 00000000..e307bfc7 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back@2x.png new file mode 100644 index 00000000..7ead641d Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back@3x.png new file mode 100644 index 00000000..00b05459 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_back.imageset/stp_card_form_back@3x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/Contents.json new file mode 100644 index 00000000..943a813d --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_card_form_front.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_card_form_front@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_card_form_front@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front.png new file mode 100644 index 00000000..08271ba1 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front@2x.png new file mode 100644 index 00000000..e5a90487 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front@3x.png new file mode 100644 index 00000000..6512912e Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Cards/stp_card_form_front.imageset/stp_card_form_front@3x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/Contents.json new file mode 100644 index 00000000..569c890b --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_icon_add.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_icon_add@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_icon_add@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add.png new file mode 100644 index 00000000..f9595642 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add@2x.png new file mode 100644 index 00000000..2c695a3d Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add@3x.png new file mode 100644 index 00000000..8a108a76 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_add.imageset/stp_icon_add@3x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/Contents.json new file mode 100644 index 00000000..a368e565 --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_icon_bank.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_icon_bank@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_icon_bank@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank.png new file mode 100644 index 00000000..cdb49f12 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank@2x.png new file mode 100644 index 00000000..6a02de3c Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank@3x.png new file mode 100644 index 00000000..f49ebe9a Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_bank.imageset/stp_icon_bank@3x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/Contents.json new file mode 100644 index 00000000..79d652fe --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_icon_checkmark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_icon_checkmark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_icon_checkmark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark.png new file mode 100644 index 00000000..253ea054 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark@2x.png new file mode 100644 index 00000000..875e0a1f Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark@3x.png new file mode 100644 index 00000000..25831dc8 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_icon_checkmark.imageset/stp_icon_checkmark@3x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/Contents.json b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/Contents.json new file mode 100644 index 00000000..166215bf --- /dev/null +++ b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "stp_shipping_form.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_shipping_form@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_shipping_form@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form.png new file mode 100644 index 00000000..33eb8e1d Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form@2x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form@2x.png new file mode 100644 index 00000000..c7dead3f Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form@2x.png differ diff --git a/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form@3x.png b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form@3x.png new file mode 100644 index 00000000..34ede140 Binary files /dev/null and b/Stripe/StripeiOS/Resources/StripeiOS.xcassets/stp_shipping_form.imageset/stp_shipping_form@3x.png differ diff --git a/Stripe/StripeiOS/Source/PKAddPaymentPassRequest+Stripe_Error.swift b/Stripe/StripeiOS/Source/PKAddPaymentPassRequest+Stripe_Error.swift new file mode 100644 index 00000000..ab0f3bbe --- /dev/null +++ b/Stripe/StripeiOS/Source/PKAddPaymentPassRequest+Stripe_Error.swift @@ -0,0 +1,30 @@ +// +// PKAddPaymentPassRequest+Stripe_Error.swift +// StripeiOS +// +// Created by Jack Flintermann on 9/29/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import ObjectiveC +import PassKit + +var stpAddPaymentPassRequest: UInt8 = 0 + +// This is used to store an error on a PKAddPaymentPassRequest +// so that STPFakeAddPaymentPassViewController can inspect it for debugging. +extension PKAddPaymentPassRequest { + @objc var stp_error: NSError? { + get { + return objc_getAssociatedObject(self, &stpAddPaymentPassRequest) as? NSError + } + set(stp_error) { + objc_setAssociatedObject( + self, + &stpAddPaymentPassRequest, + stp_error, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + } + } +} diff --git a/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift b/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift new file mode 100644 index 00000000..21bf9d13 --- /dev/null +++ b/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift @@ -0,0 +1,24 @@ +// +// PKPaymentAuthorizationViewController+Stripe_Blocks.swift +// StripeiOS +// +// Created by Ben Guo on 4/19/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import ObjectiveC +import PassKit + +typealias STPApplePayPaymentMethodHandlerBlock = (STPPaymentMethod, @escaping STPPaymentStatusBlock) + -> Void +typealias STPPaymentCompletionBlock = (STPPaymentStatus, Error?) -> Void +typealias STPPaymentAuthorizationBlock = (PKPayment) -> Void + +typealias STPApplePayShippingMethodCompletionBlock = ( + PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]? +) -> Void +typealias STPApplePayShippingAddressCompletionBlock = ( + PKPaymentAuthorizationStatus, [PKShippingMethod]?, [PKPaymentSummaryItem]? +) -> Void + +typealias STPPaymentAuthorizationStatusCallback = (PKPaymentAuthorizationStatus) -> Void diff --git a/Stripe/StripeiOS/Source/STPAPIClient+PushProvisioning.swift b/Stripe/StripeiOS/Source/STPAPIClient+PushProvisioning.swift new file mode 100644 index 00000000..8d681367 --- /dev/null +++ b/Stripe/StripeiOS/Source/STPAPIClient+PushProvisioning.swift @@ -0,0 +1,46 @@ +// +// STPAPIClient+PushProvisioning.swift +// StripeiOS +// +// Created by Jack Flintermann on 9/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripePaymentsUI + +typealias STPPushProvisioningDetailsCompletionBlock = (STPPushProvisioningDetails?, Error?) -> Void +extension STPAPIClient { + func retrievePushProvisioningDetails( + with params: STPPushProvisioningDetailsParams, + ephemeralKey: STPEphemeralKey, + completion: @escaping STPPushProvisioningDetailsCompletionBlock + ) { + + let endpoint = "issuing/cards/\(params.cardId)/push_provisioning_details" + let parameters = [ + "ios": [ + "certificates": params.certificatesBase64, + "nonce": params.nonceHex, + "nonce_signature": params.nonceSignatureHex, + ] as [String: Any], + ] + + APIRequest.getWith( + self, + endpoint: endpoint, + additionalHeaders: authorizationHeader(using: ephemeralKey), + parameters: parameters + ) { details, _, error in + completion(details, error) + } + } + // MARK: Helpers + + /// A helper method that returns the Authorization header to use for API requests. If ephemeralKey is nil, uses self.publishableKey instead. + func authorizationHeader(using ephemeralKey: STPEphemeralKey? = nil) -> [String: String] { + return authorizationHeader(using: ephemeralKey?.secret) + } +} diff --git a/Stripe/StripeiOS/Source/STPAnalyticsClient+Payments.swift b/Stripe/StripeiOS/Source/STPAnalyticsClient+Payments.swift new file mode 100644 index 00000000..6706b95d --- /dev/null +++ b/Stripe/StripeiOS/Source/STPAnalyticsClient+Payments.swift @@ -0,0 +1,27 @@ +// +// STPAnalyticsClient+Payments.swift +// StripeiOS +// +// Created by David Estes on 1/24/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// An analytic specific to payments that serializes payment-specific +/// information into its params. +@_spi(STP) public protocol PaymentAnalytic: Analytic { + var additionalParams: [String: Any] { get } +} + +@_spi(STP) extension PaymentAnalytic { + public var params: [String: Any] { + var params = additionalParams + + params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) + params["ocr_type"] = PaymentsSDKVariant.ocrTypeString + params["pay_var"] = PaymentsSDKVariant.variant + return params + } +} diff --git a/Stripe/StripeiOS/Source/STPApplePayContextDelegate.swift b/Stripe/StripeiOS/Source/STPApplePayContextDelegate.swift new file mode 100644 index 00000000..0814a5de --- /dev/null +++ b/Stripe/StripeiOS/Source/STPApplePayContextDelegate.swift @@ -0,0 +1,98 @@ +// +// STPApplePayContextDelegate.swift +// StripeiOS +// +// Created by David Estes on 9/15/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeApplePay +@_spi(STP) import StripeCore + +/// Implement the required methods of this delegate to supply a PaymentIntent to STPApplePayContext and be notified of the completion of the Apple Pay payment. +/// You may also implement the optional delegate methods to handle shipping methods and shipping address changes e.g. to verify you can ship to the address, or update the payment amount. +@objc public protocol STPApplePayContextDelegate: _stpinternal_STPApplePayContextDelegateBase { + /// Called after the customer has authorized Apple Pay. Implement this method to call the completion block with the client secret of a PaymentIntent or SetupIntent. + /// - Parameters: + /// - paymentMethod: The PaymentMethod that represents the customer's Apple Pay payment method. + /// If you create the PaymentIntent with confirmation_method=manual, pass `paymentMethod.stripeId` as the payment_method and confirm=true. Otherwise, you can ignore this parameter. + /// - paymentInformation: The underlying PKPayment created by Apple Pay. + /// If you create the PaymentIntent with confirmation_method=manual, you can collect shipping information using its `shippingContact` and `shippingMethod` properties. + /// - completion: Call this with the PaymentIntent or SetupIntent client secret, or the error that occurred creating the PaymentIntent or SetupIntent. + @objc(applePayContext:didCreatePaymentMethod:paymentInformation:completion:) + func applePayContext( + _ context: STPApplePayContext, + didCreatePaymentMethod paymentMethod: STPPaymentMethod, + paymentInformation: PKPayment, + completion: @escaping STPIntentClientSecretCompletionBlock + ) + + /// Called after the Apple Pay sheet is dismissed with the result of the payment. + /// Your implementation could stop a spinner and display a receipt view or error to the customer, for example. + /// - Parameters: + /// - status: The status of the payment + /// - error: The error that occurred, if any. + @objc(applePayContext:didCompleteWithStatus:error:) + func applePayContext( + _ context: STPApplePayContext, + didCompleteWith status: STPPaymentStatus, + error: Error? + ) +} + +/// A helper class used to bridge StripeApplePay.framework with the legacy Stripe.framework objects. +@objc(STPApplePayContextLegacyHelper) +class STPApplePayContextLegacyHelper: NSObject { + @objc class func performDidCreatePaymentMethod( + _ storage: _stpinternal_ApplePayContextDidCreatePaymentMethodStorage + ) { + let delegate = storage.delegate as! STPApplePayContextDelegate + // Convert the PaymentMethod to an STPPaymentMethod: + guard + let stpPaymentMethod = STPPaymentMethod.decodedObject( + fromAPIResponse: storage.paymentMethod.allResponseFields + ) + else { + assertionFailure("Failed to convert PaymentMethod to STPPaymentMethod") + return + } + delegate.applePayContext( + storage.context, + didCreatePaymentMethod: stpPaymentMethod, + paymentInformation: storage.paymentInformation, + completion: storage.completion + ) + } + + @objc class func performDidComplete(_ storage: _stpinternal_ApplePayContextDidCompleteStorage) { + let delegate = storage.delegate as! STPApplePayContextDelegate + let stpStatus = STPPaymentStatus(applePayStatus: storage.status) + + // If this is a modern API error, convert it down to a legacy STPError. + // This is to avoid changing the API experience for users. + // We can re-evaluate this as we release more of the modern API. + if let modernError = storage.error as? StripeError { + storage.error = NSError.stp_error(from: modernError) + } + + delegate.applePayContext(storage.context, didCompleteWith: stpStatus, error: storage.error) + } + +} + +extension STPPaymentStatus { + init( + applePayStatus: STPApplePayContext.PaymentStatus + ) { + switch applePayStatus { + case .success: + self = .success + case .error: + self = .error + case .userCancellation: + self = .userCancellation + } + } +} diff --git a/Stripe/StripeiOS/Source/STPEphemeralKey.swift b/Stripe/StripeiOS/Source/STPEphemeralKey.swift new file mode 100644 index 00000000..de27b5cf --- /dev/null +++ b/Stripe/StripeiOS/Source/STPEphemeralKey.swift @@ -0,0 +1,102 @@ +// +// STPEphemeralKey.swift +// StripeiOS +// +// Created by Ben Guo on 5/4/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripePayments + +class STPEphemeralKey: NSObject, STPAPIResponseDecodable { + private(set) var stripeID: String + private(set) var created: Date + private(set) var livemode = false + private(set) var secret: String + private(set) var expires: Date + private(set) var issuingCardID: String? + + /// You cannot directly instantiate an `STPEphemeralKey`. You should instead use + /// `decodedObjectFromAPIResponse:` to create a key from a JSON response. + required init( + stripeID: String, + created: Date, + secret: String, + expires: Date + ) { + self.stripeID = stripeID + self.created = created + self.secret = secret + self.expires = expires + super.init() + } + + private(set) var allResponseFields: [AnyHashable: Any] = [:] + + class func decodedObject(fromAPIResponse response: [AnyHashable: Any]?) -> Self? { + guard let response = response else { + return nil + } + let dict = response.stp_dictionaryByRemovingNulls() + + // required fields + guard + let stripeId = dict.stp_string(forKey: "id"), + let created = dict.stp_date(forKey: "created"), + let secret = dict.stp_string(forKey: "secret"), + let expires = dict.stp_date(forKey: "expires"), + let associatedObjects = dict.stp_array(forKey: "associated_objects"), + dict["livemode"] != nil + else { + return nil + } + + var customerID: String? + var issuingCardID: String? + for obj in associatedObjects { + if let obj = obj as? [AnyHashable: Any] { + let type = obj.stp_string(forKey: "type") + if type == "customer" { + customerID = obj.stp_string(forKey: "id") + } + if type == "issuing.card" { + issuingCardID = obj.stp_string(forKey: "id") + } + } + } + if customerID == nil && issuingCardID == nil { + return nil + } + let key = self.init(stripeID: stripeId, created: created, secret: secret, expires: expires) + key.issuingCardID = issuingCardID + key.stripeID = stripeId + key.livemode = dict.stp_bool(forKey: "livemode", or: true) + key.created = created + key.secret = secret + key.expires = expires + key.allResponseFields = response + return key + } + + override var hash: Int { + return stripeID.hash + } + + override func isEqual(_ object: Any?) -> Bool { + if self === (object as? STPEphemeralKey) { + return true + } + if object == nil || !(object is STPEphemeralKey) { + return false + } + if let object = object as? STPEphemeralKey { + return isEqual(to: object) + } + return false + } + + func isEqual(to other: STPEphemeralKey) -> Bool { + return stripeID == other.stripeID + } +} diff --git a/Stripe/StripeiOS/Source/STPEphemeralKeyManager.swift b/Stripe/StripeiOS/Source/STPEphemeralKeyManager.swift new file mode 100644 index 00000000..b9955c98 --- /dev/null +++ b/Stripe/StripeiOS/Source/STPEphemeralKeyManager.swift @@ -0,0 +1,179 @@ +// +// STPEphemeralKeyManager.swift +// StripeiOS +// +// Created by Ben Guo on 5/9/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePaymentsUI +import UIKit + +protocol STPEphemeralKeyManagerProtocol { + /// If the retriever's stored ephemeral key has not expired, it will be + /// returned immediately to the given callback. If the stored key is expiring, a + /// new key will be requested from the key provider, and returned to the callback. + /// If the retriever is unable to provide an unexpired key, an error will be returned. + /// - Parameter completion: The callback to be run with the returned key, or an error. + func getOrCreateKey(_ completion: @escaping STPEphemeralKeyCompletionBlock) +} + +typealias STPEphemeralKeyCompletionBlock = (STPEphemeralKey?, Error?) -> Void +class STPEphemeralKeyManager: NSObject, STPEphemeralKeyManagerProtocol { + private var _expirationInterval: TimeInterval = 0.0 + /// If the current ephemeral key expires in less than this time interval, a call + /// to `getOrCreateKey` will request a new key from the manager's key provider. + /// The maximum allowed value is one hour – higher values will be clamped. + var expirationInterval: TimeInterval { + get { + _expirationInterval + } + set(expirationInterval) { + _expirationInterval = TimeInterval(min(expirationInterval, 60 * 60)) + } + } + /// If this value is YES, the manager will eagerly refresh its key on app foregrounding. + private(set) var performsEagerFetching = false + + /// Initializes a new `STPEphemeralKeyManager` with the specified key provider. + /// - Parameters: + /// - keyProvider: The key provider the manager will use. + /// - apiVersion: The Stripe API version the manager will use. + /// - performsEagerFetching: If the manager should eagerly refresh its key on app foregrounding. + /// - Returns: the newly-initiated `STPEphemeralKeyManager`. + @objc init( + keyProvider: Any?, + apiVersion: String, + performsEagerFetching: Bool + ) { + super.init() + assert( + keyProvider is STPCustomerEphemeralKeyProvider + || keyProvider is STPIssuingCardEphemeralKeyProvider, + "Your STPEphemeralKeyProvider must either implement `STPCustomerEphemeralKeyProvider` or `STPIssuingCardEphemeralKeyProvider`." + ) + expirationInterval = DefaultExpirationInterval + self.keyProvider = keyProvider + self.apiVersion = apiVersion + self.performsEagerFetching = performsEagerFetching + NotificationCenter.default.addObserver( + self, + selector: #selector(handleWillForegroundNotification), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + } + + @objc dynamic func getOrCreateKey(_ completion: @escaping STPEphemeralKeyCompletionBlock) { + if currentKeyIsUnexpired() { + completion(ephemeralKey, nil) + } else { + if let createKeyPromise = createKeyPromise { + // coalesce repeated calls into one request + createKeyPromise.onSuccess({ key in + completion(key, nil) + }).onFailure({ error in + completion(nil, error) + }) + } else { + createKeyPromise = STPPromise.init().onSuccess({ key in + self.ephemeralKey = key + completion(key, nil) + }).onFailure({ error in + completion(nil, error) + }) + _createKey() + } + } + } + + @objc internal var ephemeralKey: STPEphemeralKey? + private var apiVersion: String? + private var keyProvider: Any? + @objc internal var lastEagerKeyRefresh: Date? + private var createKeyPromise: STPPromise? + + deinit { + NotificationCenter.default.removeObserver( + self, + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + } + + func currentKeyIsUnexpired() -> Bool { + return ephemeralKey != nil + && (ephemeralKey?.expires.timeIntervalSinceNow ?? 0.0) > expirationInterval + } + + func shouldPerformEagerRefresh() -> Bool { + return performsEagerFetching + && (lastEagerKeyRefresh == nil + || (lastEagerKeyRefresh?.timeIntervalSinceNow ?? 0.0) > MinEagerRefreshInterval) + } + + @objc func handleWillForegroundNotification() { + // To make sure we don't end up hitting the ephemeral keys endpoint on every + // foreground (e.g. if there's an issue decoding the ephemeral key), throttle + // eager refreshes to once per hour. + if !currentKeyIsUnexpired() && shouldPerformEagerRefresh() { + lastEagerKeyRefresh = Date() + getOrCreateKey({ _, _ in + // getOrCreateKey sets the self.ephemeralKey. Nothing left to do for us here + }) + } + } + + func _createKey() { + let jsonCompletion = + { (jsonResponse: [AnyHashable: Any]?, error: Error?) in + let key = STPEphemeralKey.decodedObject(fromAPIResponse: jsonResponse) + if let key = key { + self.createKeyPromise?.succeed(key) + } else { + // the API request failed + if let error = error { + self.createKeyPromise?.fail(error) + } else { + // the ephemeral key could not be decoded + self.createKeyPromise?.fail(NSError.stp_ephemeralKeyDecodingError()) + if self.keyProvider is STPCustomerEphemeralKeyProvider { + assert( + false, + "Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/basic#prepare-your-api" + ) + } else if self.keyProvider is STPIssuingCardEphemeralKeyProvider { + assert( + false, + "Could not parse the ephemeral key response following protocol STPIssuingCardEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/basic#prepare-your-api" + ) + } + assert( + false, + "Could not parse the ephemeral key response. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/basic#prepare-your-api" + ) + } + } + self.createKeyPromise = nil + } as STPJSONResponseCompletionBlock + + if keyProvider is STPCustomerEphemeralKeyProvider { + weak var provider = keyProvider as? STPCustomerEphemeralKeyProvider + provider?.createCustomerKey( + withAPIVersion: apiVersion ?? "", + completion: jsonCompletion + ) + } else if keyProvider is STPIssuingCardEphemeralKeyProvider { + weak var provider = keyProvider as? STPIssuingCardEphemeralKeyProvider + provider?.createIssuingCardKey( + withAPIVersion: apiVersion ?? "", + completion: jsonCompletion + ) + } + } +} + +private let DefaultExpirationInterval: TimeInterval = 60 +private let MinEagerRefreshInterval: TimeInterval = 60 * 60 diff --git a/Stripe/StripeiOS/Source/STPEphemeralKeyProvider.swift b/Stripe/StripeiOS/Source/STPEphemeralKeyProvider.swift new file mode 100644 index 00000000..9bac4bb2 --- /dev/null +++ b/Stripe/StripeiOS/Source/STPEphemeralKeyProvider.swift @@ -0,0 +1,74 @@ +// +// STPEphemeralKeyProvider.swift +// StripeiOS +// +// Created by Ben Guo on 5/9/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// You should make your application's API client conform to this interface. +/// It provides a way for Stripe utility classes to request a new ephemeral key from +/// your backend, which it will use to retrieve and update Stripe API objects. +@objc public protocol STPCustomerEphemeralKeyProvider: NSObjectProtocol { + /// Creates a new ephemeral key for retrieving and updating a Stripe customer. + /// On your backend, you should create a new ephemeral key for the Stripe customer + /// associated with your user, and return the raw JSON response from the Stripe API. + /// For an example Ruby implementation of this API, refer to our example backend: + /// https://github.com/stripe/example-mobile-backend/blob/v18.1.0/web.rb + /// Back in your iOS app, once you have a response from this API, call the provided + /// completion block with the JSON response, or an error if one occurred. + /// - Parameters: + /// - apiVersion: The Stripe API version to use when creating a key. + /// You should pass this parameter to your backend, and use it to set the API version + /// in your key creation request. Passing this version parameter ensures that the + /// Stripe SDK can always parse the ephemeral key response from your server. + /// - completion: Call this callback when you're done fetching a new ephemeral + /// key from your backend. For example, `completion(json, nil)` (if your call succeeds) + /// or `completion(nil, error)` if an error is returned. + @objc(createCustomerKeyWithAPIVersion:completion:) func createCustomerKey( + withAPIVersion apiVersion: String, + completion: @escaping STPJSONResponseCompletionBlock + ) +} + +/// You should make your application's API client conform to this interface. +/// It provides a way for Stripe utility classes to request a new ephemeral key from +/// your backend, which it will use to retrieve and update Stripe API objects. +@objc public protocol STPIssuingCardEphemeralKeyProvider: NSObjectProtocol { + /// Creates a new ephemeral key for retrieving and updating a Stripe Issuing Card. + /// On your backend, you should create a new ephemeral key for your logged-in user's + /// primary Issuing Card, and return the raw JSON response from the Stripe API. + /// For an example Ruby implementation of this API, refer to our example backend: + /// https://github.com/stripe/example-mobile-backend/blob/v18.1.0/web.rb + /// Back in your iOS app, once you have a response from this API, call the provided + /// completion block with the JSON response, or an error if one occurred. + /// - Parameters: + /// - apiVersion: The Stripe API version to use when creating a key. + /// You should pass this parameter to your backend, and use it to set the API version + /// in your key creation request. Passing this version parameter ensures that the + /// Stripe SDK can always parse the ephemeral key response from your server. + /// - completion: Call this callback when you're done fetching a new ephemeral + /// key from your backend. For example, `completion(json, nil)` (if your call succeeds) + /// or `completion(nil, error)` if an error is returned. + @objc(createIssuingCardKeyWithAPIVersion:completion:) func createIssuingCardKey( + withAPIVersion apiVersion: String, + completion: @escaping STPJSONResponseCompletionBlock + ) +} + +/// You should make your application's API client conform to this interface. +/// It provides a way for Stripe utility classes to request a new ephemeral key from +/// your backend, which it will use to retrieve and update Stripe API objects. +/// @deprecated use `STPCustomerEphemeralKeyProvider` or `STPIssuingCardEphemeralKeyProvider` +/// depending on the type of key that will@objc be fetched. + +@available( + *, + deprecated, + message: + "use `STPCustomerEphemeralKeyProvider` or `STPIssuingCardEphemeralKeyProvider` depending on the type of key that will be fetched." +) +@objc public protocol STPEphemeralKeyProvider: STPCustomerEphemeralKeyProvider { +} diff --git a/Stripe/StripeiOS/Source/STPFakeAddPaymentPassViewController.swift b/Stripe/StripeiOS/Source/STPFakeAddPaymentPassViewController.swift new file mode 100644 index 00000000..34f553ac --- /dev/null +++ b/Stripe/StripeiOS/Source/STPFakeAddPaymentPassViewController.swift @@ -0,0 +1,279 @@ +// +// STPFakeAddPaymentPassViewController.swift +// StripeiOS +// +// Created by Jack Flintermann on 9/28/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import PassKit +@_spi(STP) import StripeCore +import UIKit + +/// This class is a piece of fake UI that is intended to mimic `PKAddPaymentPassViewController`. That class is restricted to apps with a special entitlement from Apple, and as such can be difficult to build and test against. This class implements the same public API as `PKAddPaymentPassViewController`, and can be used to develop against the Stripe API in *testmode only*. (Obviously it will not actually place cards into the user's Apple Pay wallet either.) When it's time to go to production, you may simply replace all references to `STPFakeAddPaymentPassViewController` in your app with `PKAddPaymentPassViewController` and it will continue to function. For more information on developing against this API, please see https://stripe.com/docs/issuing/cards/digital-wallets . +public class STPFakeAddPaymentPassViewController: UIViewController { + /// @see PKAddPaymentPassViewController + @objc + public class func canAddPaymentPass() -> Bool { + return true + } + + /// @see PKAddPaymentPassViewController + @objc(initWithRequestConfiguration:delegate:) + public required init?( + requestConfiguration configuration: PKAddPaymentPassRequestConfiguration, + delegate: PKAddPaymentPassViewControllerDelegate? + ) { + super.init(nibName: nil, bundle: nil) + assert(delegate != nil, "Invalid parameter not satisfying: delegate != nil") + state = .initial + self.delegate = delegate + self.configuration = configuration + if configuration.primaryAccountSuffix == nil && configuration.cardholderName == nil { + assert( + false, + "Your PKAddPaymentPassRequestConfiguration must provide either a cardholderName or a primaryAccountSuffix." + ) + } + } + + /// @see PKAddPaymentPassViewController + @objc public weak var delegate: PKAddPaymentPassViewControllerDelegate? + private var configuration: PKAddPaymentPassRequestConfiguration? + + private var _state: STPFakeAddPaymentPassViewControllerState = .initial + private var state: STPFakeAddPaymentPassViewControllerState { + get { + _state + } + set(state) { + _state = state + let cancelItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(cancel(_:)) + ) + let nextButton = UIButton(type: .system) + nextButton.addTarget(self, action: #selector(next(_:)), for: .touchUpInside) + let indicatorView = UIActivityIndicatorView(style: .medium) + indicatorView.startAnimating() + let loadingItem = UIBarButtonItem(customView: indicatorView) + nextButton.setTitle(STPNonLocalizedString("Next"), for: .normal) + let nextItem = UIBarButtonItem(customView: nextButton) + let doneItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(done(_:)) + ) + + switch state { + case .initial: + contentLabel?.text = STPNonLocalizedString( + "This class simulates the delegate methods that PKAddPaymentPassViewController will call in your app. Press next to continue." + ) + navigationItem.leftBarButtonItem = cancelItem + navigationItem.rightBarButtonItem = nextItem + case .loading: + contentLabel?.text = STPNonLocalizedString("Fetching encrypted card details...") + cancelItem.isEnabled = false + navigationItem.leftBarButtonItem = cancelItem + navigationItem.rightBarButtonItem = loadingItem + case .error: + contentLabel?.text = STPNonLocalizedString("Error: " + (errorText ?? "")) + doneItem.isEnabled = false + navigationItem.leftBarButtonItem = cancelItem + navigationItem.rightBarButtonItem = doneItem + case .success: + contentLabel?.text = STPNonLocalizedString( + "Success! In production, your card would now have been added to your Apple Pay wallet. Your app's success callback will be triggered when the user presses 'Done'." + ) + cancelItem.isEnabled = false + navigationItem.leftBarButtonItem = cancelItem + navigationItem.rightBarButtonItem = doneItem + } + } + } + private var contentLabel: UILabel? + private var errorText: String? + + /// :nodoc: + @objc public convenience override init( + nibName nibNameOrNil: String?, + bundle nibBundleOrNil: Bundle? + ) { + self.init(requestConfiguration: PKAddPaymentPassRequestConfiguration(), delegate: nil)! + } + + /// :nodoc: + @objc public required convenience init?( + coder aDecoder: NSCoder + ) { + self.init(requestConfiguration: PKAddPaymentPassRequestConfiguration(), delegate: nil) + } + + /// :nodoc: + @objc + public override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.white + + let navBar = UINavigationBar() + view.addSubview(navBar) + navBar.isTranslucent = false + navBar.backgroundColor = UIColor.white + navBar.items = [navigationItem] + navBar.translatesAutoresizingMaskIntoConstraints = false + navBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + navBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + navBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + + let contentLabel = UILabel(frame: CGRect.zero) + self.contentLabel = contentLabel + contentLabel.textAlignment = .center + contentLabel.textColor = UIColor.black + contentLabel.numberOfLines = 0 + contentLabel.font = UIFont.systemFont(ofSize: 18) + contentLabel.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(contentLabel) + contentLabel.topAnchor.constraint(equalTo: navBar.bottomAnchor).isActive = true + contentLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10.0).isActive = true + contentLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = + true + contentLabel.heightAnchor.constraint(equalToConstant: 150).isActive = true + + var pairs: [AnyHashable] = [] + if configuration?.cardholderName != nil { + pairs.append(["Name", configuration?.cardholderName]) + } + if configuration?.primaryAccountSuffix != nil { + pairs.append([ + "Card Number", + "···· \(configuration?.primaryAccountSuffix ?? "")", + ]) + } + var rows: [AnyHashable] = [] + for pair in pairs { + guard let pair = pair as? [AnyHashable] else { + continue + } + let left = UILabel() + left.text = pair[0] as? String + left.textAlignment = .left + left.font = UIFont.boldSystemFont(ofSize: 16) + let right = UILabel() + right.text = pair[1] as? String + right.textAlignment = .left + right.textColor = UIColor.lightGray + let row = UIStackView(arrangedSubviews: [left, right]) + row.axis = .horizontal + row.distribution = .fillEqually + row.alignment = .fill + row.translatesAutoresizingMaskIntoConstraints = false + rows.append(row) + } + var pairsTable: UIStackView? + if let rows = rows as? [UIView] { + pairsTable = UIStackView(arrangedSubviews: rows) + } + pairsTable?.isLayoutMarginsRelativeArrangement = true + pairsTable?.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) + pairsTable?.axis = .vertical + pairsTable?.translatesAutoresizingMaskIntoConstraints = false + if let pairsTable = pairsTable { + view.addSubview(pairsTable) + } + + pairsTable?.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + pairsTable?.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + pairsTable?.topAnchor.constraint(equalTo: contentLabel.bottomAnchor).isActive = true + pairsTable?.heightAnchor.constraint(equalToConstant: CGFloat((rows.count * 50))).isActive = + true + state = .initial + } + + @objc func cancel(_ sender: Any?) { + let castedVC = unsafeBitCast(self, to: PKAddPaymentPassViewController.self) + delegate?.addPaymentPassViewController( + castedVC, + didFinishAdding: nil, + error: NSError( + domain: PKPassKitErrorDomain, + code: PKAddPaymentPassError.userCancelled.rawValue, + userInfo: nil + ) + ) + } + + @objc func next(_ sender: Any?) { + state = .loading + let certificates = [ + "cert1".data(using: .utf8), + "cert2".data(using: .utf8), + ] + let nonce = "nonce".data(using: .utf8) + let nonceSignature = "nonceSignature".data(using: .utf8) + + DispatchQueue.main.asyncAfter( + deadline: DispatchTime.now() + Double(Int64(10 * Double(NSEC_PER_SEC))) + / Double(NSEC_PER_SEC), + execute: { + if self.state == .loading { + self.errorText = + "You exceeded the timeout of 10 seconds to call the request completion handler. Please check your PKAddPaymentPassViewControllerDelegate implementation, and make sure you are calling the `completionHandler` in `addPaymentPassViewController:generateRequestWithCertificateChain:nonce:nonceSignature:completionHandler`." + self.state = .error + } + } + ) + if let nonce = nonce, let nonceSignature = nonceSignature { + let castedVC = unsafeBitCast(self, to: PKAddPaymentPassViewController.self) + delegate?.addPaymentPassViewController( + castedVC, + generateRequestWithCertificateChain: certificates.compactMap { $0 }, + nonce: nonce, + nonceSignature: nonceSignature, + completionHandler: { request in + if self.state == .loading { + var contents: String? + if request.encryptedPassData != nil { + if let encryptedPassData1 = request.encryptedPassData { + contents = String(data: encryptedPassData1, encoding: .utf8) + } + } + if request.stp_error != nil { + var error = + (request.stp_error as NSError?)?.userInfo[STPError.errorMessageKey] + as? String + if error == nil { + error = + (request.stp_error as NSError?)?.userInfo[ + NSLocalizedDescriptionKey + ] as? String + } + self.errorText = error + self.state = .error + } else if contents == "TESTMODE_CONTENTS" { + self.state = .success + } else { + self.errorText = + "Your server response contained the wrong encrypted card details. Please ensure that you are not modifying the response from the Stripe API in any way, and that your request is in testmode." + self.state = .error + } + } + } + ) + } + } + + @objc func done(_ sender: Any?) { + let pass = PKPaymentPass() + let castedVC = unsafeBitCast(self, to: PKAddPaymentPassViewController.self) + delegate?.addPaymentPassViewController(castedVC, didFinishAdding: pass, error: nil) + } +} + +enum STPFakeAddPaymentPassViewControllerState: Int { + case initial + case loading + case error + case success +} diff --git a/Stripe/StripeiOS/Source/STPPinManagementService.swift b/Stripe/StripeiOS/Source/STPPinManagementService.swift new file mode 100644 index 00000000..3ba38b8e --- /dev/null +++ b/Stripe/StripeiOS/Source/STPPinManagementService.swift @@ -0,0 +1,144 @@ +// +// STPPinManagementService.swift +// StripeiOS +// +// Created by Arnaud Cavailhez on 4/29/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripePaymentsUI +import UIKit + +/// STPAPIClient extensions to manage PIN on Stripe Issuing cards +@available(iOS, deprecated: 100000.0, message: "Please use Issuing Elements instead: https://stripe.com/docs/issuing/elements") +public class STPPinManagementService: NSObject { + /// The API Client to use to make requests. + /// Defaults to STPAPIClient.shared + public var apiClient: STPAPIClient = STPAPIClient.shared + + /// Create a STPPinManagementService, you must provide an implementation of STPIssuingCardEphemeralKeyProvider + @objc + public init( + keyProvider: STPIssuingCardEphemeralKeyProvider + ) { + super.init() + keyManager = STPEphemeralKeyManager( + keyProvider: keyProvider as Any, + apiVersion: STPAPIClient.apiVersion, + performsEagerFetching: false + ) + } + + /// Retrieves a PIN number for a given card, + /// this call is asynchronous, implement the completion block to receive the updates + @objc + public func retrievePin( + _ cardId: String, + verificationId: String, + oneTimeCode: String, + completion: @escaping STPPinCompletionBlock + ) { + let endpoint = "issuing/cards/\(cardId)/pin" + let parameters = [ + "verification": [ + "id": verificationId, + "one_time_code": oneTimeCode, + ], + ] + keyManager?.getOrCreateKey({ ephemeralKey, keyError in + if ephemeralKey == nil { + completion(nil, .ephemeralKeyError, keyError) + return + } + + if let ephemeralKey = ephemeralKey { + APIRequest.getWith( + self.apiClient, + endpoint: endpoint, + additionalHeaders: self.apiClient.authorizationHeader(using: ephemeralKey), + parameters: parameters + ) { details, _, error in + // Find if there were errors + if details?.error != nil { + let code = details?.error?["code"] as? String + if "api_key_expired" == code { + completion(nil, .ephemeralKeyError, error) + } else if "expired" == code { + completion(nil, .errorVerificationExpired, nil) + } else if "incorrect_code" == code { + completion(nil, .errorVerificationCodeIncorrect, nil) + } else if "too_many_attempts" == code { + completion(nil, .errorVerificationTooManyAttempts, nil) + } else if "already_redeemed" == code { + completion(nil, .errorVerificationAlreadyRedeemed, nil) + } else { + completion(nil, .unknownError, error) + } + return + } + completion(details, .success, nil) + } + } + }) + } + + /// Updates a PIN number for a given card, + /// this call is asynchronous, implement the completion block to receive the updates + @objc + public func updatePin( + _ cardId: String, + newPin: String, + verificationId: String, + oneTimeCode: String, + completion: @escaping STPPinCompletionBlock + ) { + let endpoint = "issuing/cards/\(cardId)/pin" + let parameters = + [ + "verification": [ + "id": verificationId, + "one_time_code": oneTimeCode, + ], + "pin": newPin, + ] as [String: Any] + keyManager?.getOrCreateKey({ ephemeralKey, keyError in + if ephemeralKey == nil { + completion(nil, .ephemeralKeyError, keyError) + return + } + if let ephemeralKey = ephemeralKey { + APIRequest.post( + with: self.apiClient, + endpoint: endpoint, + additionalHeaders: self.apiClient.authorizationHeader(using: ephemeralKey), + parameters: parameters + ) { details, _, error in + // Find if there were errors + if details?.error != nil { + let code = details?.error?["code"] as? String + if "api_key_expired" == code { + completion(nil, .ephemeralKeyError, error) + } else if "expired" == code { + completion(nil, .errorVerificationExpired, nil) + } else if "incorrect_code" == code { + completion(nil, .errorVerificationCodeIncorrect, nil) + } else if "too_many_attempts" == code { + completion(nil, .errorVerificationTooManyAttempts, nil) + } else if "already_redeemed" == code { + completion(nil, .errorVerificationAlreadyRedeemed, nil) + } else { + completion(nil, .unknownError, error) + } + return + } + completion(details, .success, nil) + } + } + }) + } + + private var keyManager: STPEphemeralKeyManagerProtocol? +} diff --git a/Stripe/StripeiOS/Source/STPPushProvisioningContext.swift b/Stripe/StripeiOS/Source/STPPushProvisioningContext.swift new file mode 100644 index 00000000..62973701 --- /dev/null +++ b/Stripe/StripeiOS/Source/STPPushProvisioningContext.swift @@ -0,0 +1,144 @@ +// +// STPPushProvisioningContext.swift +// StripeiOS +// +// Created by Jack Flintermann on 9/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore +@_spi(STP) import StripePaymentsUI + +/// This class makes it easier to implement "Push Provisioning", the process by which an end-user can add a card to their Apple Pay wallet without having to type their number. This process is mediated by an Apple class called `PKAddPaymentPassViewController`; this class will help you implement that class' delegate methods. Note that this flow requires a special entitlement from Apple; for more information please see https://stripe.com/docs/issuing/cards/digital-wallets . +public class STPPushProvisioningContext: NSObject { + /// The API Client to use to make requests. + /// Defaults to STPAPIClient.shared + public var apiClient: STPAPIClient = .shared + + /// This is a helper method to generate a PKAddPaymentPassRequestConfiguration that will work with + /// Stripe's Issuing APIs. Pass the returned configuration object to `PKAddPaymentPassViewController`'s `initWithRequestConfiguration:delegate:` initializer. + /// @deprecated Use requestConfiguration(withName:description:last4:brand:primaryAccountIdentifier:) instead. + /// - Parameters: + /// - name: Your cardholder's name. Example: John Appleseed + /// - description: A localized description of your card's name. This will appear in Apple's UI as "{description} will be available in Wallet". Example: Platinum Rewards Card + /// - last4: The last 4 of the card to be added to the user's Apple Pay wallet. Example: 4242 + /// - brand: The brand of the card. Example: `STPCardBrandVisa` + @objc + @available( + *, + deprecated, + message: + "Use `requestConfiguration(withName:description:last4:brand:primaryAccountIdentifier:)` instead.", + renamed: "requestConfiguration(withName:description:last4:brand:primaryAccountIdentifier:)" + ) + public class func requestConfiguration( + withName name: String, + description: String?, + last4: String?, + brand: STPCardBrand + ) -> PKAddPaymentPassRequestConfiguration { + return self.requestConfiguration( + withName: name, + description: description, + last4: last4, + brand: brand, + primaryAccountIdentifier: nil + ) + } + + /// This is a helper method to generate a PKAddPaymentPassRequestConfiguration that will work with + /// Stripe's Issuing APIs. Pass the returned configuration object to `PKAddPaymentPassViewController`'s `initWithRequestConfiguration:delegate:` initializer. + /// - Parameters: + /// - name: Your cardholder's name. Example: John Appleseed + /// - description: A localized description of your card's name. This will appear in Apple's UI as "{description} will be available in Wallet". Example: Platinum Rewards Card + /// - last4: The last 4 of the card to be added to the user's Apple Pay wallet. Example: 4242 + /// - brand: The brand of the card. Example: `STPCardBrandVisa` + /// - primaryAccountIdentifier: The `primary_account_identifier` value from the issued card. + @objc + public class func requestConfiguration( + withName name: String, + description: String?, + last4: String?, + brand: STPCardBrand, + primaryAccountIdentifier: String? + ) -> PKAddPaymentPassRequestConfiguration { + let config = PKAddPaymentPassRequestConfiguration(encryptionScheme: .ECC_V2) + config?.cardholderName = name + config?.primaryAccountSuffix = last4 + config?.localizedDescription = description + config?.style = .payment + if brand == .visa { + config?.paymentNetwork = .visa + } + if brand == .mastercard { + config?.paymentNetwork = .masterCard + } + config?.primaryAccountIdentifier = primaryAccountIdentifier + return config! + } + + /// In order to retreive the encrypted payload that PKAddPaymentPassViewController expects, the Stripe SDK must talk to the Stripe API. As this requires privileged access, you must write a "key provider" that generates an Ephemeral Key on your backend and provides it to the SDK when requested. For more information, see https://stripe.com/docs/mobile/ios/basic#ephemeral-key + @objc + public init( + keyProvider: STPIssuingCardEphemeralKeyProvider + ) { + apiClient = STPAPIClient.shared + keyManager = STPEphemeralKeyManager( + keyProvider: keyProvider, + apiVersion: STPAPIClient.apiVersion, + performsEagerFetching: false + ) + super.init() + } + + /// This method lines up with the method of the same name on `PKAddPaymentPassViewControllerDelegate`. You should implement that protocol in your own app, and when that method is called, call this method on your `STPPushProvisioningContext`. This in turn will first initiate a call to your `keyProvider` (see above) to obtain an Ephemeral Key, then make a call to the Stripe Issuing API to fetch an encrypted payload for the card in question, then return that payload to iOS. + @objc + public func addPaymentPassViewController( + _ controller: PKAddPaymentPassViewController, + generateRequestWithCertificateChain certificates: [Data], + nonce: Data, + nonceSignature: Data, + completionHandler handler: @escaping (PKAddPaymentPassRequest) -> Void + ) { + keyManager.getOrCreateKey({ ephemeralKey, keyError in + if let keyError = keyError { + let request = PKAddPaymentPassRequest() + request.stp_error = keyError as NSError + // handler, bizarrely, cannot take an NSError, but passing an empty PKAddPaymentPassRequest causes roughly equivalent behavior. + handler(request) + return + } + let params = STPPushProvisioningDetailsParams( + cardId: ephemeralKey?.issuingCardID ?? "", + certificates: certificates, + nonce: nonce, + nonceSignature: nonceSignature + ) + if let ephemeralKey = ephemeralKey { + self.apiClient.retrievePushProvisioningDetails( + with: params, + ephemeralKey: ephemeralKey + ) { + details, + error in + if let error = error { + let request = PKAddPaymentPassRequest() + request.stp_error = error as NSError + handler(request) + return + } + let request = PKAddPaymentPassRequest() + request.activationData = details?.activationData + request.encryptedPassData = details?.encryptedPassData + request.ephemeralPublicKey = details?.ephemeralPublicKey + handler(request) + } + } + }) + } + + private var keyManager: STPEphemeralKeyManagerProtocol + private var ephemeralKey: STPEphemeralKey? +} diff --git a/Stripe/StripeiOS/Source/STPPushProvisioningDetails.swift b/Stripe/StripeiOS/Source/STPPushProvisioningDetails.swift new file mode 100644 index 00000000..ae260456 --- /dev/null +++ b/Stripe/StripeiOS/Source/STPPushProvisioningDetails.swift @@ -0,0 +1,97 @@ +// +// STPPushProvisioningDetails.swift +// StripeiOS +// +// Created by Jack Flintermann on 9/26/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripePayments + +class STPPushProvisioningDetails: NSObject, STPAPIResponseDecodable { + let cardId: String + let livemode: Bool + let encryptedPassData: Data + let activationData: Data + let ephemeralPublicKey: Data + + required init( + cardId: String, + livemode: Bool, + encryptedPass encryptedPassData: Data, + activationData: Data, + ephemeralPublicKey: Data + ) { + self.cardId = cardId + self.livemode = livemode + self.encryptedPassData = encryptedPassData + self.activationData = activationData + self.ephemeralPublicKey = ephemeralPublicKey + super.init() + } + private(set) var allResponseFields: [AnyHashable: Any] = [:] + + // MARK: - STPAPIResponseDecodable + class func decodedObject(fromAPIResponse response: [AnyHashable: Any]?) -> Self? { + guard + let dict = response?.stp_dictionaryByRemovingNulls() + else { + return nil + } + + // required fields + let cardId = dict.stp_string(forKey: "card") + let livemode = dict.stp_bool(forKey: "livemode", or: false) + let encryptedPassString = dict.stp_string(forKey: "contents") + let encryptedPassData = + encryptedPassString != nil + ? Data(base64Encoded: encryptedPassString ?? "", options: []) : nil + + let activationString = dict.stp_string(forKey: "activation_data") + let activationData = + activationString != nil ? Data(base64Encoded: activationString ?? "", options: []) : nil + + let ephemeralPublicKeyString = dict.stp_string(forKey: "ephemeral_public_key") + let ephemeralPublicKeyData = + ephemeralPublicKeyString != nil + ? Data(base64Encoded: ephemeralPublicKeyString ?? "", options: []) : nil + + if cardId == nil || encryptedPassData == nil || activationData == nil + || ephemeralPublicKeyData == nil + { + return nil + } + + if let encryptedPassData = encryptedPassData, let activationData = activationData, + let ephemeralPublicKeyData = ephemeralPublicKeyData + { + let details = self.init( + cardId: cardId ?? "", + livemode: livemode, + encryptedPass: encryptedPassData, + activationData: activationData, + ephemeralPublicKey: ephemeralPublicKeyData + ) + details.allResponseFields = dict + return details + } + return nil + } + + // MARK: - Equality + override func isEqual(_ object: Any?) -> Bool { + if let details = object as? STPPushProvisioningDetails { + return isEqual(to: details) + } + return false + } + + override var hash: Int { + return activationData.hashValue + } + + func isEqual(to details: STPPushProvisioningDetails) -> Bool { + return details.activationData == self.activationData + } +} diff --git a/Stripe/StripeiOS/Source/STPPushProvisioningDetailsParams.swift b/Stripe/StripeiOS/Source/STPPushProvisioningDetailsParams.swift new file mode 100644 index 00000000..c7fbcccb --- /dev/null +++ b/Stripe/StripeiOS/Source/STPPushProvisioningDetailsParams.swift @@ -0,0 +1,73 @@ +// +// STPPushProvisioningDetailsParams.swift +// StripeiOS +// +// Created by Jack Flintermann on 9/26/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// A helper class for turning the raw certificate array, nonce, and nonce signature emitted by PKAddPaymentPassViewController into a format that is understandable by the Stripe API. +/// If you are using STPPushProvisioningContext to implement your integration, you do not need to use this class. +public class STPPushProvisioningDetailsParams: NSObject { + /// The Stripe ID of the Issuing card object to retrieve details for. + @objc public private(set) var cardId: String + /// An array of certificates that should be used to encrypt the card details. + @objc public private(set) var certificates: [Data] + /// A nonce that should be used during the encryption of the card details. + @objc public private(set) var nonce: Data + /// A nonce signature that should be used during the encryption of the card details. + @objc public private(set) var nonceSignature: Data + /// Implemented for convenience - the Stripe API expects the certificate chain as an array of base64-encoded strings. + + @objc public var certificatesBase64: [String] { + var base64Certificates: [AnyHashable] = [] + for certificate in certificates { + base64Certificates.append(certificate.base64EncodedString(options: [])) + } + return base64Certificates as? [String] ?? [] + } + /// Implemented for convenience - the Stripe API expects the nonce as a hex-encoded string. + + @objc public var nonceHex: String { + STPPushProvisioningDetailsParams.hexadecimalString(for: nonce) + } + /// Implemented for convenience - the Stripe API expects the nonce signature as a hex-encoded string. + + @objc public var nonceSignatureHex: String { + STPPushProvisioningDetailsParams.hexadecimalString(for: nonceSignature) + } + + /// Instantiates a new params object with the provided attributes. + @objc public required init( + cardId: String, + certificates: [Data], + nonce: Data, + nonceSignature: Data + ) { + self.cardId = cardId + self.certificates = certificates + self.nonce = nonce + self.nonceSignature = nonceSignature + } + + @objc(paramsWithCardId:certificates:nonce:nonceSignature:) class func paramsWithCardId( + cardId: String, + certificates: [Data], + nonce: Data, + nonceSignature: Data + ) -> Self { + return self.init( + cardId: cardId, + certificates: certificates, + nonce: nonce, + nonceSignature: nonceSignature + ) + } + + // Adapted from https://stackoverflow.com/questions/39075043/how-to-convert-data-to-hex-string-in-swift + class func hexadecimalString(for data: Data) -> String { + return data.map { String(format: "%02hhx", $0) }.joined() + } +} diff --git a/Stripe/StripeiOS/Source/Stripe+Exports.swift b/Stripe/StripeiOS/Source/Stripe+Exports.swift new file mode 100644 index 00000000..16160f1e --- /dev/null +++ b/Stripe/StripeiOS/Source/Stripe+Exports.swift @@ -0,0 +1,13 @@ +// +// Stripe+Exports.swift +// StripeiOS +// +// This file re-exports classes from Stripe's dependent SDKs, enabling users to use classes from all +// Stripe Payments SDKs with one `import Stripe` declaration. +// + +import Foundation +@_exported import StripeApplePay +@_exported import StripeCore +@_exported import StripePayments +@_exported import StripePaymentsUI diff --git a/Stripe/StripeiOS/Stripe-umbrella.h b/Stripe/StripeiOS/Stripe-umbrella.h new file mode 100644 index 00000000..b062ed0c --- /dev/null +++ b/Stripe/StripeiOS/Stripe-umbrella.h @@ -0,0 +1,11 @@ +// +// Stripe-umbrella.h +// StripeiOS +// +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#ifndef Stripe_umbrella_h +#define Stripe_umbrella_h + +#endif /* Stripe_umbrella_h */ diff --git a/Stripe/StripeiOS/Stripe.modulemap b/Stripe/StripeiOS/Stripe.modulemap new file mode 100644 index 00000000..35dad19e --- /dev/null +++ b/Stripe/StripeiOS/Stripe.modulemap @@ -0,0 +1,6 @@ +framework module Stripe { + umbrella header "Stripe-umbrella.h" + + export * + module * { export * } +} diff --git a/Stripe/StripeiOSAppHostedTests/Info.plist b/Stripe/StripeiOSAppHostedTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/Stripe/StripeiOSAppHostedTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Stripe/StripeiOSTestHostApp/AppDelegate.swift b/Stripe/StripeiOSTestHostApp/AppDelegate.swift new file mode 100644 index 00000000..729e9852 --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/AppDelegate.swift @@ -0,0 +1,18 @@ +// +// AppDelegate.swift +// StripeiOSTestHostApp +// +// Created by Cameron Sabol on 11/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } +} diff --git a/Stripe/StripeiOSTestHostApp/Info.plist b/Stripe/StripeiOSTestHostApp/Info.plist new file mode 100644 index 00000000..5b531f7b --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/Contents.json b/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe/StripeiOSTestHostApp/Resources/Base.lproj/LaunchScreen.storyboard b/Stripe/StripeiOSTestHostApp/Resources/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/Resources/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stripe/StripeiOSTestHostApp/Resources/Base.lproj/Main.storyboard b/Stripe/StripeiOSTestHostApp/Resources/Base.lproj/Main.storyboard new file mode 100644 index 00000000..25a76385 --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/Resources/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stripe/StripeiOSTestHostApp/ViewController.swift b/Stripe/StripeiOSTestHostApp/ViewController.swift new file mode 100644 index 00000000..a8daad08 --- /dev/null +++ b/Stripe/StripeiOSTestHostApp/ViewController.swift @@ -0,0 +1,18 @@ +// +// ViewController.swift +// StripeiOSTestHostApp +// +// Created by Cameron Sabol on 11/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + +} diff --git a/Stripe/StripeiOSTests.xctestplan b/Stripe/StripeiOSTests.xctestplan new file mode 100644 index 00000000..13c50bee --- /dev/null +++ b/Stripe/StripeiOSTests.xctestplan @@ -0,0 +1,45 @@ +{ + "configurations" : [ + { + "id" : "E4E61B3B-0FEC-4C2A-BE82-E8558946FFBC", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "environmentVariableEntries" : [ + { + "key" : "FB_REFERENCE_IMAGE_DIR", + "value" : "$(SOURCE_ROOT)\/..\/Tests\/ReferenceImages" + } + ], + "locationScenario" : { + "identifier" : "San Francisco, CA, USA", + "referenceType" : "built-in" + }, + "region" : "US", + "targetForVariableExpansion" : { + "containerPath" : "container:Stripe.xcodeproj", + "identifier" : "ADF894AA8F6022D9BED17346", + "name" : "StripeiOS" + } + }, + "testTargets" : [ + { + "skippedTests" : [ + "PaymentMethodMessagingViewSnapshotTests", + "STPCardBINMetadataTests", + "TextFieldElementCardTest\/testBINRangeThatRequiresNetworkCallToValidate()" + ], + "target" : { + "containerPath" : "container:Stripe.xcodeproj", + "identifier" : "8BE23AD5D9A3D939AF46F31E", + "name" : "StripeiOSTests" + } + } + ], + "version" : 1 +} diff --git a/Stripe/StripeiOSTests/APIRequestTest.swift b/Stripe/StripeiOSTests/APIRequestTest.swift new file mode 100644 index 00000000..2a097d20 --- /dev/null +++ b/Stripe/StripeiOSTests/APIRequestTest.swift @@ -0,0 +1,294 @@ +// +// APIRequestTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 9/23/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class AnyAPIResponse: NSObject, STPAPIResponseDecodable { + override required init() { + super.init() + } + + static func decodedObject(fromAPIResponse response: [AnyHashable: Any]?) -> Self? { + guard let response = response else { + return nil + } + let a = Self() + a.allResponseFields = response + return a + } + + var allResponseFields: [AnyHashable: Any] = [:] + +} + +class APIRequestTest: STPNetworkStubbingTestCase { + var apiClient: STPAPIClient! + override func setUp() { + super.setUp() + apiClient = STPAPIClient() + // HTTPBin clone + apiClient.apiURL = URL(string: "https://luxurious-alpine-devourer.glitch.me") + } + + func testPublishableKeyAuthorization() { + let e = expectation(description: "Request completed") + apiClient.publishableKey = "pk_foo" + APIRequest.getWith( + apiClient, + endpoint: "bearer", + parameters: ["foo": "bar"] + ) { + (obj, _, error) in + guard let obj = obj, + let token = obj.allResponseFields["token"] as? String + else { + XCTFail() + XCTAssertNil(error) + return + } + XCTAssertEqual(token, self.apiClient.publishableKey) + e.fulfill() + } + waitForExpectations(timeout: 2) + } + + func testStripeAccountAuthorization() { + + } + + func testGet() { + let e = expectation(description: "Request completed") + APIRequest.getWith(apiClient, endpoint: "get", parameters: [:]) { + (obj, response, error) in + XCTAssertNil(error) + XCTAssertEqual(response?.statusCode, 200) + XCTAssertNotNil(obj) + e.fulfill() + } + waitForExpectations(timeout: 2) + } + + func testPost() { + let parameters = ["foo": "bar"] + let e = expectation(description: "Request completed") + APIRequest.post( + with: apiClient, + endpoint: "post", + parameters: ["foo": "bar"] + ) { + (obj, response, error) in + XCTAssertNil(error) + XCTAssertEqual(response?.statusCode, 200) + guard let obj = obj, + let form = obj.allResponseFields["form"] as? [String: String], + let headers = obj.allResponseFields["headers"] as? [String: String] + else { + XCTFail() + return + } + XCTAssertNotNil(headers["X-Stripe-User-Agent"]) + XCTAssertEqual(headers["Stripe-Version"], STPAPIClient.apiVersion) + XCTAssertEqual(form, parameters) + e.fulfill() + } + waitForExpectations(timeout: 2) + } + + func testDelete() { + let e = expectation(description: "Request completed") + APIRequest.delete(with: apiClient, endpoint: "delete", parameters: [:]) { + (obj, response, error) in + XCTAssertNil(error) + XCTAssertEqual(response?.statusCode, 200) + guard let obj = obj, + let headers = obj.allResponseFields["headers"] as? [String: String] + else { + XCTFail() + return + } + XCTAssertNotNil(headers["X-Stripe-User-Agent"]) + XCTAssertEqual(headers["Stripe-Version"], STPAPIClient.apiVersion) + + e.fulfill() + } + waitForExpectations(timeout: 2) + } + + func testParseResponseWithConnectionError() { + let expectation = self.expectation(description: "parseResponse") + + let httpURLResponse = HTTPURLResponse() + let json: [AnyHashable: Any] = [:] + let body = try? JSONSerialization.data(withJSONObject: json, options: []) + let errorParameter = NSError( + domain: NSURLErrorDomain, + code: NSURLErrorNotConnectedToInternet, + userInfo: nil + ) + + APIRequest.parseResponse( + httpURLResponse, + body: body, + error: errorParameter + ) { (object: STPCard?, response, error) in + guard let error = error else { + XCTFail() + return + } + XCTAssertNil(object) + XCTAssertEqual(response, httpURLResponse) + XCTAssertEqual(error as NSError, errorParameter) + expectation.fulfill() + } + + waitForExpectations(timeout: 2.0, handler: nil) + } + + func testParseResponseWithReturnedError() { + let expectation = self.expectation(description: "parseResponse") + + let httpURLResponse = HTTPURLResponse() + let json = [ + "error": [ + "type": "invalid_request_error", + "message": "Your card number is incorrect.", + "code": "incorrect_number", + ], + ] + let body = try? JSONSerialization.data(withJSONObject: json, options: []) + let errorParameter: NSError? = nil + let expectedError = NSError.stp_error(fromStripeResponse: json) + + APIRequest.parseResponse( + httpURLResponse, + body: body, + error: errorParameter + ) { (object: STPCard?, response, error) in + guard let error = error, let expectedError = expectedError else { + XCTFail() + return + } + XCTAssertNil(object) + XCTAssertEqual(response, httpURLResponse) + XCTAssertEqual(error as NSError, expectedError as NSError) + expectation.fulfill() + } + + waitForExpectations(timeout: 2.0, handler: nil) + } + + func testParseResponseWithMissingError() { + let expectation = self.expectation(description: "parseResponse") + + let httpURLResponse = HTTPURLResponse() + let json: [AnyHashable: Any] = [:] + let body = try? JSONSerialization.data(withJSONObject: json, options: []) + let errorParameter: NSError? = nil + let expectedError = NSError.stp_genericFailedToParseResponseError() + + APIRequest.parseResponse( + httpURLResponse, + body: body, + error: errorParameter + ) { (object: STPCard?, response, error) in + guard let error = error else { + XCTFail() + return + } + XCTAssertNil(object) + XCTAssertEqual(response, httpURLResponse) + XCTAssertEqual(error as NSError, expectedError as NSError) + expectation.fulfill() + } + + waitForExpectations(timeout: 2.0, handler: nil) + } + + func testParseResponseWithResponseObjectAndReturnedError() { + let expectation = self.expectation(description: "parseResponse") + + let httpURLResponse = HTTPURLResponse() + let json: [AnyHashable: Any] = STPTestUtils.jsonNamed("CardSource")! + let body = try? JSONSerialization.data(withJSONObject: json, options: []) + let errorParameter: NSError? = NSError( + domain: NSURLErrorDomain, + code: NSURLErrorUnknown, + userInfo: nil + ) + + APIRequest.parseResponse( + httpURLResponse, + body: body, + error: errorParameter + ) { (object: STPCard?, response, error) in + guard let error = error else { + XCTFail() + return + } + XCTAssertNil(object) + XCTAssertEqual(response, httpURLResponse) + XCTAssertEqual(error as NSError, errorParameter) + expectation.fulfill() + } + + waitForExpectations(timeout: 2.0, handler: nil) + } + + func test429Backoff() { + let oldMaxRetries = StripeAPI.maxRetries + StripeAPI.maxRetries = 2 + var inProgress = true + + let e = expectation(description: "Request completed") + // We expect this request to retry a few times with exponential backoff before calling the completion handler. + APIRequest.getWith(apiClient, endpoint: "status/429", parameters: [:]) { + (_, response, _) in + XCTAssertEqual(response?.statusCode, 429) + inProgress = false + e.fulfill() + } + + let checkedStillInProgress = expectation( + description: "Checked that we're still in progress after 2s" + ) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { + // Make sure we're still in progress after 2 seconds + // This shows that we're retrying the request a few times + // while applying an appropriate amount of backoff. + XCTAssertEqual(inProgress, true) + checkedStillInProgress.fulfill() + } + + wait(for: [e, checkedStillInProgress], timeout: 30) + StripeAPI.maxRetries = oldMaxRetries + } + + func test429NoBackoff() { + let oldMaxRetries = StripeAPI.maxRetries + StripeAPI.maxRetries = 0 + + let e = expectation(description: "Request completed") + APIRequest.getWith(apiClient, endpoint: "status/429", parameters: [:]) { + (_, response, _) in + XCTAssertEqual(response?.statusCode, 429) + e.fulfill() + } + + // We expect this request to return ~immediately, so we set a timeout lower than the highest + // amount of backoff. + wait(for: [e], timeout: 5.0) + StripeAPI.maxRetries = oldMaxRetries + } +} diff --git a/Stripe/StripeiOSTests/AfterpayPriceBreakdownViewSnapshotTests.swift b/Stripe/StripeiOSTests/AfterpayPriceBreakdownViewSnapshotTests.swift new file mode 100644 index 00000000..13f80611 --- /dev/null +++ b/Stripe/StripeiOSTests/AfterpayPriceBreakdownViewSnapshotTests.swift @@ -0,0 +1,52 @@ +// +// AfterpayPriceBreakdownViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Jaime Park on 6/15/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class AfterpayPriceBreakdownViewSnapshotTests: STPSnapshotTestCase { + func embedInRenderableView( + _ priceBreakdownView: AfterpayPriceBreakdownView, + width: Int, + height: Int + ) -> UIView { + let containingView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: height)) + containingView.addSubview(priceBreakdownView) + priceBreakdownView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + priceBreakdownView.leadingAnchor.constraint(equalTo: containingView.leadingAnchor), + containingView.trailingAnchor.constraint(equalTo: priceBreakdownView.trailingAnchor), + priceBreakdownView.topAnchor.constraint(equalTo: containingView.topAnchor), + containingView.bottomAnchor.constraint(equalTo: priceBreakdownView.bottomAnchor), + ]) + + return containingView + } + + func testClearpayInMultiRow() { + NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_GB")) { [self] in + let priceBreakdownView = AfterpayPriceBreakdownView() + let containingView = embedInRenderableView(priceBreakdownView, width: 320, height: 50) + + STPSnapshotVerifyView(containingView) + } + } + + func testAfterpayInSingleRow() { + let priceBreakdownView = AfterpayPriceBreakdownView() + let containingView = embedInRenderableView(priceBreakdownView, width: 500, height: 30) + + STPSnapshotVerifyView(containingView) + } +} diff --git a/Stripe/StripeiOSTests/AnalyticsHelperTests.swift b/Stripe/StripeiOSTests/AnalyticsHelperTests.swift new file mode 100644 index 00000000..e82d89e1 --- /dev/null +++ b/Stripe/StripeiOSTests/AnalyticsHelperTests.swift @@ -0,0 +1,60 @@ +// +// AnalyticsHelperTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 2/22/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class AnalyticsHelperTests: XCTestCase { + + func test_getDuration() { + let (sut, timeReference) = makeSUT() + + sut.startTimeMeasurement(.linkPopup) + + // Advance the clock by 10 seconds. + timeReference.advanceBy(10) + XCTAssertEqual(sut.getDuration(for: .linkPopup), 10) + + // Advance the clock by 5 seconds. + timeReference.advanceBy(5) + XCTAssertEqual(sut.getDuration(for: .linkPopup), 15) + } + + func test_getDuration_returnsNilWhenNotStarted() { + let (sut, _) = makeSUT() + XCTAssertNil(sut.getDuration(for: .linkPopup)) + } + +} + +extension AnalyticsHelperTests { + + class MockTimeReference { + var date = Date() + + func advanceBy(_ timeInterval: TimeInterval) { + date = date.addingTimeInterval(timeInterval) + } + + func now() -> Date { + return date + } + } + + func makeSUT() -> (AnalyticsHelper, MockTimeReference) { + let timeReference = MockTimeReference() + let helper = AnalyticsHelper(timeProvider: timeReference.now) + return (helper, timeReference) + } + +} diff --git a/Stripe/StripeiOSTests/AutoCompleteViewControllerSnapshotTests.swift b/Stripe/StripeiOSTests/AutoCompleteViewControllerSnapshotTests.swift new file mode 100644 index 00000000..d603df4b --- /dev/null +++ b/Stripe/StripeiOSTests/AutoCompleteViewControllerSnapshotTests.swift @@ -0,0 +1,144 @@ +// +// AutoCompleteViewControllerSnapshotTests.swift +// StripeiOS Tests +// +// Created by Nick Porter on 6/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class AutoCompleteViewControllerSnapshotTests: STPSnapshotTestCase { + + private var configuration: AddressViewController.Configuration { + return AddressViewController.Configuration() + } + + private let addressSpecProvider: AddressSpecProvider = { + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec( + format: "ACSZP", + require: "AZ", + cityNameType: .post_town, + stateNameType: .state, + zip: "", + zipNameType: .pin + ), + ] + return specProvider + }() + + private let mockSearchResults: [AddressSearchResult] = [ + MockAddressSearchResult( + title: "199 Water Street", + subtitle: "New York, NY 10038 United States", + titleHighlightRanges: [NSValue(range: NSRange(location: 0, length: 6))], + subtitleHighlightRanges: [NSValue(range: NSRange(location: 2, length: 4))] + ), + MockAddressSearchResult( + title: "354 Oyster Point Blvd", + subtitle: "San Francisco, CA 94080 United States", + titleHighlightRanges: [NSValue(range: NSRange(location: 2, length: 4))], + subtitleHighlightRanges: [NSValue(range: NSRange(location: 4, length: 2))] + ), + MockAddressSearchResult( + title: "10 Boulevard", + subtitle: "Haussmann Paris 75009 France", + titleHighlightRanges: [NSValue(range: NSRange(location: 4, length: 2))], + subtitleHighlightRanges: [NSValue(range: NSRange(location: 0, length: 4))] + ), + ] + + func testAutoCompleteViewController() { + let testWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 428, height: 500)) + testWindow.isHidden = false + let vc = AutoCompleteViewController( + configuration: configuration, + initialLine1Text: nil, + addressSpecProvider: addressSpecProvider + ) + vc.results = mockSearchResults + testWindow.rootViewController = vc + + verify(vc.view) + } + + func testAutoCompleteViewController_darkMode() { + let testWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 428, height: 500)) + testWindow.isHidden = false + testWindow.overrideUserInterfaceStyle = .dark + let vc = AutoCompleteViewController( + configuration: configuration, + initialLine1Text: nil, + addressSpecProvider: addressSpecProvider + ) + + vc.results = mockSearchResults + testWindow.rootViewController = vc + + verify(vc.view) + } + + func testAutoCompleteViewController_appearance() { + let testWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 428, height: 500)) + testWindow.isHidden = false + + var config = configuration + config.appearance.colors.background = .blue + config.appearance.colors.text = .yellow + config.appearance.colors.textSecondary = .red + config.appearance.colors.componentPlaceholderText = .cyan + config.appearance.colors.componentBackground = .red + config.appearance.colors.componentDivider = .green + config.appearance.cornerRadius = 0.0 + config.appearance.borderWidth = 2.0 + config.appearance.font.base = UIFont(name: "AmericanTypeWriter", size: 12)! + config.appearance.colors.primary = .red + + let vc = AutoCompleteViewController( + configuration: config, + initialLine1Text: nil, + addressSpecProvider: addressSpecProvider + ) + vc.results = mockSearchResults + testWindow.rootViewController = vc + + verify(vc.view) + } + + func verify( + _ view: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + STPSnapshotVerifyView( + view, + identifier: identifier, + suffixes: FBSnapshotTestCaseDefaultSuffixes(), + file: file, + line: line + ) + } +} + +private struct MockAddressSearchResult: AddressSearchResult { + let title: String + let subtitle: String + let titleHighlightRanges: [NSValue] + let subtitleHighlightRanges: [NSValue] + + func asAddress(completion: @escaping (PaymentSheet.Address?) -> Void) { + completion(nil) + } +} diff --git a/Stripe/StripeiOSTests/CardExpiryDateTests.swift b/Stripe/StripeiOSTests/CardExpiryDateTests.swift new file mode 100644 index 00000000..8f92f3a2 --- /dev/null +++ b/Stripe/StripeiOSTests/CardExpiryDateTests.swift @@ -0,0 +1,81 @@ +// +// CardExpiryDateTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 4/15/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class CardExpiryDateTests: XCTestCase { + + func test_init() { + let sut = CardExpiryDate(month: 2, year: 2026) + XCTAssertEqual(sut.month, 2) + XCTAssertEqual(sut.year, 2026) + } + + func test_init_shouldNormalizeTheYear() { + let sut = CardExpiryDate(month: 2, year: 50) + XCTAssertEqual(sut.year, 2050) + } + + func test_initFromString() { + let sut = CardExpiryDate("0226") + XCTAssertEqual(sut?.month, 2) + XCTAssertEqual(sut?.year, 2026) + } + + func test_initFromString_withInvalidString() { + XCTAssertNil(CardExpiryDate("")) // empty + XCTAssertNil(CardExpiryDate("0")) // missing 3 digits + XCTAssertNil(CardExpiryDate("023")) // missing a digit + XCTAssertNil(CardExpiryDate("1234567890")) // too many digits + XCTAssertNil(CardExpiryDate("abcd")) // alpha + + // month out of range + XCTAssertNil(CardExpiryDate("1326")) + XCTAssertNil(CardExpiryDate("0026")) + XCTAssertNil(CardExpiryDate("-126")) + + // year out of range + XCTAssertNil(CardExpiryDate("02-1")) + } + + func test_displayString() { + let sut = CardExpiryDate(month: 2, year: 2026) + XCTAssertEqual(sut.displayString, "0226") + } + + func test_expired() throws { + let calendar = Calendar(identifier: .gregorian) + + let sut = CardExpiryDate(month: 2, year: 2026) + + let aDayBefore = try XCTUnwrap(calendar.date(from: .init(year: 2026, month: 2, day: 28))) + let aMonthBefore = try XCTUnwrap(calendar.date(from: .init(year: 2026, month: 1, day: 31))) + let aDayAfter = try XCTUnwrap(calendar.date(from: .init(year: 2026, month: 3, day: 1))) + let aMonthAfter = try XCTUnwrap(calendar.date(from: .init(year: 2026, month: 3, day: 30))) + + XCTAssertFalse(sut.expired(now: aDayBefore)) + XCTAssertFalse(sut.expired(now: aMonthBefore)) + XCTAssertTrue(sut.expired(now: aDayAfter)) + XCTAssertTrue(sut.expired(now: aMonthAfter)) + } + + func test_90s_not_allowed() throws { + let sutPast = CardExpiryDate(month: 2, year: 1995) + let sutFuture = CardExpiryDate(month: 2, year: 2095) + + XCTAssertTrue(sutPast.expired()) + XCTAssertTrue(sutFuture.expired()) + } + +} diff --git a/Stripe/StripeiOSTests/CircularButtonSnapshotTests.swift b/Stripe/StripeiOSTests/CircularButtonSnapshotTests.swift new file mode 100644 index 00000000..95a9e6b0 --- /dev/null +++ b/Stripe/StripeiOSTests/CircularButtonSnapshotTests.swift @@ -0,0 +1,68 @@ +// +// CircularButtonSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 1/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) import StripeUICore + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class CircularButtonSnapshotTests: STPSnapshotTestCase { + + func testNormal() { + let button = CircularButton(style: .close) + verify(button) + } + + func testDisabled() { + let button = CircularButton(style: .close) + button.isEnabled = false + verify(button) + } + + func verify( + _ button: CircularButton, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + // Ensures that the button shadow gets captured + let wrapper = UIView() + wrapper.translatesAutoresizingMaskIntoConstraints = false + wrapper.addAndPinSubview( + button, + insets: .insets(top: 10, leading: 10, bottom: 10, trailing: 10) + ) + + // Adding the view to a window updates the traits + let window = UIWindow() + window.addSubview(wrapper) + + // Give the wrapper some constraints so that it has something to anchor to + NSLayoutConstraint.activate([ + wrapper.heightAnchor.constraint(equalToConstant: 40), + wrapper.widthAnchor.constraint(equalToConstant: 40), + wrapper.topAnchor.constraint(equalTo: window.topAnchor), + wrapper.leftAnchor.constraint(equalTo: window.leftAnchor), + ]) + + // Test light mode + wrapper.overrideUserInterfaceStyle = .light + STPSnapshotVerifyView(wrapper, identifier: identifier, file: file, line: line) + + // Test dark mode + wrapper.overrideUserInterfaceStyle = .dark + let updatedIdentifier = (identifier ?? "").appending("darkMode") + STPSnapshotVerifyView(wrapper, identifier: updatedIdentifier, file: file, line: line) + } + +} diff --git a/Stripe/StripeiOSTests/ConfirmButtonSnapshotTests.swift b/Stripe/StripeiOSTests/ConfirmButtonSnapshotTests.swift new file mode 100644 index 00000000..4840e2d7 --- /dev/null +++ b/Stripe/StripeiOSTests/ConfirmButtonSnapshotTests.swift @@ -0,0 +1,207 @@ +// +// ConfirmButtonSnapshotTests.swift +// StripeiOS Tests +// +// Created by Nick Porter on 3/11/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import iOSSnapshotTestCase +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet + +class ConfirmButtonSnapshotTests: STPSnapshotTestCase { + + func testConfirmButton() { + let confirmButton = ConfirmButton(style: .stripe, callToAction: .setup, didTap: {}) + + verify(confirmButton) + } + + // Tests that `primaryButton` appearance is used over standard variables + func testConfirmButtonBackgroundColor() { + var appearance = PaymentSheet.Appearance.default + var button = PaymentSheet.Appearance.PrimaryButton() + button.backgroundColor = .red + appearance.primaryButton = button + + let confirmButton = ConfirmButton( + style: .stripe, + callToAction: .setup, + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + func testConfirmButtonCustomFont() throws { + var appearance = PaymentSheet.Appearance.default + appearance.font.base = try XCTUnwrap(UIFont(name: "AmericanTypewriter", size: 12.0)) + + let confirmButton = ConfirmButton( + style: .stripe, + callToAction: .custom(title: "Custom Title"), + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + func testConfirmButtonCustomFontScales() throws { + var appearance = PaymentSheet.Appearance.default + appearance.font.base = try XCTUnwrap(UIFont(name: "AmericanTypewriter", size: 12.0)) + appearance.font.sizeScaleFactor = 0.85 + + let confirmButton = ConfirmButton( + style: .stripe, + callToAction: .custom(title: "Custom Title"), + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` disabled color is correct for the default theme + func testConfirmButtonDefaultDisabledColor() { + let confirmButton = ConfirmButton( + state: .disabled, + style: .stripe, + callToAction: .setup, + appearance: .default, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` disabled color matches the primary color when no background color or diabled color set + func testConfirmButtonDisabledColorWhenSetPrimaryColorAndNoSetBackgroundColorOrDisabledColor() { + var appearance = PaymentSheet.Appearance.default + var button = PaymentSheet.Appearance.PrimaryButton() + button.disabledTextColor = .green.withAlphaComponent(0.6) + appearance.primaryButton = button + appearance.colors.primary = .yellow + + + let confirmButton = ConfirmButton( + state: .disabled, + style: .stripe, + callToAction: .setup, + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` disabled color matches the background color when background color is set but disabled color is not + func testConfirmButtonDisabledColorWhenSetBackgroundColorAndNoSetDisabledColor() { + var appearance = PaymentSheet.Appearance.default + var button = PaymentSheet.Appearance.PrimaryButton() + button.backgroundColor = .yellow + button.disabledTextColor = .green.withAlphaComponent(0.6) + appearance.primaryButton = button + + + let confirmButton = ConfirmButton( + state: .disabled, + style: .stripe, + callToAction: .setup, + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` disabled color matches the disabled color when disabled color, background color, and primary color are set + func testConfirmButtonDisabledColorWhenSetDisabledBackgroundAndPrimaryColors() { + var appearance = PaymentSheet.Appearance.default + var button = PaymentSheet.Appearance.PrimaryButton() + button.backgroundColor = .red + button.disabledBackgroundColor = .black + button.disabledTextColor = .green.withAlphaComponent(0.6) + appearance.primaryButton = button + appearance.colors.primary = .yellow + + + let confirmButton = ConfirmButton( + state: .disabled, + style: .stripe, + callToAction: .setup, + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` disabled color is updated properly + func testConfirmButtonDisabledColor() { + var appearance = PaymentSheet.Appearance.default + var button = PaymentSheet.Appearance.PrimaryButton() + button.disabledBackgroundColor = .red + button.disabledTextColor = .green.withAlphaComponent(0.6) + appearance.primaryButton = button + + let confirmButton = ConfirmButton( + state: .disabled, + style: .stripe, + callToAction: .setup, + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` success color is correct for the default theme + func testConfirmButtonDefaultSuccessColor() { + let confirmButton = ConfirmButton( + state: .succeeded, + style: .stripe, + callToAction: .setup, + appearance: .default, + didTap: {} + ) + + verify(confirmButton) + } + + // Tests that `primaryButton` success color is updated properly + func testConfirmButtonSuccessColor() { + var appearance = PaymentSheet.Appearance.default + var button = PaymentSheet.Appearance.PrimaryButton() + button.successBackgroundColor = .red + button.successTextColor = .green + appearance.primaryButton = button + + let confirmButton = ConfirmButton( + state: .succeeded, + style: .stripe, + callToAction: .setup, + appearance: appearance, + didTap: {} + ) + + verify(confirmButton) + } + + func verify( + _ view: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + view.autosizeHeight(width: 300) + STPSnapshotVerifyView(view, identifier: identifier, file: file, line: line) + } +} diff --git a/Stripe/StripeiOSTests/ConfirmButtonTests.swift b/Stripe/StripeiOSTests/ConfirmButtonTests.swift new file mode 100644 index 00000000..57b559a9 --- /dev/null +++ b/Stripe/StripeiOSTests/ConfirmButtonTests.swift @@ -0,0 +1,91 @@ +// +// ConfirmButtonTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 10/6/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class ConfirmButtonTests: XCTestCase { + + func testBuyButtonShouldAutomaticallyAdjustItsForegroundColor() { + let testCases: [(background: UIColor, foreground: UIColor)] = [ + // Dark backgrounds + (background: .systemBlue, foreground: .white), + (background: .black, foreground: .white), + // Light backgrounds + ( + background: UIColor(red: 1.0, green: 0.87, blue: 0.98, alpha: 1.0), + foreground: .black + ), + ( + background: UIColor(red: 1.0, green: 0.89, blue: 0.35, alpha: 1.0), + foreground: .black + ), + ] + + for (backgroundColor, expectedForeground) in testCases { + let button = ConfirmButton.BuyButton() + button.tintColor = backgroundColor + button.update( + status: .enabled, + callToAction: .pay(amount: 900, currency: "usd"), + animated: false + ) + + XCTAssertEqual( + // Test against `.cgColor` because any color set as `.backgroundColor` + // will be automatically wrapped in `UIDynamicModifiedColor` (private subclass) by iOS. + button.backgroundColor?.cgColor, + backgroundColor.cgColor + ) + + XCTAssertEqual( + button.foregroundColor, + expectedForeground, + "The foreground color should contrast with the background color" + ) + } + } + + func testUpdateShouldCallTheCompletionBlock() { + let sut = ConfirmButton( + style: .stripe, + callToAction: .pay(amount: 1000, currency: "usd"), + didTap: {} + ) + + let expectation = XCTestExpectation(description: "Should call the completion block") + + sut.update(state: .disabled, animated: false) { + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1) + } + + func testUpdateShouldCallTheCompletionBlockWhenAnimated() { + let sut = ConfirmButton( + style: .stripe, + callToAction: .pay(amount: 1000, currency: "usd"), + didTap: {} + ) + + let expectation = XCTestExpectation(description: "Should call the completion block") + + sut.update(state: .disabled, animated: true) { + expectation.fulfill() + } + + wait(for: [expectation], timeout: 3) + } + +} diff --git a/Stripe/StripeiOSTests/ConsumerSessionTests.swift b/Stripe/StripeiOSTests/ConsumerSessionTests.swift new file mode 100644 index 00000000..3dd7681f --- /dev/null +++ b/Stripe/StripeiOSTests/ConsumerSessionTests.swift @@ -0,0 +1,293 @@ +// +// ConsumerSessionTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 4/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class ConsumerSessionTests: STPNetworkStubbingTestCase { + + var apiClient: STPAPIClient! + + override func setUp() { + strictParamsEnforcement = false + super.setUp() + apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + } + + func testLookupSession_noParams() { + let expectation = self.expectation(description: "Lookup ConsumerSession") + + ConsumerSession.lookupSession(for: nil, with: apiClient) { + result in + switch result { + case .success(let lookupResponse): + switch lookupResponse.responseType { + case .found: + XCTFail("Got a response without any params") + + case .notFound(let errorMessage): + XCTFail("Got not found response with \(errorMessage)") + + case .noAvailableLookupParams: + break // Pass + } + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + expectation.fulfill() + } + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + } + + func testLookupSession_existingConsumer() { + let expectation = self.expectation(description: "Lookup ConsumerSession") + + ConsumerSession.lookupSession( + for: "mobile-payments-sdk-ci+a-consumer@stripe.com", + with: apiClient + ) { result in + switch result { + case .success(let lookupResponse): + switch lookupResponse.responseType { + case .found: + break // Pass + + case .notFound(let errorMessage): + XCTFail("Got not found response with \(errorMessage)") + + case .noAvailableLookupParams: + XCTFail("Got no available lookup params") + } + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + expectation.fulfill() + } + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + } + + func testLookupSession_newConsumer() { + let expectation = self.expectation(description: "Lookup ConsumerSession") + + ConsumerSession.lookupSession( + for: "mobile-payments-sdk-ci+not-a-consumer+\(UUID())@stripe.com", + with: apiClient + ) { result in + switch result { + case .success(let lookupResponse): + switch lookupResponse.responseType { + case .found(let consumerSession): + XCTFail("Got unexpected found response with \(consumerSession)") + + case .notFound: + break // Pass + + case .noAvailableLookupParams: + XCTFail("Got no available lookup params") + } + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + expectation.fulfill() + } + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + } + + // tests signup, createPaymentDetails + func testSignUpAndCreateDetailsAndLogout() { + let expectation = self.expectation(description: "consumer sign up") + let newAccountEmail = "mobile-payments-sdk-ci+\(UUID())@stripe.com" + + var sessionWithKey: ConsumerSession.SessionWithPublishableKey? + + ConsumerSession.signUp( + email: newAccountEmail, + phoneNumber: "+13105551234", + legalName: nil, + countryCode: "US", + consentAction: PaymentSheetLinkAccount.ConsentAction.checkbox_v0.rawValue, + with: apiClient + ) { result in + switch result { + case .success(let signupResponse): + XCTAssertTrue(signupResponse.consumerSession.isVerifiedForSignup) + XCTAssertTrue( + signupResponse.consumerSession.verificationSessions.isVerifiedForSignup + ) + XCTAssertTrue( + signupResponse.consumerSession.verificationSessions.contains(where: { + $0.type == .signup + }) + ) + + sessionWithKey = signupResponse + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + expectation.fulfill() + } + + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + + let consumerSession = sessionWithKey!.consumerSession + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4242424242424242" + cardParams.expMonth = 12 + cardParams.expYear = NSNumber( + value: Calendar.autoupdatingCurrent.component(.year, from: Date()) + 1 + ) + cardParams.cvc = "123" + + let billingParams = STPPaymentMethodBillingDetails() + billingParams.name = "Payments SDK CI" + let address = STPPaymentMethodAddress() + address.postalCode = "55555" + address.country = "US" + billingParams.address = address + + let paymentMethodParams = STPPaymentMethodParams.paramsWith( + card: cardParams, + billingDetails: billingParams, + metadata: nil + ) + + let createExpectation = self.expectation(description: "create payment details") + let logoutExpectation = self.expectation(description: "logout") + let useDetailsAfterLogoutExpectation = self.expectation(description: "try using payment details after logout") + consumerSession.createPaymentDetails( + paymentMethodParams: paymentMethodParams, + with: apiClient, + consumerAccountPublishableKey: sessionWithKey?.publishableKey + ) { result in + switch result { + case .success: + // If this succeeds, log out... + consumerSession.logout(with: self.apiClient, consumerAccountPublishableKey: sessionWithKey?.publishableKey) { logoutResult in + switch logoutResult { + case .success: + // Try to use the session again, it shouldn't work + consumerSession.createPaymentDetails(paymentMethodParams: paymentMethodParams, with: self.apiClient, consumerAccountPublishableKey: sessionWithKey?.publishableKey) { loggedOutAuthenticatedActionResult in + switch loggedOutAuthenticatedActionResult { + case .success: + XCTFail("Logout failed to invalidate token") + case .failure(let error): + + guard let stripeError = error as? StripeError, + case let .apiError(stripeAPIError) = stripeError else { + XCTFail("Received unexpected error response") + return + } + XCTAssertEqual(stripeAPIError.code, "consumer_session_credentials_invalid") + } + useDetailsAfterLogoutExpectation.fulfill() + } + case .failure(let error): + XCTFail("Logout failed: \(error.nonGenericDescription)") + } + logoutExpectation.fulfill() + } + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + createExpectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + // tests signup, createPaymentDetails, Connect Account + func testSignUpAndCreateDetailsConnectAccount() { + let expectation = self.expectation(description: "consumer sign up") + let newAccountEmail = "mobile-payments-sdk-ci+\(UUID())@stripe.com" + apiClient.stripeAccount = "acct_1QPtbqFZrlYv4BIL" + + var sessionWithKey: ConsumerSession.SessionWithPublishableKey? + + ConsumerSession.signUp( + email: newAccountEmail, + phoneNumber: "+13105551234", + legalName: nil, + countryCode: "US", + consentAction: PaymentSheetLinkAccount.ConsentAction.checkbox_v0.rawValue, + with: apiClient + ) { result in + switch result { + case .success(let signupResponse): + XCTAssertTrue(signupResponse.consumerSession.isVerifiedForSignup) + XCTAssertTrue( + signupResponse.consumerSession.verificationSessions.isVerifiedForSignup + ) + XCTAssertTrue( + signupResponse.consumerSession.verificationSessions.contains(where: { + $0.type == .signup + }) + ) + + sessionWithKey = signupResponse + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + expectation.fulfill() + } + + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + + let consumerSession = sessionWithKey!.consumerSession + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4242424242424242" + cardParams.expMonth = 12 + cardParams.expYear = NSNumber( + value: Calendar.autoupdatingCurrent.component(.year, from: Date()) + 1 + ) + cardParams.cvc = "123" + + let billingParams = STPPaymentMethodBillingDetails() + billingParams.name = "Payments SDK CI" + let address = STPPaymentMethodAddress() + address.postalCode = "55555" + address.country = "US" + billingParams.address = address + + let paymentMethodParams = STPPaymentMethodParams.paramsWith( + card: cardParams, + billingDetails: billingParams, + metadata: nil + ) + + let createExpectation = self.expectation(description: "create payment details") + consumerSession.createPaymentDetails( + paymentMethodParams: paymentMethodParams, + with: apiClient, + consumerAccountPublishableKey: sessionWithKey?.publishableKey + ) { result in + switch result { + case .success: + break + case .failure(let error): + XCTFail("Received error: \(error.nonGenericDescription)") + } + + createExpectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/CustomerAdapterTests.swift b/Stripe/StripeiOSTests/CustomerAdapterTests.swift new file mode 100644 index 00000000..3b30eee4 --- /dev/null +++ b/Stripe/StripeiOSTests/CustomerAdapterTests.swift @@ -0,0 +1,288 @@ +// +// CustomerAdapterTests.swift +// StripePaymentSheetTests +// + +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift +@_spi(STP) @testable import StripeCore +@_spi(STP) @testable import StripeCore +@testable import StripeCoreTestUtils +@_spi(STP) @testable import StripePayments +@_spi(STP) @_spi(CustomerSessionBetaAccess) @testable import StripePaymentSheet +@_spi(STP) @testable import StripePaymentsTestUtils +import XCTest + +enum MockEphemeralKeyEndpoint { + case customerEphemeralKey(CustomerEphemeralKey) + case error(Error) + + init(_ error: Error) { + self = .error(error) + } + + init(_ customerEphemeralKey: CustomerEphemeralKey) { + self = .customerEphemeralKey(customerEphemeralKey) + } + + func getEphemeralKey() async throws -> CustomerEphemeralKey { + switch self { + case .customerEphemeralKey(let key): + return key + case .error(let error): + throw error + } + } +} + +class CustomerAdapterTests: APIStubbedTestCase { + + func stubListPaymentMethods( + key: CustomerEphemeralKey, + paymentMethodType: String, + paymentMethodJSONs: [[AnyHashable: Any]], + apiClient: STPAPIClient + ) { + stub { urlRequest in + if urlRequest.url?.absoluteString.contains("/payment_methods") ?? false + && urlRequest.url?.absoluteString.contains("type=\(paymentMethodType)") ?? false + && urlRequest.httpMethod == "GET" + { + // Check to make sure we pass the ephemeral key correctly + let keyFromHeader = urlRequest.allHTTPHeaderFields!["Authorization"]? + .replacingOccurrences(of: "Bearer ", with: "") + XCTAssertEqual(keyFromHeader, key.ephemeralKeySecret) + return true + } + return false + } response: { urlRequest in + let paymentMethodsJSON = """ + { + "object": "list", + "url": "/v1/payment_methods", + "has_more": false, + "data": [ + ] + } + """ + var pmList = + try! JSONSerialization.jsonObject( + with: paymentMethodsJSON.data(using: .utf8)!, + options: [] + ) as! [AnyHashable: Any] + // Only send the example cards for a card request + if urlRequest.url?.absoluteString.contains("card") ?? false { + pmList["data"] = paymentMethodJSONs + } else if urlRequest.url?.absoluteString.contains("us_bank_account") ?? false { + pmList["data"] = paymentMethodJSONs + } + return HTTPStubsResponse(jsonObject: pmList, statusCode: 200, headers: nil) + } + } + func stubElementsSession( + paymentMethodJSONs: [[AnyHashable: Any]]? + ) { + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/v1/elements/sessions") ?? false + } response: { _ in + let elementsSessionJSON = """ + { + "payment_method_preference": {"ordered_payment_method_types": ["card"], + "country_code": "US" + }, + "ordered_payment_method_types" : ["card"], + "session_id": "123", + "apple_pay_preference": "enabled", + "customer": {"payment_methods": [ + ], + "customer_session": { + "id": "cuss_654321", + "livemode": false, + "api_key": "ek_12345", + "api_key_expiry": 1899787184, + "customer": "cus_12345" + } + } + } + """ + var elementSession = try! JSONSerialization.jsonObject( + with: elementsSessionJSON.data(using: .utf8)!, + options: [] + ) as! [AnyHashable: Any] + if var customer = elementSession["customer"] as? [AnyHashable: Any], + paymentMethodJSONs != nil { + customer["payment_methods"] = paymentMethodJSONs + elementSession["customer"] = customer + } + return HTTPStubsResponse(jsonObject: elementSession, statusCode: 200, headers: nil) + } + } + + func testGetOrCreateKeyErrorForwardedToFetchPMs() async throws { + let exp = expectation(description: "fetchPMs") + let expectedError = NSError(domain: "test", code: 123, userInfo: nil) + let apiClient = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/payment_methods") ?? false + } response: { _ in + XCTFail("Retrieve PMs should not be called") + return HTTPStubsResponse(error: NSError(domain: "test", code: 100, userInfo: nil)) + } + let ekm = MockEphemeralKeyEndpoint(expectedError) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, apiClient: apiClient) + do { + _ = try await sut.fetchPaymentMethods() + } catch { + XCTAssertEqual((error as NSError?)?.domain, expectedError.domain) + exp.fulfill() + } + await fulfillment(of: [exp]) + } + + let exampleKey = CustomerEphemeralKey(customerId: "abc123", ephemeralKeySecret: "ek_123") + + func testFetchPMs() async throws { + let expectedPaymentMethods = [STPFixtures.paymentMethod()] + let expectedPaymentMethodsJSON = [STPFixtures.paymentMethodJSON()] + let apiClient = stubbedAPIClient() + // Expect 1 call per PM: cards + stubListPaymentMethods(key: exampleKey, paymentMethodType: "card", paymentMethodJSONs: expectedPaymentMethodsJSON, apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "us_bank_account", paymentMethodJSONs: [], apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "sepa_debit", paymentMethodJSONs: [], apiClient: apiClient) + + let ekm = MockEphemeralKeyEndpoint(exampleKey) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, apiClient: apiClient) + let pms = try await sut.fetchPaymentMethods() + + XCTAssertEqual(pms.count, 1) + XCTAssertEqual(pms[0].stripeId, expectedPaymentMethods[0].stripeId) + } + + func testFetchPM_CardAndUSBankAccount() async throws { + let expectedPaymentMethods_card = [STPFixtures.paymentMethod()] + let expectedPaymentMethods_cardJSON = [STPFixtures.paymentMethodJSON()] + + let expectedPaymentMethods_usbank = [STPFixtures.bankAccountPaymentMethod()] + let expectedPaymentMethods_usbankJSON = [STPFixtures.bankAccountPaymentMethodJSON()] + + let apiClient = stubbedAPIClient() + stubListPaymentMethods(key: exampleKey, paymentMethodType: "card", paymentMethodJSONs: expectedPaymentMethods_cardJSON, apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "us_bank_account", paymentMethodJSONs: expectedPaymentMethods_usbankJSON, apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "sepa_debit", paymentMethodJSONs: [], apiClient: apiClient) + + let ekm = MockEphemeralKeyEndpoint(exampleKey) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, + setupIntentClientSecretProvider: { return "si_" }, + apiClient: apiClient) + let pms = try await sut.fetchPaymentMethods() + XCTAssertEqual(pms.count, 2) + XCTAssertEqual(pms[0].stripeId, expectedPaymentMethods_card[0].stripeId) + XCTAssertEqual(pms[1].stripeId, expectedPaymentMethods_usbank[0].stripeId) + } + + func testAttachPM() async throws { + let expectedPaymentMethods = [STPFixtures.paymentMethod()] + let expectedPaymentMethodsJSON = [STPFixtures.paymentMethodJSON()] + let apiClient = stubbedAPIClient() + // Expect 1 call per PM: cards + stubListPaymentMethods(key: exampleKey, paymentMethodType: "card", paymentMethodJSONs: expectedPaymentMethodsJSON, apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "sepa_debit", paymentMethodJSONs: [], apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "us_bank_account", paymentMethodJSONs: [], apiClient: apiClient) + + let ekm = MockEphemeralKeyEndpoint(exampleKey) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, apiClient: apiClient) + let pms = try await sut.fetchPaymentMethods() + + XCTAssertEqual(pms.count, 1) + XCTAssertEqual(pms[0].stripeId, expectedPaymentMethods[0].stripeId) + } + + func testAttachPaymentMethodCallsAPIClientCorrectly() async { + let apiClient = stubbedAPIClient() + let expectedPaymentMethodJSON = STPFixtures.paymentMethodJSON() + let expectedPaymentMethods = [STPFixtures.paymentMethod()] + + let exp = expectation(description: "payment method attach") + // We're attaching 1 payment method: + exp.expectedFulfillmentCount = 1 + stub { urlRequest in + if urlRequest.url?.absoluteString.contains("/payment_method") ?? false + && urlRequest.httpMethod == "POST" + { + return true + } + return false + } response: { _ in + exp.fulfill() + return HTTPStubsResponse( + jsonObject: expectedPaymentMethodJSON, + statusCode: 200, + headers: nil + ) + } + + let ekm = MockEphemeralKeyEndpoint(exampleKey) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, apiClient: apiClient) + try! await sut.attachPaymentMethod(expectedPaymentMethods.first!.stripeId) + await fulfillment(of: [exp]) + } + + func testDetachPaymentMethodCallsAPIClientCorrectly() async { + let apiClient = stubbedAPIClient() + let expectedPaymentMethodJSON = STPFixtures.paymentMethodJSON() + let expectedPaymentMethods = [STPFixtures.paymentMethod()] + + let exp = expectation(description: "payment method detach") + // We're detaching 1 payment method: + exp.expectedFulfillmentCount = 1 + stub { urlRequest in + if urlRequest.url?.absoluteString.contains("/payment_method") ?? false + && urlRequest.httpMethod == "POST" + { + return true + } + return false + } response: { _ in + exp.fulfill() + return HTTPStubsResponse( + jsonObject: expectedPaymentMethodJSON, + statusCode: 200, + headers: nil + ) + } + + let ekm = MockEphemeralKeyEndpoint(exampleKey) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, apiClient: apiClient) + try! await sut.detachPaymentMethod(paymentMethodId: expectedPaymentMethods.first!.stripeId) + await fulfillment(of: [exp]) + } + + func testCustomerSheetLoadFiltersSavedApplePayCards() async throws { + let apiClient = stubbedAPIClient() + // Given a Customer with a saved card... + var savedCardJSON = STPFixtures.paymentMethodJSON() + savedCardJSON["id"] = "pm_saved_card" + // ...and a saved card that came from Apple Pay... + var savedApplePayCardJSON = STPFixtures.paymentMethodJSON() + savedApplePayCardJSON[jsonDict: "card"]?[jsonDict: "wallet"] = ["type": "apple_pay"] + savedApplePayCardJSON["id"] = "pm_saved_apple_pay_card" + + // ...fetching the customer's payment methods... + stubListPaymentMethods(key: exampleKey, paymentMethodType: "card", paymentMethodJSONs: [savedCardJSON, savedApplePayCardJSON], apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "sepa_debit", paymentMethodJSONs: [], apiClient: apiClient) + stubListPaymentMethods(key: exampleKey, paymentMethodType: "us_bank_account", paymentMethodJSONs: [], apiClient: apiClient) + + let ekm = MockEphemeralKeyEndpoint(exampleKey) + let sut = StripeCustomerAdapter(customerEphemeralKeyProvider: ekm.getEphemeralKey, apiClient: apiClient) + let pms = try await sut.fetchPaymentMethods() + + // ...should return the saved card but not the Apple Pay saved card + XCTAssertEqual(pms.count, 1) + XCTAssertEqual(pms[0].stripeId, "pm_saved_card") + } + + func configuration() -> CustomerSheet.Configuration { + return CustomerSheet.Configuration() + } +} diff --git a/Stripe/StripeiOSTests/Error+PaymentSheetTests.swift b/Stripe/StripeiOSTests/Error+PaymentSheetTests.swift new file mode 100644 index 00000000..eb2980b2 --- /dev/null +++ b/Stripe/StripeiOSTests/Error+PaymentSheetTests.swift @@ -0,0 +1,54 @@ +// +// Error+PaymentSheetTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 3/21/23. +// + +import Foundation +import XCTest + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet + +class Error_PaymentSheetTests: XCTestCase { + + private enum TestableError: LocalizedError { + case generic + case custom(String) + + var errorDescription: String? { + switch self { + case .generic: + return NSError.stp_unexpectedErrorMessage() + case .custom(let errorMessage): + return errorMessage + } + } + } + + func testPaymentSheetError_UsesDebugDescription() { + let error = PaymentSheetError.unknown(debugDescription: "Test debugDescription") + + XCTAssertEqual("An unknown error occurred in PaymentSheet. Test debugDescription", error.nonGenericDescription) + } + + func testError_HasGenericLocalizedDescription_NoSeverError() { + XCTAssertEqual(NSError.stp_unexpectedErrorMessage(), TestableError.generic.nonGenericDescription) + } + + func testError_HasGenericLocalizedDescription_WithServerError() { + let serverErrorMessage = "Test failed server response error messasge" + let info = [NSLocalizedDescriptionKey: NSError.stp_unexpectedErrorMessage(), STPError.errorMessageKey: serverErrorMessage] + let error = NSError(domain: "Test error domain", code: 123, userInfo: info) + + XCTAssertEqual(serverErrorMessage, error.nonGenericDescription) + } + + func testError_HasLocalizedDescription() { + let errorMessage = "Test errorMessage" + let error = TestableError.custom(errorMessage) + + XCTAssertEqual(errorMessage, error.nonGenericDescription) + } +} diff --git a/Stripe/StripeiOSTests/FBSnapshotTestCase+STPViewControllerLoading.swift b/Stripe/StripeiOSTests/FBSnapshotTestCase+STPViewControllerLoading.swift new file mode 100644 index 00000000..19fdc578 --- /dev/null +++ b/Stripe/StripeiOSTests/FBSnapshotTestCase+STPViewControllerLoading.swift @@ -0,0 +1,43 @@ +// +// FBSnapshotTestCase+STPViewControllerLoading.swift +// StripeiOS Tests +// +// Created by Brian Dorfman on 12/11/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase + +extension FBSnapshotTestCase { + /// Embeds the given controller in a navigation controller, prepares it for + /// snapshot testing and returns the view controller's view. + @objc(stp_preparedAndSizedViewForSnapshotTestFromViewController:) + func stp_preparedAndSizedViewForSnapshotTest(from viewController: UIViewController?) -> UIView? + { + let navController = stp_navigationControllerForSnapshotTest(withRootVC: viewController) + return stp_preparedAndSizedViewForSnapshotTest(from: navController) + } + + /// Returns a navigation controller initialized with the given root view controller + /// and prepares it for snapshot testing (adding it to a UIWindow and loading views) + @objc func stp_navigationControllerForSnapshotTest( + withRootVC viewController: UIViewController? + ) + -> UINavigationController? + { + var navController: UINavigationController? + if let viewController = viewController { + navController = UINavigationController(rootViewController: viewController) + } + let testWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) + testWindow.rootViewController = navController + testWindow.isHidden = false + + // Test that views loaded properly + loads them on first call + XCTAssertNotNil(navController?.view) + XCTAssertNotNil(viewController?.view) + + return navController + } + +} diff --git a/Stripe/StripeiOSTests/FormSpecProviderTest.swift b/Stripe/StripeiOSTests/FormSpecProviderTest.swift new file mode 100644 index 00000000..1453fdbc --- /dev/null +++ b/Stripe/StripeiOSTests/FormSpecProviderTest.swift @@ -0,0 +1,252 @@ +// +// FormSpecProviderTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class FormSpecProviderTest: XCTestCase { + func testLoadsJSON() throws { + let e = expectation(description: "Loads form specs file") + let sut = FormSpecProvider() + sut.load { loaded in + XCTAssertTrue(loaded) + e.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) + + guard let eps = sut.formSpec(for: "eps") else { + XCTFail() + return + } + XCTAssertEqual(eps.fields.count, 5) + XCTAssertEqual( + eps.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + + // ...and iDEAL has the correct dropdown spec + guard let ideal = sut.formSpec(for: "ideal"), + case .name = ideal.fields[0], + case .selector(let selector) = ideal.fields[3] + else { + XCTFail() + return + } + XCTAssertEqual(selector.apiPath?["v1"], "ideal[bank]") + XCTAssertEqual(selector.items.count, 13) + } + + func testLoadJsonCanOverwriteLoadedSpecs() throws { + let e1 = expectation(description: "Loads form specs file") + let sut = FormSpecProvider() + let paymentMethodType = "eps" + sut.load { loaded in + XCTAssertTrue(loaded) + e1.fulfill() + } + wait(for: [e1], timeout: 2) + let eps = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(eps.fields.count, 5) + XCTAssertEqual( + eps.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + let updatedSpecJson = + """ + [{ + "type": "eps", + "async": false, + "fields": [ + { + "type": "name", + "api_path": { + "v1": "billing_details[someOtherValue]" + } + } + ] + }] + """.data(using: .utf8)! + let formSpec = try! JSONSerialization.jsonObject(with: updatedSpecJson) + + let result = sut.loadFrom(formSpec) + XCTAssert(result) + + let epsUpdated = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(epsUpdated.fields.count, 1) + XCTAssertEqual( + epsUpdated.fields.first, + .name( + FormSpec.NameFieldSpec( + apiPath: ["v1": "billing_details[someOtherValue]"], + translationId: nil + ) + ) + ) + + // If load is called again, ensure that on-disk specs do not override + let e2 = expectation(description: "Loads form specs file, 2nd time") + sut.load { loaded in + XCTAssertTrue(loaded) + e2.fulfill() + } + wait(for: [e2], timeout: 2) + XCTAssertEqual( + epsUpdated.fields.first, + .name( + FormSpec.NameFieldSpec( + apiPath: ["v1": "billing_details[someOtherValue]"], + translationId: nil + ) + ) + ) + } + + func testLoadJsonFailsGracefully() throws { + let e = expectation(description: "Loads form specs file") + let sut = FormSpecProvider() + let paymentMethodType = "eps" + sut.load { loaded in + XCTAssertTrue(loaded) + e.fulfill() + } + waitForExpectations(timeout: 20, handler: nil) + let eps = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(eps.fields.count, 5) + XCTAssertEqual( + eps.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + let updatedSpecJson = + """ + [{ + "INVALID_type": "eps", + "async": false, + "fields": [ + { + "type": "name", + "api_path": { + "v1": "billing_details[someOtherValue]" + } + } + ] + }] + """.data(using: .utf8)! + let formSpec = try! JSONSerialization.jsonObject(with: updatedSpecJson) + + let result = sut.loadFrom(formSpec) + XCTAssertFalse(result) + let epsUpdated = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(epsUpdated.fields.count, 5) + XCTAssertEqual( + epsUpdated.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + } + + func testLoadNotValidJsonFailsGracefully() throws { + let e = expectation(description: "Loads form specs file") + let sut = FormSpecProvider() + let paymentMethodType = "eps" + sut.load { loaded in + XCTAssertTrue(loaded) + e.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) + + let eps = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(eps.fields.count, 5) + XCTAssertEqual( + eps.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + + let updatedSpecJson = + """ + NOT VALID JSON + """.data(using: .utf8)! + + let result = sut.loadFrom(updatedSpecJson) + XCTAssertFalse(result) + let epsUpdated = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(epsUpdated.fields.count, 5) + XCTAssertEqual( + epsUpdated.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + } + + func testLoadJsonDoesOverwrites() throws { + let e = expectation(description: "Loads form specs file") + let sut = FormSpecProvider() + let paymentMethodType = "eps" + sut.load { loaded in + XCTAssertTrue(loaded) + e.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) + let eps = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(eps.fields.count, 5) + XCTAssertEqual( + eps.fields.first, + .name(FormSpec.NameFieldSpec(apiPath: ["v1": "billing_details[name]"], translationId: nil)) + ) + + let updatedSpecJson = + """ + [{ + "type": "eps", + "async": false, + "fields": [ + { + "type": "name", + "api_path": { + "v1": "billing_details[someOtherValue]" + } + } + ], + "next_action_spec": { + "confirm_response_status_specs": { + "requires_action": { + "type": "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs": { + "succeeded": { + "type": "finished" + }, + "requires_action": { + "type": "finished" + } + } + } + }] + """.data(using: .utf8)! + let formSpec = try! JSONSerialization.jsonObject(with: updatedSpecJson) + + let result = sut.loadFrom(formSpec) + XCTAssert(result) + + // Validate ability to override LPM behavior of next actions + let epsUpdated = try XCTUnwrap(sut.formSpec(for: paymentMethodType)) + XCTAssertEqual(epsUpdated.fields.count, 1) + XCTAssertEqual( + epsUpdated.fields.first, + .name( + FormSpec.NameFieldSpec( + apiPath: ["v1": "billing_details[someOtherValue]"], + translationId: nil + ) + ) + ) + } +} diff --git a/Stripe/StripeiOSTests/FraudDetectionDataTest.swift b/Stripe/StripeiOSTests/FraudDetectionDataTest.swift new file mode 100644 index 00000000..3b4ba93d --- /dev/null +++ b/Stripe/StripeiOSTests/FraudDetectionDataTest.swift @@ -0,0 +1,31 @@ +// +// FraudDetectionDataTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class FraudDetectionDataTest: XCTestCase { + func testResetsSIDIfExpired() { + FraudDetectionData.shared.sidCreationDate = Date(timeInterval: -30 * 60 - 1, since: Date()) + FraudDetectionData.shared.resetSIDIfExpired() + XCTAssertNil(FraudDetectionData.shared.sid) + } + + func testSIDNotExpired() { + // Test resets sid if expired + FraudDetectionData.shared.sid = "123" + FraudDetectionData.shared.sidCreationDate = Date() + FraudDetectionData.shared.resetSIDIfExpired() + XCTAssertNotNil(FraudDetectionData.shared.sid) + } +} diff --git a/Stripe/StripeiOSTests/HostedSurfaceTest.swift b/Stripe/StripeiOSTests/HostedSurfaceTest.swift new file mode 100644 index 00000000..403f77fe --- /dev/null +++ b/Stripe/StripeiOSTests/HostedSurfaceTest.swift @@ -0,0 +1,83 @@ +// +// HostedSurfaceTest.swift +// StripeiOSTests +// +// Created by Nick Porter on 12/20/23. +// + +import Foundation +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class HostedSurfaceTest: XCTestCase { + + // Test the initializer + func testHostedSurfaceInitializer() { + let paymentSheetConfig = PaymentSheetFormFactoryConfig.paymentSheet(PaymentSheet.Configuration.init()) + + let hostedSurfaceForPaymentSheet = HostedSurface(config: paymentSheetConfig) + XCTAssertEqual(hostedSurfaceForPaymentSheet, .paymentSheet) + + let customerSheetConfig = PaymentSheetFormFactoryConfig.customerSheet(.init()) + + let hostedSurfaceForCustomerSheet = HostedSurface(config: customerSheetConfig) + XCTAssertEqual(hostedSurfaceForCustomerSheet, .customerSheet) + } + + // Test analyticEvent function for every event in CardBrandChoiceEvents + func testPaymentSheetAnalyticEvents() { + let hostedSurface = HostedSurface.paymentSheet + testAnalyticEvents(for: hostedSurface) + } + + func testCustomerSheetAnalyticEvents() { + let hostedSurface = HostedSurface.customerSheet + testAnalyticEvents(for: hostedSurface) + } + + private func testAnalyticEvents(for hostedSurface: HostedSurface) { + let events: [HostedSurface.CardBrandChoiceEvents] = [ + .displayCardBrandDropdownIndicator, + .openCardBrandDropdown, + .closeCardBrandDropDown, + .openCardBrandEditScreen, + .updateCardBrand, + .updateCardBrandFailed, + .closeEditScreen, + ] + + let expectedEventsPaymentSheet: [HostedSurface.CardBrandChoiceEvents: STPAnalyticEvent] = [ + .displayCardBrandDropdownIndicator: .paymentSheetDisplayCardBrandDropdownIndicator, + .openCardBrandDropdown: .paymentSheetOpenCardBrandDropdown, + .closeCardBrandDropDown: .paymentSheetCloseCardBrandDropDown, + .openCardBrandEditScreen: .paymentSheetOpenCardBrandEditScreen, + .updateCardBrand: .paymentSheetUpdateCardBrand, + .updateCardBrandFailed: .paymentSheetUpdateCardBrandFailed, + .closeEditScreen: .paymentSheetClosesEditScreen, + ] + + let expectedEventsCustomerSheet: [HostedSurface.CardBrandChoiceEvents: STPAnalyticEvent] = [ + .displayCardBrandDropdownIndicator: .customerSheetDisplayCardBrandDropdownIndicator, + .openCardBrandDropdown: .customerSheetOpenCardBrandDropdown, + .closeCardBrandDropDown: .customerSheetCloseCardBrandDropDown, + .openCardBrandEditScreen: .customerSheetOpenCardBrandEditScreen, + .updateCardBrand: .customerSheetUpdateCardBrand, + .updateCardBrandFailed: .customerSheetUpdateCardBrandFailed, + .closeEditScreen: .customerSheetClosesEditScreen, + ] + + for event in events { + let analyticEvent = hostedSurface.analyticEvent(for: event) + // assert that the event is the expected one + switch hostedSurface { + case .paymentSheet: + XCTAssertEqual(analyticEvent, expectedEventsPaymentSheet[event]) + case .customerSheet: + XCTAssertEqual(analyticEvent, expectedEventsCustomerSheet[event]) + } + } + } +} diff --git a/Stripe/StripeiOSTests/ImageTest.swift b/Stripe/StripeiOSTests/ImageTest.swift new file mode 100644 index 00000000..3c31b62e --- /dev/null +++ b/Stripe/StripeiOSTests/ImageTest.swift @@ -0,0 +1,27 @@ +// +// ImageTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/19/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class ImageTest: XCTestCase { + func testAllImagesExist() throws { + for image in Image.allCases { + let image = UIImage( + named: image.rawValue, + in: StripePaymentSheetBundleLocator.resourcesBundle, + compatibleWith: nil + ) + XCTAssertNotNil(image) + } + } +} diff --git a/Stripe/StripeiOSTests/Info.plist b/Stripe/StripeiOSTests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/Stripe/StripeiOSTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Stripe/StripeiOSTests/LinkInlineSignupElementSnapshotTests.swift b/Stripe/StripeiOSTests/LinkInlineSignupElementSnapshotTests.swift new file mode 100644 index 00000000..90d1e10a --- /dev/null +++ b/Stripe/StripeiOSTests/LinkInlineSignupElementSnapshotTests.swift @@ -0,0 +1,170 @@ +// +// LinkInlineSignupElementSnapshotTests.swift +// StripeiOS Tests +// + +import iOSSnapshotTestCase +@_spi(STP) import StripeCoreTestUtils +@_spi(STP) import StripeUICore +import UIKit + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class LinkInlineSignupElementSnapshotTests: STPSnapshotTestCase { + + // MARK: Normal mode + + func testDefaultState() { + let sut = makeSUT() + verify(sut) + } + + // WARNING: If this tests fails, see go/link-signup-consent-action-log to determine if a new consent_action is needed. + func testExpandedState() { + let sut = makeSUT(saveCheckboxChecked: true, userTypedEmailAddress: "user@example.com") + verify(sut) + } + + // WARNING: If this tests fails, see go/link-signup-consent-action-log to determine if a new consent_action is needed. + func testExpandedState_nonUS() { + let sut = makeSUT( + saveCheckboxChecked: true, + userTypedEmailAddress: "user@example.com", + country: "CA" + ) + verify(sut) + } + + func testExpandedState_nonUS_preFilled() { + let sut = makeSUT( + saveCheckboxChecked: true, + userTypedEmailAddress: "user@example.com", + country: "CA", + preFillName: "Jane Diaz", + preFillPhone: "+13105551234" + ) + verify(sut) + } + + // MARK: Textfield only mode + + func testDefaultState_textFieldsOnly() { + let sut = makeSUT(showCheckbox: false) + verify(sut) + } + + // WARNING: If this tests fails, see go/link-signup-consent-action-log to determine if a new consent_action is needed. + func testExpandedState_textFieldsOnly() { + let sut = makeSUT(saveCheckboxChecked: true, userTypedEmailAddress: "user@example.com", showCheckbox: false) + verify(sut) + } + + // WARNING: If this tests fails, see go/link-signup-consent-action-log to determine if a new consent_action is needed. + func testExpandedState_nonUS_textFieldsOnly() { + let sut = makeSUT( + saveCheckboxChecked: true, + userTypedEmailAddress: "user@example.com", + country: "CA", + showCheckbox: false + ) + verify(sut) + } + + // WARNING: If this tests fails, see go/link-signup-consent-action-log to determine if a new consent_action is needed. + func testExpandedState_nonUS_preFilled_textFieldsOnly() { + // In textFieldsOnly mode, the phone number should *not* be prefilled. + let sut = makeSUT( + saveCheckboxChecked: true, + linkAccountEmailAddress: "user@example.com", + country: "CA", + preFillName: "Jane Diaz", + preFillPhone: "+13105551234", + showCheckbox: false + ) + verify(sut) + } + + func verify( + _ element: LinkInlineSignupElement, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + element.view.autosizeHeight(width: 340) + STPSnapshotVerifyView(element.view, identifier: identifier, file: file, line: line) + } + +} + +extension LinkInlineSignupElementSnapshotTests { + + struct MockAccountService: LinkAccountServiceProtocol { + func lookupAccount( + withEmail email: String?, + completion: @escaping (Result) -> Void + ) { + completion( + .success( + PaymentSheetLinkAccount( + email: "user@example.com", + session: nil, + publishableKey: nil + ) + ) + ) + } + + func hasEmailLoggedOut(email: String) -> Bool { + // TODO(porter): Determine if we want to implement this in tests + return false + } + } + + func makeSUT( + saveCheckboxChecked: Bool = false, + linkAccountEmailAddress: String? = nil, + userTypedEmailAddress: String? = nil, + country: String = "US", + preFillName: String? = nil, + preFillPhone: String? = nil, + showCheckbox: Bool = true + ) -> LinkInlineSignupElement { + var configuration = PaymentSheet.Configuration() + configuration.merchantDisplayName = "[Merchant]" + configuration.defaultBillingDetails.name = preFillName + configuration.defaultBillingDetails.phone = preFillPhone + + var linkAccount: PaymentSheetLinkAccount? + + if let linkAccountEmailAddress { + linkAccount = PaymentSheetLinkAccount(email: linkAccountEmailAddress, session: nil, publishableKey: nil) + } + + let viewModel = LinkInlineSignupViewModel( + configuration: configuration, + showCheckbox: showCheckbox, + accountService: MockAccountService(), + linkAccount: linkAccount, + country: country + ) + + viewModel.saveCheckboxChecked = saveCheckboxChecked + // Won't trigger the "email address prefilled" path, because it wasn't there when initialized + if let userTypedEmailAddress { + viewModel.emailAddress = userTypedEmailAddress + } + + if userTypedEmailAddress != nil || linkAccountEmailAddress != nil { + // Wait for account to load + let expectation = notNullExpectation(for: viewModel, keyPath: \.linkAccount) + wait(for: [expectation], timeout: 10) + } + + return .init(viewModel: viewModel) + } + +} diff --git a/Stripe/StripeiOSTests/LinkLegalTermsViewSnapshotTests.swift b/Stripe/StripeiOSTests/LinkLegalTermsViewSnapshotTests.swift new file mode 100644 index 00000000..4f4417d0 --- /dev/null +++ b/Stripe/StripeiOSTests/LinkLegalTermsViewSnapshotTests.swift @@ -0,0 +1,102 @@ +// +// LinkLegalTermsViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 1/26/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class LinkLegalTermsViewSnapshotTests: STPSnapshotTestCase { + + func testDefault() { + let sut = makeSUT() + verify(sut) + } + + func testCentered() { + let sut = makeSUT(textAlignment: .center) + verify(sut) + } + + func testColorCustomization() { + let sut = makeSUT() + sut.textColor = .orange + sut.tintColor = .purple + verify(sut) + } + + func testLocalization_de() { + performLocalizedSnapshotTest(forLanguage: "de") + } + func testLocalization_es() { + performLocalizedSnapshotTest(forLanguage: "es") + } + func testLocalization_el_GR() { + performLocalizedSnapshotTest(forLanguage: "el-GR") + } + func testLocalization_it() { + performLocalizedSnapshotTest(forLanguage: "it") + } + func testLocalization_ja() { + performLocalizedSnapshotTest(forLanguage: "ja") + } + func testLocalization_ko() { + performLocalizedSnapshotTest(forLanguage: "ko") + } + func testLocalization_zh_hans() { + performLocalizedSnapshotTest(forLanguage: "zh-Hans") + } + +} + +// MARK: - Helpers + +extension LinkLegalTermsViewSnapshotTests { + + func verify( + _ view: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + view.autosizeHeight(width: 250) + STPSnapshotVerifyView(view, identifier: identifier, file: file, line: line) + } + + func performLocalizedSnapshotTest( + forLanguage language: String, + file: StaticString = #filePath, + line: UInt = #line + ) { + STPLocalizationUtils.overrideLanguage(to: language) + let sut = makeSUT() + STPLocalizationUtils.overrideLanguage(to: nil) + verify(sut, identifier: language, file: file, line: line) + } + +} + +// MARK: - Factory + +extension LinkLegalTermsViewSnapshotTests { + + func makeSUT() -> LinkLegalTermsView { + return LinkLegalTermsView() + } + + func makeSUT(textAlignment: NSTextAlignment) -> LinkLegalTermsView { + return LinkLegalTermsView(textAlignment: textAlignment) + } + +} diff --git a/Stripe/StripeiOSTests/LinkSignupViewModelTests.swift b/Stripe/StripeiOSTests/LinkSignupViewModelTests.swift new file mode 100644 index 00000000..a5d2441e --- /dev/null +++ b/Stripe/StripeiOSTests/LinkSignupViewModelTests.swift @@ -0,0 +1,216 @@ +// +// LinkSignupViewModelTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 1/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import StripeCoreTestUtils +@_spi(STP) import StripeUICore +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class LinkInlineSignupViewModelTests: STPNetworkStubbingTestCase { + + // Should be ~4x the debounce time for best results. + let accountLookupTimeout: TimeInterval = 4 + + func test_defaults() { + let sut = makeSUT(country: "US", showCheckbox: true) + + XCTAssertFalse(sut.shouldShowEmailField) + XCTAssertFalse(sut.shouldShowPhoneField) + XCTAssertFalse(sut.shouldShowNameField) + XCTAssertFalse(sut.shouldShowLegalTerms) + } + + func test_shouldShowEmailFieldWhenCheckboxIsChecked() { + let sut = makeSUT(country: "US", showCheckbox: true) + + sut.saveCheckboxChecked = true + XCTAssertTrue(sut.shouldShowEmailField) + + sut.saveCheckboxChecked = false + XCTAssertFalse(sut.shouldShowEmailField) + } + + func test_shouldShowRegistrationFieldsWhenEmailIsProvided() { + let sut = makeSUT(country: "US", showCheckbox: true) + + sut.saveCheckboxChecked = true + sut.emailAddress = "user@example.com" + + // Wait for async change on `shouldShowPhoneField`. + let showPhoneFieldExpectation = expectation( + for: sut, + keyPath: \.shouldShowPhoneField, + equalsToValue: true + ) + wait(for: [showPhoneFieldExpectation], timeout: accountLookupTimeout) + + XCTAssertFalse(sut.shouldShowNameField, "Should not show name field for US customers") + XCTAssertTrue( + sut.shouldShowLegalTerms, + "Should show legal terms when creating a new account" + ) + + sut.emailAddress = nil + + // Wait for async change on `shouldShowPhoneField`. + let hidePhoneFieldExpectation = expectation( + for: sut, + keyPath: \.shouldShowPhoneField, + equalsToValue: false + ) + wait(for: [hidePhoneFieldExpectation], timeout: accountLookupTimeout) + XCTAssertFalse(sut.shouldShowNameField) + sut.saveCheckboxChecked = false + XCTAssertFalse(sut.shouldShowLegalTerms) + } + + func test_shouldShowNameField_nonUSCustomers() { + let sut = makeSUT(country: "CA", showCheckbox: true, hasAccountObject: true) + sut.saveCheckboxChecked = true + XCTAssertTrue(sut.shouldShowNameField, "Should show name field for non-US customers") + } + + func test_shouldShowLegalText() { + let sut = makeSUT(country: "US", showCheckbox: true, hasAccountObject: false) + sut.saveCheckboxChecked = false + XCTAssertFalse(sut.shouldShowLegalTerms) + sut.saveCheckboxChecked = true + XCTAssertTrue(sut.shouldShowLegalTerms) + } + + func test_action_returnsNilUnlessPhoneRequirementIsFulfilled() { + let sut = makeSUT(country: "US", showCheckbox: true, hasAccountObject: true) + + sut.saveCheckboxChecked = true + XCTAssertNil(sut.action) + + sut.phoneNumber = PhoneNumber(number: "5555555555", countryCode: "US") + XCTAssertNotNil(sut.action) + } + + func test_action_returnsNilUnlessNameRequirementIsFulfilled() { + // Non-US customers require providing a name + let sut = makeSUT(country: "CA", showCheckbox: true, hasAccountObject: true) + + sut.saveCheckboxChecked = true + sut.phoneNumber = PhoneNumber(number: "5555555555", countryCode: "CA") + XCTAssertNil(sut.action, "`action` must be nil unless a name is provided") + + sut.legalName = "Jane Doe" + XCTAssertNotNil(sut.action) + } + + func test_action_returnsContinueWithoutLinkIfCheckboxIsNotChecked() { + let sut = makeSUT(country: "US", showCheckbox: true) + + sut.saveCheckboxChecked = false + XCTAssertEqual(sut.action, .continueWithoutLink) + } + + func test_action_returnsContinueWithoutLinkIfLookupFails() { + let sut = makeSUT(country: "US", showCheckbox: true, shouldFailLookup: true) + + sut.saveCheckboxChecked = true + sut.emailAddress = "user@example.com" + + // Wait for lookup to fail + let lookupFailedExpectation = expectation( + for: sut, + keyPath: \.lookupFailed, + equalsToValue: true + ) + wait(for: [lookupFailedExpectation], timeout: accountLookupTimeout) + + XCTAssertEqual(sut.action, .continueWithoutLink) + } + + func test_consentAction_checkbox() { + let sut = makeSUT(country: "US", showCheckbox: true, hasAccountObject: false) + XCTAssertEqual(sut.consentAction, .checkbox_v0) + } + + func test_consentAction_checkbox_prefillEmail() { + let sut = makeSUT(country: "US", showCheckbox: true, hasAccountObject: true) + XCTAssertEqual(sut.consentAction, .checkbox_v0_0) + } + + func test_consentAction_checkbox_prefillEmailAndPhone() { + let sut = makeSUT(country: "US", showCheckbox: true, hasAccountObject: true) + sut.phoneNumber = PhoneNumber(number: "555555555", countryCode: "1") + sut.phoneNumberWasPrefilled = true + XCTAssertEqual(sut.consentAction, .checkbox_v0_1) + } + + func test_consentAction_implied() { + let sut = makeSUT(country: "US", showCheckbox: false, hasAccountObject: false) + XCTAssertEqual(sut.consentAction, .implied_v0) + } + + func test_consentAction_implied_prefillEmail() { + let sut = makeSUT(country: "US", showCheckbox: false, hasAccountObject: true) + XCTAssertEqual(sut.consentAction, .implied_v0_0) + } +} + +extension LinkInlineSignupViewModelTests { + + struct MockAccountService: LinkAccountServiceProtocol { + let shouldFailLookup: Bool + + func lookupAccount( + withEmail email: String?, + completion: @escaping (Result) -> Void + ) { + if shouldFailLookup { + completion(.failure(NSError.stp_genericConnectionError())) + } else { + completion( + .success( + PaymentSheetLinkAccount( + email: "user@example.com", + session: nil, + publishableKey: nil + ) + ) + ) + } + } + + func hasEmailLoggedOut(email: String) -> Bool { + // TODO(porter): Determine if we want to implement this in tests + return false + } + } + + func makeSUT( + country: String, + showCheckbox: Bool, + hasAccountObject: Bool = false, + shouldFailLookup: Bool = false + ) -> LinkInlineSignupViewModel { + let linkAccount: PaymentSheetLinkAccount? = hasAccountObject + ? PaymentSheetLinkAccount(email: "user@example.com", session: nil, publishableKey: nil) + : nil + + return LinkInlineSignupViewModel( + configuration: PaymentSheet.Configuration(), + showCheckbox: showCheckbox, + accountService: MockAccountService(shouldFailLookup: shouldFailLookup), + linkAccount: linkAccount, + country: country + ) + } + +} diff --git a/Stripe/StripeiOSTests/MKPlacemark+PaymentSheetTests.swift b/Stripe/StripeiOSTests/MKPlacemark+PaymentSheetTests.swift new file mode 100644 index 00000000..711e3863 --- /dev/null +++ b/Stripe/StripeiOSTests/MKPlacemark+PaymentSheetTests.swift @@ -0,0 +1,209 @@ +// +// MKPlacemark+PaymentSheetTests.swift +// StripeiOS Tests +// +// Created by Nick Porter on 6/13/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Contacts +import MapKit +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class MKPlacemark_PaymentSheetTests: XCTestCase { + + // All address dictionaries are based on an actual placemark of an `MKLocalSearchCompletion` + + func testAsAddress_UnitedStates() { + // Search string used to generate address dictionary: "4 Pennsylvania Pl" + let addressDictionary = + [ + CNPostalAddressStreetKey: "4 Pennsylvania Plaza", + CNPostalAddressStateKey: "NY", + CNPostalAddressCountryKey: "United States", + CNPostalAddressISOCountryCodeKey: "US", + CNPostalAddressCityKey: "New York", + "SubThoroughfare": "4", + CNPostalAddressPostalCodeKey: "10001", + "Thoroughfare": "Pennsylvania Plaza", + CNPostalAddressSubLocalityKey: "Manhattan", + CNPostalAddressSubAdministrativeAreaKey: "New York County", + "Name": "4 Pennsylvania Plaza", + ] as [String: Any] + let placemark = MKPlacemark( + coordinate: CLLocationCoordinate2D(), + addressDictionary: addressDictionary + ) + let expectedAddress = PaymentSheet.Address( + city: "New York", + country: "US", + line1: "4 Pennsylvania Plaza", + line2: nil, + postalCode: "10001", + state: "NY" + ) + + XCTAssertEqual(placemark.asAddress, expectedAddress) + } + + func testAsAddress_Canada() { + // Search string used to generate address dictionary: "40 Bay St To" + let addressDictionary = + [ + CNPostalAddressStreetKey: "40 Bay St", + CNPostalAddressStateKey: "ON", + CNPostalAddressISOCountryCodeKey: "CA", + CNPostalAddressCountryKey: "Canada", + CNPostalAddressCityKey: "Toronto", + "SubThoroughfare": "40", + CNPostalAddressPostalCodeKey: "M5J 2X2", + "Thoroughfare": "Bay St", + CNPostalAddressSubLocalityKey: "Downtown Toronto", + CNPostalAddressSubAdministrativeAreaKey: "SubAdministrativeArea", + "Name": "40 Bay St", + ] as [String: Any] + let placemark = MKPlacemark( + coordinate: CLLocationCoordinate2D(), + addressDictionary: addressDictionary + ) + let expectedAddress = PaymentSheet.Address( + city: "Toronto", + country: "CA", + line1: "40 Bay St", + line2: nil, + postalCode: "M5J 2X2", + state: "ON" + ) + + XCTAssertEqual(placemark.asAddress, expectedAddress) + } + + func testAsAddress_Germany() { + // Search string used to generate address dictionary: "Rüsternallee 14" + let addressDictionary = + [ + CNPostalAddressStreetKey: "Rüsternallee 14", + CNPostalAddressISOCountryCodeKey: "DE", + CNPostalAddressCountryKey: "Germany", + CNPostalAddressCityKey: "Berlin", + CNPostalAddressPostalCodeKey: "14050", + "SubThoroughfare": "14", + "Thoroughfare": "Rüsternallee", + CNPostalAddressSubLocalityKey: "Charlottenburg-Wilmersdorf", + CNPostalAddressSubAdministrativeAreaKey: "Berlin", + "Name": "Rüsternallee 14", + ] as [String: Any] + let placemark = MKPlacemark( + coordinate: CLLocationCoordinate2D(), + addressDictionary: addressDictionary + ) + let expectedAddress = PaymentSheet.Address( + city: "Berlin", + country: "DE", + line1: "Rüsternallee 14", + line2: nil, + postalCode: "14050", + state: nil + ) + + XCTAssertEqual(placemark.asAddress, expectedAddress) + } + + func testAsAddress_Brazil() { + // Search string used to generate address dictionary: "Avenida Paulista 500" + let addressDictionary = + [ + CNPostalAddressStreetKey: "Avenida Paulista, 500", + CNPostalAddressStateKey: "SP", + CNPostalAddressISOCountryCodeKey: "BR", + CNPostalAddressCountryKey: "Brazil", + CNPostalAddressCityKey: "Paulínia", + "SubThoroughfare": "500", + CNPostalAddressPostalCodeKey: "13145-089", + "Thoroughfare": "Avenida Paulista", + CNPostalAddressSubLocalityKey: "Jardim Planalto", + "Name": "Avenida Paulista, 500", + ] as [String: Any] + let placemark = MKPlacemark( + coordinate: CLLocationCoordinate2D(), + addressDictionary: addressDictionary + ) + let expectedAddress = PaymentSheet.Address( + city: "Paulínia", + country: "BR", + line1: "Avenida Paulista, 500", + line2: nil, + postalCode: "13145-089", + state: "SP" + ) + + XCTAssertEqual(placemark.asAddress, expectedAddress) + } + + func testAsAddress_Japan() { + // Search string used to generate address dictionary: "Nagatacho 2" + let addressDictionary = + [ + CNPostalAddressStreetKey: "Nagatacho 2-Chōme", + CNPostalAddressStateKey: "Tokyo", + CNPostalAddressISOCountryCodeKey: "JP", + CNPostalAddressCountryKey: "Japan", + CNPostalAddressCityKey: "Chiyoda", + "Thoroughfare": "Nagatacho 2-Chōme", + CNPostalAddressSubLocalityKey: "Nagatacho", + "Name": "Nagatacho 2-Chōme", + ] as [String: Any] + let placemark = MKPlacemark( + coordinate: CLLocationCoordinate2D(), + addressDictionary: addressDictionary + ) + let expectedAddress = PaymentSheet.Address( + city: "Chiyoda", + country: "JP", + line1: "Nagatacho 2-Chōme", + line2: nil, + postalCode: nil, + state: "Tokyo" + ) + + XCTAssertEqual(placemark.asAddress, expectedAddress) + } + + func testAsAddress_Australia() { + // Search string used to generate address dictionary: "488 George St Syd" + let addressDictionary = + [ + CNPostalAddressStreetKey: "488 George St", + CNPostalAddressStateKey: "NSW", + CNPostalAddressISOCountryCodeKey: "AU", + CNPostalAddressCountryKey: "Australia", + CNPostalAddressCityKey: "Sydney", + CNPostalAddressPostalCodeKey: "2000", + "SubThoroughfare": "488", + "Thoroughfare": "George St", + CNPostalAddressSubAdministrativeAreaKey: "Sydney", + "Name": "488 George St", + ] as [String: Any] + let placemark = MKPlacemark( + coordinate: CLLocationCoordinate2D(), + addressDictionary: addressDictionary + ) + let expectedAddress = PaymentSheet.Address( + city: "Sydney", + country: "AU", + line1: "488 George St", + line2: nil, + postalCode: "2000", + state: "NSW" + ) + + XCTAssertEqual(placemark.asAddress, expectedAddress) + } + +} diff --git a/Stripe/StripeiOSTests/NSArray+StripeTest.swift b/Stripe/StripeiOSTests/NSArray+StripeTest.swift new file mode 100644 index 00000000..8e12190e --- /dev/null +++ b/Stripe/StripeiOSTests/NSArray+StripeTest.swift @@ -0,0 +1,67 @@ +// +// NSArray+StripeTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 1/19/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +import XCTest + +class Array_StripeTest: XCTestCase { + func test_arrayByRemovingNulls_removesNullsDeeply() { + let array: [Any] = [ + "id", + NSNull(), // null in root + [ + "user": "user_123", + "country": NSNull(), // null in dictionary + "nicknames": ["john", "johnny", NSNull()], + "profiles": [ + "facebook": "fb_123", + "twitter": NSNull(), + ], + ], + [ + NSNull(), // null in array + [ + "id": "fee_123", + "frequency": NSNull(), + ], + ["payment", NSNull()], + ], + ] + + let expected: [Any] = [ + "id", + [ + "user": "user_123", + "nicknames": ["john", "johnny"], + "profiles": [ + "facebook": "fb_123" + ], + ], + [ + [ + "id": "fee_123" + ], ["payment"], + ], + ] + + let result = array.stp_arrayByRemovingNulls() + + XCTAssertEqual(result as NSArray, expected as NSArray) + } + + func test_arrayByRemovingNulls_keepsEmptyLeaves() { + let array = [NSNull()] + let result = array.stp_arrayByRemovingNulls() + + XCTAssertEqual(result as NSArray, [] as NSArray) + } +} diff --git a/Stripe/StripeiOSTests/NSDecimalNumber+StripeTest.swift b/Stripe/StripeiOSTests/NSDecimalNumber+StripeTest.swift new file mode 100644 index 00000000..0f4cf485 --- /dev/null +++ b/Stripe/StripeiOSTests/NSDecimalNumber+StripeTest.swift @@ -0,0 +1,80 @@ +// +// NSDecimalNumber+StripeTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 4/19/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +import XCTest + +class NSDecimalNumberStripeTest: XCTestCase { + // an incomplete list of 2 decimal point currencies + private let twoDecimalPointCurrencies = [ + "usd", + "dkk", + "eur", + "aud", + "sek", + "sgd", + + // Special cases: + "cop", + "pkr", + "lak", + "rsd", + ] + + private let noDecimalPointCurrencies = [ + "bif", + "clp", + "djf", + "gnf", + "jpy", + "kmf", + "krw", + "mga", + "pyg", + "rwf", + "vnd", + "vuv", + "xaf", + "xof", + "xpf", + ] + + private let threeDecimalCurrencies = [ + "bhd", + "jod", + "kwd", + "omr", + "tnd", + ] + + func testDecimalAmount_twoDecimal() { + for twoDecimalPointCurrency in twoDecimalPointCurrencies { + let decimalNumber = NSDecimalNumber.stp_decimalNumber(withAmount: 92123, currency: twoDecimalPointCurrency) + XCTAssertEqual(decimalNumber, NSDecimalNumber(string: "921.23")) + } + } + + func testDecimalAmount_noDecimal() { + for currency in noDecimalPointCurrencies { + let decimalNumber = NSDecimalNumber.stp_decimalNumber(withAmount: 92123, currency: currency) + XCTAssertEqual(decimalNumber, NSDecimalNumber(string: "92123")) + } + } + + func testDecimalAmount_threeDecimal() { + for currency in threeDecimalCurrencies { + let decimalNumber = NSDecimalNumber.stp_decimalNumber(withAmount: 92123, currency: currency) + XCTAssertEqual(decimalNumber, NSDecimalNumber(string: "92.123")) + } + } + +} diff --git a/Stripe/StripeiOSTests/NSDictionary+StripeTest.swift b/Stripe/StripeiOSTests/NSDictionary+StripeTest.swift new file mode 100644 index 00000000..84287033 --- /dev/null +++ b/Stripe/StripeiOSTests/NSDictionary+StripeTest.swift @@ -0,0 +1,254 @@ +// +// Dictionary+StripeTest.swift +// StripeiOS Tests +// +// Created by Joey Dong on 7/24/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +import XCTest + +class Dictionary_StripeTest: XCTestCase { + // MARK: - dictionaryByRemovingNullsValidatingRequiredFields + func test_dictionaryByRemovingNulls_removesNullsDeeply() { + let dictionary = + [ + "id": "card_123", + "tokenization_method": NSNull(), // null in root + "metadata": [ + "user": "user_123", + "country": NSNull(), // null in dictionary + "nicknames": ["john", "johnny", NSNull()], + "profiles": [ + "facebook": "fb_123", + "twitter": NSNull(), + ], + ], + "fees": [ + NSNull(), // null in array + [ + "id": "fee_123", + "frequency": NSNull(), + ], + ["payment", NSNull()], + ], + ] as [AnyHashable: Any] + + let expected = + [ + "id": "card_123", + "metadata": [ + "user": "user_123", + "nicknames": ["john", "johnny"], + "profiles": [ + "facebook": "fb_123" + ], + ], + "fees": [ + [ + "id": "fee_123" + ], ["payment"], + ], + ] as [AnyHashable: Any] + + let result = dictionary.stp_dictionaryByRemovingNulls() + XCTAssertEqual(result as NSDictionary, expected as NSDictionary) + } + + func test_dictionaryByRemovingNullsValidatingRequiredFields_keepsEmptyLeaves() { + let dictionary = + [ + "id": NSNull() + ] as [AnyHashable: Any] + let result = dictionary.stp_dictionaryByRemovingNulls() + + XCTAssertEqual(result as NSDictionary, [:] as NSDictionary) + } + + // MARK: - dictionaryByRemovingNonStrings + func test_dictionaryByRemovingNonStrings_basicCases() { + // Empty dictionary + var dictionary = [:] as [AnyHashable: Any] + var expected = [:] as [AnyHashable: Any] + var result = dictionary.stp_dictionaryByRemovingNonStrings() + XCTAssertEqual(result as NSDictionary, expected as NSDictionary) + + // Regular case + dictionary = + [ + "user": "user_123", + "nicknames": "John, Johnny", + ] + expected = + [ + "user": "user_123", + "nicknames": "John, Johnny", + ] + result = dictionary.stp_dictionaryByRemovingNonStrings() + XCTAssertEqual(result as NSDictionary, expected as NSDictionary) + + // Strips non-NSString keys and values + dictionary = + [ + "user": "user_123", + "nicknames": "John, Johnny", + "profiles": NSNull(), + NSNull(): "San Francisco, CA", + "age": NSNumber(value: 21), + NSNumber(value: 21): "age", + "fees": [ + "plan": "monthly" + ], + "visits": ["january", "february"], + ] + expected = + [ + "user": "user_123", + "nicknames": "John, Johnny", + ] + result = dictionary.stp_dictionaryByRemovingNonStrings() + XCTAssertEqual(result as NSDictionary, expected as NSDictionary) + + // Strips non-NSString keys and values + dictionary = + [ + "user": "user_123", + "nicknames": "John, Johnny", + "profiles": NSNull(), + NSNull(): NSNull(), + "age": NSNumber(value: 21), + NSNumber(value: 21): NSNumber(value: 21), + "fees": [ + "plan": "monthly" + ], + "visits": ["january", "february"], + ] + expected = + [ + "user": "user_123", + "nicknames": "John, Johnny", + ] + result = dictionary.stp_dictionaryByRemovingNonStrings() + XCTAssertEqual(result as NSDictionary, expected as NSDictionary) + } + + // MARK: - Getters + func testArrayForKey() { + let dict = + [ + "a": ["foo"] + ] as [AnyHashable: Any] + + XCTAssertEqual(dict.stp_array(forKey: "a") as! [String], ["foo"]) + XCTAssertNil(dict.stp_array(forKey: "b")) + } + + func testBoolForKey() { + let dict = + [ + "a": NSNumber(value: 1), + "b": NSNumber(value: 0), + "c": "true", + "d": "false", + "e": "1", + "f": "foo", + ] as [AnyHashable: Any] + + XCTAssertTrue(dict.stp_bool(forKey: "a", or: false)) + XCTAssertFalse(dict.stp_bool(forKey: "b", or: true)) + XCTAssertTrue(dict.stp_bool(forKey: "c", or: false)) + XCTAssertFalse(dict.stp_bool(forKey: "d", or: true)) + XCTAssertTrue(dict.stp_bool(forKey: "e", or: false)) + XCTAssertFalse(dict.stp_bool(forKey: "f", or: false)) + } + + func testIntForKey() { + let dict = + [ + "a": NSNumber(value: 1), + "b": NSNumber(value: -1), + "c": "1", + "d": "-1", + "e": "10.0", + "f": "10.5", + "g": NSNumber(value: 10.0), + "h": NSNumber(value: 10.5), + "i": "foo", + ] as [AnyHashable: Any] + + XCTAssertEqual(dict.stp_int(forKey: "a", or: 0), 1) + XCTAssertEqual(dict.stp_int(forKey: "b", or: 0), -1) + XCTAssertEqual(dict.stp_int(forKey: "c", or: 0), 1) + XCTAssertEqual(dict.stp_int(forKey: "d", or: 0), -1) + XCTAssertEqual(dict.stp_int(forKey: "e", or: 0), 10) + XCTAssertEqual(dict.stp_int(forKey: "f", or: 0), 10) + XCTAssertEqual(dict.stp_int(forKey: "g", or: 0), 10) + XCTAssertEqual(dict.stp_int(forKey: "h", or: 0), 10) + XCTAssertEqual(dict.stp_int(forKey: "i", or: 0), 0) + } + + func testDateForKey() { + let dict = + [ + "a": NSNumber(value: 0), + "b": "0", + ] as [AnyHashable: Any] + let expectedDate = Date(timeIntervalSince1970: 0) + + XCTAssertEqual(dict.stp_date(forKey: "a"), expectedDate) + XCTAssertEqual(dict.stp_date(forKey: "b"), expectedDate) + XCTAssertNil(dict.stp_date(forKey: "c")) + } + + func testDictionaryForKey() { + let dict = + [ + "a": [ + "foo": "bar" + ], + ] as [AnyHashable: Any] + + XCTAssertEqual( + dict.stp_dictionary(forKey: "a")! as NSDictionary, + [ + "foo": "bar" + ] as NSDictionary + ) + XCTAssertNil(dict.stp_dictionary(forKey: "b")) + } + + func testNumberForKey() { + let dict = + [ + "a": NSNumber(value: 1) + ] as [AnyHashable: Any] + + XCTAssertEqual(dict.stp_number(forKey: "a"), NSNumber(value: 1)) + XCTAssertNil(dict.stp_number(forKey: "b")) + } + + func testStringForKey() { + let dict = + [ + "a": "foo" + ] as [AnyHashable: Any] + XCTAssertEqual(dict.stp_string(forKey: "a"), "foo") + XCTAssertNil(dict.stp_string(forKey: "b")) + } + + func testURLForKey() { + let dict = + [ + "a": "https://example.com", + "b": "not a url", + ] as [AnyHashable: Any] + XCTAssertEqual(dict.stp_url(forKey: "a"), URL(string: "https://example.com")) + XCTAssertNil(dict.stp_url(forKey: "b")) + XCTAssertNil(dict.stp_url(forKey: "c")) + } +} diff --git a/Stripe/StripeiOSTests/NSLocale+STPSwizzling.swift b/Stripe/StripeiOSTests/NSLocale+STPSwizzling.swift new file mode 100644 index 00000000..bff5b323 --- /dev/null +++ b/Stripe/StripeiOSTests/NSLocale+STPSwizzling.swift @@ -0,0 +1,96 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +import Foundation +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +// +// NSLocale+STPSwizzling.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 7/17/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import ObjectiveC + +class STPLocaleSwizzling { + static var stpLocaleOverride: NSLocale? + static var hasSwizzled: Bool = false + +} + +extension NSLocale { + class func swizzleIfNeeded() { + if !STPLocaleSwizzling.hasSwizzled { + self.stp_swizzleClassMethod(#selector(getter: current), withReplacement: #selector(stp_current)) + self.stp_swizzleClassMethod(#selector(getter: autoupdatingCurrent), withReplacement: #selector(stp_autoUpdatingCurrent)) + self.stp_swizzleClassMethod(#selector(getter: system), withReplacement: #selector(stp_system)) + STPLocaleSwizzling.hasSwizzled = true + } + } + + class func stp_withLocale(as locale: NSLocale?, perform block: @escaping () -> Void) { + swizzleIfNeeded() + let currentLocale = NSLocale.current as NSLocale + self.stp_setCurrentLocale(locale) + block() + self.stp_resetCurrentLocale() + assert((currentLocale as Locale == NSLocale.current), "Failed to reset locale.") + } + + class func stp_setCurrentLocale(_ locale: NSLocale?) { + swizzleIfNeeded() + STPLocaleSwizzling.stpLocaleOverride = locale + } + + class func stp_resetCurrentLocale() { + swizzleIfNeeded() + self.stp_setCurrentLocale(nil) + } + + @objc class func stp_current() -> NSLocale { + return STPLocaleSwizzling.stpLocaleOverride ?? self.stp_current() + } + + @objc class func stp_autoUpdatingCurrent() -> NSLocale { + return STPLocaleSwizzling.stpLocaleOverride ?? self.stp_autoUpdatingCurrent() + } + + @objc class func stp_system() -> NSLocale { + return STPLocaleSwizzling.stpLocaleOverride ?? self.stp_system() + } +} + +extension NSObject { + class func stp_swizzleClassMethod(_ original: Selector, withReplacement replacement: Selector) { + let `class`: AnyClass? = object_getClass(self) + let originalMethod = class_getClassMethod(self, original) + let replacementMethod = class_getClassMethod(self, replacement) + + var addedMethod = false + if let replacementMethod { + addedMethod = class_addMethod( + `class`, + original, + method_getImplementation(replacementMethod), + method_getTypeEncoding(replacementMethod)) + } + if addedMethod { + if let originalMethod { + class_replaceMethod( + `class`, + replacement, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)) + } + } else { + if let originalMethod, let replacementMethod { + method_exchangeImplementations(originalMethod, replacementMethod) + } + } + } +} diff --git a/Stripe/StripeiOSTests/NSString+StripeTest.swift b/Stripe/StripeiOSTests/NSString+StripeTest.swift new file mode 100644 index 00000000..970cfe9e --- /dev/null +++ b/Stripe/StripeiOSTests/NSString+StripeTest.swift @@ -0,0 +1,120 @@ +// +// NSString+StripeTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 3/22/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +import XCTest + +class NSString_StripeTest: XCTestCase { + + func testIsBlank() { + XCTAssertTrue("".isBlank) + XCTAssertTrue(" ".isBlank) + XCTAssertTrue("\t\t\t".isBlank) + XCTAssertFalse("a".isBlank) + XCTAssertFalse(" a ".isBlank) + } + + func testSafeSubstringToIndex() { + XCTAssertEqual("foo".stp_safeSubstring(to: 0), "") + XCTAssertEqual("foo".stp_safeSubstring(to: 500), "foo") + XCTAssertEqual("foo".stp_safeSubstring(to: 1), "f") + XCTAssertEqual("foo".stp_safeSubstring(to: -1), "") + XCTAssertEqual("foo".stp_safeSubstring(to: -100), "") + XCTAssertEqual("".stp_safeSubstring(to: 0), "") + XCTAssertEqual("".stp_safeSubstring(to: 1), "") + } + + func testSafeSubstringFromIndex() { + XCTAssertEqual("foo".stp_safeSubstring(from: 0), "foo") + XCTAssertEqual("foo".stp_safeSubstring(from: 1), "oo") + XCTAssertEqual("foo".stp_safeSubstring(from: 3), "") + XCTAssertEqual("foo".stp_safeSubstring(from: -1), "foo") + XCTAssertEqual("foo".stp_safeSubstring(from: -100), "foo") + XCTAssertEqual("".stp_safeSubstring(from: 0), "") + XCTAssertEqual("".stp_safeSubstring(from: 1), "") + } + + func testStringByRemovingSuffix() { + XCTAssertEqual("foobar".stp_string(byRemovingSuffix: "bar"), "foo") + XCTAssertEqual("foobar".stp_string(byRemovingSuffix: "baz"), "foobar") + XCTAssertEqual("foobar".stp_string(byRemovingSuffix: nil), "foobar") + XCTAssertEqual("foobar".stp_string(byRemovingSuffix: "foobar"), "") + XCTAssertEqual("foobar".stp_string(byRemovingSuffix: ""), "foobar") + XCTAssertEqual("foobar".stp_string(byRemovingSuffix: "oba"), "foobar") + + XCTAssertEqual("foobar☺¿".stp_string(byRemovingSuffix: "bar☺¿"), "foo") + XCTAssertEqual("foobar☺¿".stp_string(byRemovingSuffix: "bar¿"), "foobar☺¿") + + XCTAssertEqual("foobar\u{202C}".stp_string(byRemovingSuffix: "bar"), "foobar\u{202C}") + XCTAssertEqual("foobar\u{202C}".stp_string(byRemovingSuffix: "bar\u{202C}"), "foo") + + // e + \u0041 => é + XCTAssertEqual("foobare\u{0301}".stp_string(byRemovingSuffix: "bare"), "foobare\u{0301}") + XCTAssertEqual("foobare\u{0301}".stp_string(byRemovingSuffix: "bare\u{0301}"), "foo") + XCTAssertEqual("foobare".stp_string(byRemovingSuffix: "bare\u{0301}"), "foobare") + + } + + func testLocalizedAmountDisplayString() { + XCTAssertEqual(String.localizedAmountDisplayString( + for: 1099, + currency: "USD", + locale: Locale(identifier: "en_US") + ), + "$10.99" + ) + + XCTAssertEqual( + String.localizedAmountDisplayString( + for: 1099, + currency: "USD", + locale: Locale(identifier: "fr_FR") + ), + "10,99 $US" + ) + XCTAssertEqual( + String.localizedAmountDisplayString( + for: 1099, + currency: "USD", + locale: Locale(identifier: "zh_HANT") + ), + "US$10.99" + ) + + XCTAssertEqual( + String.localizedAmountDisplayString( + for: 1099, + currency: "ZZZ", + locale: Locale(identifier: "z") + ), + "ZZZ 10.99" + ) + + XCTAssertEqual( + String.localizedAmountDisplayString( + for: 1000, + currency: "IDR", + locale: Locale(identifier: "en_US") + ), + "IDR 10" + ) + + XCTAssertEqual( + String.localizedAmountDisplayString( + for: 199400, + currency: "ISK", + locale: Locale(identifier: "en_US") + ), + "ISK 1,994" + ) + } +} diff --git a/Stripe/StripeiOSTests/NSURLComponents_StripeTest.swift b/Stripe/StripeiOSTests/NSURLComponents_StripeTest.swift new file mode 100644 index 00000000..86da11a4 --- /dev/null +++ b/Stripe/StripeiOSTests/NSURLComponents_StripeTest.swift @@ -0,0 +1,40 @@ +// +// NSURLComponents_StripeTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/24/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +import XCTest + +class NSURLComponents_StripeTest: XCTestCase { + func testCaseInsensitiveSchemeComparison() { + let lhs = NSURLComponents(string: "com.bar.foo://host")! + let rhs = NSURLComponents(string: "COM.BAR.FOO://HOST")! + XCTAssert(lhs.stp_matchesURLComponents(lhs)) // sanity + XCTAssert(lhs.stp_matchesURLComponents(rhs)) + XCTAssert(rhs.stp_matchesURLComponents(lhs)) + } + + func testMatchesURLsWithQueryString() { + // e.g. STPSourceFunctionalTest passes "https://shop.example.com/crtABC" for the return_url, + // but the Source object returned by the API comes has "https://shop.example.com/crtABC?redirect_merchant_name=xctest" + let expectedComponents = NSURLComponents( + string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest" + )! + let components = NSURLComponents(string: "https://shop.example.com/crtABC")! + XCTAssertTrue(components.stp_matchesURLComponents(expectedComponents)) + } + + func testMatchesURLWithNilParameters() { + let nil1 = NSURLComponents(string: "")! + let nil2 = NSURLComponents(string: "")! + XCTAssert(nil1.stp_matchesURLComponents(nil2)) + } +} diff --git a/Stripe/StripeiOSTests/OneTimeCodeTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/OneTimeCodeTextFieldSnapshotTests.swift new file mode 100644 index 00000000..a965a083 --- /dev/null +++ b/Stripe/StripeiOSTests/OneTimeCodeTextFieldSnapshotTests.swift @@ -0,0 +1,63 @@ +// +// OneTimeCodeTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 11/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class OneTimeCodeTextFieldSnapshotTests: STPSnapshotTestCase { + + func testEmpty() { + let field = OneTimeCodeTextField( + configuration: OneTimeCodeTextField.Configuration( + numberOfDigits: 6 + ), + theme: .default + ) + verify(field) + } + + func testFilled() { + let field = OneTimeCodeTextField( + configuration: OneTimeCodeTextField.Configuration( + numberOfDigits: 6 + ), + theme: .default + ) + field.value = "123456" + verify(field) + } + + func testDisabled() { + let field = OneTimeCodeTextField( + configuration: OneTimeCodeTextField.Configuration( + numberOfDigits: 6 + ), + theme: .default + ) + field.value = "123456" + field.isEnabled = false + verify(field) + } + + func verify( + _ view: UIView, + file: StaticString = #filePath, + line: UInt = #line + ) { + view.autosizeHeight(width: 300) + STPSnapshotVerifyView(view, file: file, line: line) + } +} diff --git a/Stripe/StripeiOSTests/OneTimeCodeTextFieldTests.swift b/Stripe/StripeiOSTests/OneTimeCodeTextFieldTests.swift new file mode 100644 index 00000000..51002390 --- /dev/null +++ b/Stripe/StripeiOSTests/OneTimeCodeTextFieldTests.swift @@ -0,0 +1,327 @@ +// +// OneTimeCodeTextFieldTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 11/5/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class OneTimeCodeTextFieldTests: XCTestCase { + + func test_isComplete() { + let field = makeSUT() + + field.value = "12345" + XCTAssertFalse(field.isComplete) + + field.value = "123456" + XCTAssertTrue(field.isComplete) + } + + func test_insertText() { + let field = makeSUT() + + field.insertText("1") + XCTAssertEqual(field.value, "1") + + field.insertText("2") + XCTAssertEqual(field.value, "12") + XCTAssertEqual(field.selectedTextRange?.start, field.endOfDocument) + XCTAssertEqual(field.selectedTextRange?.end, field.endOfDocument) + } + + func test_insertText_shouldNotInsertBeyondNumberOfDigits() { + let field = makeSUT(numberOfDigits: 4) + field.value = "123" + field.selectedTextRange = field.textRange( + from: field.endOfDocument, + to: field.endOfDocument + ) + field.insertText("45") + XCTAssertEqual(field.value, "1234") + } + + func test_insertText_shouldIgnoreInvalidCharacters() { + let field = makeSUT() + field.insertText("123-456") + XCTAssertEqual(field.value, "123456") + } + + func test_deleteBackward() throws { + let field = makeSUT(value: "12") + + field.selectedTextRange = field.textRange( + from: try XCTUnwrap(field.position(from: field.endOfDocument, in: .left, offset: 1)), + to: field.endOfDocument + ) + field.deleteBackward() + XCTAssertEqual(field.value, "1") + + field.selectedTextRange = field.textRange( + from: try XCTUnwrap(field.position(from: field.endOfDocument, in: .left, offset: 1)), + to: field.endOfDocument + ) + field.deleteBackward() + XCTAssertEqual(field.value, "") + + // Delete while empty + field.selectedTextRange = field.textRange( + from: field.beginningOfDocument, + to: field.endOfDocument + ) + field.deleteBackward() + XCTAssertEqual(field.value, "") + } + +// TODO(RUN_MOBILESDK-1848): This test is broken on iOS 16, as it invokes the pasteboard permission dialog +// func test_paste() { +// UIPasteboard.general.string = "123-456" +// +// let field = makeSUT() +// field.paste(nil) +// XCTAssertEqual(field.value, "123456") +// } + + // MARK: - UITextInput conformance + + func test_beginningOfDocument() throws { + let field = makeSUT(value: "123456") + + let position = try XCTUnwrap( + field.beginningOfDocument as? OneTimeCodeTextField.TextPosition + ) + XCTAssertEqual(position.index, 0) + } + + func test_endOfDocument() throws { + let field = makeSUT(value: "123456") + + let position = try XCTUnwrap(field.endOfDocument as? OneTimeCodeTextField.TextPosition) + XCTAssertEqual(position.index, 6) + } + + func test_textInRange() { + let field = makeSUT(value: "123456") + + let result = field.text( + in: OneTimeCodeTextField.TextRange( + start: OneTimeCodeTextField.TextPosition(0), + end: OneTimeCodeTextField.TextPosition(3) + ) + ) + + XCTAssertEqual(result, "123") + } + + func test_textInRange_emptyRange() { + let field = makeSUT(value: "123456") + + let result = field.text( + in: OneTimeCodeTextField.TextRange( + start: OneTimeCodeTextField.TextPosition(0), + end: OneTimeCodeTextField.TextPosition(0) + ) + ) + + XCTAssertNil(result) + } + + func test_positionFromOffset() { + let field = makeSUT(value: "123456") + + XCTAssertEqual( + field.position(from: field.beginningOfDocument, offset: 3), + OneTimeCodeTextField.TextPosition(3) + ) + + XCTAssertNil( + field.position(from: field.beginningOfDocument, offset: 10), + "Should return nil when offsetting to an out of bounds position" + ) + + XCTAssertNil( + field.position(from: field.beginningOfDocument, offset: -1), + "Should return nil when offsetting to an out of bounds position" + ) + } + + func test_positionInDirection() { + let field = makeSUT(value: "123456") + + XCTAssertEqual( + field.position(from: field.beginningOfDocument, in: .right, offset: 1), + OneTimeCodeTextField.TextPosition(1) + ) + + XCTAssertEqual( + field.position(from: field.endOfDocument, in: .left, offset: 1), + OneTimeCodeTextField.TextPosition(5) + ) + + // Y axis + XCTAssertEqual( + field.position(from: field.beginningOfDocument, in: .up, offset: 1), + field.beginningOfDocument + ) + + XCTAssertEqual( + field.position(from: field.beginningOfDocument, in: .down, offset: 1), + field.endOfDocument + ) + } + + func test_compare() { + let field = makeSUT(value: "123456") + + XCTAssertEqual( + field.compare(field.beginningOfDocument, to: field.beginningOfDocument), + .orderedSame + ) + + XCTAssertEqual( + field.compare(field.beginningOfDocument, to: field.endOfDocument), + .orderedAscending + ) + + XCTAssertEqual( + field.compare(field.endOfDocument, to: field.beginningOfDocument), + .orderedDescending + ) + } + + func test_offsetToPosition() { + let field = makeSUT(value: "123456") + + XCTAssertEqual(field.offset(from: field.beginningOfDocument, to: field.endOfDocument), 6) + XCTAssertEqual(field.offset(from: field.endOfDocument, to: field.beginningOfDocument), -6) + XCTAssertEqual( + field.offset(from: field.beginningOfDocument, to: OneTimeCodeTextField.TextPosition(3)), + 3 + ) + } + + func test_positionFarthestInDirection() throws { + let field = makeSUT(value: "123456") + + let position = try XCTUnwrap( + OneTimeCodeTextField.TextRange( + start: field.beginningOfDocument, + end: field.endOfDocument + ) + ) + + XCTAssertEqual( + field.position(within: position, farthestIn: .left), + field.beginningOfDocument + ) + + XCTAssertEqual( + field.position(within: position, farthestIn: .right), + field.endOfDocument + ) + + // Y axis + XCTAssertEqual( + field.position(within: position, farthestIn: .up), + field.beginningOfDocument + ) + + XCTAssertEqual( + field.position(within: position, farthestIn: .down), + field.endOfDocument + ) + } + + func test_characterRangeByExtendingInDirection() throws { + let field = makeSUT(value: "123456") + + let position = OneTimeCodeTextField.TextPosition(3) + + XCTAssertEqual( + field.characterRange(byExtending: position, in: .left), + OneTimeCodeTextField.TextRange(start: field.beginningOfDocument, end: position) + ) + + XCTAssertEqual( + field.characterRange(byExtending: position, in: .right), + OneTimeCodeTextField.TextRange(start: position, end: field.endOfDocument) + ) + + // Y axis + XCTAssertNil(field.characterRange(byExtending: position, in: .up)) + XCTAssertNil(field.characterRange(byExtending: position, in: .down)) + } + + func test_firstRectForRange_singleDigit() { + let sut = makeSUT(value: "123456") + + // A [0,1] text range + let range = OneTimeCodeTextField.TextRange( + start: OneTimeCodeTextField.TextPosition(0), + end: OneTimeCodeTextField.TextPosition(1) + ) + let rect = sut.firstRect(for: range) + XCTAssertEqual(rect.minX, 0, accuracy: 0.2) + XCTAssertEqual(rect.minY, 0, accuracy: 0.2) + XCTAssertEqual(rect.width, 46.0, accuracy: 0.2) + XCTAssertEqual(rect.height, 60, accuracy: 0.2) + } + + func test_firstRectForRange_multipleDigits() { + let sut = makeSUT(value: "123456") + + // A [0,3] Text range + let range = OneTimeCodeTextField.TextRange( + start: OneTimeCodeTextField.TextPosition(0), + end: OneTimeCodeTextField.TextPosition(3) + ) + let rect = sut.firstRect(for: range) + XCTAssertEqual(rect.minX, 0, accuracy: 0.2) + XCTAssertEqual(rect.minY, 0, accuracy: 0.2) + XCTAssertEqual(rect.width, 150, accuracy: 0.2) + XCTAssertEqual(rect.height, 60, accuracy: 0.2) + } + + func test_caretRectForPosition() { + let sut = makeSUT() + let frame = sut.caretRect(for: OneTimeCodeTextField.TextPosition(1)) + XCTAssertEqual(frame.minX, 74, accuracy: 0.2) + XCTAssertEqual(frame.minY, 20.47, accuracy: 0.2) + XCTAssertEqual(frame.width, 2, accuracy: 0.2) + XCTAssertEqual(frame.height, 19.04, accuracy: 0.2) + } + +} + +// MARK: - Factory methods + +extension OneTimeCodeTextFieldTests { + + fileprivate func makeSUT(numberOfDigits: Int = 6) -> OneTimeCodeTextField { + let sut = OneTimeCodeTextField( + configuration: OneTimeCodeTextField.Configuration( + numberOfDigits: numberOfDigits + ), + theme: .default + ) + sut.frame = CGRect(x: 0, y: 0, width: 320, height: 60) + sut.layoutIfNeeded() + return sut + } + + fileprivate func makeSUT(value: String) -> OneTimeCodeTextField { + let sut = makeSUT() + sut.value = value + return sut + } + +} diff --git a/Stripe/StripeiOSTests/OperationDebouncerTests.swift b/Stripe/StripeiOSTests/OperationDebouncerTests.swift new file mode 100644 index 00000000..7a6f3ae0 --- /dev/null +++ b/Stripe/StripeiOSTests/OperationDebouncerTests.swift @@ -0,0 +1,45 @@ +// +// OperationDebouncerTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 1/23/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class OperationDebouncerTests: XCTestCase { + + func testEnqueueShouldDebounce() { + let sut = makeSUT() + + let expectation = self.expectation(description: "Should execute the block just once") + expectation.assertForOverFulfill = true + + // Call `enqueue(block:)` 3 times + for _ in 0..<3 { + sut.enqueue { + expectation.fulfill() + } + } + + Thread.sleep(forTimeInterval: 1) + + wait(for: [expectation], timeout: 1) + } + +} + +extension OperationDebouncerTests { + + func makeSUT() -> OperationDebouncer { + return OperationDebouncer(debounceTime: .milliseconds(500)) + } + +} diff --git a/Stripe/StripeiOSTests/PKPayment+StripeTest.swift b/Stripe/StripeiOSTests/PKPayment+StripeTest.swift new file mode 100644 index 00000000..c71eee52 --- /dev/null +++ b/Stripe/StripeiOSTests/PKPayment+StripeTest.swift @@ -0,0 +1,36 @@ +// +// PKPayment+StripeTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 7/6/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +import PassKit +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class PKPayment_StripeTest: XCTestCase { + func testIsSimulated() { + let payment = PKPayment() + let paymentToken = PKPaymentToken() + + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wundeclared-selector" + paymentToken.perform(Selector(("setTransactionIdentifier:")), with: "Simulated Identifier") + payment.perform(#selector(setter: STPPaymentMethodCardParams.token), with: paymentToken) + // #pragma clang diagnostic pop + + XCTAssertTrue(payment.stp_isSimulated()) + } + + func testTransactionIdentifier() { + let identifier = PKPayment.stp_testTransactionIdentifier() + XCTAssertTrue(identifier.contains("ApplePayStubs~4242424242424242~0~USD~")) + } +} diff --git a/Stripe/StripeiOSTests/PayWithLinkButtonSnapshotTests.swift b/Stripe/StripeiOSTests/PayWithLinkButtonSnapshotTests.swift new file mode 100644 index 00000000..2b5cb94c --- /dev/null +++ b/Stripe/StripeiOSTests/PayWithLinkButtonSnapshotTests.swift @@ -0,0 +1,106 @@ +// +// PayWithLinkButtonSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 11/17/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class PayWithLinkButtonSnapshotTests: STPSnapshotTestCase { + + private let emailAddress = "customer@example.com" + private let longEmailAddress = "long.customer.name@example.com" + + func testDefault() { + let sut = makeSUT() + sut.linkAccount = makeAccountStub(email: emailAddress, isRegistered: false) + verify(sut) + + sut.isHighlighted = true + verify(sut, identifier: "Highlighted") + } + + func testDefault_rounded() { + let sut = makeSUT() + sut.cornerRadius = 16 + sut.linkAccount = makeAccountStub(email: emailAddress, isRegistered: false) + verify(sut) + } + + func testDisabled() { + let sut = makeSUT() + sut.isEnabled = false + verify(sut) + } + + func testRegistered() { + let sut = makeSUT() + sut.linkAccount = makeAccountStub(email: emailAddress, isRegistered: true) + verify(sut) + } + + func testRegistered_rounded() { + let sut = makeSUT() + sut.cornerRadius = 16 + sut.linkAccount = makeAccountStub(email: emailAddress, isRegistered: true) + verify(sut) + } + + func testRegistered_square() { + let sut = makeSUT() + sut.cornerRadius = 0 + sut.linkAccount = makeAccountStub(email: emailAddress, isRegistered: true) + verify(sut) + } + + func testRegistered_withLongEmailAddress() { + let sut = makeSUT() + sut.linkAccount = makeAccountStub(email: longEmailAddress, isRegistered: true) + verify(sut) + } + + func verify( + _ sut: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + sut.autosizeHeight(width: 300) + STPSnapshotVerifyView(sut, identifier: identifier, file: file, line: line) + } + +} + +extension PayWithLinkButtonSnapshotTests { + + fileprivate struct LinkAccountStub: PaymentSheetLinkAccountInfoProtocol { + let email: String + let isRegistered: Bool + var redactedPhoneNumber: String? + var isLoggedIn: Bool + } + + fileprivate func makeAccountStub(email: String, isRegistered: Bool) -> LinkAccountStub { + return LinkAccountStub( + email: email, + isRegistered: isRegistered, + redactedPhoneNumber: "+1********55", + isLoggedIn: true + ) + } + + fileprivate func makeSUT() -> PayWithLinkButton { + return PayWithLinkButton() + } + +} diff --git a/Stripe/StripeiOSTests/PaymentTypeCellSnapshotTests.swift b/Stripe/StripeiOSTests/PaymentTypeCellSnapshotTests.swift new file mode 100644 index 00000000..aa6d1bf5 --- /dev/null +++ b/Stripe/StripeiOSTests/PaymentTypeCellSnapshotTests.swift @@ -0,0 +1,76 @@ +// +// PaymentTypeCellSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 12/17/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class PaymentTypeCellSnapshotTests: STPSnapshotTestCase { + + func testCardUnselected() { + let cell = PaymentMethodTypeCollectionView.PaymentTypeCell() + cell.paymentMethodType = .stripe(.card) + cell.frame = CGRect( + origin: .zero, + size: CGSize( + width: PaymentMethodTypeCollectionView.cellHeight, + height: PaymentMethodTypeCollectionView.cellHeight + ) + ) + STPSnapshotVerifyView(cell) + } + + func testCardSelected() { + let cell = PaymentMethodTypeCollectionView.PaymentTypeCell() + cell.paymentMethodType = .stripe(.card) + cell.frame = CGRect( + origin: .zero, + size: CGSize( + width: PaymentMethodTypeCollectionView.cellHeight, + height: PaymentMethodTypeCollectionView.cellHeight + ) + ) + cell.isSelected = true + STPSnapshotVerifyView(cell) + } + + func testCardUnselected_forceDarkMode() { + let cell = PaymentMethodTypeCollectionView.PaymentTypeCell() + cell.overrideUserInterfaceStyle = .dark + cell.paymentMethodType = .stripe(.card) + cell.frame = CGRect( + origin: .zero, + size: CGSize( + width: PaymentMethodTypeCollectionView.cellHeight, + height: PaymentMethodTypeCollectionView.cellHeight + ) + ) + STPSnapshotVerifyView(cell) + } + + func testCardSelected_forceDarkMode() { + let cell = PaymentMethodTypeCollectionView.PaymentTypeCell() + cell.appearance.colors.componentBackground = .black + cell.overrideUserInterfaceStyle = .dark + cell.paymentMethodType = .stripe(.card) + cell.frame = CGRect( + origin: .zero, + size: CGSize( + width: PaymentMethodTypeCollectionView.cellHeight, + height: PaymentMethodTypeCollectionView.cellHeight + ) + ) + cell.isSelected = true + STPSnapshotVerifyView(cell) + } +} diff --git a/Stripe/StripeiOSTests/Resources/Images.xcassets/Contents.json b/Stripe/StripeiOSTests/Resources/Images.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Stripe/StripeiOSTests/Resources/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stripe/StripeiOSTests/Resources/MockFiles/paymentIntentResponse.json b/Stripe/StripeiOSTests/Resources/MockFiles/paymentIntentResponse.json new file mode 100644 index 00000000..53200cdf --- /dev/null +++ b/Stripe/StripeiOSTests/Resources/MockFiles/paymentIntentResponse.json @@ -0,0 +1,53 @@ +{ + "id": "pi_3LN3c2L123456789", + "object": "payment_intent", + "amount": 5099, + "amount_details": { + "tip": {} + }, + "automatic_payment_methods": null, + "canceled_at": null, + "cancellation_reason": null, + "capture_method": "automatic", + "client_secret": "pi_123456_secret_654321", + "confirmation_method": "automatic", + "created": 1658187870, + "currency": "usd", + "description": null, + "last_payment_error": null, + "livemode": false, + "next_action": , + "payment_method": , + "payment_method_options": { + "us_bank_account": { + "verification_method": "automatic" + } + }, + "payment_method_types": [ + "card", + "afterpay_clearpay", + "klarna", + "us_bank_account", + "affirm", + "blik" + ], + "processing": null, + "receipt_email": null, + "setup_future_usage": null, + "shipping": { + "address": { + "city": "San Francisco", + "country": "US", + "line1": "510 Townsend St", + "line2": null, + "postal_code": "94102", + "state": "California" + }, + "carrier": null, + "name": "John Doe", + "phone": null, + "tracking_number": null + }, + "source": null, + "status": +} diff --git a/Stripe/StripeiOSTests/Resources/stp_test_upload_image.jpeg b/Stripe/StripeiOSTests/Resources/stp_test_upload_image.jpeg new file mode 100644 index 00000000..bb7e0e73 Binary files /dev/null and b/Stripe/StripeiOSTests/Resources/stp_test_upload_image.jpeg differ diff --git a/Stripe/StripeiOSTests/RotatingCardBrandsViewSnapshotTests.swift b/Stripe/StripeiOSTests/RotatingCardBrandsViewSnapshotTests.swift new file mode 100644 index 00000000..0f68bef7 --- /dev/null +++ b/Stripe/StripeiOSTests/RotatingCardBrandsViewSnapshotTests.swift @@ -0,0 +1,32 @@ +// +// RotatingCardBrandsViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 6/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) import StripePayments +import XCTest + +@testable import Stripe +@testable @_spi(STP) import StripePaymentSheet + +class RotatingCardBrandsViewSnapshotTests: STPSnapshotTestCase { + func testAllCardBrands() { + let rotatingCardBrandsView = RotatingCardBrandsView() + rotatingCardBrandsView.cardBrands = RotatingCardBrandsView.orderedCardBrands(from: STPCardBrand.allCases) + rotatingCardBrandsView.autosizeHeight(width: 140) + STPSnapshotVerifyView(rotatingCardBrandsView) + } + + func testSingleCardBrand() { + let rotatingCardBrandsView = RotatingCardBrandsView() + rotatingCardBrandsView.cardBrands = [.visa] + rotatingCardBrandsView.autosizeHeight(width: 140) + STPSnapshotVerifyView(rotatingCardBrandsView) + } + +} diff --git a/Stripe/StripeiOSTests/RotatingCardBrandsViewTests.swift b/Stripe/StripeiOSTests/RotatingCardBrandsViewTests.swift new file mode 100644 index 00000000..b52ff221 --- /dev/null +++ b/Stripe/StripeiOSTests/RotatingCardBrandsViewTests.swift @@ -0,0 +1,42 @@ +// +// RotatingCardBrandsViewTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 6/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripePayments +import XCTest + +@testable import Stripe +@testable @_spi(STP) import StripePaymentSheet + +class RotatingCardBrandsViewTests: XCTestCase { + + func testOrdering() { + XCTAssertEqual([.visa, + .mastercard, + .amex, + .discover, + .dinersClub, + .JCB, + .unionPay, + ], RotatingCardBrandsView.orderedCardBrands(from: STPCardBrand.allCases)) + } + + func testRotatesOnMoreThreeOrMoreBrands() { + let rotatingCardBrandsView = RotatingCardBrandsView() + rotatingCardBrandsView.cardBrands = [.visa] + XCTAssertTrue(rotatingCardBrandsView.rotatingCardBrandView.isHidden) + rotatingCardBrandsView.cardBrands = [.visa, .mastercard] + XCTAssertTrue(rotatingCardBrandsView.rotatingCardBrandView.isHidden) + rotatingCardBrandsView.cardBrands = [.visa, .mastercard, .amex] + XCTAssertTrue(rotatingCardBrandsView.rotatingCardBrandView.isHidden) + rotatingCardBrandsView.cardBrands = [.visa, .mastercard, .amex, .dinersClub] + XCTAssertFalse(rotatingCardBrandsView.rotatingCardBrandView.isHidden) + rotatingCardBrandsView.cardBrands = [.visa, .mastercard, .amex, .dinersClub, .JCB] + XCTAssertFalse(rotatingCardBrandsView.rotatingCardBrandView.isHidden) + } + +} diff --git a/Stripe/StripeiOSTests/STPAPIClient+LinkAccountSessionTest.swift b/Stripe/StripeiOSTests/STPAPIClient+LinkAccountSessionTest.swift new file mode 100644 index 00000000..636f4eed --- /dev/null +++ b/Stripe/StripeiOSTests/STPAPIClient+LinkAccountSessionTest.swift @@ -0,0 +1,29 @@ +// +// STPAPIClient+LinkAccountSessionTest.swift +// StripeiOSTests +// +// Created by Yuki Tokuhiro on 4/26/23. +// + +@testable @_spi(STP) import StripePayments +import XCTest + +final class STPAPIClient_LinkAccountSessionTest: XCTestCase { + + func testCreateLinkAccountSessionForDeferredIntent() { + let e = expectation(description: "create link account session") + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + apiClient.createLinkAccountSessionForDeferredIntent( + sessionId: "mobile_test_\(UUID().uuidString)", + amount: nil, + currency: nil, + onBehalfOf: nil, + linkMode: nil + ) { linkAccountSession, error in + XCTAssertNil(error) + XCTAssertNotNil(linkAccountSession) + e.fulfill() + } + waitForExpectations(timeout: 10) + } +} diff --git a/Stripe/StripeiOSTests/STPAPIClientNetworkBridgeTest.swift b/Stripe/StripeiOSTests/STPAPIClientNetworkBridgeTest.swift new file mode 100644 index 00000000..e6bb42aa --- /dev/null +++ b/Stripe/StripeiOSTests/STPAPIClientNetworkBridgeTest.swift @@ -0,0 +1,412 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPAPIClientNetworkBridgeTest.m +// StripeiOS +// +// Created by David Estes on 9/23/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import PassKit +import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@_spi(STP) import StripePayments +import StripePaymentsTestUtils +import XCTest + +class StripeAPIBridgeNetworkTest: STPNetworkStubbingTestCase { + var client: STPAPIClient! + + override func setUp() { + super.setUp() + client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + } + + // MARK: Bank Account + func testCreateTokenWithBankAccount() { + let exp = expectation(description: "Request complete") + let params = STPBankAccountParams() + params.accountNumber = "000123456789" + params.routingNumber = "110000000" + params.country = "US" + + client?.createToken(withBankAccount: params) { token, error in + XCTAssertNotNil(token) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: PII + + func testCreateTokenWithPII() { + let exp = expectation(description: "Create token") + + client?.createToken(withPersonalIDNumber: "123456789") { token, error in + XCTAssertNotNil(token) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateTokenWithSSNLast4() { + let exp = expectation(description: "Create SSN") + + client?.createToken(withSSNLast4: "1234") { token, error in + XCTAssertNotNil(token) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Connect Accounts + + func testCreateConnectAccount() { + let exp = expectation(description: "Create connect account") + let companyParams = STPConnectAccountCompanyParams() + companyParams.name = "Company" + let params = STPConnectAccountParams(company: companyParams) + client?.createToken(withConnectAccount: params) { token, error in + XCTAssertNotNil(token) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Upload + + func testUploadFile() { + let exp = expectation(description: "Upload file") + let image = UIImage( + named: "stp_test_upload_image.jpeg", + in: Bundle(for: StripeAPIBridgeNetworkTest.self), + compatibleWith: nil)! + + client?.uploadImage(image, purpose: .disputeEvidence) { file, error in + XCTAssertNotNil(file) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Credit Cards + + func testCardToken() { + let exp = expectation(description: "Create card token") + let params = STPCardParams() + params.number = "4242424242424242" + params.expYear = 42 + params.expMonth = 12 + params.cvc = "123" + + client?.createToken(withCard: params) { token, error in + XCTAssertNotNil(token) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCVCUpdate() { + let exp = expectation(description: "CVC Update") + + client?.createToken(forCVCUpdate: "123") { token, error in + XCTAssertNotNil(token) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Sources + + func testCreateRetrieveAndPollSource() { + let exp = expectation(description: "Upload file") + let expR = expectation(description: "Retrieve source") + let expP = expectation(description: "Poll source") + + let card = STPCardParams() + card.number = "4242424242424242" + card.expYear = 42 + card.expMonth = 12 + card.cvc = "123" + + let params = STPSourceParams.cardParams(withCard: card) + + client.createSource(with: params) { [self] source, error in + guard let source = source else { + XCTFail() + return + } + XCTAssertNil(error) + exp.fulfill() + + client?.retrieveSource(withId: source.stripeID, clientSecret: source.clientSecret!) { source2, error2 in + XCTAssertNotNil(source2) + XCTAssertNil(error2) + expR.fulfill() + } + + client?.startPollingSource(withId: source.stripeID, clientSecret: source.clientSecret!, timeout: 10) { [self] source2, error2 in + XCTAssertNotNil(source2) + XCTAssertNil(error2) + client?.stopPollingSource(withId: source.stripeID) + expP.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Payment Intents + + func testRetrievePaymentIntent() { + let exp = expectation(description: "Fetch") + let exp2 = expectation(description: "Fetch with expansion") + + let testClient = STPTestingAPIClient.shared() + testClient.createPaymentIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + client?.retrievePaymentIntent(withClientSecret: clientSecret!) { pi, error2 in + XCTAssertNotNil(pi) + XCTAssertNil(error2) + exp.fulfill() + } + + client?.retrievePaymentIntent(withClientSecret: clientSecret!, expand: ["metadata"]) { pi, error2 in + XCTAssertNotNil(pi) + XCTAssertNil(error2) + exp2.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmPaymentIntent() { + let exp = expectation(description: "Confirm") + let exp2 = expectation(description: "Confirm with expansion") + let testClient = STPTestingAPIClient.shared() + + let card = STPPaymentMethodCardParams() + card.number = "4242424242424242" + card.expYear = NSNumber(value: 42) + card.expMonth = NSNumber(value: 12) + card.cvc = "123" + + testClient.createPaymentIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + params.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + client?.confirmPaymentIntent(with: params) { pi, error2 in + XCTAssertNotNil(pi) + XCTAssertNil(error2) + exp.fulfill() + } + } + + testClient.createPaymentIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + params.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + client?.confirmPaymentIntent(with: params) { pi, error2 in + XCTAssertNotNil(pi) + XCTAssertNil(error2) + exp2.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRefreshPaymentIntent() { + let exp = expectation(description: "Refresh") + + let testClient = STPTestingAPIClient.shared() + testClient.createPaymentIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + client?.refreshPaymentIntent(withClientSecret: clientSecret!) { pi, error2 in + XCTAssertNotNil(pi) + XCTAssertNil(error2) + exp.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Setup Intents + + func testRetrieveSetupIntent() { + let exp = expectation(description: "Fetch") + + let testClient = STPTestingAPIClient.shared() + testClient.createSetupIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + client?.retrieveSetupIntent(withClientSecret: clientSecret!) { si, error2 in + XCTAssertNotNil(si) + XCTAssertNil(error2) + exp.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmSetupIntent() { + let exp = expectation(description: "Confirm") + let testClient = STPTestingAPIClient.shared() + + let card = STPPaymentMethodCardParams() + card.number = "4242424242424242" + card.expYear = NSNumber(value: 42) + card.expMonth = NSNumber(value: 12) + card.cvc = "123" + + testClient.createSetupIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + let params = STPSetupIntentConfirmParams(clientSecret: clientSecret!) + params.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + client?.confirmSetupIntent(with: params) { si, error2 in + XCTAssertNotNil(si) + XCTAssertNil(error2) + exp.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRefreshSetupIntent() { + let exp = expectation(description: "Refresh") + + let testClient = STPTestingAPIClient.shared() + testClient.createSetupIntent(withParams: nil) { [self] clientSecret, error in + XCTAssertNil(error) + + client?.refreshSetupIntent(withClientSecret: clientSecret!) { si, error2 in + XCTAssertNotNil(si) + XCTAssertNil(error2) + exp.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Payment Methods + + func testCreatePaymentMethod() { + let exp = expectation(description: "Create PaymentMethod") + + let card = STPPaymentMethodCardParams() + card.number = "4242424242424242" + card.expYear = NSNumber(value: 42) + card.expMonth = NSNumber(value: 12) + card.cvc = "123" + + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + client?.createPaymentMethod(with: params) { pm, error in + XCTAssertNotNil(pm) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: Radar + + func testCreateRadarSession() { + let exp = expectation(description: "Create session") + + // Set fake SID/MUID to make this test replicable + FraudDetectionData.shared.sid = "123" + FraudDetectionData.shared.muid = "123" + FraudDetectionData.shared.sidCreationDate = Date() + + client?.createRadarSession { session, error in + XCTAssertNotNil(session) + XCTAssertNil(error) + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: ApplePay + + func testCreateApplePayToken() { + let exp = expectation(description: "CreateToken") + let exp2 = expectation(description: "CreateSource") + let exp3 = expectation(description: "CreatePM") + let payment = STPFixtures.applePayPayment() + client?.createToken(with: payment) { token, error in + // The certificate used to sign our fake Apple Pay test payment is invalid, which makes sense. + // Expect an error. + XCTAssertNil(token) + XCTAssertNotNil(error) + exp.fulfill() + } + + client?.createSource(with: payment) { source, error in + XCTAssertNil(source) + XCTAssertNotNil(error) + exp2.fulfill() + } + + client?.createPaymentMethod(with: payment) { pm, error in + XCTAssertNil(pm) + XCTAssertNotNil(error) + exp3.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testPKPaymentError() { + let exp = expectation(description: "Upload file") + let params = STPCardParams() + params.number = "4242424242424242" + params.expYear = 20 + params.expMonth = 12 + params.cvc = "123" + + client?.createToken(withCard: params) { token, error in + XCTAssertNil(token) + XCTAssertNotNil(error) + XCTAssertNotNil(STPAPIClient.pkPaymentError(forStripeError: error)) + + exp.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} + +// These are a little redundant with the existing +// API tests, but it's a good way to test that the +// bridge works correctly. diff --git a/Stripe/StripeiOSTests/STPAPIClientStubbedTest.swift b/Stripe/StripeiOSTests/STPAPIClientStubbedTest.swift new file mode 100644 index 00000000..c2fd8a5f --- /dev/null +++ b/Stripe/StripeiOSTests/STPAPIClientStubbedTest.swift @@ -0,0 +1,313 @@ +// +// STPAPIClientStubbedTest.swift +// StripeiOS Tests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet + +class STPAPIClientStubbedTest: APIStubbedTestCase { + func testCreatePaymentMethodWithAdditionalPaymentUserAgentValues() { + let sut = stubbedAPIClient() + stub { urlRequest in + guard let queryItems = urlRequest.queryItems else { + return false + } + XCTAssertTrue(queryItems.contains(where: { item in + // The additional payment user agent values "foo" and "bar" should be in the payment_user_agent field + item.name == "payment_user_agent" && item.value!.hasSuffix("%3B%20foo%3B%20bar") + })) + return true + } response: { _ in + return .init() + } + let e = expectation(description: "") + sut.createPaymentMethod(with: ._testValidCardValue(), additionalPaymentUserAgentValues: ["foo", "bar"]) { _, _ in + e.fulfill() + } + waitForExpectations(timeout: 10) + } + + func testSetupIntent_LinkAccountSessionForUSBankAccount() { + let sut = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains( + "/setup_intents/seti_12345/link_account_sessions" + ) ?? false + } response: { urlRequest in + guard let data = urlRequest.httpBodyOrBodyStream, + let body = String(data: data, encoding: .utf8) + else { + return HTTPStubsResponse( + data: "".data(using: .utf8)!, + statusCode: 400, + headers: nil + ) + } + XCTAssert(body.contains("client_secret=si_client_secret_123")) + XCTAssert( + body.contains( + "payment_method_data%5Bbilling_details%5D%5Bemail%5D=test%40example.com" + ) + ) + XCTAssert( + body.contains("payment_method_data%5Bbilling_details%5D%5Bname%5D=Test%20Tester") + ) + XCTAssert(body.contains("payment_method_data%5Btype%5D=us_bank_account")) + + let jsonText = """ + { + "id": "xxxxx", + "object": "link_account_session", + "client_secret": "las_client_secret_123456", + "linked_accounts": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/linked_accounts" + }, + "livemode": false + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let expectCallback = expectation(description: "bindings serialize/deserialize") + sut.createLinkAccountSession( + setupIntentID: "seti_12345", + clientSecret: "si_client_secret_123", + paymentMethodType: .USBankAccount, + customerName: "Test Tester", + customerEmailAddress: "test@example.com", + linkMode: nil + ) { intent, _ in + guard let intent = intent else { + XCTFail("Intent was null") + return + } + XCTAssertEqual(intent.clientSecret, "las_client_secret_123456") + expectCallback.fulfill() + } + + wait(for: [expectCallback], timeout: 2.0) + } + + func testPaymentIntent_LinkAccountSessionForUSBankAccount() { + let sut = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains( + "/payment_intents/pi_12345/link_account_sessions" + ) ?? false + } response: { urlRequest in + guard let data = urlRequest.httpBodyOrBodyStream, + let body = String(data: data, encoding: .utf8) + else { + return HTTPStubsResponse( + data: "".data(using: .utf8)!, + statusCode: 400, + headers: nil + ) + } + XCTAssert(body.contains("client_secret=si_client_secret_123")) + XCTAssert( + body.contains( + "payment_method_data%5Bbilling_details%5D%5Bemail%5D=test%40example.com" + ) + ) + XCTAssert( + body.contains("payment_method_data%5Bbilling_details%5D%5Bname%5D=Test%20Tester") + ) + XCTAssert(body.contains("payment_method_data%5Btype%5D=us_bank_account")) + + let jsonText = """ + { + "id": "las_12345", + "object": "link_account_session", + "client_secret": "las_client_secret_654321", + "linked_accounts": { + "object": "list", + "data": [ + + ], + "has_more": false, + "total_count": 0, + "url": "/v1/linked_accounts" + }, + "livemode": false + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let expectCallback = expectation(description: "bindings serialize/deserialize") + sut.createLinkAccountSession( + paymentIntentID: "pi_12345", + clientSecret: "si_client_secret_123", + paymentMethodType: .USBankAccount, + customerName: "Test Tester", + customerEmailAddress: "test@example.com", + linkMode: nil + ) { intent, _ in + guard let intent = intent else { + XCTFail("Intent was null") + return + } + XCTAssertEqual(intent.clientSecret, "las_client_secret_654321") + expectCallback.fulfill() + } + + wait(for: [expectCallback], timeout: 2.0) + } + + func testSetupIntent_LinkAccountSessionAttach() { + let sut = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains( + "/setup_intents/seti_12345/link_account_sessions/las_123456/attach" + ) ?? false + } response: { urlRequest in + guard let data = urlRequest.httpBodyOrBodyStream, + let body = String(data: data, encoding: .utf8) + else { + return HTTPStubsResponse( + data: "".data(using: .utf8)!, + statusCode: 400, + headers: nil + ) + } + XCTAssert(body.contains("client_secret=si_client_secret_123")) + + let jsonText = """ + { + "id": "seti_12345", + "object": "setup_intent", + "cancellation_reason": null, + "client_secret": "seti_abc_secret_def", + "created": 1647000000, + "description": null, + "last_setup_error": null, + "livemode": false, + "next_action": null, + "payment_method": "pm_abcdefg", + "payment_method_options": { + "us_bank_account": { + "verification_method": "instant" + } + }, + "payment_method_types": [ + "us_bank_account" + ], + "status": "requires_confirmation", + "usage": "off_session" + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let expectCallback = expectation(description: "bindings serialize/deserialize") + sut.attachLinkAccountSession( + setupIntentID: "seti_12345", + linkAccountSessionID: "las_123456", + clientSecret: "si_client_secret_123" + ) { intent, _ in + guard let intent = intent else { + XCTFail("Intent was null") + return + } + XCTAssertEqual(intent.paymentMethodID, "pm_abcdefg") + expectCallback.fulfill() + } + + wait(for: [expectCallback], timeout: 2.0) + } + + func testPaymentIntent_LinkAccountSessionAttach() { + let sut = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains( + "/payment_intents/pi_12345/link_account_sessions/las_123456/attach" + ) ?? false + } response: { urlRequest in + guard let data = urlRequest.httpBodyOrBodyStream, + let body = String(data: data, encoding: .utf8) + else { + return HTTPStubsResponse( + data: "".data(using: .utf8)!, + statusCode: 400, + headers: nil + ) + } + XCTAssert(body.contains("client_secret=pi_client_secret_123")) + + let jsonText = """ + { + "id": "pi_12345", + "object": "payment_intent", + "amount": 100, + "currency": "usd", + "cancellation_reason": null, + "client_secret": "seti_abc_secret_def", + "created": 1647000000, + "description": null, + "last_setup_error": null, + "livemode": false, + "next_action": null, + "payment_method": "pm_abcdefg", + "payment_method_options": { + "us_bank_account": { + "verification_method": "instant" + } + }, + "payment_method_types": [ + "us_bank_account" + ], + "status": "requires_payment_method" + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let expectCallback = expectation(description: "bindings serialize/deserialize") + sut.attachLinkAccountSession( + paymentIntentID: "pi_12345", + linkAccountSessionID: "las_123456", + clientSecret: "pi_client_secret_123" + ) { intent, _ in + guard let intent = intent else { + XCTFail("Intent was null") + return + } + XCTAssertEqual(intent.paymentMethodId, "pm_abcdefg") + expectCallback.fulfill() + } + + wait(for: [expectCallback], timeout: 2.0) + } +} diff --git a/Stripe/StripeiOSTests/STPAPIClientTest.swift b/Stripe/StripeiOSTests/STPAPIClientTest.swift new file mode 100644 index 00000000..24f19f61 --- /dev/null +++ b/Stripe/StripeiOSTests/STPAPIClientTest.swift @@ -0,0 +1,133 @@ +// +// STPAPIClientTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 12/19/14. +// Copyright (c) 2014 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPAPIClientTest: XCTestCase { + func testSharedClient() { + XCTAssert(STPAPIClient.shared === STPAPIClient.shared) + } + + func testSetDefaultPublishableKey() { + let clientInitializedBefore = STPAPIClient() + StripeAPI.defaultPublishableKey = "test" + let clientInitializedAfter = STPAPIClient() + let sharedClient = STPAPIClient.shared + XCTAssertEqual(clientInitializedBefore.publishableKey, "test") + XCTAssertEqual(clientInitializedAfter.publishableKey, "test") + + // Setting the STPAPIClient instance overrides Stripe.defaultPublishableKey... + sharedClient.publishableKey = "test2" + XCTAssertEqual(sharedClient.publishableKey, "test2") + + // ...while Stripe.defaultPublishableKey remains the same + XCTAssertEqual(StripeAPI.defaultPublishableKey, "test") + } + + func testInitWithPublishableKey() { + let sut = STPAPIClient(publishableKey: "pk_foo") + let authHeader = sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: [:] + ).allHTTPHeaderFields?["Authorization"] + XCTAssertEqual(authHeader, "Bearer pk_foo") + } + + func testSetPublishableKey() { + let sut = STPAPIClient(publishableKey: "pk_foo") + var authHeader = sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: [:] + ).allHTTPHeaderFields?["Authorization"] + XCTAssertEqual(authHeader, "Bearer pk_foo") + sut.publishableKey = "pk_bar" + authHeader = + sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: [:] + ) + .allHTTPHeaderFields?["Authorization"] + XCTAssertEqual(authHeader, "Bearer pk_bar") + } + + func testEphemeralKeyOverwritesHeader() { + let sut = STPAPIClient(publishableKey: "pk_foo") + let ephemeralKey = STPFixtures.ephemeralKey() + let additionalHeaders = sut.authorizationHeader(using: ephemeralKey) + let authHeader = sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: additionalHeaders + ).allHTTPHeaderFields?["Authorization"] + XCTAssertEqual(authHeader, "Bearer " + (ephemeralKey.secret)) + } + + func testSetStripeAccount() { + let sut = STPAPIClient(publishableKey: "pk_foo") + var accountHeader = sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: [:] + ).allHTTPHeaderFields?["Stripe-Account"] + XCTAssertNil(accountHeader) + sut.stripeAccount = "acct_123" + accountHeader = + sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: [:] + ) + .allHTTPHeaderFields?["Stripe-Account"] + XCTAssertEqual(accountHeader, "acct_123") + } + + private struct MockUAUsageClass: STPAnalyticsProtocol { + static let stp_analyticsIdentifier = "MockUAUsageClass" + } + + func testPaymentUserAgent() { + STPAnalyticsClient.sharedClient.productUsage = .init() + STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: MockUAUsageClass.self) + var params: [String: Any] = [:] + params = STPAPIClient.paramsAddingPaymentUserAgent(params) + XCTAssertEqual(params["payment_user_agent"] as! String, "stripe-ios/\(StripeAPIConfiguration.STPSDKVersion); variant.paymentsheet; MockUAUsageClass") + + params = STPAPIClient.paramsAddingPaymentUserAgent(params, additionalValues: ["foo"]) + XCTAssertEqual(params["payment_user_agent"] as! String, "stripe-ios/\(StripeAPIConfiguration.STPSDKVersion); variant.paymentsheet; MockUAUsageClass; foo") + } + + func testSetAppInfo() { + let sut = STPAPIClient(publishableKey: "pk_foo") + sut.appInfo = STPAppInfo( + name: "MyAwesomeLibrary", + partnerId: "pp_partner_1234", + version: "1.2.34", + url: "https://myawesomelibrary.info" + ) + let userAgentHeader = sut.configuredRequest( + for: URL(string: "https://www.stripe.com")!, + additionalHeaders: [:] + ).allHTTPHeaderFields?["X-Stripe-User-Agent"] + var userAgentHeaderDict: [AnyHashable: Any]? + do { + if let data = userAgentHeader?.data(using: .utf8) { + userAgentHeaderDict = + try JSONSerialization.jsonObject(with: data, options: []) as? [AnyHashable: Any] + } + } catch { + } + XCTAssertEqual(userAgentHeaderDict?["name"] as! String, "MyAwesomeLibrary") + XCTAssertEqual(userAgentHeaderDict?["partner_id"] as! String, "pp_partner_1234") + XCTAssertEqual(userAgentHeaderDict?["version"] as! String, "1.2.34") + XCTAssertEqual(userAgentHeaderDict?["url"] as! String, "https://myawesomelibrary.info") + } +} diff --git a/Stripe/StripeiOSTests/STPAUBECSDebitFormViewSnapshotTests.swift b/Stripe/StripeiOSTests/STPAUBECSDebitFormViewSnapshotTests.swift new file mode 100644 index 00000000..649193e1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPAUBECSDebitFormViewSnapshotTests.swift @@ -0,0 +1,115 @@ +// +// STPAUBECSDebitFormViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPAUBECSDebitFormViewSnapshotTests: STPSnapshotTestCase { + + func testDefaultAppearance() { + let view = _newFormView() + _size(toFit: view) + STPSnapshotVerifyView(view, identifier: "STPAUBECSDebitFormView.defaultAppearance") + } + + func testNoDataCustomization() { + let view = _newFormView() + + _applyCustomization(view) + + _size(toFit: view) + + STPSnapshotVerifyView(view, identifier: "STPAUBECSDebitFormView.noDataCustomization") + } + + func testWithDataAppearance() { + let view = _newFormView() + view.nameTextField().text = "Jenny Rosen" + view.emailTextField().text = "jrosen@example.com" + view.bsbNumberTextField().text = "111111" + view.accountNumberTextField().text = "123456" + _size(toFit: view) + + STPSnapshotVerifyView(view, identifier: "STPAUBECSDebitFormView.withDataAppearance") + } + + func testWithDataCustomization() { + let view = _newFormView() + view.nameTextField().text = "Jenny Rosen" + view.emailTextField().text = "jrosen@example.com" + view.bsbNumberTextField().text = "111111" + view.accountNumberTextField().text = "123456" + _applyCustomization(view) + _size(toFit: view) + + STPSnapshotVerifyView(view, identifier: "STPAUBECSDebitFormView.withDataAppearance") + } + + func testInvalidBSBAndEmailAppearance() { + let view = _newFormView() + view.nameTextField().text = "Jenny Rosen" + view.emailTextField().text = "jrosen" + view.bsbNumberTextField().text = "666666" + view.accountNumberTextField().text = "123456" + _size(toFit: view) + + STPSnapshotVerifyView( + view, + identifier: "STPAUBECSDebitFormView.invalidBSBAndEmailAppearance" + ) + } + + func testInvalidBSBAndEmailCustomization() { + let view = _newFormView() + view.nameTextField().text = "Jenny Rosen" + view.emailTextField().text = "jrosen" + view.bsbNumberTextField().text = "666666" + view.accountNumberTextField().text = "123456" + _applyCustomization(view) + _size(toFit: view) + + STPSnapshotVerifyView( + view, + identifier: "STPAUBECSDebitFormView.invalidBSBAndEmailCustomization" + ) + } + + // MARK: - Helpers + func _newFormView() -> STPAUBECSDebitFormView { + let formView = STPAUBECSDebitFormView(companyName: "Snapshotter") + formView.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 600.0) + return formView + } + + func _applyCustomization(_ view: STPAUBECSDebitFormView?) { + view?.formFont = UIFont.boldSystemFont(ofSize: 12.0) + view?.formTextColor = UIColor.blue + view?.formTextErrorColor = UIColor.orange + view?.formPlaceholderColor = UIColor.black + view?.formCursorColor = UIColor.red + view?.formBackgroundColor = UIColor( + red: 255.0 / 255.0, + green: 45.0 / 255.0, + blue: 85.0 / 255.0, + alpha: 1.0 + ) + } + + func _size(toFit view: STPAUBECSDebitFormView?) { + var adjustedFrame = view?.frame + adjustedFrame?.size.height = + view?.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height ?? 0.0 + view?.frame = adjustedFrame! + } +} diff --git a/Stripe/StripeiOSTests/STPAUBECSFormViewModelTests.swift b/Stripe/StripeiOSTests/STPAUBECSFormViewModelTests.swift new file mode 100644 index 00000000..829f50c6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPAUBECSFormViewModelTests.swift @@ -0,0 +1,584 @@ +// +// STPAUBECSFormViewModelTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPAUBECSFormViewModelTests: XCTestCase { + func testBECSDebitParams() { + do { + // Test empty data + let model = STPAUBECSFormViewModel() + XCTAssertNil(model.becsDebitParams, "params with no data should be nil") + } + + do { + // Test complete/valid data + let model = STPAUBECSFormViewModel() + model.accountNumber = "123456" + model.bsbNumber = "111-111" + + let params = model.becsDebitParams + XCTAssertNotNil(params, "Failed to create BECS Debit params") + XCTAssertEqual(params?.accountNumber, "123456") + XCTAssertEqual(params?.bsbNumber, "111111") + } + + do { + // Test complete/valid data w/o formatting + let model = STPAUBECSFormViewModel() + model.accountNumber = "123456" + model.bsbNumber = "111111" + + let params = model.becsDebitParams + XCTAssertNotNil(params, "Failed to create BECS Debit params") + XCTAssertEqual(params?.accountNumber, "123456") + XCTAssertEqual(params?.bsbNumber, "111111") + } + + do { + // Test complete/valid accountNumber, incomplete bsb number + let model = STPAUBECSFormViewModel() + model.accountNumber = "123456" + model.bsbNumber = "111-" + + let params = model.becsDebitParams + XCTAssertNil(params, "Should not create params with incomplete bsb number") + } + + do { + // Test incomplete accountNumber, complete/valid bsb number + let model = STPAUBECSFormViewModel() + model.accountNumber = "1234" + model.bsbNumber = "111-111" + + let params = model.becsDebitParams + XCTAssertNil(params, "Should not create params with incomplete account number") + } + + do { + // Test invalid accountNumber, complete/valid bsb number + let model = STPAUBECSFormViewModel() + model.accountNumber = "12345678910" + model.bsbNumber = "111-111" + + let params = model.becsDebitParams + XCTAssertNil(params, "Should not create params with invalid account number") + } + + do { + // Test complete/valid accountNumber, invalid bsb number + let model = STPAUBECSFormViewModel() + model.accountNumber = "123456" + model.bsbNumber = "666-666" + + let params = model.becsDebitParams + XCTAssertNil(params, "Should not create params with incomplete bsb number") + } + } + + func testPaymentMethodParams() { + do { + /// Test empty + let model = STPAUBECSFormViewModel() + XCTAssertNil(model.paymentMethodParams, "params with no data should be nil") + } + + do { + /// name: + + /// email: + + /// bsb: + (formatting) + /// account: + + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrosen@example.com" + model.accountNumber = "123456" + model.bsbNumber = "111-111" + + let params = model.paymentMethodParams + XCTAssertNotNil(params, "Failed to create BECS Debit params") + XCTAssertEqual(params?.billingDetails?.name, "Jenny Rosen") + XCTAssertEqual(params?.billingDetails?.email, "jrosen@example.com") + XCTAssertEqual(params?.auBECSDebit?.accountNumber, "123456") + XCTAssertEqual(params?.auBECSDebit?.bsbNumber, "111111") + } + + do { + /// name: + + /// email: + + /// bsb: + + /// account: + + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrosen@example.com" + model.accountNumber = "123456" + model.bsbNumber = "111111" + + let params = model.paymentMethodParams + XCTAssertNotNil(params, "Failed to create BECS Debit params") + XCTAssertEqual(params?.billingDetails?.name, "Jenny Rosen") + XCTAssertEqual(params?.billingDetails?.email, "jrosen@example.com") + XCTAssertEqual(params?.auBECSDebit?.accountNumber, "123456") + XCTAssertEqual(params?.auBECSDebit?.bsbNumber, "111111") + } + + do { + /// name: + + /// email: + + /// bsb: x (incomplete) + /// account: + + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrosen@example.com" + model.accountNumber = "123456" + model.bsbNumber = "111-" + + let params = model.paymentMethodParams + XCTAssertNil(params, "Should not create params with incomplete bsb number") + } + + do { + /// name: + + /// email: + + /// bsb: + + /// account: x (incomplete) + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrosen@example.com" + model.accountNumber = "1234" + model.bsbNumber = "111-111" + + let params = model.paymentMethodParams + XCTAssertNil(params, "Should not create params with incomplete account number") + } + + do { + /// name: + + /// email: + + /// bsb: + + /// account: x + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrosen@example.com" + model.accountNumber = "12345678910" + model.bsbNumber = "111-111" + + let params = model.paymentMethodParams + XCTAssertNil(params, "Should not create params with invalid account number") + } + + do { + /// name: + + /// email: + + /// bsb: x + /// account: + + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrosen@example.com" + model.accountNumber = "123456" + model.bsbNumber = "666-666" + + let params = model.paymentMethodParams + XCTAssertNil(params, "Should not create params with incomplete bsb number") + } + + do { + /// name: x + /// email: + + /// bsb: + (formatting) + /// account: + + let model = STPAUBECSFormViewModel() + model.name = "" + model.email = "jrosen@example.com" + model.accountNumber = "123456" + model.bsbNumber = "111-111" + + let params = model.paymentMethodParams + XCTAssertNil(params, "Should not create payment method params without name.") + } + + do { + /// name: + + /// email: x + /// bsb: + (formatting) + /// account: + + let model = STPAUBECSFormViewModel() + model.name = "Jenny Rosen" + model.email = "jrose" + model.accountNumber = "123456" + model.bsbNumber = "111-111" + + let params = model.paymentMethodParams + XCTAssertNil(params, "Should not create payment method params with invalid email.") + } + } + + func testBSBLabelForInput() { + do { + // empty test + let model = STPAUBECSFormViewModel() + var isErrorString = true + var bsbLabel = model.bsbLabel( + forInput: "", + editing: false, + isErrorString: &isErrorString + ) + XCTAssertFalse(isErrorString, "Empty input shouldn't be an error.") + XCTAssertNil(bsbLabel, "No bsb label for empty input.") + + isErrorString = true + bsbLabel = model.bsbLabel(forInput: nil, editing: true, isErrorString: &isErrorString) + XCTAssertFalse(isErrorString, "nil input shouldn't be an error.") + XCTAssertNil(bsbLabel, "No bsb label for nil input.") + } + + do { + // invalid test + let model = STPAUBECSFormViewModel() + var isErrorString = false + var bsbLabel = model.bsbLabel( + forInput: "666-666", + editing: false, + isErrorString: &isErrorString + ) + XCTAssertTrue(isErrorString, "Invalid input should be an error.") + XCTAssertEqual(bsbLabel, "The BSB you entered is invalid.") + + isErrorString = false + bsbLabel = model.bsbLabel( + forInput: "666-666", + editing: true, + isErrorString: &isErrorString + ) + XCTAssertTrue(isErrorString, "Invalid input should be an error (editing).") + XCTAssertEqual(bsbLabel, "The BSB you entered is invalid.") + } + + do { + // incomplete test + let model = STPAUBECSFormViewModel() + var isErrorString = false + var bsbLabel = model.bsbLabel( + forInput: "111-11", + editing: false, + isErrorString: &isErrorString + ) + XCTAssertTrue(isErrorString, "Incomplete input should be an error when not editing.") + XCTAssertEqual(bsbLabel, "The BSB you entered is incomplete.") + + isErrorString = true + bsbLabel = model.bsbLabel( + forInput: "111-11", + editing: true, + isErrorString: &isErrorString + ) + XCTAssertFalse(isErrorString, "Incomplete input should not be an error when editing.") + XCTAssertEqual(bsbLabel, "St George Bank (division of Westpac Bank)") + } + + do { + // valid test + let model = STPAUBECSFormViewModel() + var isErrorString = true + var bsbLabel = model.bsbLabel( + forInput: "111-111", + editing: false, + isErrorString: &isErrorString + ) + XCTAssertFalse(isErrorString, "Complete input should be not an error when not editing.") + XCTAssertEqual(bsbLabel, "St George Bank (division of Westpac Bank)") + + isErrorString = true + bsbLabel = model.bsbLabel( + forInput: "111-111", + editing: true, + isErrorString: &isErrorString + ) + XCTAssertFalse(isErrorString, "Complete input should not be an error when editing.") + XCTAssertEqual(bsbLabel, "St George Bank (division of Westpac Bank)") + } + } + + func testIsInputValid() { + do { + // name + let model = STPAUBECSFormViewModel() + XCTAssertTrue( + model.isInputValid("", for: .name, editing: false), + "Name should always be valid." + ) + XCTAssertTrue( + model.isInputValid("Jen", for: .name, editing: true), + "Name should always be valid." + ) + } + + do { + // email + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isInputValid("jrosen", for: .email, editing: false), + "Partial email is invalid when not editing." + ) + XCTAssertTrue( + model.isInputValid("jrosen", for: .email, editing: true), + "Partial email is valid when editing." + ) + + XCTAssertTrue( + model.isInputValid("", for: .email, editing: false), + "Empty email is always valid." + ) + XCTAssertTrue( + model.isInputValid("", for: .email, editing: true), + "Empty email is always valid." + ) + + XCTAssertTrue( + model.isInputValid("jrosen@example.com", for: .email, editing: false), + "Valid email." + ) + XCTAssertTrue( + model.isInputValid("jrosen@example.com", for: .email, editing: true), + "Valid email." + ) + } + + do { + // bsb + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isInputValid("111-1", for: .BSBNumber, editing: false), + "Partial bsb is invalid when not editing." + ) + XCTAssertTrue( + model.isInputValid("111-1", for: .BSBNumber, editing: true), + "Partial bsb is valid when editing." + ) + + XCTAssertTrue( + model.isInputValid("", for: .BSBNumber, editing: false), + "Empty bsb is always valid." + ) + XCTAssertTrue( + model.isInputValid("", for: .BSBNumber, editing: true), + "Empty bsb is always valid." + ) + + XCTAssertTrue( + model.isInputValid("111-111", for: .BSBNumber, editing: false), + "Valid bsb." + ) + XCTAssertTrue( + model.isInputValid("111-111", for: .BSBNumber, editing: true), + "Valid bsb." + ) + + XCTAssertFalse( + model.isInputValid("666-6", for: .BSBNumber, editing: false), + "Invalid partial bsb is always invalid." + ) + XCTAssertFalse( + model.isInputValid("666-6", for: .BSBNumber, editing: true), + "Invalid partial bsb is always invalid." + ) + + XCTAssertFalse( + model.isInputValid("666-666", for: .BSBNumber, editing: false), + "Invalid full bsb is always invalid." + ) + XCTAssertFalse( + model.isInputValid("666-666", for: .BSBNumber, editing: true), + "Invalid full bsb is always invalid." + ) + } + + do { + // account + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isInputValid("1234", for: .accountNumber, editing: false), + "Partial account number is invalid when not editing." + ) + XCTAssertTrue( + model.isInputValid("1234", for: .accountNumber, editing: true), + "Partial account number is valid when editing." + ) + + XCTAssertTrue( + model.isInputValid("", for: .accountNumber, editing: false), + "Empty account number is always valid." + ) + XCTAssertTrue( + model.isInputValid("", for: .accountNumber, editing: true), + "Empty account number is always valid." + ) + + XCTAssertTrue( + model.isInputValid("12345", for: .accountNumber, editing: false), + "Valid account number." + ) + XCTAssertTrue( + model.isInputValid("12345", for: .accountNumber, editing: true), + "Valid account number." + ) + + XCTAssertFalse( + model.isInputValid("12345678910", for: .accountNumber, editing: false), + "Invalid account number is always invalid." + ) + XCTAssertFalse( + model.isInputValid("12345678910", for: .accountNumber, editing: true), + "Invalid account number is always invalid." + ) + } + } + + func testIsFieldComplete() { + do { + // name + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isFieldComplete(withInput: "", in: .name, editing: false), + "Empty name is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "", in: .name, editing: true), + "Empty name is not complete." + ) + + XCTAssertTrue( + model.isFieldComplete(withInput: "Jen", in: .name, editing: false), + "Non-empty name is complete." + ) + XCTAssertTrue( + model.isFieldComplete(withInput: "Jenny Rosen", in: .name, editing: true), + "Non-empty name is complete." + ) + } + + do { + // email + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isFieldComplete(withInput: "jrosen", in: .email, editing: false), + "Partial email is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "jrosen", in: .email, editing: true), + "Partial email is not complete." + ) + + XCTAssertTrue( + model.isFieldComplete(withInput: "jrosen@example.com", in: .email, editing: false), + "Full email is complete." + ) + XCTAssertTrue( + model.isFieldComplete(withInput: "jrosen@example.com", in: .email, editing: true), + "Full email is complete." + ) + } + + do { + // bsb + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isFieldComplete(withInput: "111-1", in: .BSBNumber, editing: false), + "Partial bsb is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "111-1", in: .BSBNumber, editing: true), + "Partial bsb is not complete." + ) + + XCTAssertFalse( + model.isFieldComplete(withInput: "", in: .BSBNumber, editing: false), + "Empty bsb is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "", in: .BSBNumber, editing: true), + "Empty bsb is not complete." + ) + + XCTAssertTrue( + model.isFieldComplete(withInput: "111-111", in: .BSBNumber, editing: false), + "Full bsb is complete." + ) + XCTAssertTrue( + model.isFieldComplete(withInput: "111-111", in: .BSBNumber, editing: true), + "Full bsb is complete." + ) + + XCTAssertFalse( + model.isFieldComplete(withInput: "666-6", in: .BSBNumber, editing: false), + "Invalid partial bsb is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "666-6", in: .BSBNumber, editing: true), + "Invalid partial bsb is not complete." + ) + + XCTAssertFalse( + model.isFieldComplete(withInput: "666-666", in: .BSBNumber, editing: false), + "Invalid full bsb is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "666-666", in: .BSBNumber, editing: true), + "Invalid full bsb is not complete." + ) + } + + do { + // account + let model = STPAUBECSFormViewModel() + XCTAssertFalse( + model.isFieldComplete(withInput: "1234", in: .accountNumber, editing: false), + "Partial account number is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "1234", in: .accountNumber, editing: true), + "Partial account number is not complete." + ) + + XCTAssertFalse( + model.isFieldComplete(withInput: "", in: .accountNumber, editing: false), + "Empty account number is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "", in: .accountNumber, editing: true), + "Empty account number is not complete." + ) + + XCTAssertTrue( + model.isFieldComplete(withInput: "12345", in: .accountNumber, editing: false), + "Min length account number is complete when not editing." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "12345", in: .accountNumber, editing: true), + "Min length account number is not complete when editing." + ) + + XCTAssertTrue( + model.isFieldComplete(withInput: "123456789", in: .accountNumber, editing: true), + "Max length account number is complete when editing." + ) + + XCTAssertFalse( + model.isFieldComplete(withInput: "12345678910", in: .accountNumber, editing: false), + "Invalid account number is not complete." + ) + XCTAssertFalse( + model.isFieldComplete(withInput: "12345678910", in: .accountNumber, editing: true), + "Invalid account number is not complete." + ) + } + } +} diff --git a/Stripe/StripeiOSTests/STPAddressTests.swift b/Stripe/StripeiOSTests/STPAddressTests.swift new file mode 100644 index 00000000..d89f70d1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPAddressTests.swift @@ -0,0 +1,242 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPAddressTests.m +// Stripe +// +// Created by Ben Guo on 4/13/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Contacts +import PassKit + +class STPAddressTests: XCTestCase { + func testInitWithPKContact_complete() { + let contact = PKContact() + do { + var name = PersonNameComponents() + name.givenName = "John" + name.familyName = "Doe" + contact.name = name + + contact.emailAddress = "foo@example.com" + contact.phoneNumber = CNPhoneNumber(stringValue: "888-555-1212") + + let address = CNMutablePostalAddress() + address.street = "55 John St" + address.city = "New York" + address.state = "NY" + address.postalCode = "10002" + address.isoCountryCode = "US" + address.country = "United States" + contact.postalAddress = address + } + + let address = STPAddress(pkContact: contact) + XCTAssertEqual("John Doe", address.name) + XCTAssertEqual("8885551212", address.phone) + XCTAssertEqual("foo@example.com", address.email) + XCTAssertEqual("55 John St", address.line1) + XCTAssertEqual("New York", address.city) + XCTAssertEqual("NY", address.state) + XCTAssertEqual("10002", address.postalCode) + XCTAssertEqual("US", address.country) + } + + func testInitWithPKContact_partial() { + let contact = PKContact() + do { + var name = PersonNameComponents() + name.givenName = "John" + contact.name = name + + let address = CNMutablePostalAddress() + address.state = "VA" + contact.postalAddress = address + } + + let address = STPAddress(pkContact: contact) + XCTAssertEqual("John", address.name) + XCTAssertNil(address.phone) + XCTAssertNil(address.email) + XCTAssertNil(address.line1) + XCTAssertNil(address.city) + XCTAssertEqual("VA", address.state) + XCTAssertNil(address.postalCode) + XCTAssertNil(address.country) + } + + func testInitWithCNContact_complete() { + let contact = CNMutableContact() + do { + contact.givenName = "John" + contact.familyName = "Doe" + + contact.emailAddresses = [ + CNLabeledValue( + label: CNLabelHome, + value: "foo@example.com"), + CNLabeledValue( + label: CNLabelWork, + value: "bar@example.com"), + ] + + contact.phoneNumbers = [ + CNLabeledValue( + label: CNLabelHome, + value: CNPhoneNumber(stringValue: "888-555-1212")), + CNLabeledValue( + label: CNLabelWork, + value: CNPhoneNumber(stringValue: "555-555-5555")), + ] + + let address = CNMutablePostalAddress() + address.street = "55 John St" + address.city = "New York" + address.state = "NY" + address.postalCode = "10002" + address.isoCountryCode = "US" + address.country = "United States" + contact.postalAddresses = [ + CNLabeledValue( + label: CNLabelHome, + value: address), + ] + } + + let address = STPAddress(cnContact: contact) + XCTAssertEqual("John Doe", address.name) + XCTAssertEqual("8885551212", address.phone) + XCTAssertEqual("foo@example.com", address.email) + XCTAssertEqual("55 John St", address.line1) + XCTAssertEqual("New York", address.city) + XCTAssertEqual("NY", address.state) + XCTAssertEqual("10002", address.postalCode) + XCTAssertEqual("US", address.country) + } + + func testInitWithCNContact_partial() { + let contact = CNMutableContact() + do { + contact.givenName = "John" + + let address = CNMutablePostalAddress() + address.state = "VA" + contact.postalAddresses = [ + CNLabeledValue( + label: CNLabelHome, + value: address), + ] + } + + let address = STPAddress(cnContact: contact) + XCTAssertEqual("John", address.name) + XCTAssertNil(address.phone) + XCTAssertNil(address.email) + XCTAssertNil(address.line1) + XCTAssertNil(address.city) + XCTAssertEqual("VA", address.state) + XCTAssertNil(address.postalCode) + XCTAssertNil(address.country) + } + + func testPKContactValue() { + let address = STPAddress() + address.name = "John Smith Doe" + address.phone = "8885551212" + address.email = "foo@example.com" + address.line1 = "55 John St" + address.city = "New York" + address.state = "NY" + address.postalCode = "10002" + address.country = "US" + + let contact = address.pkContactValue() + XCTAssertEqual(contact.name?.givenName, "John") + XCTAssertEqual(contact.name?.familyName, "Smith Doe") + XCTAssertEqual(contact.phoneNumber?.stringValue, "8885551212") + XCTAssertEqual(contact.emailAddress, "foo@example.com") + let postalAddress = contact.postalAddress + XCTAssertEqual(postalAddress?.street, "55 John St") + XCTAssertEqual(postalAddress?.city, "New York") + XCTAssertEqual(postalAddress?.state, "NY") + XCTAssertEqual(postalAddress?.postalCode, "10002") + XCTAssertEqual(postalAddress?.country, "US") + } + + func testShippingInfoForCharge() { + let address = STPFixtures.address() + let method = PKShippingMethod() + method.label = "UPS Ground" + let info = STPAddress.shippingInfoForCharge( + with: address, + shippingMethod: method) as NSDictionary? + let expected: NSDictionary = [ + "address": [ + "city": address.city, + "country": address.country, + "line1": address.line1, + "line2": address.line2, + "postal_code": address.postalCode, + "state": address.state, + ], + "name": address.name as Any, + "phone": address.phone as Any, + "carrier": method.label, + ] + XCTAssertEqual(expected, info) + } + + // MARK: STPFormEncodable Tests + + func testRootObjectName() { + XCTAssertNil(STPAddress.rootObjectName()) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let address = STPAddress() + + let mapping = STPAddress.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(address.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(formFieldName.count > 0) + } + + XCTAssertEqual(mapping.values.count, mapping.values.count) + } + + // MARK: NSCopying Tests + + func testCopyWithZone() { + let address = STPFixtures.address() + let copiedAddress = address.copy() as! STPAddress + + XCTAssertNotEqual(address, copiedAddress, "should be different objects") + + // The property names we expect to *not* be equal objects + let notEqualProperties = [ + // these include the object's address, so they won't be the same across copies + "debugDescription", + "description", + "hash", + ] + // use runtime inspection to find the list of properties. If a new property is + // added to the fixture, but not the `copyWithZone:` implementation, this should catch it + for property in STPTestUtils.propertyNames(of: address) { + if notEqualProperties.contains(property) { + XCTAssertNotEqual( + address.value(forKey: property) as! NSObject, + copiedAddress.value(forKey: property) as! NSObject) + } else { + XCTAssertEqual( + address.value(forKey: property) as! NSObject, + copiedAddress.value(forKey: property) as! NSObject) + } + } + } +} diff --git a/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift b/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift new file mode 100644 index 00000000..536ee8b3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift @@ -0,0 +1,162 @@ +// +// STPAnalyticsClientPaymentsTest.swift +// StripeiOS Tests +// +// Created by Mel Ludowise on 5/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeApplePay +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPAnalyticsClientPaymentsTest: XCTestCase { + private var client: STPAnalyticsClient! + + override func setUp() { + super.setUp() + client = STPAnalyticsClient() + } + + func testAdditionalInfo() { + XCTAssertEqual(client.additionalInfo(), []) + + // Add some info + client.addAdditionalInfo("hello") + client.addAdditionalInfo("i'm additional info") + client.addAdditionalInfo("how are you?") + + XCTAssertEqual(client.additionalInfo(), ["hello", "how are you?", "i'm additional info"]) + } + + func testPayloadFromAnalytic() throws { + AnalyticsHelper.shared.generateSessionID() + + client.addAdditionalInfo("test_additional_info") + + let mockAnalytic = MockAnalytic() + let payload = client.payload(from: mockAnalytic) + + XCTAssertEqual(payload.count, 17) + + // Verify event name is included + XCTAssertEqual(payload["event"] as? String, mockAnalytic.event.rawValue) + + // Verify additionalInfo is included + XCTAssertEqual(payload["additional_info"] as? [String], ["test_additional_info"]) + + // Verify all the analytic params are in the payload + XCTAssertEqual(payload["test_param1"] as? Int, 1) + XCTAssertEqual(payload["test_param2"] as? String, "two") + + // Verify productUsage is included + XCTAssertNotNil(payload["product_usage"]) + + // Verify install method is Xcode + XCTAssertEqual(payload["install"] as? String, "X") + + // Verify is_development + XCTAssertTrue(payload["is_development"] as? Bool ?? false) + + // Verify locale + XCTAssertEqual(payload["locale"] as? String, Locale.autoupdatingCurrent.identifier) + } + + // MARK: - Error tests + + enum MockError: Error { + case someErrorCase + } + + func testLogErrorAnalytic() { + let error = MockError.someErrorCase + let errorAnalytic = ErrorAnalytic(event: .luxeSerializeFailure, error: error) + let payload = client.payload(from: errorAnalytic) + + // Verify payload event name is correct + XCTAssertEqual(payload["event"] as? String, STPAnalyticEvent.luxeSerializeFailure.rawValue) + + // Verify error details are included + XCTAssertEqual(payload["error_type"] as? String, "StripeiOS_Tests.STPAnalyticsClientPaymentsTest.MockError") + XCTAssertEqual(payload["error_code"] as? String, "someErrorCase") + + let errorAnalyticWithAdditionalParams = ErrorAnalytic(event: .luxeSerializeFailure, error: error, additionalNonPIIParams: ["additional_param": "value"]) + let payloadWithAdditionalParams = client.payload(from: errorAnalyticWithAdditionalParams) + + // Verify additional params in ErrorAnalytic are included + XCTAssertEqual(payloadWithAdditionalParams["additional_param"] as? String, "value") + } + + // MARK: - Other tests + + func testTokenTypeFromParameters() { + let card = STPFixtures.cardParams() + let cardDict = buildTokenParams(card) + XCTAssertEqual(STPAnalyticsClient.tokenType(fromParameters: cardDict), "card") + + let account = STPFixtures.accountParams() + let accountDict = buildTokenParams(account) + XCTAssertEqual(STPAnalyticsClient.tokenType(fromParameters: accountDict), "account") + + let bank = STPFixtures.bankAccountParams() + let bankDict = buildTokenParams(bank) + XCTAssertEqual(STPAnalyticsClient.tokenType(fromParameters: bankDict), "bank_account") + + let applePay = STPFixtures.applePayPayment() + let applePayDict = addTelemetry(applePay.stp_tokenParameters(apiClient: .shared)) + XCTAssertEqual(STPAnalyticsClient.tokenType(fromParameters: applePayDict), "apple_pay") + } + + // MARK: - Tests various classes report usage + + func testCardTextFieldAddsUsage() { + _ = STPPaymentCardTextField() + XCTAssertTrue( + STPAnalyticsClient.sharedClient.productUsage.contains("STPPaymentCardTextField") + ) + } + + func testApplePayContextAddsUsage() { + _ = STPApplePayContext(paymentRequest: STPFixtures.applePayRequest(), delegate: nil) + XCTAssertTrue(STPAnalyticsClient.sharedClient.productUsage.contains("STPApplePayContext")) + } +} + +// MARK: - Helpers + +extension STPAnalyticsClientPaymentsTest { + fileprivate func buildTokenParams(_ object: T) -> [String: Any] + { + return addTelemetry(STPFormEncoder.dictionary(forObject: object)) + } + + fileprivate func addTelemetry(_ params: [String: Any]) -> [String: Any] { + // STPAPIClient adds these before determining the token type, + // so do the same in the test + return STPTelemetryClient.shared.paramsByAddingTelemetryFields(toParams: params) + } +} + +// MARK: - Mock types + +private struct MockAnalytic: Analytic { + let event = STPAnalyticEvent.sourceCreation + + let params: [String: Any] = [ + "test_param1": 1, + "test_param2": "two", + ] +} + +private struct MockAnalyticsClass1: STPAnalyticsProtocol { + static let stp_analyticsIdentifier = "MockAnalyticsClass1" +} + +private struct MockAnalyticsClass2: STPAnalyticsProtocol { + static let stp_analyticsIdentifier = "MockAnalyticsClass2" +} diff --git a/Stripe/StripeiOSTests/STPApplePayContextFunctionalTest.swift b/Stripe/StripeiOSTests/STPApplePayContextFunctionalTest.swift new file mode 100644 index 00000000..83c5c034 --- /dev/null +++ b/Stripe/StripeiOSTests/STPApplePayContextFunctionalTest.swift @@ -0,0 +1,362 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPApplePayContextFunctionalTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/5/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import OHHTTPStubs +@testable import Stripe +@testable import StripeApplePay +@testable import StripeCoreTestUtils +@testable import StripePayments +@testable import StripePaymentsObjcTestUtils +@testable import StripePaymentsTestUtils + +class STPTestApplePayContextDelegate: NSObject, STPApplePayContextDelegate { + func applePayContext(_ context: StripeApplePay.STPApplePayContext, didCreatePaymentMethod paymentMethod: StripePayments.STPPaymentMethod, paymentInformation: PKPayment, completion: @escaping StripeApplePay.STPIntentClientSecretCompletionBlock) { + didCreatePaymentMethodDelegateMethod!(paymentMethod, paymentInformation, completion) + } + + func applePayContext(_ context: StripeApplePay.STPApplePayContext, didCompleteWith status: StripePayments.STPPaymentStatus, error: Error?) { + didCompleteDelegateMethod!(status, error) + } + + var didCompleteDelegateMethod: ((_ status: STPPaymentStatus, _ error: Error?) -> Void)? + var didCreatePaymentMethodDelegateMethod: ((_ paymentMethod: STPPaymentMethod?, _ paymentInformation: PKPayment?, _ completion: @escaping STPIntentClientSecretCompletionBlock) -> Void)? +} + +class STPApplePayContextFunctionalTest: STPNetworkStubbingTestCase { + var apiClient: STPApplePayContextFunctionalTestAPIClient! + var delegate: STPTestApplePayContextDelegate! + var context: STPApplePayContext! + + override func setUp() { + super.setUp() + delegate = STPTestApplePayContextDelegate() + let apiClient = STPApplePayContextFunctionalTestAPIClient(publishableKey: STPTestingDefaultPublishableKey) + apiClient.setupStubs() + apiClient.applePayContext = context + self.apiClient = apiClient + + context = STPApplePayContext(paymentRequest: STPFixtures.applePayRequest(), delegate: delegate) + self.apiClient.applePayContext = context + context?.apiClient = self.apiClient + context?.authorizationController = STPTestPKPaymentAuthorizationController() + } + + override func tearDown() { + HTTPStubs.removeAllStubs() + super.tearDown() + } + + func testCompletesManualConfirmationPaymentIntent() { + var clientSecret: String? + // A manual confirmation PI confirmed server-side... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { paymentMethod, paymentInformation, completion in + XCTAssertNotNil(paymentInformation) + if let stripeId = paymentMethod?.stripeId { + STPTestingAPIClient.shared.createPaymentIntent(withParams: [ + "confirmation_method": "manual", + "payment_method": stripeId, + "confirm": NSNumber(value: true), + ]) { _clientSecret, _ in + XCTAssertNotNil(_clientSecret) + clientSecret = _clientSecret + completion(clientSecret, nil) + } + } + } + + // ...used with ApplePayContext + let context = STPApplePayContext(paymentRequest: STPFixtures.applePayRequest(), delegate: self.delegate)! + context.apiClient = apiClient + _startApplePayForContext(withExpectedStatus: .success) + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { [self] status, error in + XCTAssertEqual(status, .success) + XCTAssertNil(error) + + // ...and results in a successful PI + apiClient?.retrievePaymentIntent(withClientSecret: clientSecret!) { paymentIntent, paymentIntentRetrieveError in + XCTAssertNil(paymentIntentRetrieveError) + XCTAssert(paymentIntent?.status == .succeeded) + didCallCompletion.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCompletesAutomaticConfirmationPaymentIntent() { + var clientSecret: String? + // An automatic confirmation PI with the PaymentMethod attached... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { newClientSecret, _ in + clientSecret = newClientSecret + completion(newClientSecret, nil) + } + } + + // ...used with ApplePayContext + _startApplePayForContext(withExpectedStatus: .success) + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { [self] status, error in + XCTAssertEqual(status, .success) + XCTAssertNil(error) + + // ...and results in a successful PI + apiClient?.retrievePaymentIntent(withClientSecret: clientSecret!) { paymentIntent, paymentIntentRetrieveError in + XCTAssertNil(paymentIntentRetrieveError) + XCTAssert(paymentIntent?.status == .succeeded) + XCTAssertEqual(paymentIntent?.shipping?.name, "Jane Doe") + XCTAssertEqual(paymentIntent?.shipping?.address?.line1, "510 Townsend St") + didCallCompletion.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCompletesAutomaticConfirmationPaymentIntentManualCapture() { + var clientSecret: String? + // An automatic confirmation PI with the PaymentMethod attached... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + STPTestingAPIClient.shared.createPaymentIntent(withParams: ["capture_method": "manual"]) { newClientSecret, _ in + clientSecret = newClientSecret + completion(newClientSecret, nil) + } + } + + // ...used with ApplePayContext + _startApplePayForContext(withExpectedStatus: .success) + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { [self] status, error in + XCTAssertEqual(status, .success) + XCTAssertNil(error) + + // ...and results in a successful PI + apiClient?.retrievePaymentIntent(withClientSecret: clientSecret!) { paymentIntent, paymentIntentRetrieveError in + XCTAssertNil(paymentIntentRetrieveError) + XCTAssert(paymentIntent?.status == .requiresCapture) + didCallCompletion.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCompletesSetupIntent() { + var clientSecret: String? + // An automatic confirmation SI... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + STPTestingAPIClient.shared.createSetupIntent(withParams: nil) { newClientSecret, _ in + clientSecret = newClientSecret + completion(newClientSecret, nil) + } + } + + // ...used with ApplePayContext + _startApplePayForContext(withExpectedStatus: .success) + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { [self] status, error in + XCTAssertEqual(status, .success) + XCTAssertNil(error) + + // ...and results in a successful PI + apiClient?.retrieveSetupIntent(withClientSecret: clientSecret!) { setupIntent, setupIntentRetrieveError in + XCTAssertNil(setupIntentRetrieveError) + XCTAssert(setupIntent?.status == .succeeded) + didCallCompletion.fulfill() + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Error tests + + func testBadPaymentIntentClientSecretErrors() { + var clientSecret: String? + // An invalid PaymentIntent client secret... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + DispatchQueue.main.async { + clientSecret = "pi_bad_secret_1234" + completion(clientSecret, nil) + } + } + + // ...used with ApplePayContext + _startApplePayForContext(withExpectedStatus: .failure) + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { status, error in + // ...and results in an error + XCTAssertEqual(status, .error) + XCTAssertNotNil(error) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain) + didCallCompletion.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testBadSetupIntentClientSecretErrors() { + var clientSecret: String? + // An invalid SetupIntent client secret... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + DispatchQueue.main.async { + clientSecret = "seti_bad_secret_1234" + completion(clientSecret, nil) + } + } + + // ...used with ApplePayContext + _startApplePayForContext(withExpectedStatus: .failure) + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { status, error in + // ...and results in an error + XCTAssertEqual(status, .error) + XCTAssertNotNil(error) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain) + didCallCompletion.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Cancel tests + + func testCancelBeforeIntentConfirmsCancels() { + // Cancelling Apple Pay *before* the context attempts to confirms the PI/SI... + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + self.context.paymentAuthorizationControllerDidFinish(self.context.authorizationController!) // Simulate cancel before passing PI to the context + // ...should never retrieve the PI (b/c it is cancelled before) + completion("A 'client secret' that triggers an exception if fetched", nil) + } + // Simulate user tapping 'Pay' button in Apple Pay + self.context.paymentAuthorizationController(self.context.authorizationController!, didAuthorizePayment: STPFixtures.simulatorApplePayPayment()) { _ in } + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { status, error in + // ...and results in a 'user cancel' status + XCTAssertEqual(status, .userCancellation) + XCTAssertNil(error) + didCallCompletion.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCancelAfterPaymentIntentConfirmsStillSucceeds() { + // Cancelling Apple Pay *after* the context attempts to confirm the PI... + apiClient?.shouldSimulateCancelAfterConfirmBegins = true + + var clientSecret: String? + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { newClientSecret, _ in + clientSecret = newClientSecret + completion(newClientSecret, nil) + } + } + // Simulate user tapping 'Pay' button in Apple Pay + self.context.paymentAuthorizationController(self.context.authorizationController!, didAuthorizePayment: STPFixtures.simulatorApplePayPayment()) { _ in } + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { [self] status, error in + XCTAssertEqual(status, .success) + XCTAssertNil(error) + + // ...and results in a successful PI + apiClient?.retrievePaymentIntent(withClientSecret: clientSecret!) { paymentIntent, paymentIntentRetrieveError in + XCTAssertNil(paymentIntentRetrieveError) + XCTAssert(paymentIntent?.status == .succeeded) + didCallCompletion.fulfill() + } + } + + waitForExpectations(timeout: 20.0, handler: nil) // give this a longer timeout, it tends to take a while + } + + func testCancelAfterSetupIntentConfirmsStillSucceeds() { + // Cancelling Apple Pay *after* the context attempts to confirm the SI... + apiClient?.shouldSimulateCancelAfterConfirmBegins = true + + var clientSecret: String? + let delegate = self.delegate + delegate?.didCreatePaymentMethodDelegateMethod = { _, _, completion in + STPTestingAPIClient.shared.createSetupIntent(withParams: nil) { newClientSecret, _ in + clientSecret = newClientSecret + completion(newClientSecret, nil) + } + } + // Simulate user tapping 'Pay' button in Apple Pay + self.context.paymentAuthorizationController(self.context.authorizationController!, didAuthorizePayment: STPFixtures.simulatorApplePayPayment()) { _ in } + + // ...calls applePayContext:didCompleteWithStatus:error: + let didCallCompletion = expectation(description: "applePayContext:didCompleteWithStatus: called") + delegate?.didCompleteDelegateMethod = { [self] status, error in + XCTAssertEqual(status, .success) + XCTAssertNil(error) + + // ...and results in a successful SI + apiClient?.retrieveSetupIntent(withClientSecret: clientSecret!) { setupIntent, setupIntentRetrieveError in + XCTAssertNil(setupIntentRetrieveError) + XCTAssert(setupIntent?.status == .succeeded) + didCallCompletion.fulfill() + } + } + + waitForExpectations(timeout: 20.0, handler: nil) // give this a longer timeout, it tends to take a while + } + + // MARK: - Helper + + /// Simulates user tapping 'Pay' button in Apple Pay sheet + func _startApplePayForContext(withExpectedStatus expectedStatus: PKPaymentAuthorizationStatus) { + // When the user taps 'Pay', PKPaymentAuthorizationController calls `didAuthorizePayment:completion:` + // After you call its completion block, it calls `paymentAuthorizationControllerDidFinish:` + let didCallAuthorizePaymentCompletion = expectation(description: "ApplePayContext called completion block of paymentAuthorizationController:didAuthorizePayment:completion:") + if let authorizationController = context?.authorizationController { + context?.paymentAuthorizationController(authorizationController, didAuthorizePayment: STPFixtures.simulatorApplePayPayment(), handler: { [self] result in + XCTAssertEqual(expectedStatus, result.status) + DispatchQueue.main.async(execute: { [self] in + if let authorizationController = context?.authorizationController { + context?.paymentAuthorizationControllerDidFinish(authorizationController) + } + didCallAuthorizePaymentCompletion.fulfill() + }) + }) + } + } +} + +class STPTestPKPaymentAuthorizationController: PKPaymentAuthorizationController { + // Stub dismissViewControllerAnimated: to just call its completion block + override func dismiss(completion: (() -> Void)? = nil) { + completion?() + } +} diff --git a/Stripe/StripeiOSTests/STPApplePayContextFunctionalTestExtras.swift b/Stripe/StripeiOSTests/STPApplePayContextFunctionalTestExtras.swift new file mode 100644 index 00000000..15a7394b --- /dev/null +++ b/Stripe/StripeiOSTests/STPApplePayContextFunctionalTestExtras.swift @@ -0,0 +1,44 @@ +// +// STPApplePayContextFunctionalTestExtras.swift +// StripeiOS Tests +// +// Created by David Estes on 3/23/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPApplePayContextFunctionalTestAPIClient: STPAPIClient { + @objc var applePayContext: STPApplePayContext? + @objc var shouldSimulateCancelAfterConfirmBegins: Bool = false + + @objc func setupStubs() { + stub { urlRequest in + // Hook SetupIntent or PaymentIntent confirmation + if let urlString = urlRequest.url?.absoluteString, + urlString.contains("_intents/"), + urlString.hasSuffix("/confirm") + { + if self.shouldSimulateCancelAfterConfirmBegins { + self.applePayContext!.paymentAuthorizationControllerDidFinish( + self.applePayContext!.authorizationController! + ) + } + } + // Let everything pass through to the underlying API + return false + } response: { _ in + // This doesn't matter, we're not sending responses for anything. + return HTTPStubsResponse() + } + } +} diff --git a/Stripe/StripeiOSTests/STPApplePayContextTest.swift b/Stripe/StripeiOSTests/STPApplePayContextTest.swift new file mode 100644 index 00000000..28296c1b --- /dev/null +++ b/Stripe/StripeiOSTests/STPApplePayContextTest.swift @@ -0,0 +1,202 @@ +// +// STPApplePayContextTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 2/20/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripePayments + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPApplePayTestDelegateiOS11: NSObject, STPApplePayContextDelegate { + var didChangeCouponCodeCalled: Bool = false + func applePayContext( + _ context: STPApplePayContext, + didSelectShippingContact contact: PKContact, + handler completion: @escaping (PKPaymentRequestShippingContactUpdate) -> Void + ) { + completion(PKPaymentRequestShippingContactUpdate()) + } + + func applePayContext( + _ context: STPApplePayContext, + didSelect shippingMethod: PKShippingMethod, + handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void + ) { + completion(PKPaymentRequestShippingMethodUpdate()) + } + + @available(iOS 15.0, *) + func applePayContext( + _ context: STPApplePayContext, + didChangeCouponCode couponCode: String, + handler completion: @escaping (PKPaymentRequestCouponCodeUpdate) -> Void + ) { + didChangeCouponCodeCalled = true + completion(.init(errors: nil, paymentSummaryItems: [], shippingMethods: [])) + } + + func applePayContext( + _ context: STPApplePayContext, + didCompleteWith status: STPPaymentStatus, + error: Error? + ) { + } + + func applePayContext( + _ context: STPApplePayContext, + didCreatePaymentMethod paymentMethod: STPPaymentMethod, + paymentInformation: PKPayment, + completion: STPIntentClientSecretCompletionBlock + ) { + } +} + +class STPApplePayContextTest: XCTestCase { + func testInvalidPaymentRequest() { + // An invalid request (missing payment summary items)... + let request = StripeAPI.paymentRequest( + withMerchantIdentifier: "foo", + country: "US", + currency: "USD" + ) + // ...should cause ApplePayContext to be nil + let applePayContext = STPApplePayContext(paymentRequest: request, delegate: STPApplePayTestDelegateiOS11()) + XCTAssertNil(applePayContext) + } + + // MARK: - STPApplePayTestDelegateiOS11 + func testiOS11ApplePayDelegateMethodsForwarded() { + // With a user that only implements iOS 11 delegate methods... + let delegate = STPApplePayTestDelegateiOS11() + let request = StripeAPI.paymentRequest( + withMerchantIdentifier: "foo", + country: "US", + currency: "USD" + ) + request.paymentSummaryItems = [ + PKPaymentSummaryItem(label: "bar", amount: NSDecimalNumber(string: "1.00")), + ] + let context = STPApplePayContext(paymentRequest: request, delegate: delegate)! + + // ...the context should respondToSelector appropriately... + XCTAssertTrue( + context.responds( + to: #selector( + PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( + _: + didSelectShippingContact: + handler: + )) + ) + ) + XCTAssertFalse( + context.responds( + to: #selector( + PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( + _: + didSelectShippingContact: + completion: + )) + ) + ) + + // ...and forward the PassKit delegate method to its delegate + let vc: PKPaymentAuthorizationController = PKPaymentAuthorizationController() + // 1) ..didSelectShippingContact.. delegate method + let contact = PKContact() + let shippingContactExpectation = expectation( + description: "didSelectShippingContact forwarded" + ) + context.paymentAuthorizationController( + vc, + didSelectShippingContact: contact, + handler: { _ in + shippingContactExpectation.fulfill() + } + ) + + // 2) ..didSelectShippingMethod.. delegate method + let method = PKShippingMethod() + let shippingMethodExpectation = expectation( + description: "didSelectShippingMethod forwarded" + ) + context.paymentAuthorizationController( + vc, + didSelectShippingMethod: method, + handler: { _ in + shippingMethodExpectation.fulfill() + } + ) + + // 3) ..didChangeCouponCode.. delegate method + if #available(iOS 15.0, *) { + XCTAssertFalse(delegate.didChangeCouponCodeCalled) + let couponCodeExpectation = expectation(description: "didChangeCouponCode forwarded") + context.paymentAuthorizationController(vc, didChangeCouponCode: "coupon_123") { _ in + couponCodeExpectation.fulfill() + } + wait(for: [couponCodeExpectation], timeout: 1) + XCTAssertTrue(delegate.didChangeCouponCodeCalled) + } else { + // Fallback on earlier versions + } + + waitForExpectations(timeout: 2, handler: nil) + } + + func testConvertsShippingDetails() { + let delegate = STPApplePayTestDelegateiOS11() + let request = StripeAPI.paymentRequest( + withMerchantIdentifier: "foo", + country: "US", + currency: "USD" + ) + request.paymentSummaryItems = [ + PKPaymentSummaryItem(label: "bar", amount: NSDecimalNumber(string: "1.00")), + ] + let context = STPApplePayContext(paymentRequest: request, delegate: delegate) + + let payment = STPFixtures.simulatorApplePayPayment() + let shipping = PKContact() + shipping.name = PersonNameComponentsFormatter().personNameComponents(from: "Jane Doe") + shipping.phoneNumber = CNPhoneNumber(stringValue: "555-555-5555") + let address = CNMutablePostalAddress() + address.street = "510 Townsend St" + address.city = "San Francisco" + address.state = "CA" + address.isoCountryCode = "US" + address.postalCode = "94105" + shipping.postalAddress = address + payment.perform(#selector(setter: PKPaymentRequest.shippingContact), with: shipping) + + let shippingParams = context!._shippingDetails(from: payment) + XCTAssertNotNil(shippingParams) + XCTAssertEqual(shippingParams?.name, "Jane Doe") + XCTAssertNil(shippingParams?.carrier) + XCTAssertEqual(shippingParams?.phone, "555-555-5555") + XCTAssertNil(shippingParams?.trackingNumber) + + XCTAssertEqual(shippingParams?.address.line1, "510 Townsend St") + XCTAssertNil(shippingParams?.address.line2) + XCTAssertEqual(shippingParams?.address.city, "San Francisco") + XCTAssertEqual(shippingParams?.address.state, "CA") + XCTAssertEqual(shippingParams?.address.country, "US") + XCTAssertEqual(shippingParams?.address.postalCode, "94105") + } + + // Tests stp_tokenParameters in StripeApplePay, not StripePayments + func testStpTokenParameters() { + let applePay = STPFixtures.applePayPayment() + let applePayDict = applePay.stp_tokenParameters(apiClient: .shared) + XCTAssertNotNil(applePayDict["pk_token"]) + XCTAssertEqual((applePayDict["card"] as! NSDictionary)["name"] as! String, "Test Testerson") + XCTAssertEqual(applePayDict["pk_token_instrument_name"] as! String, "Master Charge") + } +} diff --git a/Stripe/StripeiOSTests/STPApplePayFunctionalTest.swift b/Stripe/StripeiOSTests/STPApplePayFunctionalTest.swift new file mode 100644 index 00000000..f11f9123 --- /dev/null +++ b/Stripe/StripeiOSTests/STPApplePayFunctionalTest.swift @@ -0,0 +1,107 @@ +// +// STPApplePayFunctionalTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 12/21/14. +// Copyright (c) 2014 Stripe, Inc. All rights reserved. +// + +import PassKit +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP)@_spi(StripeApplePayTokenization) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPApplePayFunctionalTest: STPNetworkStubbingTestCase { + + // TODO: regenerate these fixtures with a fresh/real PKPayment + func testCreateTokenWithPaymentClassic() { + let payment = STPFixtures.applePayPayment() + let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") + + let expectation = self.expectation(description: "Apple pay token creation") + client.createToken( + with: payment + ) { token, error in + expectation.fulfill() + XCTAssertNil(token, "token should be nil") + guard let error = error else { + XCTFail("error should not be nil") + return + } + + // Since we can't actually generate a new cryptogram in a CI environment, we should just post a blob of expired token data and + // make sure we get the "invalid cert" error. This at least asserts that our blob has been correctly formatted and + // can be decrypted by the backend. + XCTAssert( + ((error as NSError).userInfo[STPError.errorMessageKey] as? NSString)?.range( + of: "certificate used to sign your request is invalid" + ).location != NSNotFound, + "Error is unrelated to cert expiration: \(error)" + ) + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testCreateTokenWithPayment() { + let payment = STPFixtures.applePayPayment() + let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") + + let expectation = self.expectation(description: "Apple pay token creation") + StripeAPI.Token.create( + apiClient: client, + payment: payment + ) { result in + expectation.fulfill() + XCTAssertNil(try? result.get(), "token should be nil") + guard case .failure(let error) = result else { + XCTFail("error should not be nil") + return + } + + // Since we can't actually generate a new cryptogram in a CI environment, we should just post a blob of expired token data and + // make sure we get the "invalid cert" error. This at least asserts that our blob has been correctly formatted and + // can be decrypted by the backend. + XCTAssert( + ((error as NSError).userInfo[STPError.errorMessageKey] as? NSString)?.range( + of: "certificate used to sign your request is invalid" + ).location != NSNotFound, + "Error is unrelated to cert expiration: \(error)" + ) + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testCreateSourceWithPayment() { + let payment = STPFixtures.applePayPayment() + let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") + + let expectation = self.expectation(description: "Apple pay source creation") + client.createSource( + with: payment + ) { source, error in + expectation.fulfill() + XCTAssertNil(source, "token should be nil") + guard let error = error else { + XCTFail("error should not be nil") + return + } + + // Since we can't actually generate a new cryptogram in a CI environment, we should just post a blob of expired token data and + // make sure we get the "invalid cert" error. This at least asserts that our blob has been correctly formatted and + // can be decrypted by the backend. + XCTAssert( + ((error as NSError).userInfo[STPError.errorMessageKey] as? NSString)?.range( + of: "certificate used to sign your request is invalid" + ).location != NSNotFound, + "Error is unrelated to cert expiration: \(error)" + ) + } + waitForExpectations(timeout: 5.0, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPApplePayTest.swift b/Stripe/StripeiOSTests/STPApplePayTest.swift new file mode 100644 index 00000000..858a47a9 --- /dev/null +++ b/Stripe/StripeiOSTests/STPApplePayTest.swift @@ -0,0 +1,94 @@ +// +// STPApplePayTest.swift +// StripeiOS Tests +// +// Created by David Estes on 9/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPApplePaySwiftTest: XCTestCase { + func testAdditionalPaymentNetwork() { + XCTAssertFalse(StripeAPI.supportedPKPaymentNetworks().contains(.JCB)) + StripeAPI.additionalEnabledApplePayNetworks = [.JCB] + XCTAssertTrue(StripeAPI.supportedPKPaymentNetworks().contains(.JCB)) + StripeAPI.additionalEnabledApplePayNetworks = [] + } + + func testAdditionalPaymentNetworkCartesBancaires() { + XCTAssertFalse(StripeAPI.supportedPKPaymentNetworks().contains(.cartesBancaires)) + StripeAPI.additionalEnabledApplePayNetworks = [.cartesBancaires] + XCTAssertTrue(StripeAPI.supportedPKPaymentNetworks().contains(.cartesBancaires)) + StripeAPI.additionalEnabledApplePayNetworks = [] + } + + func testAdditionalPaymentNetworksGetPrepended() { + XCTAssertFalse(StripeAPI.supportedPKPaymentNetworks().contains(.cartesBancaires)) + StripeAPI.additionalEnabledApplePayNetworks = [.cartesBancaires] + XCTAssertEqual(StripeAPI.supportedPKPaymentNetworks().first, .cartesBancaires) + StripeAPI.additionalEnabledApplePayNetworks = [] + } + + // Tests stp_tokenParameters in StripePayments, not StripeApplePay + func testStpTokenParameters() { + let applePay = STPFixtures.applePayPayment() + let applePayDict = applePay.stp_tokenParameters(apiClient: .shared) + XCTAssertNotNil(applePayDict["pk_token"]) + XCTAssertEqual((applePayDict["card"] as! NSDictionary)["name"] as! String, "Test Testerson") + XCTAssertEqual(applePayDict["pk_token_instrument_name"] as! String, "Master Charge") + } + + func testPaymentRequestWithMerchantIdentifierCountryCurrency() { + let paymentRequest = StripeAPI.paymentRequest(withMerchantIdentifier: "foo", country: "GB", currency: "GBP") + XCTAssertEqual(paymentRequest.merchantIdentifier, "foo") + let expectedNetworks = Set([ + .amex, + .masterCard, + .visa, + .discover, + .maestro, + ]) + XCTAssertEqual(Set(paymentRequest.supportedNetworks), expectedNetworks) + XCTAssertEqual(paymentRequest.merchantCapabilities, PKMerchantCapability.capability3DS) + XCTAssertEqual(paymentRequest.countryCode, "GB") + XCTAssertEqual(paymentRequest.currencyCode, "GBP") + XCTAssertEqual(paymentRequest.requiredBillingContactFields, Set([.postalAddress])) + } + + func testCanSubmitPaymentRequestReturnsYES() { + let request = PKPaymentRequest() + request.merchantIdentifier = "foo" + request.paymentSummaryItems = [ + PKPaymentSummaryItem(label: "bar", amount: NSDecimalNumber(string: "1.00")) + ] + + XCTAssertTrue(StripeAPI.canSubmitPaymentRequest(request)) + } + + func testCanSubmitPaymentRequestIfTotalIsZero() { + let request = PKPaymentRequest() + request.merchantIdentifier = "foo" + request.paymentSummaryItems = [ + PKPaymentSummaryItem(label: "bar", amount: NSDecimalNumber(string: "0.00")) + ] + + XCTAssertTrue(StripeAPI.canSubmitPaymentRequest(request)) + } + + func testCanSubmitPaymentRequestReturnsNOIfMerchantIdentifierIsNil() { + let request = PKPaymentRequest() + request.paymentSummaryItems = [ + PKPaymentSummaryItem(label: "bar", amount: NSDecimalNumber(string: "1.00")) + ] + + XCTAssertFalse(StripeAPI.canSubmitPaymentRequest(request)) + } +} diff --git a/Stripe/StripeiOSTests/STPBECSDebitAccountNumberValidatorTests.swift b/Stripe/StripeiOSTests/STPBECSDebitAccountNumberValidatorTests.swift new file mode 100644 index 00000000..4e06739c --- /dev/null +++ b/Stripe/StripeiOSTests/STPBECSDebitAccountNumberValidatorTests.swift @@ -0,0 +1,240 @@ +// +// STPBECSDebitAccountNumberValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPBECSDebitAccountNumberValidatorTests: XCTestCase { + func testValidationStateForText() { + let tests = [ + // empty input + [ + "input": "", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.empty.rawValue), + ], + [ + "input": "", + "bsb": "0", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.empty.rawValue), + ], + [ + "input": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.empty.rawValue), + ], + [ + "input": "", + "bsb": "00", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.empty.rawValue), + ], + // incomplete input + [ + "input": "1", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "1", + "bsb": "0", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "1", + "bsb": "00", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "1", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "12345", + "bsb": "06", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + // incomplete input (editing) + [ + "input": "1", + "bsb": "", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "1", + "bsb": "0", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "1", + "bsb": "00", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "1", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "12345", + "bsb": "06", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + [ + "input": "12345678", + "bsb": "", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.incomplete.rawValue), + ], + // complete + [ + "input": "12345", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.complete.rawValue), + ], + [ + "input": "123456", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.complete.rawValue), + ], + [ + "input": "1234567", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.complete.rawValue), + ], + [ + "input": "12345678", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.complete.rawValue), + ], + [ + "input": "123456789", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.complete.rawValue), + ], + // complete (editing) + [ + "input": "123456789", + "bsb": "", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.complete.rawValue), + ], + // invalid + [ + "input": "12345678910", + "bsb": "", + "editing": NSNumber(value: false), + "expected": NSNumber(value: STPTextValidationState.invalid.rawValue), + ], + // invalid (editing) + [ + "input": "12345678910", + "bsb": "", + "editing": NSNumber(value: true), + "expected": NSNumber(value: STPTextValidationState.invalid.rawValue), + ], + ] + + for test in tests { + let input = (test["input"] as? String)! + let bsb = test["bsb"] as? String + let editing = (test["editing"] as? NSNumber)!.boolValue + let expected = STPTextValidationState( + rawValue: (test["expected"] as! NSNumber).intValue + )! + + XCTAssertEqual( + STPBECSDebitAccountNumberValidator.validationState( + forText: input, + withBSBNumber: bsb, + completeOnMaxLengthOnly: editing + ), + expected + ) + } + } + + func testformattedSanitizedTextFromString() { + let tests = [ + [ + "input": "", + "bsb": "00", + "expected": "", + ], + [ + "input": "1", + "bsb": "00", + "expected": "1", + ], + [ + "input": "--111111--", + "bsb": "00", + "expected": "111111", + ], + [ + "input": "12345678910", + "bsb": "00", + "expected": "123456789", + ], + [ + "input": "", + "bsb": "06", + "expected": "", + ], + [ + "input": "1", + "bsb": "06", + "expected": "1", + ], + [ + "input": "--111111--", + "bsb": "06", + "expected": "111111", + ], + [ + "input": "12345678910", + "bsb": "06", + "expected": "123456789", + ], + ] + + for test in tests { + let input = (test["input"])! + let bsb = test["bsb"] + let expected = test["expected"] + XCTAssertEqual( + STPBECSDebitAccountNumberValidator.formattedSanitizedText( + from: input, + withBSBNumber: bsb + ), + expected + ) + } + } +} diff --git a/Stripe/StripeiOSTests/STPBSBNumberValidatorTests.swift b/Stripe/StripeiOSTests/STPBSBNumberValidatorTests.swift new file mode 100644 index 00000000..efe7b37a --- /dev/null +++ b/Stripe/StripeiOSTests/STPBSBNumberValidatorTests.swift @@ -0,0 +1,92 @@ +// +// STPBSBNumberValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPBSBNumberValidatorTests: XCTestCase { + func testValidationStateForText() { + // Don't use the special test key behavior of treating 00 as a valid BSB. + STPAPIClient.shared.publishableKey = "pk_live_not_a_real_key" + let tests: [(String, STPTextValidationState)] = [ + ("", .empty), + ("1", .incomplete), + ("11", .incomplete), + ("00", .invalid), + ("111111", .complete), + ("111-111", .complete), + ("--111-111--", .complete), + ("1234567", .invalid), + ] + + for test in tests { + XCTAssertEqual(STPBSBNumberValidator.validationState(forText: test.0), test.1) + } + } + + func testformattedSanitizedTextFromString() { + let tests = [ + ["", ""], + ["1", "1"], + ["11", "11"], + ["111", "111-"], + ["111111", "111-111"], + ["--111111--", "111-111"], + ["1234567", "123-456"], + ] + + for test in tests { + XCTAssertEqual(STPBSBNumberValidator.formattedSanitizedText(from: test[0]), test[1]) + } + } + + func testIdentityForText() { + let tests = [ + ["", NSNull()], + ["9", NSNull()], + ["94", NSNull()], + ["941", "Delphi Bank (division of Bendigo and Adelaide Bank)"], + ["942", "Bank of Sydney"], + ["942942", "Bank of Sydney"], + ["40", "Commonwealth Bank of Australia"], + ["942-942", "Bank of Sydney"], + ["942942111", "Bank of Sydney"], + ] + + for test in tests { + if test[1] as! NSObject == NSNull() { + XCTAssertNil(STPBSBNumberValidator.identity(forText: test[0] as! String)) + } else { + XCTAssertEqual( + STPBSBNumberValidator.identity(forText: test[0] as! String), + test[1] as? String + ) + } + } + } + + func testIconForText() { + // Don't use the special test key behavior of treating 00 as a valid BSB. + STPAPIClient.shared.publishableKey = "pk_live_not_a_real_key" + let defaultIcon = STPBSBNumberValidator.icon(forText: nil) + XCTAssertNotNil(defaultIcon, "Nil default icon") + + XCTAssertEqual(defaultIcon, STPBSBNumberValidator.icon(forText: "00")) + + let bankIcon = STPBSBNumberValidator.icon(forText: "11") + XCTAssertNotNil(bankIcon, "Nil icon for bank `11`") + XCTAssertFalse((defaultIcon == bankIcon), "Icon for `11` is same as default") + + XCTAssertEqual(bankIcon, STPBSBNumberValidator.icon(forText: "111-111")) + } +} diff --git a/Stripe/StripeiOSTests/STPBankAccountFunctionalTest.swift b/Stripe/StripeiOSTests/STPBankAccountFunctionalTest.swift new file mode 100644 index 00000000..9f34c476 --- /dev/null +++ b/Stripe/StripeiOSTests/STPBankAccountFunctionalTest.swift @@ -0,0 +1,62 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPBankAccountFunctionalTest.m +// Stripe +// +// Created by Charles Scalesse on 10/2/14. +// +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPBankAccountFunctionalTest: STPNetworkStubbingTestCase { + func testCreateAndRetreiveBankAccountToken() { + let bankAccount = STPBankAccountParams() + bankAccount.accountNumber = "000123456789" + bankAccount.routingNumber = "110000000" + bankAccount.country = "US" + bankAccount.accountHolderName = "Jimmy bob" + bankAccount.accountHolderType = STPBankAccountHolderType.company + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "Bank account creation") + client.createToken( + withBankAccount: bankAccount) { token, error in + expectation.fulfill() + XCTAssertNil(error, "error should be nil") + XCTAssertNotNil(token, "token should not be nil") + + XCTAssertNotNil(token?.tokenId) + XCTAssertEqual(token?.type, .bankAccount) + XCTAssertNotNil(token?.bankAccount?.stripeID) + XCTAssertEqual("STRIPE TEST BANK", token?.bankAccount?.bankName) + XCTAssertEqual("6789", token?.bankAccount?.last4) + XCTAssertEqual("Jimmy bob", token?.bankAccount?.accountHolderName) + XCTAssertEqual(token?.bankAccount?.accountHolderType, STPBankAccountHolderType.company) + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testInvalidKey() { + let bankAccount = STPBankAccountParams() + bankAccount.accountNumber = "000123456789" + bankAccount.routingNumber = "110000000" + bankAccount.country = "US" + + let client = STPAPIClient(publishableKey: "not_a_valid_key_asdf") + + let expectation = self.expectation(description: "Bad bank account creation") + + client.createToken( + withBankAccount: bankAccount) { token, error in + expectation.fulfill() + XCTAssertNil(token, "token should be nil") + XCTAssertNotNil(error, "error should not be nil") + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPBankAccountParamsTest.swift b/Stripe/StripeiOSTests/STPBankAccountParamsTest.swift new file mode 100644 index 00000000..847b59d1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPBankAccountParamsTest.swift @@ -0,0 +1,106 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPBankAccountParamsTest.m +// Stripe +// +// Created by Joey Dong on 6/19/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import Stripe +@testable import StripePayments +import XCTest + +class STPBankAccountParamsTest: XCTestCase { + // MARK: - + + func testLast4ReturnsAccountNumberLast4() { + let bankAccountParams = STPBankAccountParams() + bankAccountParams.accountNumber = "000123456789" + XCTAssertEqual(bankAccountParams.last4, "6789") + } + + func testLast4ReturnsNilWhenNoAccountNumberSet() { + let bankAccountParams = STPBankAccountParams() + XCTAssertNil(bankAccountParams.last4) + } + + func testLast4ReturnsNilWhenAccountNumberIsLessThanLength4() { + let bankAccountParams = STPBankAccountParams() + bankAccountParams.accountNumber = "123" + XCTAssertNil(bankAccountParams.last4) + } + + // MARK: - STPBankAccountHolderType Tests + + func testAccountHolderTypeFromString() { + XCTAssertEqual(STPBankAccountParams.accountHolderType(from: "individual"), STPBankAccountHolderType.individual) + XCTAssertEqual(STPBankAccountParams.accountHolderType(from: "INDIVIDUAL"), STPBankAccountHolderType.individual) + + XCTAssertEqual(STPBankAccountParams.accountHolderType(from: "company"), STPBankAccountHolderType.company) + XCTAssertEqual(STPBankAccountParams.accountHolderType(from: "COMPANY"), STPBankAccountHolderType.company) + + XCTAssertEqual(STPBankAccountParams.accountHolderType(from: "garbage"), STPBankAccountHolderType.individual) + XCTAssertEqual(STPBankAccountParams.accountHolderType(from: "GARBAGE"), STPBankAccountHolderType.individual) + } + + func testStringFromAccountHolderType() { + let values = [ + STPBankAccountHolderType.individual, + STPBankAccountHolderType.company, + ] + + for accountHolderType in values { + let string = STPBankAccountParams.string(from: accountHolderType) + + switch accountHolderType { + case STPBankAccountHolderType.individual: + XCTAssertEqual(string, "individual") + case STPBankAccountHolderType.company: + XCTAssertEqual(string, "company") + default: + break + } + } + } + + // MARK: - Description Tests + + func testDescription() { + let bankAccountParams = STPBankAccountParams() + XCTAssertNotNil(bankAccountParams.description) + } + + // MARK: - STPFormEncodable Tests + + func testRootObjectName() { + XCTAssertEqual(STPBankAccountParams.rootObjectName(), "bank_account") + } + + func testPropertyNamesToFormFieldNamesMapping() { + let bankAccountParams = STPBankAccountParams() + + let mapping = STPBankAccountParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(bankAccountParams.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(!formFieldName.isEmpty) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } + + func testAccountHolderTypeString() { + let bankAccountParams = STPBankAccountParams() + + bankAccountParams.accountHolderType = STPBankAccountHolderType.individual + XCTAssertEqual(bankAccountParams.accountHolderTypeString(), "individual") + + bankAccountParams.accountHolderType = .company + XCTAssertEqual(bankAccountParams.accountHolderTypeString(), "company") + } +} diff --git a/Stripe/StripeiOSTests/STPBankAccountTest.swift b/Stripe/StripeiOSTests/STPBankAccountTest.swift new file mode 100644 index 00000000..8ddb033b --- /dev/null +++ b/Stripe/StripeiOSTests/STPBankAccountTest.swift @@ -0,0 +1,124 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPBankAccountTest.m +// Stripe +// +// Created by Charles Scalesse on 10/2/14. +// +// + +@testable import StripePayments +import XCTest + +class STPBankAccountTest: XCTestCase { + // MARK: - STPBankAccountStatus Tests + + func testStatusFromString() { + XCTAssertEqual(STPBankAccount.status(from: "new"), STPBankAccountStatus.new) + XCTAssertEqual(STPBankAccount.status(from: "NEW"), STPBankAccountStatus.new) + + XCTAssertEqual(STPBankAccount.status(from: "validated"), STPBankAccountStatus.validated) + XCTAssertEqual(STPBankAccount.status(from: "VALIDATED"), STPBankAccountStatus.validated) + + XCTAssertEqual(STPBankAccount.status(from: "verified"), STPBankAccountStatus.verified) + XCTAssertEqual(STPBankAccount.status(from: "VERIFIED"), STPBankAccountStatus.verified) + + XCTAssertEqual(STPBankAccount.status(from: "verification_failed"), STPBankAccountStatus.verificationFailed) + XCTAssertEqual(STPBankAccount.status(from: "VERIFICATION_FAILED"), STPBankAccountStatus.verificationFailed) + + XCTAssertEqual(STPBankAccount.status(from: "errored"), STPBankAccountStatus.errored) + XCTAssertEqual(STPBankAccount.status(from: "ERRORED"), STPBankAccountStatus.errored) + + XCTAssertEqual(STPBankAccount.status(from: "garbage"), STPBankAccountStatus.new) + XCTAssertEqual(STPBankAccount.status(from: "GARBAGE"), STPBankAccountStatus.new) + } + + func testStringFromStatus() { + let values = [ + STPBankAccountStatus.new, + STPBankAccountStatus.validated, + STPBankAccountStatus.verified, + STPBankAccountStatus.verificationFailed, + STPBankAccountStatus.errored, + ] + + for status in values { + let string = STPBankAccount.string(from: status) + + switch status { + case STPBankAccountStatus.new: + XCTAssertEqual(string, "new") + case STPBankAccountStatus.validated: + XCTAssertEqual(string, "validated") + case STPBankAccountStatus.verified: + XCTAssertEqual(string, "verified") + case STPBankAccountStatus.verificationFailed: + XCTAssertEqual(string, "verification_failed") + case STPBankAccountStatus.errored: + XCTAssertEqual(string, "errored") + default: + break + } + } + } + + // MARK: - Equality Tests + + func testBankAccountEquals() { + let bankAccount1 = STPBankAccount.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("BankAccount")) + let bankAccount2 = STPBankAccount.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("BankAccount")) + + XCTAssertEqual(bankAccount1, bankAccount1) + XCTAssertEqual(bankAccount1, bankAccount2) + + XCTAssertEqual(bankAccount1?.hash, bankAccount1?.hash) + XCTAssertEqual(bankAccount1?.hash, bankAccount2?.hash) + } + + // MARK: - Description Tests + + func testDescription() { + let bankAccount = STPBankAccount.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("BankAccount")) + XCTAssertNotNil(bankAccount?.description) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = [ + "id", + "last4", + "bank_name", + "country", + "currency", + "status", + ] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed("BankAccount") + response!.removeValue(forKey: field) + + XCTAssertNil(STPBankAccount.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPBankAccount.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("BankAccount"))) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("BankAccount") + let bankAccount = STPBankAccount.decodedObject(fromAPIResponse: response) + + XCTAssertEqual(bankAccount?.stripeID, "ba_1AZmya2eZvKYlo2CQzt7Fwnz") + XCTAssertEqual(bankAccount?.accountHolderName, "Jane Austen") + XCTAssertEqual(bankAccount?.accountHolderType, .individual) + XCTAssertEqual(bankAccount?.bankName, "STRIPE TEST BANK") + XCTAssertEqual(bankAccount?.country, "US") + XCTAssertEqual(bankAccount?.currency, "usd") + XCTAssertEqual(bankAccount?.fingerprint, "1JWtPxqbdX5Gamtc") + XCTAssertEqual(bankAccount?.last4, "6789") + XCTAssertEqual(bankAccount?.routingNumber, "110000000") + XCTAssertEqual(bankAccount?.status, STPBankAccountStatus.new) + + XCTAssertEqual(bankAccount!.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPBinRangeTest.swift b/Stripe/StripeiOSTests/STPBinRangeTest.swift new file mode 100644 index 00000000..8bd42f92 --- /dev/null +++ b/Stripe/StripeiOSTests/STPBinRangeTest.swift @@ -0,0 +1,190 @@ +// +// STPBinRangeTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 5/24/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPBinRangeTest: XCTestCase { + func testAllRanges() { + for binRange in STPBINController.shared.allRanges() { + XCTAssertEqual(binRange.accountRangeLow.count, binRange.accountRangeHigh.count) + } + } + + func testMatchesNumber() { + var binRange = STPBINRange( + panLength: 0, + brand: .unknown, + accountRangeLow: "134", + accountRangeHigh: "167", + country: nil + ) + + XCTAssertFalse(binRange.matchesNumber("0")) + XCTAssertTrue(binRange.matchesNumber("1")) + XCTAssertFalse(binRange.matchesNumber("2")) + + XCTAssertFalse(binRange.matchesNumber("00")) + XCTAssertTrue(binRange.matchesNumber("13")) + XCTAssertTrue(binRange.matchesNumber("14")) + XCTAssertTrue(binRange.matchesNumber("16")) + XCTAssertFalse(binRange.matchesNumber("20")) + + XCTAssertFalse(binRange.matchesNumber("133")) + XCTAssertTrue(binRange.matchesNumber("134")) + XCTAssertTrue(binRange.matchesNumber("135")) + XCTAssertTrue(binRange.matchesNumber("167")) + XCTAssertFalse(binRange.matchesNumber("168")) + + XCTAssertFalse(binRange.matchesNumber("1244")) + XCTAssertTrue(binRange.matchesNumber("1340")) + XCTAssertTrue(binRange.matchesNumber("1344")) + XCTAssertTrue(binRange.matchesNumber("1444")) + XCTAssertTrue(binRange.matchesNumber("1670")) + XCTAssertTrue(binRange.matchesNumber("1679")) + XCTAssertFalse(binRange.matchesNumber("1680")) + + binRange = STPBINRange( + panLength: 0, + brand: .unknown, + accountRangeLow: "004", + accountRangeHigh: "017", + country: nil + ) + + XCTAssertTrue(binRange.matchesNumber("0")) + XCTAssertFalse(binRange.matchesNumber("1")) + + XCTAssertTrue(binRange.matchesNumber("00")) + XCTAssertTrue(binRange.matchesNumber("01")) + XCTAssertFalse(binRange.matchesNumber("10")) + XCTAssertFalse(binRange.matchesNumber("20")) + + XCTAssertFalse(binRange.matchesNumber("000")) + XCTAssertFalse(binRange.matchesNumber("002")) + XCTAssertTrue(binRange.matchesNumber("004")) + XCTAssertTrue(binRange.matchesNumber("009")) + XCTAssertTrue(binRange.matchesNumber("014")) + XCTAssertTrue(binRange.matchesNumber("017")) + XCTAssertFalse(binRange.matchesNumber("019")) + XCTAssertFalse(binRange.matchesNumber("020")) + XCTAssertFalse(binRange.matchesNumber("100")) + + XCTAssertFalse(binRange.matchesNumber("0000")) + XCTAssertFalse(binRange.matchesNumber("0021")) + XCTAssertTrue(binRange.matchesNumber("0044")) + XCTAssertTrue(binRange.matchesNumber("0098")) + XCTAssertTrue(binRange.matchesNumber("0143")) + XCTAssertTrue(binRange.matchesNumber("0173")) + XCTAssertFalse(binRange.matchesNumber("0195")) + XCTAssertFalse(binRange.matchesNumber("0202")) + XCTAssertFalse(binRange.matchesNumber("1004")) + + binRange = STPBINRange( + panLength: 0, + brand: .unknown, + accountRangeLow: "", + accountRangeHigh: "", + country: nil + ) + XCTAssertTrue(binRange.matchesNumber("")) + XCTAssertTrue(binRange.matchesNumber("1")) + } + + func testBinRangesForNumber() { + var binRanges: [STPBINRange]? + + binRanges = STPBINController.shared.binRanges(forNumber: "4136000000008") + XCTAssertEqual(binRanges?.count, 3) + + binRanges = STPBINController.shared.binRanges(forNumber: "4242424242424242") + XCTAssertEqual(binRanges?.count, 2) + + binRanges = STPBINController.shared.binRanges(forNumber: "5555555555554444") + XCTAssertEqual(binRanges?.count, 2) + + binRanges = STPBINController.shared.binRanges(forNumber: "") + XCTAssertEqual(binRanges?.count, STPBINController.shared.allRanges().count) + + binRanges = STPBINController.shared.binRanges(forNumber: "123") + XCTAssertEqual(binRanges?.count, 1) + } + + func testBinRangesForBrand() { + let allBrands: [STPCardBrand] = [ + .visa, + .amex, + .mastercard, + .discover, + .JCB, + .dinersClub, + .unionPay, + .unknown, + ] + for brand in allBrands { + let binRanges = STPBINController.shared.binRanges(for: brand) + for binRange in binRanges { + XCTAssertEqual(binRange.brand, brand) + } + } + } + + func testMostSpecificBinRangeForNumber() { + var binRange: STPBINRange? + + binRange = STPBINController.shared.mostSpecificBINRange(forNumber: "") + XCTAssertNotEqual(binRange?.brand, .unknown) + + binRange = STPBINController.shared.mostSpecificBINRange(forNumber: "4242424242422") + XCTAssertEqual(binRange?.brand, .visa) + XCTAssertEqual(binRange?.panLength, 16) + + binRange = STPBINController.shared.mostSpecificBINRange(forNumber: "4136000000008") + XCTAssertEqual(binRange?.brand, .visa) + XCTAssertEqual(binRange?.panLength, 13) + + binRange = STPBINController.shared.mostSpecificBINRange(forNumber: "4242424242424242") + XCTAssertEqual(binRange?.brand, .visa) + XCTAssertEqual(binRange?.panLength, 16) + } + + func testMostSpecificBinRangePrefersKnownBrand() { + // 624478 is a real world case that returns ranges for UnionPay and NYCE, the latter being handled as unknown. + let mockedRanges = [ + STPBINRange( + panLength: 16, + brand: .unionPay, + accountRangeLow: "6244780000000000", + accountRangeHigh: "6244789999999999", + country: "HK" + ), + STPBINRange( + panLength: 16, + brand: .unknown, + accountRangeLow: "6244780000000000", + accountRangeHigh: "6244789999999999", + country: "CN" + ), + ] + + STPBINController.shared.sRetrievedRanges["624478"] = mockedRanges + STPBINController.shared.sAllRanges += mockedRanges + + let binRange = STPBINController.shared.mostSpecificBINRange(forNumber: "624478") + XCTAssertEqual(binRange.accountRangeLow, "6244780000000000") + XCTAssertEqual(binRange.accountRangeHigh, "6244789999999999") + XCTAssertEqual(binRange.brand, .unionPay) + + // Cleanup added values to avoid issues caused by singleton state. + STPBINController.shared.sRetrievedRanges["624478"] = nil + STPBINController.shared.sAllRanges = STPBINController.STPBINRangeInitialRanges + } +} diff --git a/Stripe/StripeiOSTests/STPBlocks.h b/Stripe/StripeiOSTests/STPBlocks.h new file mode 100644 index 00000000..6b18f5a0 --- /dev/null +++ b/Stripe/StripeiOSTests/STPBlocks.h @@ -0,0 +1,206 @@ +// +// STPBlocks.h +// Stripe +// +// Created by Jack Flintermann on 3/23/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +#import +#import + +@class STP3DS2AuthenticateResponse; +@class STPToken; +@class STPFile; +@class STPSource; +@class STPCustomer; +@protocol STPSourceProtocol; +@class STPPaymentIntent; +@class STPSetupIntent; +@class STPPaymentMethod; +@class STPIssuingCardPin; + +/** + An enum representing the status of a payment requested from the user. + */ +typedef NS_ENUM(NSUInteger, STPPaymentStatus) { + /** + The payment succeeded. + */ + STPPaymentStatusSuccess, + /** + The payment failed due to an unforeseen error, such as the user's Internet connection being offline. + */ + STPPaymentStatusError, + /** + The user cancelled the payment (for example, by hitting "cancel" in the Apple Pay dialog). + */ + STPPaymentStatusUserCancellation, +}; + +/** + An empty block, called with no arguments, returning nothing. + */ +typedef void (^STPVoidBlock)(void); + +/** + A block that may optionally be called with an error. + + @param error The error that occurred, if any. + */ +typedef void (^STPErrorBlock)(NSError * __nullable error); + +/** + A block that contains a boolean success param and may optionally be called with an error. + + @param success Whether the task succeeded. + @param error The error that occurred, if any. + */ +typedef void (^STPBooleanSuccessBlock)(BOOL success, NSError * __nullable error); + +/** + A callback to be run with a JSON response. + + @param jsonResponse The JSON response, or nil if an error occured. + @param error The error that occurred, if any. + */ +typedef void (^STPJSONResponseCompletionBlock)(NSDictionary * __nullable jsonResponse, NSError * __nullable error); + +/** + A callback to be run with a token response from the Stripe API. + + @param token The Stripe token from the response. Will be nil if an error occurs. @see STPToken + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPTokenCompletionBlock)(STPToken * __nullable token, NSError * __nullable error); + +/** + A callback to be run with a source response from the Stripe API. + + @param source The Stripe source from the response. Will be nil if an error occurs. @see STPSource + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPSourceCompletionBlock)(STPSource * __nullable source, NSError * __nullable error); + +/** + A callback to be run with a source or card response from the Stripe API. + + @param source The Stripe source from the response. Will be nil if an error occurs. @see STPSourceProtocol + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPSourceProtocolCompletionBlock)(id __nullable source, NSError * __nullable error); + +/** + A callback to be run with a PaymentIntent response from the Stripe API. + + @param paymentIntent The Stripe PaymentIntent from the response. Will be nil if an error occurs. @see STPPaymentIntent + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPPaymentIntentCompletionBlock)(STPPaymentIntent * __nullable paymentIntent, NSError * __nullable error); + +/** + A callback to be run with a PaymentIntent response from the Stripe API. + + @param setupIntent The Stripe SetupIntent from the response. Will be nil if an error occurs. @see STPSetupIntent + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPSetupIntentCompletionBlock)(STPSetupIntent * __nullable setupIntent, NSError * __nullable error); + +/** + A callback to be run with a PaymentMethod response from the Stripe API. + + @param paymentMethod The Stripe PaymentMethod from the response. Will be nil if an error occurs. @see STPPaymentMethod + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPPaymentMethodCompletionBlock)(STPPaymentMethod * __nullable paymentMethod, NSError * __nullable error); + +/** + A callback to be run with an array of PaymentMethods response from the Stripe API. + + @param paymentMethods An array of PaymentMethod from the response. Will be nil if an error occurs. @see STPPaymentMethod + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPPaymentMethodsCompletionBlock)(NSArray *__nullable paymentMethods, NSError * __nullable error); + +/** + A callback to be run with a file response from the Stripe API. + + @param file The Stripe file from the response. Will be nil if an error occurs. @see STPFile + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPFileCompletionBlock)(STPFile * __nullable file, NSError * __nullable error); + +/** + A callback to be run with a customer response from the Stripe API. + + @param customer The Stripe customer from the response, or nil if an error occurred. @see STPCustomer + @param error The error returned from the response, or nil if none occurs. + */ +typedef void (^STPCustomerCompletionBlock)(STPCustomer * __nullable customer, NSError * __nullable error); + +/** + An enum representing the success and error states of PIN management + */ +typedef NS_ENUM(NSUInteger, STPPinStatus) { + /** + The verification object was already redeemed + */ + STPPinSuccess, + /** + The verification object was already redeemed + */ + STPPinErrorVerificationAlreadyRedeemed, + /** + The one-time code was incorrect + */ + STPPinErrorVerificationCodeIncorrect, + /** + The verification object was expired + */ + STPPinErrorVerificationExpired, + /** + The verification object has been attempted too many times + */ + STPPinErrorVerificationTooManyAttempts, + /** + An error occured while retrieving the ephemeral key + */ + STPPinEphemeralKeyError, + /** + An unknown error occured + */ + STPPinUnknownError, +}; + +/** + A callback to be run with a card PIN response from the Stripe API. + + @param cardPin The Stripe card PIN from the response. Will be nil if an error occurs. @see STPIssuingCardPin + @param status The status to help you sort between different error state, or STPPinSuccess when succesful. @see STPPinStatus for possible values. + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPPinCompletionBlock)(STPIssuingCardPin * __nullable cardPin, STPPinStatus status, NSError * __nullable error); + +/** + A callback to be run with a 3DS2 authenticate response from the Stripe API. + + @param authenticateResponse The Stripe AuthenticateResponse. Will be nil if an error occurs. @see STP3DS2AuthenticateResponse + @param error The error returned from the response, or nil if none occurs. + */ +typedef void (^STP3DS2AuthenticateCompletionBlock)(STP3DS2AuthenticateResponse * _Nullable authenticateResponse, NSError * _Nullable error); + +/** + A block called with a payment status and an optional error. + + @param error The error that occurred, if any. + */ +typedef void (^STPPaymentStatusBlock)(STPPaymentStatus status, NSError * __nullable error); + +/** + A block to be run with the client secret of a PaymentIntent or SetupIntent. + + @param clientSecret The client secret of the PaymentIntent or SetupIntent. See https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret + @param error The error that occurred when creating the Intent, or nil if none occurred. + */ +typedef void (^STPIntentClientSecretCompletionBlock)(NSString * __nullable clientSecret, NSError * __nullable error); + diff --git a/Stripe/StripeiOSTests/STPCardBINMetadataTests.swift b/Stripe/StripeiOSTests/STPCardBINMetadataTests.swift new file mode 100644 index 00000000..bb98e92f --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardBINMetadataTests.swift @@ -0,0 +1,55 @@ +// +// STPCardBINMetadataTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 7/20/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardBINMetadataTests: XCTestCase { + func testAPICall() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + + let expectation = self.expectation(description: "Retrieve card metadata") + + // 625035 is a randomly selected UnionPay BIN + STPBINRange.retrieve( + forPrefix: "625035", + completion: { result in + let cardMetadata = try! result.get() + XCTAssertTrue(cardMetadata.data.count > 0) + XCTAssertEqual(cardMetadata.data.first!.brand, .unionPay) + expectation.fulfill() + } + ) + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + } + + func testLoadingInBINRange() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + + let expectation = self.expectation(description: "Retrieve card metadata") + let hardCodedBinRanges = STPBINController.shared.allRanges() + STPBINController.shared.retrieveBINRanges(forPrefix: "625035") { result in + let ranges = try! result.get() + XCTAssertTrue(ranges.count > 0) + XCTAssertTrue( + STPBINController.shared.allRanges().count == hardCodedBinRanges.count + ranges.count + ) + for range in ranges { + XCTAssertTrue(STPBINController.shared.allRanges().contains(range)) + } + expectation.fulfill() + } + wait(for: [expectation], timeout: STPTestingNetworkRequestTimeout) + + } +} diff --git a/Stripe/StripeiOSTests/STPCardBrandTest.swift b/Stripe/StripeiOSTests/STPCardBrandTest.swift new file mode 100644 index 00000000..8c74756a --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardBrandTest.swift @@ -0,0 +1,95 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPCardBrandTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/3/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPCardBrandTest: XCTestCase { + func testStringFromBrand() { + let brands = [ + NSNumber(value: STPCardBrand.amex.rawValue), + NSNumber(value: STPCardBrand.dinersClub.rawValue), + NSNumber(value: STPCardBrand.discover.rawValue), + NSNumber(value: STPCardBrand.JCB.rawValue), + NSNumber(value: STPCardBrand.mastercard.rawValue), + NSNumber(value: STPCardBrand.unionPay.rawValue), + NSNumber(value: STPCardBrand.visa.rawValue), + NSNumber(value: STPCardBrand.cartesBancaires.rawValue), + NSNumber(value: STPCardBrand.unknown.rawValue), + ] + + for brandNumber in brands { + let brand = STPCardBrand(rawValue: brandNumber.intValue) + let string = STPCardBrandUtilities.stringFrom(brand!) + + switch brand { + case .amex: + XCTAssertEqual(string, "American Express") + case .dinersClub: + XCTAssertEqual(string, "Diners Club") + case .discover: + XCTAssertEqual(string, "Discover") + case .JCB: + XCTAssertEqual(string, "JCB") + case .mastercard: + XCTAssertEqual(string, "Mastercard") + case .unionPay: + XCTAssertEqual(string, "UnionPay") + case .visa: + XCTAssertEqual(string, "Visa") + case .cartesBancaires: + XCTAssertEqual(string, "Cartes Bancaires") + case .unknown: + XCTAssertEqual(string, "Unknown") + case .none: + XCTAssertEqual(string, "Unknown") + @unknown default: + break + } + } + } + + func testApiValueFromBrand() { + let brands = [ + STPCardBrand.visa, + STPCardBrand.amex, + STPCardBrand.mastercard, + STPCardBrand.discover, + STPCardBrand.JCB, + STPCardBrand.dinersClub, + STPCardBrand.unionPay, + STPCardBrand.cartesBancaires, + STPCardBrand.unknown, + ] + + for brand in brands { + let string = STPCardBrandUtilities.apiValue(from: brand) + + switch brand { + case .amex: + XCTAssertEqual(string, "american_express") + case .dinersClub: + XCTAssertEqual(string, "diners_club") + case .discover: + XCTAssertEqual(string, "discover") + case .JCB: + XCTAssertEqual(string, "jcb") + case .mastercard: + XCTAssertEqual(string, "mastercard") + case .unionPay: + XCTAssertEqual(string, "unionpay") + case .visa: + XCTAssertEqual(string, "visa") + case .cartesBancaires: + XCTAssertEqual(string, "cartes_bancaires") + case .unknown: + XCTAssertEqual(string, "unknown") + @unknown default: + break + } + } + } +} diff --git a/Stripe/StripeiOSTests/STPCardCVCInputTextFieldFormatterTests.swift b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldFormatterTests.swift new file mode 100644 index 00000000..1288fcea --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldFormatterTests.swift @@ -0,0 +1,52 @@ +// +// STPCardCVCInputTextFieldFormatterTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardCVCInputTextFieldFormatterTests: XCTestCase { + + func testAllowedInput() { + let formatter = STPCardCVCInputTextFieldFormatter() + + formatter.cardBrand = .unknown + XCTAssertTrue(formatter.isAllowedInput("1", to: "", at: NSRange(location: 0, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("12", to: "", at: NSRange(location: 0, length: 2))) + XCTAssertTrue(formatter.isAllowedInput("2", to: "1", at: NSRange(location: 1, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("3", to: "12", at: NSRange(location: 2, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("123", to: "", at: NSRange(location: 0, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("4", to: "123", at: NSRange(location: 3, length: 1))) + XCTAssertFalse(formatter.isAllowedInput("5", to: "1234", at: NSRange(location: 4, length: 1))) + + formatter.cardBrand = .amex + XCTAssertTrue(formatter.isAllowedInput("1", to: "", at: NSRange(location: 0, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("12", to: "", at: NSRange(location: 0, length: 2))) + XCTAssertTrue(formatter.isAllowedInput("2", to: "1", at: NSRange(location: 1, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("3", to: "12", at: NSRange(location: 2, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("123", to: "", at: NSRange(location: 0, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("4", to: "123", at: NSRange(location: 3, length: 1))) + XCTAssertFalse(formatter.isAllowedInput("5", to: "1234", at: NSRange(location: 4, length: 1))) + + formatter.cardBrand = .visa + XCTAssertTrue(formatter.isAllowedInput("1", to: "", at: NSRange(location: 0, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("12", to: "", at: NSRange(location: 0, length: 2))) + XCTAssertTrue(formatter.isAllowedInput("2", to: "1", at: NSRange(location: 1, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("3", to: "12", at: NSRange(location: 2, length: 1))) + XCTAssertTrue(formatter.isAllowedInput("123", to: "", at: NSRange(location: 0, length: 1))) + XCTAssertFalse(formatter.isAllowedInput("4", to: "123", at: NSRange(location: 3, length: 1))) + XCTAssertFalse(formatter.isAllowedInput("5", to: "1234", at: NSRange(location: 4, length: 1))) + + XCTAssertFalse(formatter.isAllowedInput("a", to: "123", at: NSRange(location: 0, length: 1))) + } + +} diff --git a/Stripe/StripeiOSTests/STPCardCVCInputTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldSnapshotTests.swift new file mode 100644 index 00000000..2007bbf0 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldSnapshotTests.swift @@ -0,0 +1,57 @@ +// +// STPCardCVCInputTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardCVCInputTextFieldSnapshotTests: STPSnapshotTestCase { + + func testEmpty() { + let field = STPCardCVCInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + + STPSnapshotVerifyView(field) + } + + func testIncomplete() { + let field = STPCardCVCInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + field.text = "1" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testValid() { + let field = STPCardCVCInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + field.text = "123" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testInvalid() { + let field = STPCardCVCInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + field.text = "12345" + field.textDidChange() + + STPSnapshotVerifyView(field) + } +} diff --git a/Stripe/StripeiOSTests/STPCardCVCInputTextFieldTests.swift b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldTests.swift new file mode 100644 index 00000000..dcd15604 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldTests.swift @@ -0,0 +1,34 @@ +// +// STPCardCVCInputTextFieldTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 8/31/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardCVCInputTextFieldTests: XCTestCase { + + func testTruncatingCVCWhenTooLong() { + let cvcField = STPCardCVCInputTextField() + cvcField.cardBrand = .amex + cvcField.text = String( + repeating: "1", + count: Int(STPCardValidator.maxCVCLength(for: .amex)) + ) + XCTAssertEqual(cvcField.text?.count, Int(STPCardValidator.maxCVCLength(for: .amex))) + + // Switching the card brand to `visa` should truncate the field text to + // the max length allowed for the brand + cvcField.cardBrand = .visa + XCTAssertEqual(cvcField.text?.count, Int(STPCardValidator.maxCVCLength(for: .visa))) + } + +} diff --git a/Stripe/StripeiOSTests/STPCardCVCInputTextFieldValidatorTests.swift b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldValidatorTests.swift new file mode 100644 index 00000000..deb018dc --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardCVCInputTextFieldValidatorTests.swift @@ -0,0 +1,54 @@ +// +// STPCardCVCInputTextFieldValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardCVCInputTextFieldValidatorTests: XCTestCase { + + func testValidation() { + let validator = STPCardCVCInputTextFieldValidator() + validator.cardBrand = .visa + + validator.inputValue = "123" + if case .valid = validator.validationState { + XCTAssertTrue(true) + } else { + XCTAssertTrue(false, "123 should be valid for Visa") + } + + validator.inputValue = "1" + if case .incomplete(let description) = validator.validationState { + XCTAssertTrue(true) + XCTAssertEqual(description, "Your card's security code is incomplete.") + } else { + XCTAssertTrue(false, "1 should be incomplete for Visa") + } + + validator.inputValue = "1234" + if case .invalid(let errorMessage) = validator.validationState { + XCTAssertEqual(errorMessage, "Your card's security code is invalid.") + } else { + XCTAssertTrue(false, "1234 should be invalid for Visa") + } + + validator.cardBrand = .amex + // don't update inputValue so we know validationState is recalculated on cardBrand change + if case .valid = validator.validationState { + XCTAssertTrue(true) + } else { + XCTAssertTrue(false, "1234 should be valid for Amex") + } + } + +} diff --git a/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldFormatterTests.swift b/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldFormatterTests.swift new file mode 100644 index 00000000..ee89f7f3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldFormatterTests.swift @@ -0,0 +1,64 @@ +// +// STPCardExpiryInputTextFieldFormatterTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardExpiryInputTextFieldFormatterTests: XCTestCase { + + func testAllowedInput() { + let formatter = STPCardExpiryInputTextFieldFormatter() + XCTAssertTrue(formatter.isAllowedInput("1226", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("12/26", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("12 / 26", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("122026", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("12/2026", to: "", at: NSRange(location: 0, length: 0))) + + XCTAssertTrue(formatter.isAllowedInput("1", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("2", to: "1", at: NSRange(location: 1, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("2", to: "12", at: NSRange(location: 2, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("2", to: "12/", at: NSRange(location: 2, length: 0))) + + // the formatter does NOT verify that these are sensical dates (that is delegated to the validator) + XCTAssertTrue(formatter.isAllowedInput("16/1901", to: "", at: NSRange(location: 0, length: 0))) + + XCTAssertFalse(formatter.isAllowedInput("12 / 25 / 26", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("12 / 25 / 26", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("12.26", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("2026/12", to: "", at: NSRange(location: 0, length: 0))) + } + + func testFormattedText() { + let formatter = STPCardExpiryInputTextFieldFormatter() + XCTAssertEqual( + formatter.formattedText(from: "1226", with: [:]), + NSAttributedString(string: "12/26") + ) + XCTAssertEqual( + formatter.formattedText(from: "12/26", with: [:]), + NSAttributedString(string: "12/26") + ) + XCTAssertEqual( + formatter.formattedText(from: "12 / 26", with: [:]), + NSAttributedString(string: "12/26") + ) + XCTAssertEqual( + formatter.formattedText(from: "122026", with: [:]), + NSAttributedString(string: "12/26") + ) + XCTAssertEqual( + formatter.formattedText(from: "12 / 2026", with: [:]), + NSAttributedString(string: "12/26") + ) + } +} diff --git a/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldSnapshotTests.swift new file mode 100644 index 00000000..e597729b --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldSnapshotTests.swift @@ -0,0 +1,52 @@ +// +// STPCardExpiryInputTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardExpiryInputTextFieldSnapshotTests: STPSnapshotTestCase { + + func testEmpty() { + let field = STPCardExpiryInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + + STPSnapshotVerifyView(field) + } + + func testIncomplete() { + let field = STPCardExpiryInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + field.text = "1" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + // We can't have a valid test here because the date would have to change as time marches on + // func testValid() { + // } + + func testInvalid() { + let field = STPCardExpiryInputTextField() + field.sizeToFit() + field.frame.size.width = 200 + field.text = "16/22" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + +} diff --git a/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldValidatorTests.swift b/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldValidatorTests.swift new file mode 100644 index 00000000..5d22d3b5 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardExpiryInputTextFieldValidatorTests.swift @@ -0,0 +1,139 @@ +// +// STPCardExpiryInputTextFieldValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardExpiryInputTextFieldValidatorTests: XCTestCase { + + func testValidation() { + let now = Date() + guard + let nowMonth = Calendar(identifier: .gregorian).dateComponents( + Set([Calendar.Component.month]), + from: now + ).month, + let fullYear = Calendar(identifier: .gregorian).dateComponents( + Set([Calendar.Component.year]), + from: now + ).year + else { + XCTFail("Chaos reigns") + return + } + let nowYear = fullYear % 100 + + let validator = STPCardExpiryInputTextFieldValidator() + validator.inputValue = String(format: "01/%2d", (nowYear + 1) % 100) + if case .valid = validator.validationState { + XCTAssertTrue(true) + } else { + XCTFail("January of next year should be valid") + } + + let oneMonthAhead: String = { + if nowMonth == 12 { + return String(format: "01/%2d", (nowYear + 1) % 100) + } else { + return String(format: "%02d/%2d", nowMonth + 1, nowYear) + } + }() + validator.inputValue = oneMonthAhead + if case .valid = validator.validationState { + XCTAssertTrue(true) + } else { + XCTFail("One month ahead should be valid") + } + + let oneMonthAgo: String = { + if nowMonth == 1 { + return String(format: "01/%2d", max(0, nowYear - 1)) + } else { + return String(format: "%02d/%2d", nowMonth - 1, nowYear) + } + }() + validator.inputValue = oneMonthAgo + if case .invalid(let errorMessage) = validator.validationState { + XCTAssertEqual(errorMessage, "Your card's expiration year is invalid.") + } else { + XCTFail("One month ago should be invalid") + } + + let nonsensical = "16/55" + validator.inputValue = nonsensical + if case .invalid(let errorMessage) = validator.validationState { + XCTAssertEqual(errorMessage, "Your card's expiration date is invalid.") + } else { + XCTFail("Invalid month+year should be invalid") + } + + let nineties = "01/95" + validator.inputValue = nineties + if case .invalid(let errorMessage) = validator.validationState { + XCTAssertEqual(errorMessage, "Your card's expiration year is invalid.") + } else { + XCTFail("The 90s are over") + } + + validator.inputValue = "2" + if case .incomplete(let description) = validator.validationState { + XCTAssertEqual(description, "Your card's expiration date is incomplete.") + } else { + XCTFail("One digit should be incomplete") + } + + validator.inputValue = "2/" + if case .incomplete(let description) = validator.validationState { + XCTAssertEqual(description, "Your card's expiration date is incomplete.") + } else { + XCTFail("One digit with separator should be incomplete") + } + + validator.inputValue = String(format: "1/%2d", (nowYear + 1) % 100) + if case .incomplete(let description) = validator.validationState { + XCTAssertEqual(description, "Your card's expiration date is incomplete.") + } else { + XCTFail("Single digit month should be incomplete") + } + + validator.inputValue = "13/" + if case .invalid(let description) = validator.validationState { + XCTAssertEqual(description, "Your card's expiration month is invalid.") + } else { + XCTFail("Invalid month should be invalid") + } + } + + func testExpiryStringFormatsYear() throws { + let validator = STPCardExpiryInputTextFieldValidator() + + validator.inputValue = "02/24" + + let expiryStrings = try XCTUnwrap(validator.expiryStrings) + + XCTAssertEqual(expiryStrings.month, "02") + XCTAssertEqual(expiryStrings.year, "2024") + } + + func testExpiryStringDoesNotFormatYear() throws { + let validator = STPCardExpiryInputTextFieldValidator() + + validator.inputValue = "02/2024" + + let expiryStrings = try XCTUnwrap(validator.expiryStrings) + + XCTAssertEqual(expiryStrings.month, "02") + XCTAssertEqual(expiryStrings.year, "2024") + } + +} diff --git a/Stripe/StripeiOSTests/STPCardFormViewSnapshotTests.swift b/Stripe/StripeiOSTests/STPCardFormViewSnapshotTests.swift new file mode 100644 index 00000000..df44aa05 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardFormViewSnapshotTests.swift @@ -0,0 +1,160 @@ +// +// STPCardFormViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardFormViewSnapshotTests: STPSnapshotTestCase { + + func testEmpty() { + let formView = STPCardFormView(billingAddressCollection: .automatic) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + STPSnapshotVerifyView(formView) + } + + func testIncomplete() { + let formView = STPCardFormView(billingAddressCollection: .automatic) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 265)) + + formView.numberField.text = "4242" + formView.numberField.textDidChange() + formView.cvcField.text = "123" + formView.cvcField.textDidChange() + + STPSnapshotVerifyView(formView) + } + + // valid expiration date will change over time so we just test without it + func testCompleteWithoutExpiry() { + let formView = STPCardFormView(billingAddressCollection: .automatic) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + formView.numberField.text = "4242424242424242" + formView.numberField.textDidChange() + formView.cvcField.text = "123" + formView.cvcField.textDidChange() + formView.postalCodeField.text = "12345" + + STPSnapshotVerifyView(formView) + } + + func testEmptyHiddenPostalCode() { + let formView = STPCardFormView(billingAddressCollection: .automatic) + formView.countryCode = "AE" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + STPSnapshotVerifyView(formView) + } + + func testWithFullBillingDetails() { + let formView = STPCardFormView(billingAddressCollection: .required) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 400)) + + STPSnapshotVerifyView(formView) + } + + // MARK: - Standalone + + func testDefaultStandalone() { + let formView = STPCardFormView() + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + STPSnapshotVerifyView(formView) + } + + func testBorderlessStandalone() { + let formView = STPCardFormView(style: .borderless) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + STPSnapshotVerifyView(formView) + } + + func testCustomBackgroundStandalone() { + let formView = STPCardFormView() + formView.countryCode = "US" + formView.backgroundColor = .green + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + STPSnapshotVerifyView(formView) + } + + func testCustomBackgroundDisabledColorStandalone() { + let formView = STPCardFormView() + formView.countryCode = "US" + formView.disabledBackgroundColor = .green + formView.isUserInteractionEnabled = false + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + STPSnapshotVerifyView(formView) + } + + func testBorderlessStandaloneIncomplete() { + let formView = STPCardFormView(style: .borderless) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + formView.numberField.text = "4242" + formView.numberField.textDidChange() + formView.cvcField.text = "123" + formView.cvcField.textDidChange() + + STPSnapshotVerifyView(formView) + } + + func testCBC() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let formView = STPCardFormView(billingAddressCollection: .automatic, cbcEnabledOverride: true) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + formView.numberField.text = "4973019750239993" + formView.numberField.textDidChange() + formView.cvcField.text = "123" + formView.cvcField.textDidChange() + formView.postalCodeField.text = "12345" + let exp = expectation(description: "Wait for CBC load") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + self.STPSnapshotVerifyView(formView) + exp.fulfill() + } + waitForExpectations(timeout: 3.0) + } + + func testCBCPreselectVisa() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let formView = STPCardFormView(billingAddressCollection: .automatic, cbcEnabledOverride: true) + formView.countryCode = "US" + formView.frame = CGRect(origin: .zero, size: CGSize(width: 300, height: 225)) + + formView.numberField.text = "4973019750239993" + formView.numberField.textDidChange() + formView.cvcField.text = "123" + formView.cvcField.textDidChange() + formView.postalCodeField.text = "12345" + formView.preferredNetworks = [.visa] + let exp = expectation(description: "Wait for CBC load") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + self.STPSnapshotVerifyView(formView) + exp.fulfill() + } + waitForExpectations(timeout: 3.0) + } + +} diff --git a/Stripe/StripeiOSTests/STPCardFormViewTests.swift b/Stripe/StripeiOSTests/STPCardFormViewTests.swift new file mode 100644 index 00000000..5845045b --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardFormViewTests.swift @@ -0,0 +1,283 @@ +// +// STPCardFormViewTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 1/19/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardFormViewTests: XCTestCase { + + func testMarkFormErrorsLogic() { + let cardForm = STPCardFormView() + + let handledErrorsTypes = [ + "incorrect_number", + "invalid_number", + "invalid_expiry_month", + "invalid_expiry_year", + "expired_card", + "invalid_cvc", + "incorrect_cvc", + "incorrect_zip", + ] + + let unhandledErrorTypes = [ + "card_declined", + "processing_error", + "imaginary_error", + "", + nil, + ] + + for shouldHandle in handledErrorsTypes { + let error = NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.apiError.rawValue, + userInfo: [STPError.stripeErrorCodeKey: shouldHandle] + ) + XCTAssertTrue( + cardForm.markFormErrors(for: error), + "Failed to handle error for \(shouldHandle)" + ) + } + + for shouldNotHandle in unhandledErrorTypes { + let error: NSError + if let shouldNotHandle = shouldNotHandle { + error = NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.apiError.rawValue, + userInfo: [STPError.stripeErrorCodeKey: shouldNotHandle] + ) + } else { + error = NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.apiError.rawValue, + userInfo: nil + ) + } + XCTAssertFalse( + cardForm.markFormErrors(for: error), + "Incorrectly handled \(shouldNotHandle ?? "nil")" + ) + } + } + + func testHidingPostalCodeOnInit() { + NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "zh_Hans_HK")) { + let cardForm = STPCardFormView() + XCTAssertTrue(cardForm.postalCodeField.isHidden) + } + } + + func testHidingPostalUPECodeOnInit() { + NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "zh_Hans_HK")) { + let cardForm = STPCardFormView( + billingAddressCollection: .automatic, + style: .standard, + postalCodeRequirement: .upe, + prefillDetails: nil + ) + XCTAssertTrue(cardForm.postalCodeField.isHidden) + } + } + + func testNotHidingPostalUPECodeOnInit() { + NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_US")) { + let cardForm = STPCardFormView( + billingAddressCollection: .automatic, + style: .standard, + postalCodeRequirement: .upe, + prefillDetails: nil + ) + XCTAssertFalse(cardForm.postalCodeField.isHidden) + } + } + + func testPanLockedOnInit() { + NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_US")) { + let cardForm = STPCardFormView( + billingAddressCollection: .automatic, + style: .standard, + postalCodeRequirement: .upe, + prefillDetails: nil, + inputMode: .panLocked + ) + XCTAssertFalse(cardForm.numberField.isUserInteractionEnabled) + } + } + + func testPrefilledOnInit() { + let prefillDeatils = STPCardFormView.PrefillDetails( + last4: "4242", + expiryMonth: 12, + expiryYear: 25, + cardBrand: .amex + ) + NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_US")) { + let cardForm = STPCardFormView( + billingAddressCollection: .automatic, + style: .standard, + postalCodeRequirement: .upe, + prefillDetails: prefillDeatils, + inputMode: .panLocked + ) + + XCTAssertEqual(cardForm.numberField.text, prefillDeatils.formattedLast4) + XCTAssertEqual(cardForm.numberField.cardBrandState.brand, prefillDeatils.cardBrand) + XCTAssertEqual(cardForm.expiryField.text, prefillDeatils.formattedExpiry) + XCTAssertEqual(cardForm.cvcField.cardBrand, prefillDeatils.cardBrand) + } + } + + func testCBCWithPreferredNetwork() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let cardFormView = STPCardFormView(billingAddressCollection: .automatic, cbcEnabledOverride: true) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "5555552500001001" + cardParams.expYear = 2050 + cardParams.expMonth = 12 + cardParams.cvc = "123" + cardParams.networks = .init(preferred: "cartes_bancaires") + let billingDetails = STPPaymentMethodBillingDetails(postalCode: "12345", countryCode: "US") + let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil) + cardFormView.cardParams = paymentMethodParams + XCTAssertEqual(cardFormView.cardParams?.card?.number, cardParams.number) + let exp = expectation(description: "Wait for CBC load") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + XCTAssertEqual(cardFormView.cardParams?.card?.networks?.preferred, "cartes_bancaires") + XCTAssertEqual(cardFormView.numberField.cardBrandState.brand, .cartesBancaires) + exp.fulfill() + } + waitForExpectations(timeout: 3.0) + } + + func testCBCOBO() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let cardFormView = STPCardFormView(billingAddressCollection: .automatic, cbcEnabledOverride: true) + cardFormView.onBehalfOf = "acct_abc123" + XCTAssertEqual((cardFormView.numberField.validator as! STPCardNumberInputTextFieldValidator).cbcController.onBehalfOf, "acct_abc123") + } + + func testCBCFourDigitCVCIsInvalid() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let cardFormView = STPCardFormView(billingAddressCollection: .automatic, cbcEnabledOverride: true) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "5555552500001001" + cardParams.expYear = 2050 + cardParams.expMonth = 12 + cardParams.cvc = "1234" + let billingDetails = STPPaymentMethodBillingDetails(postalCode: "12345", countryCode: "US") + let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil) + cardFormView.cardParams = paymentMethodParams + let exp = expectation(description: "Wait for validation") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + XCTAssertFalse(cardFormView.cvcField.isValid) + exp.fulfill() + } + waitForExpectations(timeout: 0.5) + } + + // MARK: Functional Tests + // If these fail it's _possibly_ because the returned error formats have changed + + func helperFunctionalTestNumber(_ cardNumber: String, shouldHandle: Bool) { + let createPaymentIntentExpectation = self.expectation( + description: "createPaymentIntentExpectation" + ) + var retrievedClientSecret: String? + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { + (createdPIClientSecret, _) in + if let createdPIClientSecret = createdPIClientSecret { + retrievedClientSecret = createdPIClientSecret + createPaymentIntentExpectation.fulfill() + } else { + XCTFail() + } + } + wait(for: [createPaymentIntentExpectation], timeout: 8) // STPTestingNetworkRequestTimeout + guard let clientSecret = retrievedClientSecret, + let currentYear = Calendar.current.dateComponents([.year], from: Date()).year + else { + XCTFail() + return + } + + // STPTestingDefaultPublishableKey + let client = STPAPIClient(publishableKey: "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6") + + let expiryYear = NSNumber(value: currentYear + 2) + let expiryMonth = NSNumber(1) + + let cardParams = STPPaymentMethodCardParams() + cardParams.number = cardNumber + cardParams.expYear = expiryYear + cardParams.expMonth = expiryMonth + cardParams.cvc = "123" + + let address = STPPaymentMethodAddress() + address.postalCode = "12345" + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.address = address + + let paymentMethodParams = STPPaymentMethodParams.paramsWith( + card: cardParams, + billingDetails: billingDetails, + metadata: nil + ) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) + paymentIntentParams.paymentMethodParams = paymentMethodParams + + let confirmExpectation = expectation(description: "confirmExpectation") + client.confirmPaymentIntent(with: paymentIntentParams) { (_, error) in + if let error = error { + let cardForm = STPCardFormView() + if shouldHandle { + XCTAssertTrue( + cardForm.markFormErrors(for: error), + "Failed to handle \(error) for \(cardNumber)" + ) + } else { + XCTAssertFalse( + cardForm.markFormErrors(for: error), + "Incorrectly handled \(error) for \(cardNumber)" + ) + } + confirmExpectation.fulfill() + } else { + XCTFail() + } + } + wait(for: [confirmExpectation], timeout: 8) // STPTestingNetworkRequestTimeout + } + + func testExpiredCard() { + helperFunctionalTestNumber("4000000000000069", shouldHandle: true) + } + + func testIncorrectCVC() { + helperFunctionalTestNumber("4000000000000127", shouldHandle: true) + } + + func testIncorrectCardNumber() { + helperFunctionalTestNumber("4242424242424241", shouldHandle: true) + } + + func testCardDeclined() { + helperFunctionalTestNumber("4000000000000002", shouldHandle: false) + } + + func testProcessingError() { + helperFunctionalTestNumber("4000000000000119", shouldHandle: false) + } +} diff --git a/Stripe/StripeiOSTests/STPCardFunctionalTest.swift b/Stripe/StripeiOSTests/STPCardFunctionalTest.swift new file mode 100644 index 00000000..5ab15732 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardFunctionalTest.swift @@ -0,0 +1,146 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPCardFunctionalTest.m +// Stripe +// +// Created by Ray Morgan on 7/11/14. +// +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPCardFunctionalTest: STPNetworkStubbingTestCase { + func testCreateCardToken() { + let card = STPCardParams() + + card.number = "4242 4242 4242 4242" + card.expMonth = 6 + card.expYear = 2050 + card.currency = "usd" + card.address.line1 = "123 Fake Street" + card.address.line2 = "Apartment 4" + card.address.city = "New York" + card.address.state = "NY" + card.address.country = "USA" + card.address.postalCode = "10002" + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "Card creation") + + client.createToken( + withCard: card) { token, error in + XCTAssertNil(error, "error should be nil") + XCTAssertNotNil(token, "token should not be nil") + + XCTAssertNotNil(token?.tokenId) + XCTAssertEqual(token?.type, .card) + XCTAssertEqual(6, token?.card?.expMonth) + XCTAssertEqual(2050, token?.card?.expYear) + XCTAssertEqual("4242", token?.card?.last4) + XCTAssertEqual("usd", token?.card?.currency) + XCTAssertEqual("10002", token?.card?.address?.postalCode) + expectation.fulfill() + + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCardTokenCreationWithInvalidParams() { + let card = STPCardParams() + + card.number = "4242 4242 4242 4241" + card.expMonth = 6 + card.expYear = 2024 + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "Card creation") + + client.createToken( + withCard: card) { token, error in + XCTAssertNotNil(error, "error should not be nil") + XCTAssertEqual((error as NSError?)?.code, 70) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain) + XCTAssertEqual((error as NSError?)?.userInfo[STPError.errorParameterKey] as! String, "number") + XCTAssertNil(token, "token should be nil") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCardTokenCreationWithExpiredCard() { + let card = STPCardParams() + + card.number = "4242 4242 4242 4242" + card.expMonth = 6 + card.expYear = 2013 + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "Card creation") + + client.createToken( + withCard: card) { token, error in + XCTAssertNotNil(error, "error should not be nil") + XCTAssertEqual((error as NSError?)?.code, 70) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain ) + XCTAssertEqual((error as NSError?)?.userInfo[STPError.cardErrorCodeKey] as! String, STPError.invalidExpYear) + XCTAssertEqual((error as NSError?)?.userInfo[STPError.errorParameterKey] as! String, "expYear") + XCTAssertNil(token, "token should be nil") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testInvalidKey() { + let card = STPCardParams() + + card.number = "4242 4242 4242 4242" + card.expMonth = 6 + card.expYear = 2050 + + let client = STPAPIClient(publishableKey: "not_a_valid_key_asdf") + + let expectation = self.expectation(description: "Card failure") + client.createToken( + withCard: card) { token, error in + XCTAssertNil(token, "token should be nil") + XCTAssertNotNil(error, "error should not be nil") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateCVCUpdateToken() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "CVC Update Token Creation") + + client.createToken(forCVCUpdate: "1234") { token, error in + XCTAssertNil(error, "error should be nil") + XCTAssertNotNil(token, "token should not be nil") + + XCTAssertNotNil(token?.tokenId) + XCTAssertEqual(token?.type, .cvcUpdate, "token should be type CVC Update") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testInvalidCVC() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "Invalid CVC") + + client.createToken( + forCVCUpdate: "1") { token, error in + XCTAssertNil(token, "token should be nil") + XCTAssertNotNil(error, "error should not be nil") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPCardNumberInputTextFieldFormatterTests.swift b/Stripe/StripeiOSTests/STPCardNumberInputTextFieldFormatterTests.swift new file mode 100644 index 00000000..2c6bc060 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardNumberInputTextFieldFormatterTests.swift @@ -0,0 +1,73 @@ +// +// STPCardNumberInputTextFieldFormatterTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardNumberInputTextFieldFormatterTests: XCTestCase { + + func testAllowedInput() { + let formatter = STPCardNumberInputTextFieldFormatter() + XCTAssertTrue(formatter.isAllowedInput("4242424242424242", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("424242424242424", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue( + formatter.isAllowedInput("4242 4242 4242 4242", to: "", at: NSRange(location: 0, length: 0)) + ) + XCTAssertTrue(formatter.isAllowedInput("42424242 42424242", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("4242 ", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("3566002020360505", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput(" ", to: "4242", at: NSRange(location: 4, length: 0))) + + XCTAssertFalse( + formatter.isAllowedInput("4242.4242.4242.4242", to: "", at: NSRange(location: 0, length: 0)) + ) + XCTAssertFalse(formatter.isAllowedInput("4", to: "4242424242424242", at: NSRange(location: 0, length: 0))) + } + + func testFormatting() { + let formatter = STPCardNumberInputTextFieldFormatter() + var expected: NSMutableAttributedString = NSMutableAttributedString() + + expected = NSMutableAttributedString(string: "4242424242424242") + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 0, length: 3)) + expected.addAttribute(.kern, value: NSNumber(5), range: NSRange(location: 3, length: 1)) + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 4, length: 3)) + expected.addAttribute(.kern, value: NSNumber(5), range: NSRange(location: 7, length: 1)) + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 8, length: 3)) + expected.addAttribute(.kern, value: NSNumber(5), range: NSRange(location: 11, length: 1)) + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 12, length: 4)) + XCTAssertEqual(formatter.formattedText(from: "4242424242424242", with: [:]), expected) + XCTAssertEqual(formatter.formattedText(from: "4242 4242 4242 4242", with: [:]), expected) + + expected = NSMutableAttributedString(string: "4242") + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 0, length: 4)) + XCTAssertEqual(formatter.formattedText(from: "4242", with: [:]), expected) + + expected = NSMutableAttributedString(string: "42424") + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 0, length: 3)) + expected.addAttribute(.kern, value: NSNumber(5), range: NSRange(location: 3, length: 1)) + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 4, length: 1)) + XCTAssertEqual(formatter.formattedText(from: "42424", with: [:]), expected) + + expected = NSMutableAttributedString(string: "378282246310005") // 4, 6, 5, + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 0, length: 3)) + expected.addAttribute(.kern, value: NSNumber(5), range: NSRange(location: 3, length: 1)) + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 4, length: 5)) + expected.addAttribute(.kern, value: NSNumber(5), range: NSRange(location: 9, length: 1)) + expected.addAttribute(.kern, value: NSNumber(0), range: NSRange(location: 10, length: 5)) + // expected.addAttribute(.kern, value: NSNumber(5), range: NSMakeRange(11, 1)) + // expected.addAttribute(.kern, value: NSNumber(0), range: NSMakeRange(12, 4)) + XCTAssertEqual(formatter.formattedText(from: "378282246310005", with: [:]), expected) + } + +} diff --git a/Stripe/StripeiOSTests/STPCardNumberInputTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPCardNumberInputTextFieldSnapshotTests.swift new file mode 100644 index 00000000..5c4a56fd --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardNumberInputTextFieldSnapshotTests.swift @@ -0,0 +1,57 @@ +// +// STPCardNumberInputTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardNumberInputTextFieldSnapshotTests: STPSnapshotTestCase { + + func testEmpty() { + let field = STPCardNumberInputTextField() + field.sizeToFit() + field.frame.size.width = 300 + + STPSnapshotVerifyView(field) + } + + func testIncomplete() { + let field = STPCardNumberInputTextField() + field.sizeToFit() + field.frame.size.width = 300 + field.text = "42" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testValid() { + let field = STPCardNumberInputTextField() + field.sizeToFit() + field.frame.size.width = 300 + field.text = "4242424242424242" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testInvalid() { + let field = STPCardNumberInputTextField() + field.sizeToFit() + field.frame.size.width = 300 + field.text = "4242424242424241" + field.textDidChange() + + STPSnapshotVerifyView(field) + } +} diff --git a/Stripe/StripeiOSTests/STPCardNumberInputTextFieldValidatorTests.swift b/Stripe/StripeiOSTests/STPCardNumberInputTextFieldValidatorTests.swift new file mode 100644 index 00000000..c1f5e567 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardNumberInputTextFieldValidatorTests.swift @@ -0,0 +1,207 @@ +// +// STPCardNumberInputTextFieldValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardNumberInputTextFieldValidatorTests: XCTestCase { + + static let cardData: [(STPCardBrand, String, STPValidatedInputState)] = { + return [ + ( + .visa, + "4242424242424242", + .valid(message: nil) + ), + ( + .visa, + "4242424242422", + .incomplete(description: "Your card number is incomplete.") + ), + ( + .visa, + "4012888888881881", + .valid(message: nil) + ), + ( + .visa, + "4000056655665556", + .valid(message: nil) + ), + ( + .mastercard, + "5555555555554444", + .valid(message: nil) + ), + ( + .mastercard, + "5200828282828210", + .valid(message: nil) + ), + ( + .mastercard, + "5105105105105100", + .valid(message: nil) + ), + ( + .mastercard, + "2223000010089800", + .valid(message: nil) + ), + ( + .amex, + "378282246310005", + .valid(message: nil) + ), + ( + .amex, + "371449635398431", + .valid(message: nil) + ), + ( + .discover, + "6011111111111117", + .valid(message: nil) + ), + ( + .discover, + "6011000990139424", + .valid(message: nil) + ), + ( + .dinersClub, + "36227206271667", + .valid(message: nil) + ), + ( + .dinersClub, + "3056930009020004", + .valid(message: nil) + ), + ( + .JCB, + "3530111333300000", + .valid(message: nil) + ), + ( + .JCB, + "3566002020360505", + .valid(message: nil) + ), + ( + .unknown, + "1234567812345678", + .invalid(errorMessage: "Your card number is invalid.") + ), + ] + }() + + func testValidation() { + // same tests as in STPCardValidatorTest#testNumberValidation + var tests: [(STPValidatedInputState, String, STPCardBrand)] = [] + + for card in STPCardNumberInputTextFieldValidatorTests.cardData { + tests.append((card.2, card.1, card.0)) + } + + tests.append((.valid(message: nil), "4242 4242 4242 4242", .visa)) + tests.append((.valid(message: nil), "4136000000008", .visa)) + + let badCardNumbers: [(String, STPCardBrand)] = [ + ("0000000000000000", .unknown), + ("9999999999999995", .unknown), + ("1", .unknown), + ("1234123412341234", .unknown), + ("xxx", .unknown), + ("9999999999999999999999", .unknown), + ("42424242424242424242", .visa), + ("4242-4242-4242-4242", .visa), + ] + + for card in badCardNumbers { + tests.append((.invalid(errorMessage: "Your card number is invalid."), card.0, card.1)) + } + + let possibleCardNumbers: [(String, STPCardBrand)] = [ + ("4242", .visa), ("5", .mastercard), ("3", .unknown), ("", .unknown), + (" ", .unknown), ("6011", .discover), ("4012888888881", .visa), + ] + + for card in possibleCardNumbers { + tests.append( + ( + .incomplete( + description: card.0.isEmpty ? nil : "Your card number is incomplete." + ), + card.0, card.1 + ) + ) + } + + let validator = STPCardNumberInputTextFieldValidator() + for test in tests { + let card = test.1 + validator.inputValue = card + let validationState = validator.validationState + let expected = test.0 + if !(validationState == expected) { + XCTFail("Expected \(expected), got \(validationState) for number \"\(card)\"") + } + let expectedCardBrand = test.2 + if !(validator.cardBrandState.brand == expectedCardBrand) { + XCTFail( + "Expected \(expectedCardBrand), got \(validator.cardBrandState.brand) for number \(card)" + ) + } + } + + validator.inputValue = "1" + XCTAssertEqual( + .invalid(errorMessage: "Your card number is invalid."), + validator.validationState + ) + + validator.inputValue = "0000000000000000" + XCTAssertEqual( + .invalid(errorMessage: "Your card number is invalid."), + validator.validationState + ) + + validator.inputValue = "9999999999999995" + XCTAssertEqual( + .invalid(errorMessage: "Your card number is invalid."), + validator.validationState + ) + + validator.inputValue = "0000000000000000000" + XCTAssertEqual( + .invalid(errorMessage: "Your card number is invalid."), + validator.validationState + ) + + validator.inputValue = "9999999999999999998" + XCTAssertEqual( + .invalid(errorMessage: "Your card number is invalid."), + validator.validationState + ) + + validator.inputValue = "4242424242424" + XCTAssertEqual( + .incomplete(description: "Your card number is incomplete."), + validator.validationState + ) + + validator.inputValue = nil + XCTAssertEqual(.incomplete(description: nil), validator.validationState) + } +} diff --git a/Stripe/StripeiOSTests/STPCardParamsTest.swift b/Stripe/StripeiOSTests/STPCardParamsTest.swift new file mode 100644 index 00000000..cb29e726 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardParamsTest.swift @@ -0,0 +1,173 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPCardParamsTest.m +// Stripe +// +// Created by Joey Dong on 6/19/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import XCTest + +class STPCardParamsTest: XCTestCase { + // MARK: - + + func testLast4ReturnsCardNumberLast4() { + let cardParams = STPCardParams() + cardParams.number = "4242424242424242" + XCTAssertEqual(cardParams.last4(), "4242") + } + + func testLast4ReturnsNilWhenNoCardNumberSet() { + let cardParams = STPCardParams() + XCTAssertNil(cardParams.last4()) + } + + func testLast4ReturnsNilWhenCardNumberIsLessThanLength4() { + let cardParams = STPCardParams() + cardParams.number = "123" + XCTAssertNil(cardParams.last4()) + } + + func testNameSharedWithAddress() { + let cardParams = STPCardParams() + + cardParams.name = "James" + XCTAssertEqual(cardParams.name, "James") + XCTAssertEqual(cardParams.address.name, "James") + + let address = STPAddress() + address.name = "Jim" + + cardParams.address = address + XCTAssertEqual(cardParams.name, "Jim") + XCTAssertEqual(cardParams.address.name, "Jim") + + // Doesn't update `name`, since mutation invisible to the STPCardParams + cardParams.address.name = "Smith" + XCTAssertEqual(cardParams.name, "Jim") + XCTAssertEqual(cardParams.address.name, "Smith") + } + + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + + func testAddress() { + let cardParams = STPCardParams() + cardParams.name = "John Smith" + cardParams.addressLine1 = "55 John St" + cardParams.addressLine2 = "#3B" + cardParams.addressCity = "New York" + cardParams.addressState = "NY" + cardParams.addressZip = "10002" + cardParams.addressCountry = "US" + + let address = cardParams.address + + XCTAssertEqual(address.name, "John Smith") + XCTAssertEqual(address.line1, "55 John St") + XCTAssertEqual(address.line2, "#3B") + XCTAssertEqual(address.city, "New York") + XCTAssertEqual(address.state, "NY") + XCTAssertEqual(address.postalCode, "10002") + XCTAssertEqual(address.country, "US") + } + + func testSetAddress() { + let address = STPAddress() + address.name = "John Smith" + address.line1 = "55 John St" + address.line2 = "#3B" + address.city = "New York" + address.state = "NY" + address.postalCode = "10002" + address.country = "US" + + let cardParams = STPCardParams() + cardParams.address = address + + XCTAssertEqual(cardParams.name, "John Smith") + XCTAssertEqual(cardParams.addressLine1, "55 John St") + XCTAssertEqual(cardParams.addressLine2, "#3B") + XCTAssertEqual(cardParams.addressCity, "New York") + XCTAssertEqual(cardParams.addressState, "NY") + XCTAssertEqual(cardParams.addressZip, "10002") + XCTAssertEqual(cardParams.addressCountry, "US") + } + + // #pragma clang diagnostic pop + + // MARK: - Description Tests + + func testDescription() { + let cardParams = STPCardParams() + XCTAssertNotNil(cardParams.description) + } + + // MARK: - STPFormEncodable Tests + + func testRootObjectName() { + XCTAssertEqual(STPCardParams.rootObjectName(), "card") + } + + func testPropertyNamesToFormFieldNamesMapping() { + let cardParams = STPCardParams() + + let mapping = STPCardParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(cardParams.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(!formFieldName.isEmpty) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } + + // MARK: - NSCopying Tests + + func testCopyWithZone() { + let cardParams = STPFixtures.cardParams() + cardParams.address = STPFixtures.address() + let copiedCardParams = cardParams.copy() as! STPCardParams + + // The property names we expect to *not* be equal objects + let notEqualProperties = [ + // these include the object's address, so they won't be the same across copies + "debugDescription", + "description", + "hash", + // STPAddress does not override isEqual:, so this is pointer comparison + "address", + ] + + // use runtime inspection to find the list of properties. If a new property is + // added to the fixture, but not the `copyWithZone:` implementation, this should catch it + for property in STPTestUtils.propertyNames(of: cardParams) { + if notEqualProperties.contains(property) { + XCTAssertNotEqual( + cardParams.value(forKey: property) as? NSObject, + copiedCardParams.value(forKey: property) as? NSObject) + } else { + XCTAssertEqual( + cardParams.value(forKey: property) as? NSObject, + copiedCardParams.value(forKey: property) as? NSObject) + } + } + } + + func testAddressIsNotCopied() { + let cardParams = STPFixtures.cardParams() + cardParams.address = STPFixtures.address() + let secondCardParams = STPCardParams() + + secondCardParams.address = cardParams.address + cardParams.address.line1 = "123 Main" + + XCTAssertEqual(cardParams.address.line1, "123 Main") + XCTAssertEqual(secondCardParams.address.line1, "123 Main") + } +} diff --git a/Stripe/StripeiOSTests/STPCardTest.swift b/Stripe/StripeiOSTests/STPCardTest.swift new file mode 100644 index 00000000..6d6b3c80 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardTest.swift @@ -0,0 +1,264 @@ +// +// STPCardTest.swift +// StripeiOS Tests +// +// Created by Saikat Chakrabarti on 11/5/12. +// Copyright © 2012 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardTest: XCTestCase { + // MARK: - STPCardBrand Tests + + // These are only intended to be deprecated publicly. + // When removed from public header, can remove these pragmas + func testBrandFromString() { + XCTAssertEqual(STPCard.brand(from: "visa"), .visa) + XCTAssertEqual(STPCard.brand(from: "VISA"), .visa) + + XCTAssertEqual(STPCard.brand(from: "amex"), .amex) + XCTAssertEqual(STPCard.brand(from: "AMEX"), .amex) + XCTAssertEqual(STPCard.brand(from: "american express"), .amex) + XCTAssertEqual(STPCard.brand(from: "AMERICAN EXPRESS"), .amex) + XCTAssertEqual(STPCard.brand(from: "american_express"), .amex) + XCTAssertEqual(STPCard.brand(from: "AMERICAN_EXPRESS"), .amex) + + XCTAssertEqual(STPCard.brand(from: "mastercard"), .mastercard) + XCTAssertEqual(STPCard.brand(from: "MASTERCARD"), .mastercard) + + XCTAssertEqual(STPCard.brand(from: "discover"), .discover) + XCTAssertEqual(STPCard.brand(from: "DISCOVER"), .discover) + + XCTAssertEqual(STPCard.brand(from: "jcb"), .JCB) + XCTAssertEqual(STPCard.brand(from: "JCB"), .JCB) + + XCTAssertEqual(STPCard.brand(from: "diners club"), .dinersClub) + XCTAssertEqual(STPCard.brand(from: "DINERS CLUB"), .dinersClub) + XCTAssertEqual(STPCard.brand(from: "diners"), .dinersClub) + XCTAssertEqual(STPCard.brand(from: "DINERS"), .dinersClub) + XCTAssertEqual(STPCard.brand(from: "diners_club"), .dinersClub) + XCTAssertEqual(STPCard.brand(from: "DINERS_CLUB"), .dinersClub) + + XCTAssertEqual(STPCard.brand(from: "unionpay"), .unionPay) + XCTAssertEqual(STPCard.brand(from: "UNIONPAY"), .unionPay) + + XCTAssertEqual(STPCard.brand(from: "cartes bancaires"), .cartesBancaires) + XCTAssertEqual(STPCard.brand(from: "CARTES Bancaires"), .cartesBancaires) + XCTAssertEqual(STPCard.brand(from: "CARTES_Bancaires"), .cartesBancaires) + + XCTAssertEqual(STPCard.brand(from: "unknown"), .unknown) + XCTAssertEqual(STPCard.brand(from: "UNKNOWN"), .unknown) + + XCTAssertEqual(STPCard.brand(from: "garbage"), .unknown) + XCTAssertEqual(STPCard.brand(from: "GARBAGE"), .unknown) + } + + // MARK: - STPCardFundingType Tests + + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + // These are only intended to be deprecated publicly. + // When removed from public header, can remove these pragmas + func testFundingFromString() { + XCTAssertEqual(STPCard.funding(from: "credit"), .credit) + XCTAssertEqual(STPCard.funding(from: "CREDIT"), .credit) + + XCTAssertEqual(STPCard.funding(from: "debit"), .debit) + XCTAssertEqual(STPCard.funding(from: "DEBIT"), .debit) + + XCTAssertEqual(STPCard.funding(from: "prepaid"), .prepaid) + XCTAssertEqual(STPCard.funding(from: "PREPAID"), .prepaid) + + XCTAssertEqual(STPCard.funding(from: "other"), .other) + XCTAssertEqual(STPCard.funding(from: "OTHER"), .other) + + XCTAssertEqual(STPCard.funding(from: "unknown"), .other) + XCTAssertEqual(STPCard.funding(from: "UNKNOWN"), .other) + + XCTAssertEqual(STPCard.funding(from: "garbage"), .other) + XCTAssertEqual(STPCard.funding(from: "GARBAGE"), .other) + } + + // #pragma clang diagnostic pop + func testStringFromFunding() { + let values: [STPCardFundingType] = [ + .credit, + .debit, + .prepaid, + .other, + ] + + for funding in values { + let string = STPCard.string(fromFunding: funding) + + switch funding { + case .credit: + XCTAssertEqual(string, "credit") + case .debit: + XCTAssertEqual(string, "debit") + case .prepaid: + XCTAssertEqual(string, "prepaid") + case .other: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + // These tests can ber removed in the future, they should be covered by + // the equivalent response decodeable tests + func testInitWithIDBrandLast4ExpMonthExpYearFunding() { + let card = STPCard( + id: "card_1AVRojEOD54MuFwSxr93QJSx", + brand: .visa, + last4: "5556", + expMonth: 12, + expYear: 2034, + funding: .debit + ) + XCTAssertEqual(card.stripeID, "card_1AVRojEOD54MuFwSxr93QJSx") + XCTAssertEqual(card.brand, .visa) + XCTAssertEqual(card.last4, "5556") + XCTAssertEqual(card.expMonth, Int(12)) + XCTAssertEqual(card.expYear, Int(2034)) + XCTAssertEqual(card.funding, .debit) + } + + // #pragma clang diagnostic pop + func testIsApplePayCard() { + let card = STPFixtures.card() + + card.allResponseFields = [:] + XCTAssertFalse(card.isApplePayCard) + + card.allResponseFields = [ + "tokenization_method": "android_pay" + ] + XCTAssertFalse(card.isApplePayCard) + + card.allResponseFields = [ + "tokenization_method": "apple_pay" + ] + XCTAssertTrue(card.isApplePayCard) + + card.allResponseFields = [ + "tokenization_method": "garbage" + ] + XCTAssertFalse(card.isApplePayCard) + + card.allResponseFields = [ + "tokenization_method": "" + ] + XCTAssertFalse(card.isApplePayCard) + + // See: https://stripe.com/docs/api#card_object-tokenization_method + } + + func testAddressPopulated() { + let card = STPFixtures.card() + XCTAssertEqual(card.address?.name, "Jane Austen") + XCTAssertEqual(card.address?.line1, "123 Fake St") + XCTAssertEqual(card.address?.line2, "Apt 1") + XCTAssertEqual(card.address?.city, "Pittsburgh") + XCTAssertEqual(card.address?.state, "PA") + XCTAssertEqual(card.address?.postalCode, "19219") + XCTAssertEqual(card.address?.country, "US") + } + + // MARK: - Equality Tests + func testCardEquals() { + let card1 = STPFixtures.card() + let card2 = STPFixtures.card() + + XCTAssertEqual(card1, card1) + XCTAssertEqual(card1, card2) + + XCTAssertEqual(card1.hash, card1.hash) + XCTAssertEqual(card1.hash, card2.hash) + } + + // MARK: - STPAPIResponseDecodable Tests + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = ["id", "last4", "brand", "exp_month", "exp_year"] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed("Card") + response?.removeValue(forKey: field) + + XCTAssertNil(STPCard.decodedObject(fromAPIResponse: response)) + } + + XCTAssert((STPCard.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("Card")) != nil)) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("Card")! + let card = STPCard.decodedObject(fromAPIResponse: response)! + + XCTAssertEqual(card.stripeID, "card_103kbR2eZvKYlo2CDczLmw4K") + + XCTAssertEqual(card.address?.city, "Pittsburgh") + XCTAssertEqual(card.address?.country, "US") + XCTAssertEqual(card.address?.line1, "123 Fake St") + XCTAssertEqual(card.address?.line2, "Apt 1") + XCTAssertEqual(card.address?.state, "PA") + XCTAssertEqual(card.address?.postalCode, "19219") + + XCTAssertEqual(card.perform(NSSelectorFromString("cardId")).takeUnretainedValue() as? NSString, "card_103kbR2eZvKYlo2CDczLmw4K") + + XCTAssertEqual(card.perform(NSSelectorFromString("addressCity")).takeUnretainedValue() as? NSString, "Pittsburgh") + XCTAssertEqual(card.perform(NSSelectorFromString("addressCountry")).takeUnretainedValue() as? NSString, "US") + XCTAssertEqual(card.perform(NSSelectorFromString("addressLine1")).takeUnretainedValue() as? NSString, "123 Fake St") + XCTAssertEqual(card.perform(NSSelectorFromString("addressLine2")).takeUnretainedValue() as? NSString, "Apt 1") + XCTAssertEqual(card.perform(NSSelectorFromString("addressState")).takeUnretainedValue() as? NSString, "PA") + XCTAssertEqual(card.perform(NSSelectorFromString("addressZip")).takeUnretainedValue() as? NSString, "19219") + XCTAssertNil(card.perform(NSSelectorFromString("metadata"))) + + XCTAssertEqual(card.brand, .visa) + XCTAssertEqual(card.country, "US") + XCTAssertEqual(card.currency, "usd") + XCTAssertEqual(card.dynamicLast4, "5678") + XCTAssertEqual(card.expMonth, Int(5)) + XCTAssertEqual(card.expYear, Int(2017)) + XCTAssertEqual(card.funding, .credit) + XCTAssertEqual(card.last4, "4242") + XCTAssertEqual(card.name, "Jane Austen") + + XCTAssertEqual(card.allResponseFields as NSDictionary, response as NSDictionary) + } + + // MARK: - STPSourceProtocol Tests + func testStripeID() { + let card = STPFixtures.card() + XCTAssertEqual(card.stripeID, "card_103kbR2eZvKYlo2CDczLmw4K") + } + + // MARK: - + func forEachBrand(_ block: @escaping (_ brand: STPCardBrand) -> Void) { + let values: [STPCardBrand] = [ + .amex, + .dinersClub, + .discover, + .JCB, + .mastercard, + .unionPay, + .visa, + .unknown, + ] + + for brand in values { + block(brand) + } + } +} diff --git a/Stripe/StripeiOSTests/STPCardValidatorTest.swift b/Stripe/StripeiOSTests/STPCardValidatorTest.swift new file mode 100644 index 00000000..efcd911b --- /dev/null +++ b/Stripe/StripeiOSTests/STPCardValidatorTest.swift @@ -0,0 +1,480 @@ +// +// STPCardValidatorTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 7/24/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +import UIKit +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCardValidatorTest: XCTestCase { + override class func setUp() { + STPBINController.shared.reset() + } + + static let cardData: [(STPCardBrand, String, STPCardValidationState)] = { + return [ + ( + .visa, + "4242424242424242", + .valid + ), + ( + .visa, + "4242424242422", + .incomplete + ), + ( + .visa, + "4012888888881881", + .valid + ), + ( + .visa, + "4000056655665556", + .valid + ), + ( + .mastercard, + "5555555555554444", + .valid + ), + ( + .mastercard, + "5200828282828210", + .valid + ), + ( + .mastercard, + "5105105105105100", + .valid + ), + ( + .mastercard, + "2223000010089800", + .valid + ), + ( + .amex, + "378282246310005", + .valid + ), + ( + .amex, + "371449635398431", + .valid + ), + ( + .discover, + "6011111111111117", + .valid + ), + ( + .discover, + "6011000990139424", + .valid + ), + ( + .dinersClub, + "36227206271667", + .valid + ), + ( + .dinersClub, + "3056930009020004", + .valid + ), + ( + .JCB, + "3530111333300000", + .valid + ), + ( + .JCB, + "3566002020360505", + .valid + ), + ( + .unknown, + "1234567812345678", + .invalid + ), + ] + }() + + func testNumberSanitization() { + let tests = [ + ["4242424242424242", "4242424242424242"], + ["XXXXXX", ""], + ["424242424242424X", "424242424242424"], + ["X4242", "4242"], + ["4242 4242 4242 4242", "4242424242424242"], + ] + for test in tests { + XCTAssertEqual(STPCardValidator.sanitizedNumericString(for: test[0]), test[1]) + } + } + + func testNumberValidation() { + var tests: [(STPCardValidationState, String)] = [] + + for card in STPCardValidatorTest.cardData { + tests.append((card.2, card.1)) + } + + tests.append((.valid, "4242 4242 4242 4242")) + tests.append((.valid, "4136000000008")) + + let badCardNumbers = [ + "0000000000000000", + "9999999999999995", + "1", + "1234123412341234", + "xxx", + "9999999999999999999999", + "42424242424242424242", + "4242-4242-4242-4242", + ] + + for card in badCardNumbers { + tests.append((.invalid, card)) + } + + let possibleCardNumbers = ["4242", "5", "3", "", " ", "6011", "4012888888881"] + + for card in possibleCardNumbers { + tests.append((.incomplete, card)) + } + + for test in tests { + let card = test.1 + let validationState = STPCardValidator.validationState( + forNumber: card, + validatingCardBrand: true + ) + let expected = test.0 + if !(validationState == expected) { + XCTFail("Expected \(expected), got \(validationState) for number \(card)") + } + } + + XCTAssertEqual( + .incomplete, + STPCardValidator.validationState(forNumber: "1", validatingCardBrand: false) + ) + XCTAssertEqual( + .incomplete, + STPCardValidator.validationState( + forNumber: "0000000000000000", + validatingCardBrand: false + ) + ) + XCTAssertEqual( + .incomplete, + STPCardValidator.validationState( + forNumber: "9999999999999995", + validatingCardBrand: false + ) + ) + XCTAssertEqual( + .valid, + STPCardValidator.validationState( + forNumber: "0000000000000000000", + validatingCardBrand: false + ) + ) + XCTAssertEqual( + .valid, + STPCardValidator.validationState( + forNumber: "9999999999999999998", + validatingCardBrand: false + ) + ) + XCTAssertEqual( + .incomplete, + STPCardValidator.validationState(forNumber: "4242424242424", validatingCardBrand: true) + ) + XCTAssertEqual( + .incomplete, + STPCardValidator.validationState(forNumber: nil, validatingCardBrand: true) + ) + } + + func testBrand() { + for test in STPCardValidatorTest.cardData { + XCTAssertEqual(STPCardValidator.brand(forNumber: test.1), test.0) + } + } + + func testLengthsForCardBrand() { + let tests: [(STPCardBrand, Set)] = [ + (.visa, Set([13, 16])), + (.mastercard, Set([16])), + (.amex, Set([15])), + (.discover, Set([16])), + (.dinersClub, Set([14, 16])), + (.JCB, Set([16])), + (.unionPay, Set([16, 19])), + (.unknown, Set([19])), + ] + for test in tests { + let lengths = STPCardValidator.lengths(for: test.0) as NSSet + let expected = test.1 as NSSet + if !lengths.isEqual(expected) { + XCTFail("Invalid lengths for brand \(test.0): expected \(expected), got \(lengths)") + } + } + } + + func testFragmentLength() { + let tests: [(STPCardBrand, Int)] = [ + (.visa, 4), + (.mastercard, 4), + (.amex, 5), + (.discover, 4), + (.dinersClub, 4), + (.JCB, 4), + (.unionPay, 4), + (.unknown, 4), + ] + for test in tests { + XCTAssertEqual(STPCardValidator.fragmentLength(for: test.0), test.1) + } + } + + func testMonthValidation() { + let tests: [(String, STPCardValidationState)] = [ + ("", .incomplete), + ("0", .incomplete), + ("1", .incomplete), + ("2", .valid), + ("9", .valid), + ("10", .valid), + ("12", .valid), + ("13", .invalid), + ("11a", .invalid), + ("x", .invalid), + ("100", .invalid), + ("00", .invalid), + ("13", .invalid), + ] + for test in tests { + XCTAssertEqual(STPCardValidator.validationState(forExpirationMonth: test.0), test.1) + } + } + + func testYearValidation() { + let tests: [(String, String, STPCardValidationState)] = [ + ("12", "15", .valid), + ("8", "15", .valid), + ("9", "15", .valid), + ("11", "16", .valid), + ("11", "99", .invalid), + ("01", "99", .invalid), + ("11", "50", .valid), + ("01", "50", .valid), + ("1", "50", .valid), + ("1", "99", .invalid), + ("00", "99", .invalid), + ("00", "50", .invalid), + ("12", "14", .invalid), + ("7", "15", .invalid), + ("12", "00", .invalid), + ("13", "16", .invalid), + ("12", "2", .incomplete), + ("12", "1", .incomplete), + ("12", "0", .incomplete), + ] + + for test in tests { + let state = STPCardValidator.validationState( + forExpirationYear: test.1, + inMonth: test.0, + inCurrentYear: 15, + currentMonth: 8 + ) + XCTAssertEqual(state, test.2, "Failed to validate \(test.0)/\(test.1)") + } + } + + func testCVCLength() { + let tests: [(STPCardBrand, UInt)] = [ + (.visa, 3), + (.mastercard, 3), + (.amex, 4), + (.discover, 3), + (.dinersClub, 3), + (.JCB, 3), + (.unionPay, 3), + (.unknown, 4), + ] + for test in tests { + let maxCVCLength = STPCardValidator.maxCVCLength(for: test.0) + XCTAssertEqual(maxCVCLength, test.1) + } + } + + func testCVCValidation() { + let tests: [(String, STPCardBrand, STPCardValidationState)] = [ + ("x", .visa, .invalid), + ("", .visa, .incomplete), + ("1", .visa, .incomplete), + ("12", .visa, .incomplete), + ("1x3", .visa, .invalid), + ("123", .visa, .valid), + ("123", .amex, .valid), + ("123", .unknown, .valid), + ("1234", .visa, .invalid), + ("1234", .amex, .valid), + ("12345", .amex, .invalid), + ] + + for test in tests { + let state = STPCardValidator.validationState(forCVC: test.0, cardBrand: test.1) + XCTAssertEqual(state, test.2) + } + } + + func testCardValidation() { + // swiftlint:disable:next large_tuple + let tests: [(String, UInt, UInt, String, STPCardValidationState)] = [ + ( + "4242424242424242", + 12, + 15, + "123", + .valid + ), + ( + "4242424242424242", + 12, + 15, + "x", + .invalid + ), + ( + "4242424242424242", + 12, + 15, + "1", + .incomplete + ), + ( + "4242424242424242", + 12, + 14, + "123", + .invalid + ), + ( + "4242424242424242", + 21, + 15, + "123", + .invalid + ), + ( + "42424242", + 12, + 15, + "123", + .incomplete + ), + ( + "378282246310005", + 12, + 15, + "1234", + .valid + ), + ( + "378282246310005", + 12, + 15, + "123", + .valid + ), + ( + "378282246310005", + 12, + 15, + "12345", + .invalid + ), + ( + "1234567812345678", + 12, + 15, + "12345", + .invalid + ), + ] + for test in tests { + let card = STPCardParams() + card.number = test.0 + card.expMonth = test.1 + card.expYear = test.2 + card.cvc = test.3 + let state = STPCardValidator.validationState( + forCard: card, + inCurrentYear: 15, + currentMonth: 8 + ) + if state != test.4 { + XCTFail( + "Wrong validation state for \(String(describing: card.number)). Expected \(test.4), got \(state))" + ) + } + } + } + + func testCBCFetch() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let mcExp = expectation(description: "Mastercard/CBC") + let visaExp = expectation(description: "Visa/CBC") + let justVisaExp = expectation(description: "Visa Only") + let paramsExp = expectation(description: "Params") + let emptyParamsExp = expectation(description: "Empty Params") + STPCardValidator.possibleBrands(forNumber: "513130") { result in + let brands = try! result.get() + XCTAssertEqual(brands, [.cartesBancaires, .mastercard]) + mcExp.fulfill() + } + STPCardValidator.possibleBrands(forNumber: "455673") { result in + let brands = try! result.get() + XCTAssertEqual(brands, [.cartesBancaires, .visa]) + visaExp.fulfill() + } + STPCardValidator.possibleBrands(forNumber: "424242") { result in + let brands = try! result.get() + XCTAssertEqual(brands, [.visa]) + justVisaExp.fulfill() + } + + let params = STPPaymentMethodCardParams() + params.number = "5131301234" + STPCardValidator.possibleBrands(forCard: params) { result in + let brands = try! result.get() + XCTAssertEqual(brands, [.cartesBancaires, .mastercard]) + paramsExp.fulfill() + } + + let paramsEmpty = STPPaymentMethodCardParams() + STPCardValidator.possibleBrands(forCard: paramsEmpty) { result in + let brands = try! result.get() + XCTAssertEqual(brands, Set(STPCardBrand.allCases)) + emptyParamsExp.fulfill() + } + + wait(for: [mcExp, visaExp, justVisaExp, paramsExp, emptyParamsExp], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPCertTest.swift b/Stripe/StripeiOSTests/STPCertTest.swift new file mode 100644 index 00000000..5280f9fe --- /dev/null +++ b/Stripe/StripeiOSTests/STPCertTest.swift @@ -0,0 +1,68 @@ +// +// STPCertTest.swift +// StripeiOS Tests +// +// Created by Phillip Cohen on 4/14/14. +// Copyright © 2014 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +let STPExamplePublishableKey = "bad_key" + +class STPCertTest: XCTestCase { + func testNoError() { + let expectation = self.expectation(description: "Token creation") + let client = STPAPIClient(publishableKey: STPExamplePublishableKey) + client.createToken( + withParameters: [:]) { token, error in + expectation.fulfill() + // Note that this API request *will* fail, but it will return error + // messages from the server and not be blocked by local cert checks + XCTAssertNil(token, "Expected no token") + XCTAssertNotNil(error, "Expected error") + } + waitForExpectations(timeout: 20.0, handler: nil) + } + + func testExpired() { + createToken( + withBaseURL: URL(string: "https://expired.badssl.com/") + ) { token, error in + XCTAssertNil(token, "Token should be nil.") + XCTAssertEqual((error as NSError?)?.domain, "NSURLErrorDomain") + XCTAssertNotNil( + (error as NSError?)?.userInfo["NSURLErrorFailingURLPeerTrustErrorKey"], + "There should be a secTustRef for Foundation HTTPS errors" + ) + } + } + + func testMismatched() { + createToken( + withBaseURL: URL(string: "https://mismatched.stripe.com") + ) { token, error in + XCTAssertNil(token, "Token should be nil.") + XCTAssertEqual((error as NSError?)?.domain, "NSURLErrorDomain") + } + } + + // helper method + func createToken(withBaseURL baseURL: URL?, completion: @escaping STPTokenCompletionBlock) { + let expectation = self.expectation(description: "Token creation") + let client = STPAPIClient(publishableKey: STPExamplePublishableKey) + client.apiURL = baseURL + client.createToken( + withParameters: [:]) { token, error in + expectation.fulfill() + completion(token, error) + } + waitForExpectations(timeout: 20.0, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPConfirmCardOptionsTest.swift b/Stripe/StripeiOSTests/STPConfirmCardOptionsTest.swift new file mode 100644 index 00000000..23a508af --- /dev/null +++ b/Stripe/StripeiOSTests/STPConfirmCardOptionsTest.swift @@ -0,0 +1,31 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPConfirmCardOptionsTest.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 1/10/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +class STPConfirmCardOptionsTest: XCTestCase { + func testCVC() { + let cardOptions = STPConfirmCardOptions() + + XCTAssertNil(cardOptions.cvc, "Initial/default value should be nil.") + XCTAssertNil(cardOptions.network, "Initial/default value should be nil.") + + cardOptions.cvc = "123" + XCTAssertEqual(cardOptions.cvc, "123") + cardOptions.network = "visa" + XCTAssertEqual(cardOptions.network, "visa") + } + + func testEncoding() { + let propertyMap = STPConfirmCardOptions.propertyNamesToFormFieldNamesMapping() + let expected = [ + "cvc": "cvc", + "network": "network", + ] + XCTAssertEqual(propertyMap, expected) + } +} diff --git a/Stripe/StripeiOSTests/STPConfirmPaymentMethodOptionsTest.swift b/Stripe/StripeiOSTests/STPConfirmPaymentMethodOptionsTest.swift new file mode 100644 index 00000000..02cd737e --- /dev/null +++ b/Stripe/StripeiOSTests/STPConfirmPaymentMethodOptionsTest.swift @@ -0,0 +1,34 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPConfirmPaymentMethodOptionsTest.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 1/10/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +class STPConfirmPaymentMethodOptionsTest: XCTestCase { + func testCardOptions() { + let paymentMethodOptions = STPConfirmPaymentMethodOptions() + + XCTAssertNil(paymentMethodOptions.cardOptions, "Default card value should be nil.") + + let cardOptions = STPConfirmCardOptions() + paymentMethodOptions.cardOptions = cardOptions + XCTAssertEqual(paymentMethodOptions.cardOptions, cardOptions, "Should hold reference to set cardOptions.") + } + + func testFormEncoding() { + let propertyToFieldMap = STPConfirmPaymentMethodOptions.propertyNamesToFormFieldNamesMapping() + let expected = [ + "cardOptions": "card", + "alipayOptions": "alipay", + "blikOptions": "blik", + "weChatPayOptions": "wechat_pay", + "usBankAccountOptions": "us_bank_account", + "konbiniOptions": "konbini", + ] + + XCTAssertEqual(propertyToFieldMap, expected) + } +} diff --git a/Stripe/StripeiOSTests/STPConnectAccountAddressTest.swift b/Stripe/StripeiOSTests/STPConnectAccountAddressTest.swift new file mode 100644 index 00000000..573864d8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPConnectAccountAddressTest.swift @@ -0,0 +1,33 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPConnectAccountAddressTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 8/2/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPConnectAccountAddressTest: XCTestCase { + // MARK: STPFormEncodable Tests + + func testRootObjectName() { + XCTAssertNil(STPConnectAccountAddress.rootObjectName()) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let address = STPConnectAccountAddress() + + let mapping = STPConnectAccountAddress.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(address.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(!formFieldName.isEmpty) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } +} diff --git a/Stripe/StripeiOSTests/STPConnectAccountFunctionalTest.swift b/Stripe/StripeiOSTests/STPConnectAccountFunctionalTest.swift new file mode 100644 index 00000000..372986d1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPConnectAccountFunctionalTest.swift @@ -0,0 +1,85 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPConnectAccountFunctionalTest.m +// StripeiOS Tests +// +// Created by Daniel Jackson on 1/8/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils + +class STPConnectAccountFunctionalTest: XCTestCase { + /// Client with test publishable key + var client: STPAPIClient! + var individual: STPConnectAccountIndividualParams! + var company: STPConnectAccountCompanyParams! + + override func setUp() { + super.setUp() + + client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + individual = STPConnectAccountIndividualParams() + individual.firstName = "Test" + var dob = DateComponents() + dob.day = 31 + dob.month = 8 + dob.year = 2006 + individual.dateOfBirth = dob + company = STPConnectAccountCompanyParams() + company.name = "Test" + } + + func testTokenCreation_terms_nil() { + XCTAssertNil( + STPConnectAccountParams( + tosShownAndAccepted: false, + individual: individual), + "Guard to prevent trying to call this with `NO`") + XCTAssertNil( + STPConnectAccountParams( + tosShownAndAccepted: false, + company: company), + "Guard to prevent trying to call this with `NO`") + } + + func testTokenCreation_customer() { + createToken( + STPConnectAccountParams(company: company), + shouldSucceed: true) + } + + func testTokenCreation_company() { + createToken( + STPConnectAccountParams(individual: individual), + shouldSucceed: true) + } + + func testTokenCreation_empty_init() { + createToken(STPConnectAccountParams(), shouldSucceed: true) + + } + + // MARK: - + + func createToken(_ params: STPConnectAccountParams?, shouldSucceed: Bool) { + let expectation = self.expectation(description: "Connect Account Token") + + client.createToken(withConnectAccount: params!) { token, error in + expectation.fulfill() + + if shouldSucceed { + XCTAssertNil(error) + XCTAssertNotNil(token) + XCTAssertNotNil(token?.tokenId) + XCTAssertEqual(token?.type, .account) + } else { + XCTAssertNil(token) + XCTAssertNotNil(error) + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPConnectAccountParamsTest.swift b/Stripe/StripeiOSTests/STPConnectAccountParamsTest.swift new file mode 100644 index 00000000..15d9a2bf --- /dev/null +++ b/Stripe/StripeiOSTests/STPConnectAccountParamsTest.swift @@ -0,0 +1,53 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPConnectAccountParamsTest.m +// StripeiOS Tests +// +// Created by Daniel Jackson on 1/10/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments + +class STPConnectAccountParamsTest: XCTestCase { + // MARK: - STPFormEncodable Tests + + func testRootObjectName() { + XCTAssertEqual(STPConnectAccountParams.rootObjectName(), "account") + } + + func testBusinessType() { + let individual = STPConnectAccountIndividualParams() + let company = STPConnectAccountCompanyParams() + + XCTAssertEqual(STPConnectAccountParams(individual: individual).businessType, .individual) + XCTAssertEqual(STPConnectAccountParams(tosShownAndAccepted: true, individual: individual)!.businessType, .individual) + + XCTAssertEqual(STPConnectAccountParams(company: company).businessType, .company) + XCTAssertEqual(STPConnectAccountParams(tosShownAndAccepted: true, company: company)!.businessType, .company) + } + + func testBusinessTypeString() { + XCTAssertEqual("individual", STPConnectAccountParams.string(from: .individual)) + XCTAssertEqual("company", STPConnectAccountParams.string(from: .company)) + XCTAssertEqual(nil, STPConnectAccountParams.string(from: .none)) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let individual = STPConnectAccountIndividualParams() + let accountParams = STPConnectAccountParams(individual: individual) + + let mapping = STPConnectAccountParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(accountParams.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(formFieldName.count > 0) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } +} diff --git a/Stripe/StripeiOSTests/STPCountryPickerInputFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPCountryPickerInputFieldSnapshotTests.swift new file mode 100644 index 00000000..f05827b8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPCountryPickerInputFieldSnapshotTests.swift @@ -0,0 +1,27 @@ +// +// STPCountryPickerInputFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 12/2/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPCountryPickerInputFieldSnapshotTests: STPSnapshotTestCase { + + func testDefault() { + let field = STPCountryPickerInputField() + field.sizeToFit() + field.frame.size.width = 200 + + STPSnapshotVerifyView(field) + } +} diff --git a/Stripe/StripeiOSTests/STPCustomerTest.swift b/Stripe/StripeiOSTests/STPCustomerTest.swift new file mode 100644 index 00000000..54662c7a --- /dev/null +++ b/Stripe/StripeiOSTests/STPCustomerTest.swift @@ -0,0 +1,63 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPCustomerTest.m +// Stripe +// +// Created by Ben Guo on 7/14/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +class STPCustomerTest: XCTestCase { + func testDecoding_invalidJSON() { + let sut = STPCustomer.decodedObject(fromAPIResponse: [:]) + XCTAssertNil(sut) + } + + func testDecoding_validJSON() { + var card1 = STPTestUtils.jsonNamed("Card") + card1!["id"] = "card_123" + + var card2 = STPTestUtils.jsonNamed("Card") + card2!["id"] = "card_456" + + var applePayCard1 = STPTestUtils.jsonNamed("Card") + applePayCard1!["id"] = "card_apple_pay1" + applePayCard1!["tokenization_method"] = "apple_pay" + + var applePayCard2 = applePayCard1 + applePayCard2!["id"] = "card_apple_pay2" + + let cardSource = STPTestUtils.jsonNamed("CardSource") + let threeDSSource = STPTestUtils.jsonNamed("3DSSource") + + var customer = STPTestUtils.jsonNamed("Customer") + var sources = customer!["sources"] as? [AnyHashable: Any] + sources?["data"] = [applePayCard1, card1, applePayCard2, card2, cardSource, threeDSSource] + customer!["default_source"] = card1!["id"] + if let sources { + customer!["sources"] = sources + } + + guard let sut = STPCustomer.decodedObject(fromAPIResponse: customer) else { + XCTFail() + return + } + XCTAssertEqual(sut.stripeID, customer!["id"] as! String) + XCTAssertTrue(sut.sources.count == 4) + XCTAssertEqual(sut.sources[0].stripeID, card1!["id"] as! String) + XCTAssertEqual(sut.sources[1].stripeID, card2!["id"] as! String) + XCTAssertEqual(sut.defaultSource!.stripeID, card1!["id"] as! String) + XCTAssertEqual(sut.sources[2].stripeID, cardSource!["id"] as! String) + XCTAssertEqual(sut.sources[3].stripeID, threeDSSource!["id"] as! String) + + XCTAssertEqual(sut.shippingAddress!.name, (customer!["shipping"] as! [AnyHashable: Any])["name"] as? String) + XCTAssertEqual(sut.shippingAddress!.phone, (customer!["shipping"] as! [AnyHashable: Any])["phone"] as? String) + let addressDict = (customer!["shipping"] as! [AnyHashable: Any])["address"] as! [AnyHashable: String] + XCTAssertEqual(sut.shippingAddress!.city, addressDict["city"]) + XCTAssertEqual(sut.shippingAddress!.country, addressDict["country"]) + XCTAssertEqual(sut.shippingAddress!.line1, addressDict["line1"]) + XCTAssertEqual(sut.shippingAddress!.line2, addressDict["line2"]) + XCTAssertEqual(sut.shippingAddress!.postalCode, addressDict["postal_code"]) + XCTAssertEqual(sut.shippingAddress!.state, addressDict["state"]) + } +} diff --git a/Stripe/StripeiOSTests/STPE2ETest.swift b/Stripe/StripeiOSTests/STPE2ETest.swift new file mode 100644 index 00000000..7361d1db --- /dev/null +++ b/Stripe/StripeiOSTests/STPE2ETest.swift @@ -0,0 +1,156 @@ +// +// STPE2ETest.swift +// StripeiOS Tests +// +// Created by David Estes on 2/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Stripe +import XCTest + +class STPE2ETest: XCTestCase { + let E2ETestTimeout: TimeInterval = 120 + + struct E2EExpectation { + var amount: Int + var currency: String + var accountID: String + } + + class E2EBackend { + static let backendAPIURL = URL( + string: "https://stp-mobile-ci-test-backend-e1b3.stripedemos.com/e2e" + )! + + func createPaymentIntent( + completion: @escaping (STPPaymentIntentParams, E2EExpectation) -> Void + ) { + requestAPI("create_pi", method: "POST") { (json) in + let paymentIntentClientSecret = json["paymentIntent"] as! String + let expectedAmount = json["expectedAmount"] as! Int + let expectedCurrency = json["expectedCurrency"] as! String + let expectedAccountID = json["expectedAccountID"] as! String + let publishableKey = json["publishableKey"] as! String + STPAPIClient.shared.publishableKey = publishableKey + completion( + STPPaymentIntentParams(clientSecret: paymentIntentClientSecret), + E2EExpectation( + amount: expectedAmount, + currency: expectedCurrency, + accountID: expectedAccountID + ) + ) + } + } + + func fetchPaymentIntent(id: String, completion: @escaping (E2EExpectation) -> Void) { + requestAPI("fetch_pi", queryItems: [URLQueryItem(name: "pi", value: id)]) { (json) in + let resultAmount = json["amount"] as! Int + let resultCurrency = json["currency"] as! String + let resultAccountID = json["on_behalf_of"] as! String + completion( + E2EExpectation( + amount: resultAmount, + currency: resultCurrency, + accountID: resultAccountID + ) + ) + } + } + + private func requestAPI( + _ resource: String, + method: String = "GET", + queryItems: [URLQueryItem] = [], + completion: @escaping ([String: Any]) -> Void + ) { + var url = URLComponents( + url: Self.backendAPIURL.appendingPathComponent(resource), + resolvingAgainstBaseURL: false + )! + url.queryItems = queryItems + var request = URLRequest(url: url.url!) + request.httpMethod = method + let task = URLSession.shared.dataTask( + with: request, + completionHandler: { (data, _, error) in + guard let data = data, + let json = try? JSONSerialization.jsonObject(with: data, options: []) + as? [String: Any] + else { + XCTFail( + "Did not receive valid JSON response from E2E server. \(String(describing: error))" + ) + return + } + DispatchQueue.main.async { + completion(json) + } + } + ) + task.resume() + } + } + + static let TestPM: STPPaymentMethodParams = { + let testCard = STPPaymentMethodCardParams() + testCard.number = "4242424242424242" + testCard.expYear = 2050 + testCard.expMonth = 12 + testCard.cvc = "123" + return STPPaymentMethodParams(card: testCard, billingDetails: nil, metadata: nil) + }() + + // MARK: LOG.04.01c + // In this test, a PaymentIntent object is created from an example merchant backend, + // confirmed by the iOS SDK, and then retrieved to validate that the original amount, + // currency, and merchant are the same as the original inputs. + // (Don't network mock this one!) + func testE2E() throws { + continueAfterFailure = false + let backend = E2EBackend() + let createPI = XCTestExpectation(description: "Create PaymentIntent") + let fetchPIBackend = XCTestExpectation( + description: "Fetch and check PaymentIntent via backend" + ) + let fetchPIClient = XCTestExpectation( + description: "Fetch and check PaymentIntent via client" + ) + let confirmPI = XCTestExpectation(description: "Confirm PaymentIntent") + + // Create a PaymentIntent + backend.createPaymentIntent { (pip, expected) in + createPI.fulfill() + + // Confirm the PaymentIntent using a test card + pip.paymentMethodParams = STPE2ETest.TestPM + STPAPIClient.shared.confirmPaymentIntent(with: pip) { (confirmedPI, confirmError) in + confirmPI.fulfill() + XCTAssertNotNil(confirmedPI) + XCTAssertNil(confirmError) + + // Check the PI information using the backend + backend.fetchPaymentIntent(id: pip.stripeId!) { (expectationResult) in + XCTAssertEqual(expectationResult.amount, expected.amount) + XCTAssertEqual(expectationResult.accountID, expected.accountID) + XCTAssertEqual(expectationResult.currency, expected.currency) + fetchPIBackend.fulfill() + } + + // Check the PI information using the client + STPAPIClient.shared.retrievePaymentIntent(withClientSecret: pip.clientSecret) { + (fetchedPI, fetchError) in + XCTAssertNil(fetchError) + let fetchedPI = fetchedPI! + XCTAssertEqual(fetchedPI.status, .succeeded) + XCTAssertEqual(fetchedPI.amount, expected.amount) + XCTAssertEqual(fetchedPI.currency, expected.currency) + // The client can't check the "on_behalf_of" field, so we check it via the merchant test above. + fetchPIClient.fulfill() + } + } + } + wait(for: [createPI, confirmPI, fetchPIBackend, fetchPIClient], timeout: E2ETestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPEphemeralKeyManagerTest.swift b/Stripe/StripeiOSTests/STPEphemeralKeyManagerTest.swift new file mode 100644 index 00000000..90b507ee --- /dev/null +++ b/Stripe/StripeiOSTests/STPEphemeralKeyManagerTest.swift @@ -0,0 +1,155 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPEphemeralKeyManagerTest.m +// Stripe +// +// Created by Ben Guo on 5/9/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import Stripe + +class FakeEphemeralKeyProvider: NSObject, STPCustomerEphemeralKeyProvider { + var response: [AnyHashable: Any]? + var expectation: XCTestExpectation? + + init(response: [AnyHashable: Any]?, expectation: XCTestExpectation?) { + self.response = response + self.expectation = expectation + } + + func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping StripePayments.STPJSONResponseCompletionBlock) { + completion(response!, nil) + expectation?.fulfill() + } +} + +class FailingEphemeralKeyProvider: NSObject, STPCustomerEphemeralKeyProvider { + func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping StripePayments.STPJSONResponseCompletionBlock) { + XCTFail("createCustomerKey should not be called") + } +} + +class DelayingEphemeralKeyProvider: NSObject, STPCustomerEphemeralKeyProvider { + var response: [AnyHashable: Any] + var expectation: XCTestExpectation + + init(response: [AnyHashable: Any], expectation: XCTestExpectation) { + self.response = response + self.expectation = expectation + } + + func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping StripePayments.STPJSONResponseCompletionBlock) { + expectation.fulfill() + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { + completion(self.response, nil) + }) + } +} + +class STPEphemeralKeyManagerTest: XCTestCase { + let apiVersion = "2015-03-03" + + override func setUp() { + super.setUp() + } + + func mockKeyProvider(withKeyResponse keyResponse: [AnyHashable: Any]?) -> Any? { + let exp = expectation(description: "createCustomerKey") + let mockKeyProvider = FakeEphemeralKeyProvider(response: keyResponse, expectation: exp) + return mockKeyProvider + } + + func testgetOrCreateKeyCreatesNewKeyAfterInit() { + let expectedKey = STPFixtures.ephemeralKey() + let keyResponse = expectedKey.allResponseFields + let mockKeyProvider = self.mockKeyProvider(withKeyResponse: keyResponse) + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + let exp = expectation(description: "getOrCreateKey") + sut.getOrCreateKey({ resourceKey, error in + XCTAssertEqual(resourceKey, expectedKey) + XCTAssertNil(error) + exp.fulfill() + }) + waitForExpectations(timeout: 2, handler: nil) + } + + func testgetOrCreateKeyUsesStoredKeyIfNotExpiring() { + let mockKeyProvider = FailingEphemeralKeyProvider() + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + let expectedKey = STPFixtures.ephemeralKey() + sut.ephemeralKey = expectedKey + let exp = expectation(description: "getOrCreateKey") + sut.getOrCreateKey({ resourceKey, error in + XCTAssertEqual(resourceKey, expectedKey) + XCTAssertNil(error) + exp.fulfill() + }) + waitForExpectations(timeout: 2, handler: nil) + } + + func testgetOrCreateKeyCreatesNewKeyIfExpiring() { + let expectedKey = STPFixtures.ephemeralKey() + let keyResponse = expectedKey.allResponseFields + let mockKeyProvider = self.mockKeyProvider(withKeyResponse: keyResponse) + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + sut.ephemeralKey = STPFixtures.expiringEphemeralKey() + let exp = expectation(description: "retrieve") + sut.getOrCreateKey({ resourceKey, error in + XCTAssertEqual(resourceKey, expectedKey) + XCTAssertNil(error) + exp.fulfill() + }) + waitForExpectations(timeout: 2, handler: nil) + } + + func testgetOrCreateKeyCoalescesRepeatCalls() { + let expectedKey = STPFixtures.ephemeralKey() + let keyResponse = expectedKey.allResponseFields + let createExp = expectation(description: "createKey") + createExp.assertForOverFulfill = true + + let mockKeyProvider = DelayingEphemeralKeyProvider(response: keyResponse, expectation: createExp) + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + let getExp1 = expectation(description: "getOrCreateKey") + sut.getOrCreateKey({ ephemeralKey, error in + XCTAssertEqual(ephemeralKey, expectedKey) + XCTAssertNil(error) + getExp1.fulfill() + }) + let getExp2 = expectation(description: "getOrCreateKey") + sut.getOrCreateKey({ ephemeralKey, error in + XCTAssertEqual(ephemeralKey, expectedKey) + XCTAssertNil(error) + getExp2.fulfill() + }) + + waitForExpectations(timeout: 2, handler: nil) + } + + func testEnterForegroundRefreshesResourceKeyIfExpiring() { + let key = STPFixtures.expiringEphemeralKey() + let keyResponse = key.allResponseFields + let mockKeyProvider = self.mockKeyProvider(withKeyResponse: keyResponse) + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + XCTAssertNotNil(sut) + NotificationCenter.default.post(name: UIApplication.willEnterForegroundNotification, object: nil) + + waitForExpectations(timeout: 2, handler: nil) + } + + func testEnterForegroundDoesNotRefreshResourceKeyIfNotExpiring() { + let mockKeyProvider = FailingEphemeralKeyProvider() + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + sut.ephemeralKey = STPFixtures.ephemeralKey() + NotificationCenter.default.post(name: UIApplication.willEnterForegroundNotification, object: nil) + } + + func testThrottlingEnterForegroundRefreshes() { + let mockKeyProvider = FailingEphemeralKeyProvider() + let sut = STPEphemeralKeyManager(keyProvider: mockKeyProvider, apiVersion: apiVersion, performsEagerFetching: true) + sut.ephemeralKey = STPFixtures.expiringEphemeralKey() + sut.lastEagerKeyRefresh = Date(timeIntervalSinceNow: -60) + NotificationCenter.default.post(name: UIApplication.willEnterForegroundNotification, object: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift b/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift new file mode 100644 index 00000000..da37c4de --- /dev/null +++ b/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift @@ -0,0 +1,31 @@ +// +// STPEphemeralKeyTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 5/17/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPEphemeralKeyTest: XCTestCase { + func testDecoding() { + let json = STPTestUtils.jsonNamed("EphemeralKey")! + let key = STPEphemeralKey.decodedObject(fromAPIResponse: json)! + XCTAssertEqual(key.stripeID, json["id"] as! String) + XCTAssertEqual(key.secret, json["secret"] as! String) + XCTAssertEqual( + key.created, + Date(timeIntervalSince1970: TimeInterval((json["created"] as! NSNumber).doubleValue)) + ) + XCTAssertEqual( + key.expires, + Date(timeIntervalSince1970: TimeInterval((json["expires"] as! NSNumber).doubleValue)) + ) + XCTAssertEqual(key.livemode, (json["livemode"] as! NSNumber).boolValue) + } +} diff --git a/Stripe/StripeiOSTests/STPErrorBridgeTest.m b/Stripe/StripeiOSTests/STPErrorBridgeTest.m new file mode 100644 index 00000000..bdcc414d --- /dev/null +++ b/Stripe/StripeiOSTests/STPErrorBridgeTest.m @@ -0,0 +1,37 @@ +// +// STPErrorBridgeTest.m +// StripeiOS Tests +// +// Created by David Estes on 9/23/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@import Stripe; +@import XCTest; +@import PassKit; +@import StripePaymentsObjcTestUtils; + +@interface STPErrorBridgeTest : XCTestCase + +@end + +@implementation STPErrorBridgeTest + +- (void)testSTPErrorBridge { + // Grab a constant from each class, just to make sure we didn't forget to include the bridge: + XCTAssertEqual(STPInvalidRequestError, 50); + XCTAssertEqualObjects(STPError.errorMessageKey, @"com.stripe.lib:ErrorMessageKey"); + NSDictionary *json = @{ + @"error": @{ + @"type": @"invalid_request_error", + @"message": @"Your card number is incorrect.", + @"code": @"incorrect_number" + } + }; + + // Make sure we can parse a Stripe response + NSError *expectedError = [NSError stp_errorFromStripeResponse:json]; + XCTAssertEqualObjects(expectedError.domain, STPError.stripeDomain); +} + +@end diff --git a/Stripe/StripeiOSTests/STPFPXBankBrandTest.swift b/Stripe/StripeiOSTests/STPFPXBankBrandTest.swift new file mode 100644 index 00000000..a540deb2 --- /dev/null +++ b/Stripe/StripeiOSTests/STPFPXBankBrandTest.swift @@ -0,0 +1,104 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPFPXBankBrandTest.m +// StripeiOS Tests +// +// Created by David Estes on 8/26/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPFPXBankBrandTest: XCTestCase { + func testStringFromBrand() { + let brands: [STPFPXBankBrand] = [ + .affinBank, + .allianceBank, + .ambank, + .bankIslam, + .bankMuamalat, + .bankRakyat, + .BSN, + .CIMB, + .hongLeongBank, + .HSBC, + .KFH, + .maybank2E, + .maybank2U, + .ocbc, + .publicBank, + .CIMB, + .RHB, + .standardChartered, + .UOB, + .unknown, + ] + + for brand in brands { + let brandName = STPFPXBank.stringFrom(brand) + let brandID = STPFPXBank.identifierFrom(brand) + let reverseTransformedBrand = STPFPXBank.brandFrom(brandID) + XCTAssertEqual(reverseTransformedBrand, brand) + + switch brand { + case .affinBank: + XCTAssertEqual(brandID, "affin_bank") + XCTAssertEqual(brandName, "Affin Bank") + case .allianceBank: + XCTAssertEqual(brandID, "alliance_bank") + XCTAssertEqual(brandName, "Alliance Bank") + case .ambank: + XCTAssertEqual(brandID, "ambank") + XCTAssertEqual(brandName, "AmBank") + case .bankIslam: + XCTAssertEqual(brandID, "bank_islam") + XCTAssertEqual(brandName, "Bank Islam") + case .bankMuamalat: + XCTAssertEqual(brandID, "bank_muamalat") + XCTAssertEqual(brandName, "Bank Muamalat") + case .bankRakyat: + XCTAssertEqual(brandID, "bank_rakyat") + XCTAssertEqual(brandName, "Bank Rakyat") + case .BSN: + XCTAssertEqual(brandID, "bsn") + XCTAssertEqual(brandName, "BSN") + case .CIMB: + XCTAssertEqual(brandID, "cimb") + XCTAssertEqual(brandName, "CIMB Clicks") + case .hongLeongBank: + XCTAssertEqual(brandID, "hong_leong_bank") + XCTAssertEqual(brandName, "Hong Leong Bank") + case .HSBC: + XCTAssertEqual(brandID, "hsbc") + XCTAssertEqual(brandName, "HSBC BANK") + case .KFH: + XCTAssertEqual(brandID, "kfh") + XCTAssertEqual(brandName, "KFH") + case .maybank2E: + XCTAssertEqual(brandID, "maybank2e") + XCTAssertEqual(brandName, "Maybank2E") + case .maybank2U: + XCTAssertEqual(brandID, "maybank2u") + XCTAssertEqual(brandName, "Maybank2U") + case .ocbc: + XCTAssertEqual(brandID, "ocbc") + XCTAssertEqual(brandName, "OCBC Bank") + case .publicBank: + XCTAssertEqual(brandID, "public_bank") + XCTAssertEqual(brandName, "Public Bank") + case .RHB: + XCTAssertEqual(brandID, "rhb") + XCTAssertEqual(brandName, "RHB Bank") + case .standardChartered: + XCTAssertEqual(brandID, "standard_chartered") + XCTAssertEqual(brandName, "Standard Chartered") + case .UOB: + XCTAssertEqual(brandID, "uob") + XCTAssertEqual(brandName, "UOB Bank") + case .unknown: + XCTAssertEqual(brandID, "unknown") + XCTAssertEqual(brandName, "Unknown") + @unknown default: + break + } + } + } +} diff --git a/Stripe/StripeiOSTests/STPFileFunctionalTest.swift b/Stripe/StripeiOSTests/STPFileFunctionalTest.swift new file mode 100644 index 00000000..d161daa3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPFileFunctionalTest.swift @@ -0,0 +1,85 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPFileFunctionalTest.m +// Stripe +// +// Created by Charles Scalesse on 1/8/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPFileFunctionalTest: STPNetworkStubbingTestCase { + func testImage() -> UIImage { + return UIImage( + named: "stp_test_upload_image.jpeg", + in: Bundle(for: STPFileFunctionalTest.self), + compatibleWith: nil)! + } + + func testCreateFileForIdentityDocument() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "File creation for identity document") + + let image = testImage() + + client.uploadImage( + image, + purpose: .identityDocument) { file, error in + expectation.fulfill() + XCTAssertNil(error, "error should be nil") + + XCTAssertNotNil(file?.fileId) + XCTAssertNotNil(file?.created) + XCTAssertEqual(file?.purpose, .identityDocument) + XCTAssertNotNil(file?.size) + XCTAssertEqual("jpg", file?.type) + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateFileForDisputeEvidence() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "File creation for dispute evidence") + + let image = testImage() + + client.uploadImage( + image, + purpose: .disputeEvidence) { file, error in + expectation.fulfill() + XCTAssertNil(error, "error should be nil") + + XCTAssertNotNil(file?.fileId) + XCTAssertNotNil(file?.created) + XCTAssertEqual(file?.purpose, .disputeEvidence) + XCTAssertNotNil(file?.size) + XCTAssertEqual("jpg", file?.type) + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testInvalidKey() { + let client = STPAPIClient(publishableKey: "not_a_valid_key_asdf") + + let expectation = self.expectation(description: "Bad file creation") + + let image = testImage() + + client.uploadImage( + image, + purpose: .identityDocument) { file, error in + expectation.fulfill() + XCTAssertNil(file, "file should be nil") + XCTAssertNotNil(error, "error should not be nil") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPFileTest.swift b/Stripe/StripeiOSTests/STPFileTest.swift new file mode 100644 index 00000000..90383dcf --- /dev/null +++ b/Stripe/StripeiOSTests/STPFileTest.swift @@ -0,0 +1,99 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPFileTest.m +// Stripe +// +// Created by Charles Scalesse on 1/8/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class STPFileTest: XCTestCase { + // MARK: - STPFilePurpose Tests + + func testPurposeFromString() { + XCTAssertEqual(STPFile.purpose(from: "dispute_evidence"), .disputeEvidence) + XCTAssertEqual(STPFile.purpose(from: "DISPUTE_EVIDENCE"), .disputeEvidence) + + XCTAssertEqual(STPFile.purpose(from: "identity_document"), .identityDocument) + XCTAssertEqual(STPFile.purpose(from: "IDENTITY_DOCUMENT"), .identityDocument) + + XCTAssertEqual(STPFile.purpose(from: "unknown"), .unknown) + XCTAssertEqual(STPFile.purpose(from: "UNKNOWN"), .unknown) + + XCTAssertEqual(STPFile.purpose(from: "garbage"), .unknown) + XCTAssertEqual(STPFile.purpose(from: "GARBAGE"), .unknown) + } + + func testStringFromPurpose() { + let values: [STPFilePurpose] = [ + .disputeEvidence, + .identityDocument, + .unknown, + ] + + for purpose in values { + let string = STPFile.string(from: purpose) + + switch purpose { + case .disputeEvidence: + XCTAssertEqual(string, "dispute_evidence") + case .identityDocument: + XCTAssertEqual(string, "identity_document") + case .unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - Equality Tests + + func testFileEquals() { + let file1 = STPFile.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("FileUpload")) + let file2 = STPFile.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("FileUpload")) + + XCTAssertEqual(file1, file1) + XCTAssertEqual(file1, file2) + + XCTAssertEqual(file1?.hash, file1?.hash) + XCTAssertEqual(file1?.hash, file2?.hash) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = [ + "id", + "created", + "size", + "purpose", + "type", + ] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed("FileUpload") + response!.removeValue(forKey: field) + + XCTAssertNil(STPFile.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPFile.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("FileUpload"))) + } + + func testInitializingFileWithAttributeDictionary() { + let response = STPTestUtils.jsonNamed("FileUpload")! + let file = STPFile.decodedObject(fromAPIResponse: response)! + + XCTAssertEqual(file.fileId, "file_1AZl0o2eZvKYlo2CoIkwLzfd") + XCTAssertEqual(file.created, Date(timeIntervalSince1970: 1498674938)) + XCTAssertEqual(file.purpose, .disputeEvidence) + XCTAssertEqual(file.size, NSNumber(value: 34478)) + XCTAssertEqual(file.type, "jpg") + + XCTAssertEqual(file.allResponseFields as NSDictionary, response as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPFloatingPlaceholderTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPFloatingPlaceholderTextFieldSnapshotTests.swift new file mode 100644 index 00000000..38fcf912 --- /dev/null +++ b/Stripe/StripeiOSTests/STPFloatingPlaceholderTextFieldSnapshotTests.swift @@ -0,0 +1,439 @@ +// +// STPFloatingPlaceholderTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/9/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPFloatingPlaceholderTextFieldSnapshotTests: STPSnapshotTestCase { + + // MARK: Not Floating + + func testNotFloating_noBackground() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_whiteBackground() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_roundedRectBorderStyle() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_bezelBorderStyle() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_lineBorderStyle() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + // MARK: Floating + + func testFloating_noBackground() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_whiteBackground() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_roundedRectBorderStyle() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_bezelBorderStyle() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_lineBorderStyle() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + // MARK: Right/Left Views Not Floating + + func testNotFloating_noBackground_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_whiteBackground_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_roundedRectBorderStyle_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_bezelBorderStyle_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_lineBorderStyle_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_noBackground_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_whiteBackground_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_roundedRectBorderStyle_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_bezelBorderStyle_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_lineBorderStyle_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_noBackground_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_whiteBackground_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_roundedRectBorderStyle_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_bezelBorderStyle_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testNotFloating_lineBorderStyle_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + // MARK: Right/Left Views Floating + + func testFloating_noBackground_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_whiteBackground_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_roundedRectBorderStyle_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_bezelBorderStyle_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_lineBorderStyle_rightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_noBackground_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_whiteBackground_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_roundedRectBorderStyle_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_bezelBorderStyle_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_lineBorderStyle_leftView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_noBackground_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_whiteBackground_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.backgroundColor = .white + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_roundedRectBorderStyle_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .roundedRect + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_bezelBorderStyle_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .bezel + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } + + func testFloating_lineBorderStyle_leftRightView() { + let textField: STPFloatingPlaceholderTextField = STPFloatingPlaceholderTextField() + textField.placeholder = "Test Placeholder" + textField.text = "Input Text" + textField.leftView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.leftViewMode = .always + textField.rightView = UIImageView(image: STPImageLibrary.unknownCardCardImage()) + textField.rightViewMode = .always + textField.borderStyle = .line + textField.sizeToFit() + STPSnapshotVerifyView(textField) + } +} diff --git a/Stripe/StripeiOSTests/STPFormEncoderTest.swift b/Stripe/StripeiOSTests/STPFormEncoderTest.swift new file mode 100644 index 00000000..b66e07e0 --- /dev/null +++ b/Stripe/StripeiOSTests/STPFormEncoderTest.swift @@ -0,0 +1,210 @@ +// +// STPFormEncoderTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 1/8/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPTestFormEncodableObject: NSObject, STPFormEncodable { + var additionalAPIParameters: [AnyHashable: Any] = [:] + + @objc var testProperty: String? + @objc var testIgnoredProperty: String? + @objc var testArrayProperty: [AnyHashable]? + @objc var testDictionaryProperty: [AnyHashable: Any]? + @objc var testNestedObjectProperty: STPTestFormEncodableObject? + + class func rootObjectName() -> String? { + return "test_object" + } + + class func propertyNamesToFormFieldNamesMapping() -> [String: String] { + return [ + "testProperty": "test_property", + "testArrayProperty": "test_array_property", + "testDictionaryProperty": "test_dictionary_property", + "testNestedObjectProperty": "test_nested_property", + ] + } +} + +class STPTestNilRootObjectFormEncodableObject: STPTestFormEncodableObject { + override class func rootObjectName() -> String? { + return nil + } +} + +class STPFormEncoderTest: XCTestCase { + // helper test method + func encode(_ object: STPTestFormEncodableObject?) -> String? { + let dictionary = STPFormEncoder.dictionary(forObject: object!) + return URLEncoder.queryString(from: dictionary) + } + + func testFormEncoding_emptyObject() { + let testObject = STPTestFormEncodableObject() + XCTAssertEqual(encode(testObject), "") + } + + func testFormEncoding_normalObject() { + let testObject = STPTestFormEncodableObject() + testObject.testProperty = "success" + testObject.testIgnoredProperty = "ignoreme" + XCTAssertEqual(encode(testObject), "test_object[test_property]=success") + } + + func testFormEncoding_additionalAttributes() { + let testObject = STPTestFormEncodableObject() + testObject.testProperty = "success" + testObject.additionalAPIParameters = [ + "foo": "bar", + "nested": [ + "nested_key": "nested_value" + ], + ] + XCTAssertEqual( + encode(testObject), + "test_object[foo]=bar&test_object[nested][nested_key]=nested_value&test_object[test_property]=success" + ) + } + + func testFormEncoding_arrayValue_empty() { + let testObject = STPTestFormEncodableObject() + testObject.testProperty = "success" + testObject.testArrayProperty = [] + XCTAssertEqual(encode(testObject), "test_object[test_property]=success") + } + + func testFormEncoding_arrayValue() { + let testObject = STPTestFormEncodableObject() + testObject.testProperty = "success" + testObject.testArrayProperty = [NSNumber(value: 1), NSNumber(value: 2), NSNumber(value: 3)] + XCTAssertEqual( + encode(testObject), + "test_object[test_array_property][0]=1&test_object[test_array_property][1]=2&test_object[test_array_property][2]=3&test_object[test_property]=success" + ) + } + + func testFormEncoding_BoolAndNumbers() { + let testObject = STPTestFormEncodableObject() + testObject.testArrayProperty = [ + NSNumber(value: 0), + NSNumber(value: 1), + NSNumber(value: false), + NSNumber(value: true), + NSNumber(value: true), + ] + XCTAssertEqual( + encode(testObject), + """ + test_object[test_array_property][0]=0\ + &test_object[test_array_property][1]=1\ + &test_object[test_array_property][2]=false\ + &test_object[test_array_property][3]=true\ + &test_object[test_array_property][4]=true + """ + ) + } + + func testFormEncoding_arrayOfEncodable() { + let testObject = STPTestFormEncodableObject() + + let inner1 = STPTestFormEncodableObject() + inner1.testProperty = "inner1" + let inner2 = STPTestFormEncodableObject() + inner2.testArrayProperty = ["inner2"] + + testObject.testArrayProperty = [inner1, inner2] + + XCTAssertEqual( + encode(testObject), + """ + test_object[test_array_property][0][test_property]=inner1\ + &test_object[test_array_property][1][test_array_property][0]=inner2 + """ + ) + } + + func testFormEncoding_dictionaryValue_empty() { + let testObject = STPTestFormEncodableObject() + testObject.testProperty = "success" + testObject.testDictionaryProperty = [:] + XCTAssertEqual(encode(testObject), "test_object[test_property]=success") + } + + func testFormEncoding_dictionaryValue() { + let testObject = STPTestFormEncodableObject() + testObject.testProperty = "success" + testObject.testDictionaryProperty = [ + "foo": "bar" + ] + XCTAssertEqual( + encode(testObject), + "test_object[test_dictionary_property][foo]=bar&test_object[test_property]=success" + ) + } + + func testFormEncoding_dictionaryOfEncodable() { + let testObject = STPTestFormEncodableObject() + + let inner1 = STPTestFormEncodableObject() + inner1.testProperty = "inner1" + let inner2 = STPTestFormEncodableObject() + inner2.testArrayProperty = ["inner2"] + + testObject.testDictionaryProperty = [ + "one": inner1, + "two": inner2, + ] + + XCTAssertEqual( + encode(testObject), + """ + test_object[test_dictionary_property][one][test_property]=inner1\ + &test_object[test_dictionary_property][two][test_array_property][0]=inner2 + """ + ) + } + + func testFormEncoding_setOfEncodable() { + let testObject = STPTestFormEncodableObject() + + let inner = STPTestFormEncodableObject() + inner.testProperty = "inner" + + testObject.testArrayProperty = [Set([inner])] + + XCTAssertEqual( + encode(testObject), + "test_object[test_array_property][0][test_property]=inner" + ) + } + + func testFormEncoding_nestedValue() { + let testObject1 = STPTestFormEncodableObject() + let testObject2 = STPTestFormEncodableObject() + testObject2.testProperty = "nested_object" + testObject1.testProperty = "success" + testObject1.testNestedObjectProperty = testObject2 + XCTAssertEqual( + encode(testObject1), + "test_object[test_nested_property][test_property]=nested_object&test_object[test_property]=success" + ) + } + + func testFormEncoding_nilRootObject() { + let testObject = STPTestNilRootObjectFormEncodableObject() + testObject.testProperty = "success" + XCTAssertEqual(encode(testObject), "test_property=success") + } +} diff --git a/Stripe/StripeiOSTests/STPFormTextFieldTest.swift b/Stripe/StripeiOSTests/STPFormTextFieldTest.swift new file mode 100644 index 00000000..83100b32 --- /dev/null +++ b/Stripe/StripeiOSTests/STPFormTextFieldTest.swift @@ -0,0 +1,62 @@ +// +// STPFormTextFieldTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 3/22/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPFormTextFieldTest: XCTestCase { + func testAutoFormattingBehavior_None() { + let sut = STPFormTextField() + sut.autoFormattingBehavior = .none + sut.text = "123456789" + XCTAssertEqual(sut.text, "123456789") + } + + func testAutoFormattingBehavior_PhoneNumbers() { + let sut = STPFormTextField() + sut.autoFormattingBehavior = .phoneNumbers + sut.text = "123456789" + XCTAssertEqual(sut.text, "(123) 456-789") + } + + func testAutoFormattingBehavior_CardNumbers() { + let sut = STPFormTextField() + sut.autoFormattingBehavior = .cardNumbers + sut.text = "4242424242424242" + XCTAssertEqual(sut.text, "4242424242424242") + var range = NSRange() + var value = sut.attributedText!.attribute(.kern, at: 0, effectiveRange: &range) as! Int + XCTAssertEqual(value, 0) + XCTAssertEqual(range.length, Int(3)) + value = sut.attributedText!.attribute(.kern, at: 3, effectiveRange: &range) as! Int + XCTAssertEqual(value, 5) + XCTAssertEqual(range.length, Int(1)) + value = sut.attributedText!.attribute(.kern, at: 4, effectiveRange: &range) as! Int + XCTAssertEqual(value, 0) + XCTAssertEqual(range.length, Int(3)) + value = sut.attributedText!.attribute(.kern, at: 7, effectiveRange: &range) as! Int + XCTAssertEqual(value, 5) + XCTAssertEqual(range.length, Int(1)) + value = sut.attributedText!.attribute(.kern, at: 8, effectiveRange: &range) as! Int + XCTAssertEqual(value, 0) + XCTAssertEqual(range.length, Int(3)) + value = sut.attributedText?.attribute(.kern, at: 11, effectiveRange: &range) as! Int + XCTAssertEqual(value, 5) + XCTAssertEqual(range.length, Int(1)) + value = sut.attributedText?.attribute(.kern, at: 12, effectiveRange: &range) as! Int + XCTAssertEqual(value, 0) + XCTAssertEqual(range.length, Int(4)) + XCTAssertEqual(sut.attributedText!.length, Int(16)) + + sut.placeholder = "enteracardnumber" + XCTAssertNil(sut.attributedPlaceholder!.attribute(.kern, at: 3, effectiveRange: &range)) + } +} diff --git a/Stripe/StripeiOSTests/STPFormViewSnapshotTests.swift b/Stripe/StripeiOSTests/STPFormViewSnapshotTests.swift new file mode 100644 index 00000000..47973d4f --- /dev/null +++ b/Stripe/StripeiOSTests/STPFormViewSnapshotTests.swift @@ -0,0 +1,161 @@ +// +// STPFormViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/23/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPFormViewSnapshotTests: STPSnapshotTestCase { + + func testSingleInput() { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Single input" + let section = STPFormView.Section(rows: [[input]], title: nil, accessoryButton: nil) + let formView = STPFormView(sections: [section]) + formView.frame.size = formView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + STPSnapshotVerifyView(formView) + } + + func testSingleInputPerRow() { + var rows = [[STPInputTextField]]() + for row in 0..<5 { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Row \(row)" + rows.append([input]) + } + let section = STPFormView.Section(rows: rows, title: nil, accessoryButton: nil) + let formView = STPFormView(sections: [section]) + formView.frame.size = formView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + STPSnapshotVerifyView(formView) + } + + func testMultiInputPerRow() { + var rows = [[STPInputTextField]]() + for row in 0..<5 { + var rowInputs = [STPInputTextField]() + for c in ["A", "B", "C"] { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Row \(row) \(c)" + rowInputs.append(input) + } + + rows.append(rowInputs) + } + let section = STPFormView.Section(rows: rows, title: nil, accessoryButton: nil) + let formView = STPFormView(sections: [section]) + formView.frame.size = formView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + STPSnapshotVerifyView(formView) + } + + func testMixSingleMultiInputPerRow() { + var rows = [[STPInputTextField]]() + for row in 0..<5 { + var rowInputs = [STPInputTextField]() + for c in ["A", "B", "C"] { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Row \(row) \(c)" + rowInputs.append(input) + if row % 2 == 0 { + break + } + } + + rows.append(rowInputs) + } + let section = STPFormView.Section(rows: rows, title: nil, accessoryButton: nil) + let formView = STPFormView(sections: [section]) + formView.frame.size = formView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + STPSnapshotVerifyView(formView) + } + + func testSingleSectionWithTitle() { + var rows = [[STPInputTextField]]() + for row in 0..<5 { + var rowInputs = [STPInputTextField]() + for c in ["A", "B", "C"] { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Row \(row) \(c)" + rowInputs.append(input) + } + + rows.append(rowInputs) + } + let section = STPFormView.Section(rows: rows, title: "Single Section", accessoryButton: nil) + let formView = STPFormView(sections: [section]) + formView.frame.size = formView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + STPSnapshotVerifyView(formView) + } + + func testMultiSection() { + var rows1 = [[STPInputTextField]]() + for row in 0..<5 { + var rowInputs = [STPInputTextField]() + for c in ["A", "B", "C"] { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Row \(row) \(c)" + rowInputs.append(input) + } + + rows1.append(rowInputs) + } + let section1 = STPFormView.Section( + rows: rows1, + title: "First Section", + accessoryButton: nil + ) + + var rows2 = [[STPInputTextField]]() + for row in 0..<5 { + var rowInputs = [STPInputTextField]() + for c in ["A", "B", "C"] { + let input = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + input.placeholder = "Row \(row) \(c)" + rowInputs.append(input) + } + + rows2.append(rowInputs) + } + let section2 = STPFormView.Section( + rows: rows2, + title: "Second Section", + accessoryButton: nil + ) + + let formView = STPFormView(sections: [section1, section2]) + formView.frame.size = formView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + STPSnapshotVerifyView(formView) + } + +} diff --git a/Stripe/StripeiOSTests/STPGenericInputPickerFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPGenericInputPickerFieldSnapshotTests.swift new file mode 100644 index 00000000..494e102a --- /dev/null +++ b/Stripe/StripeiOSTests/STPGenericInputPickerFieldSnapshotTests.swift @@ -0,0 +1,79 @@ +// +// STPGenericInputPickerFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Mel Ludowise on 2/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +final class STPGenericInputPickerFieldSnapshotTests: STPSnapshotTestCase { + + private var field: STPGenericInputPickerField! + + override func setUp() { + super.setUp() + + field = STPGenericInputPickerField(dataSource: MockDataSource()) + field.placeholder = "Placeholder" + field.sizeToFit() + field.frame.size.width = 200 + } + + func testEmptySelection() { + STPSnapshotVerifyView(field) + } + + func testWithDefaultSelection() { + // The 0th row should be auto-selected when tapping into the field + field.delegate?.textFieldDidBeginEditing?(field) + + STPSnapshotVerifyView(field) + } + + func testWithExplicitSelection() { + let index = 5 + + // Explicitly select a row + field.pickerView.selectRow(index, inComponent: 0, animated: false) + + // Because we're interacting with the picker programatically, we need to explicitly + // call `resignFirstResponder` to commit the changes. + _ = field.resignFirstResponder() + + STPSnapshotVerifyView(field) + } +} + +/// Simple DataSource that displays numbers 0–9 +private final class MockDataSource: STPGenericInputPickerFieldDataSource { + func numberOfRows() -> Int { + return 10 + } + + func inputPickerField( + _ pickerField: STPGenericInputPickerField, + titleForRow row: Int + ) + -> String? + { + return "\(row)" + } + + func inputPickerField( + _ pickerField: STPGenericInputPickerField, + inputValueForRow row: Int + ) + -> String? + { + return "\(row)" + } +} diff --git a/Stripe/StripeiOSTests/STPGenericInputPickerFieldValidatorTest.swift b/Stripe/StripeiOSTests/STPGenericInputPickerFieldValidatorTest.swift new file mode 100644 index 00000000..0a04d8b8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPGenericInputPickerFieldValidatorTest.swift @@ -0,0 +1,45 @@ +// +// STPGenericInputPickerFieldValidatorTest.swift +// StripeiOS Tests +// +// Created by Mel Ludowise on 2/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +final class STPGenericInputPickerFieldValidatorTest: XCTestCase { + + private var validator: STPGenericInputPickerField.Validator! + + override func setUp() { + super.setUp() + + validator = STPGenericInputPickerField.Validator() + } + + func testInitial() { + XCTAssertEqual(validator.validationState, .unknown) + } + + func testValidInput() { + validator.inputValue = "hello" + XCTAssertEqual(validator.validationState, .valid(message: nil)) + } + + func testEmptyInput() { + validator.inputValue = "" + XCTAssertEqual(validator.validationState, .incomplete(description: nil)) + } + + func testNilInput() { + validator.inputValue = nil + XCTAssertEqual(validator.validationState, .incomplete(description: nil)) + } +} diff --git a/Stripe/StripeiOSTests/STPGenericInputTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPGenericInputTextFieldSnapshotTests.swift new file mode 100644 index 00000000..fa7408a6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPGenericInputTextFieldSnapshotTests.swift @@ -0,0 +1,42 @@ +// +// STPGenericInputTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 12/2/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPGenericInputTextFieldSnapshotTests: STPSnapshotTestCase { + + override func setUp() { + super.setUp() + } + + func testEmpty() { + let field = STPGenericInputTextField(placeholder: "Empty") + field.sizeToFit() + field.frame.size.width = 200 + + STPSnapshotVerifyView(field) + } + + func testWithContent() { + let field = STPGenericInputTextField(placeholder: "Has Content") + field.sizeToFit() + field.frame.size.width = 200 + field.text = "Hello" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + +} diff --git a/Stripe/StripeiOSTests/STPImageLibraryTest.swift b/Stripe/StripeiOSTests/STPImageLibraryTest.swift new file mode 100644 index 00000000..368e8e61 --- /dev/null +++ b/Stripe/StripeiOSTests/STPImageLibraryTest.swift @@ -0,0 +1,243 @@ +// +// STPImageLibraryTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 4/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class STPImageLibraryTestSwift: XCTestCase { + + static let cardBrands: [STPCardBrand] = [ + .amex, + .cartesBancaires, + .dinersClub, + .discover, + .JCB, + .mastercard, + .unionPay, + .unknown, + .visa, + ] + + func testCardIconMethods() { + STPAssertEqualImages( + STPImageLibrary.amexCardImage(), + STPImageLibrary.safeImageNamed("stp_card_amex", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.dinersClubCardImage(), + STPImageLibrary.safeImageNamed("stp_card_diners", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.discoverCardImage(), + STPImageLibrary.safeImageNamed("stp_card_discover", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.jcbCardImage(), + STPImageLibrary.safeImageNamed("stp_card_jcb", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.mastercardCardImage(), + STPImageLibrary.safeImageNamed("stp_card_mastercard", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.unionPayCardImage(), + STPImageLibrary.safeImageNamed("stp_card_unionpay", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.visaCardImage(), + STPImageLibrary.safeImageNamed("stp_card_visa", templateIfAvailable: false) + ) + STPAssertEqualImages( + STPImageLibrary.unknownCardCardImage(), + STPImageLibrary.safeImageNamed("stp_card_unknown", templateIfAvailable: false) + ) + } + + func testBrandImageForCardBrand() { + for brand in Self.cardBrands { + let image = STPImageLibrary.brandImage(for: brand, template: false) + + switch brand { + case .visa: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_visa", templateIfAvailable: false) + ) + case .amex: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_amex", templateIfAvailable: false) + ) + case .mastercard: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed( + "stp_card_mastercard", + templateIfAvailable: false + ) + ) + case .discover: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_discover", templateIfAvailable: false) + ) + case .JCB: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_jcb", templateIfAvailable: false) + ) + case .dinersClub: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_diners", templateIfAvailable: false) + ) + case .unionPay: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_unionpay", templateIfAvailable: false) + ) + case .cartesBancaires: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_cartes_bancaires", templateIfAvailable: false) + ) + case .unknown: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_unknown", templateIfAvailable: false) + ) + } + } + } + + func testUnpaddedImageForCardBrands() { + for brand in STPCardBrand.allCases { + let image = STPImageLibrary.unpaddedCardBrandImage(for: brand) + // Assert image exists + XCTAssert(image.size != .zero) + } + } + + func testCVCImageForCardBrand() { + for brand in Self.cardBrands { + let image = STPImageLibrary.cvcImage(for: brand) + + switch brand { + case .amex: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_cvc_amex", templateIfAvailable: false) + ) + default: + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_cvc", templateIfAvailable: false) + ) + } + } + } + + func testErrorImageForCardBrand() { + for brand in Self.cardBrands { + let image = STPImageLibrary.errorImage(for: brand) + STPAssertEqualImages( + image, + STPImageLibrary.safeImageNamed("stp_card_error", templateIfAvailable: false) + ) + } + } + + func testMiscImages() { + STPAssertEqualImages( + STPImageLibrary.bankIcon(), + STPImageLibrary.safeImageNamed("stp_icon_bank", templateIfAvailable: false) + ) + } + + func testBankIconCodeImagesExist() { + for iconCode in PaymentSheetImageLibrary.BankIconCodeRegexes.keys { + XCTAssertNotNil( + PaymentSheetImageLibrary.bankIcon(for: iconCode), + "Missing image for \(iconCode)" + ) + } + } + + func testBankNameToIconCode() { + // bank of america + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "bank of america"), "boa") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "BANK of AMERICA"), "boa") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "BANKof AMERICA"), "default") + + // capital one + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "capital one"), "capitalone") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Capital One"), "capitalone") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Capital One"), "default") + + // citibank + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "citibank"), "citibank") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Citibank"), "citibank") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Citi Bank"), "default") + + // compass + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "bbva"), "compass") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "BBVA"), "compass") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "compass"), "compass") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "b b v a"), "default") + + // morganchase + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Morgan Chase"), "morganchase") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "morgan chase"), "morganchase") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "jp morgan"), "morganchase") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "JP Morgan"), "morganchase") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Chase"), "morganchase") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "chase"), "morganchase") + + // pnc + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "pncbank"), "pnc") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "PNCBANK"), "pnc") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "pnc bank"), "pnc") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "PNC Bank"), "pnc") + + // suntrust + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "suntrust"), "suntrust") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "SUNTRUST"), "suntrust") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "suntrust bank"), "suntrust") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Suntrust Bank"), "suntrust") + + // svb + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Silicon Valley Bank"), "svb") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "SILICON VALLEY BANK"), "svb") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "SILICONVALLEYBANK"), "default") + + // usaa + XCTAssertEqual( + PaymentSheetImageLibrary.bankIconCode(for: "USAA Federal Savings Bank"), + "usaa" + ) + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "USAA Bank"), "usaa") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "USAA Savings Bank"), "default") + + // usbank + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "US Bank"), "usbank") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "U.S. Bank"), "usbank") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "u.s. Bank"), "usbank") + + // wellsfargo + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Wells Fargo"), "wellsfargo") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "WELLS FARGO"), "wellsfargo") + XCTAssertEqual(PaymentSheetImageLibrary.bankIconCode(for: "Well's Fargo"), "default") + } + +} diff --git a/Stripe/StripeiOSTests/STPInputTextFieldFormatterTests.swift b/Stripe/StripeiOSTests/STPInputTextFieldFormatterTests.swift new file mode 100644 index 00000000..8be567ca --- /dev/null +++ b/Stripe/StripeiOSTests/STPInputTextFieldFormatterTests.swift @@ -0,0 +1,81 @@ +// +// STPInputTextFieldFormatterTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPInputTextFieldFormatterTests: XCTestCase { + + func testAllowsDeletion() { + let formatter = STPInputTextFieldFormatter() + let textField = UITextField() + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 2), + replacementString: "" + ), + "Should allow deletion on empty" + ) + textField.text = "Hi" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 2), + replacementString: "" + ), + "Should allow full deletion" + ) + textField.text = "Hello" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 4, length: 1), + replacementString: "" + ), + "Should allow partial deletion at end" + ) + textField.text = "Hello" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 3, length: 1), + replacementString: "" + ), + "Should allow partial deletion in middle" + ) + textField.text = "Hello" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 1), + replacementString: "" + ), + "Should allow partial deletion at beginning" + ) + } + + func testAllowsInitialSpaceForAutofill() { + let formatter = STPInputTextFieldFormatter() + let textField = UITextField() + textField.textContentType = .nickname + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 0), + replacementString: " " + ) + ) + } + +} diff --git a/Stripe/StripeiOSTests/STPInputTextFieldValidatorTests.swift b/Stripe/StripeiOSTests/STPInputTextFieldValidatorTests.swift new file mode 100644 index 00000000..542ff6b3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPInputTextFieldValidatorTests.swift @@ -0,0 +1,64 @@ +// +// STPInputTextFieldValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPInputTextFieldValidatorTests: XCTestCase { + + class ObserverWithExpectation: NSObject, STPFormInputValidationObserver { + + let expectation: XCTestExpectation + init( + _ expectation: XCTestExpectation + ) { + self.expectation = expectation + super.init() + } + + func validationDidUpdate( + to state: STPValidatedInputState, + from previousState: STPValidatedInputState, + for unformattedInput: String?, + in input: STPFormInput + ) { + expectation.fulfill() + } + + } + + func testUpdatingObservers() { + let textField = STPInputTextField( + formatter: STPInputTextFieldFormatter(), + validator: STPInputTextFieldValidator() + ) + let expectationForNewValue = expectation(description: "Receives expectation with new value") + let observerForNewValue = ObserverWithExpectation(expectationForNewValue) + let validator = textField.validator + + validator.addObserver(observerForNewValue) + validator.validationState = STPValidatedInputState.valid(message: nil) + wait(for: [expectationForNewValue], timeout: 1) + validator.removeObserver(observerForNewValue) + + let expectationForSameValue = expectation( + description: "Receives expectation with same value" + ) + let observerForSameValue = ObserverWithExpectation(expectationForSameValue) + validator.validationState = .incomplete(description: nil) + validator.addObserver(observerForSameValue) + validator.validationState = .incomplete(description: nil) + wait(for: [expectationForSameValue], timeout: 1) + } + +} diff --git a/Stripe/StripeiOSTests/STPIntentActionAlipayHandleRedirectTest.swift b/Stripe/StripeiOSTests/STPIntentActionAlipayHandleRedirectTest.swift new file mode 100644 index 00000000..73a55ab7 --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionAlipayHandleRedirectTest.swift @@ -0,0 +1,55 @@ +// +// STPIntentActionAlipayHandleRedirectTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 12/2/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPIntentActionAlipayHandleRedirectTest: XCTestCase { + func testMarlinReturnURL() throws { + let testJSONString = """ + { + "alipay_handle_redirect": { + "native_url": "alipay://alipayclient/?%7B%22dataString%22%3A%22_input_charset=utf-8%26app_pay=Y%26currency=USD%26forex_biz=FP%26notify_url=https%253A%252F%252Fhooks.stripe.com%252Falipay%252Falipay%252Fhook%252REDACTED%252Fsrc_REDACTED%26out_trade_no=src_REDACTED%26partner=REDACTED%26payment_type=1%26product_code=NEW_WAP_OVERSEAS_SELLER%26return_url=https%253A%252F%252Fhooks.stripe.com%252Fadapter%252Falipay%252Fredirect%252Fcomplete%252Fsrc_REDACTED%252Fsrc_client_secret_REDACTED%26secondary_merchant_id=acct_REDACTED%26secondary_merchant_industry=5734%26secondary_merchant_name=Yuki-Test%26sendFormat=normal%26service=create_forex_trade_wap%26sign=REDACTED%26sign_type=MD5%26subject=Yuki-Test%26supplier=Yuki-Test%26timeout_rule=20m%26total_fee=1.00%26bizcontext=%7B%5C%22av%5C%22%3A%5C%221.0%5C%22%2C%5C%22ty%5C%22%3A%5C%22ios_lite%5C%22%2C%5C%22appkey%5C%22%3A%5C%22123456789%5C%22%2C%5C%22sv%5C%22%3A%5C%22h.a.3.2.5%5C%22%2C%5C%22an%5C%22%3A%5C%22com.stripe.CustomSDKExample%5C%22%7D%22%2C%22fromAppUrlScheme%22%3A%22payments-example%22%2C%22requestType%22%3A%22SafePay%22%7D", + "return_url": "payments-example://safepay/", + "url": "https://hooks.stripe.com/redirect/authenticate/src_REDACTED?client_secret=src_client_secret_REDACTED" + }, + "type": "alipay_handle_redirect" + } + """ + guard + let testJSONData = testJSONString.data(using: .utf8), + let json = try? JSONSerialization.jsonObject( + with: testJSONData, + options: .allowFragments + ) as? [AnyHashable: Any], + let nextAction = STPIntentAction.decodedObject(fromAPIResponse: json), + let alipayRedirect = nextAction.alipayHandleRedirect + else { + XCTFail() + return + } + XCTAssertEqual( + alipayRedirect.nativeURL, + URL( + string: + "alipay://alipayclient/?%7B%22dataString%22%3A%22_input_charset=utf-8%26app_pay=Y%26currency=USD%26forex_biz=FP%26notify_url=https%253A%252F%252Fhooks.stripe.com%252Falipay%252Falipay%252Fhook%252REDACTED%252Fsrc_REDACTED%26out_trade_no=src_REDACTED%26partner=REDACTED%26payment_type=1%26product_code=NEW_WAP_OVERSEAS_SELLER%26return_url=https%253A%252F%252Fhooks.stripe.com%252Fadapter%252Falipay%252Fredirect%252Fcomplete%252Fsrc_REDACTED%252Fsrc_client_secret_REDACTED%26secondary_merchant_id=acct_REDACTED%26secondary_merchant_industry=5734%26secondary_merchant_name=Yuki-Test%26sendFormat=normal%26service=create_forex_trade_wap%26sign=REDACTED%26sign_type=MD5%26subject=Yuki-Test%26supplier=Yuki-Test%26timeout_rule=20m%26total_fee=1.00%26bizcontext=%7B%5C%22av%5C%22%3A%5C%221.0%5C%22%2C%5C%22ty%5C%22%3A%5C%22ios_lite%5C%22%2C%5C%22appkey%5C%22%3A%5C%22123456789%5C%22%2C%5C%22sv%5C%22%3A%5C%22h.a.3.2.5%5C%22%2C%5C%22an%5C%22%3A%5C%22com.stripe.CustomSDKExample%5C%22%7D%22%2C%22fromAppUrlScheme%22%3A%22payments-example%22%2C%22requestType%22%3A%22SafePay%22%7D" + ) + ) + XCTAssertEqual(alipayRedirect.returnURL, URL(string: "payments-example://safepay/")) + XCTAssertEqual( + alipayRedirect.marlinReturnURL, + URL( + string: + "https://hooks.stripe.com/adapter/alipay/redirect/complete/src_REDACTED/src_client_secret_REDACTED" + ) + ) + } +} diff --git a/Stripe/StripeiOSTests/STPIntentActionMultibancoDisplayDetailsTest.swift b/Stripe/StripeiOSTests/STPIntentActionMultibancoDisplayDetailsTest.swift new file mode 100644 index 00000000..f526c3f2 --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionMultibancoDisplayDetailsTest.swift @@ -0,0 +1,44 @@ +// +// STPIntentActionMultibancoDisplayDetailsTest.swift +// StripeiOSTests +// +// Created by Nick Porter on 4/22/24. +// + +import Foundation + +class STPIntentActionMultibancoDisplayDetailsTest: XCTestCase { + func testActionDisplayDetails() throws { + let testJSONString = """ + { + "multibanco_display_details": { + "entity": "1234", + "expires_at": 1714405124, + "reference": "123456789", + "hosted_voucher_url": "https://payments.stripe.com/multibanco/voucher" + }, + "type": "multibanco_display_details" + } + """ + guard + let testJSONData = testJSONString.data(using: .utf8), + let json = try? JSONSerialization.jsonObject( + with: testJSONData, + options: .allowFragments + ) as? [AnyHashable: Any], + let nextAction = STPIntentAction.decodedObject(fromAPIResponse: json), + let multibancoDisplayDetails = nextAction.multibancoDisplayDetails + else { + XCTFail() + return + } + + XCTAssertEqual(multibancoDisplayDetails.entity, "1234") + XCTAssertEqual(multibancoDisplayDetails.expiresAt.timeIntervalSince1970, 1714405124) + XCTAssertEqual(multibancoDisplayDetails.reference, "123456789") + XCTAssertEqual( + multibancoDisplayDetails.hostedVoucherURL, + URL(string: "https://payments.stripe.com/multibanco/voucher") + ) + } +} diff --git a/Stripe/StripeiOSTests/STPIntentActionPayNowDisplayQrCodeTest.swift b/Stripe/StripeiOSTests/STPIntentActionPayNowDisplayQrCodeTest.swift new file mode 100644 index 00000000..c0ee7df8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionPayNowDisplayQrCodeTest.swift @@ -0,0 +1,37 @@ +// +// STPIntentActionPayNowDisplayQrCodeTest.swift +// StripeiOSTests +// +// Created by Nick Porter on 9/11/23. +// + +@testable@_spi(STP) import Stripe + +class STPIntentActionPayNowDisplayQrCodeTest: XCTestCase { + func testActionHostedUrl() throws { + let testJSONString = """ + { + "paynow_display_qr_code": { + "hosted_instructions_url": "stripe.com/test/paynow/qr", + }, + "type": "paynow_display_qr_code" + } + """ + guard + let testJSONData = testJSONString.data(using: .utf8), + let json = try? JSONSerialization.jsonObject( + with: testJSONData, + options: .allowFragments + ) as? [AnyHashable: Any], + let nextAction = STPIntentAction.decodedObject(fromAPIResponse: json), + let payNowDisplayQrCode = nextAction.payNowDisplayQrCode + else { + XCTFail() + return + } + XCTAssertEqual( + payNowDisplayQrCode.hostedInstructionsURL, + URL(string: "stripe.com/test/paynow/qr") + ) + } +} diff --git a/Stripe/StripeiOSTests/STPIntentActionPromptPayDisplayQrCodeTest.swift b/Stripe/StripeiOSTests/STPIntentActionPromptPayDisplayQrCodeTest.swift new file mode 100644 index 00000000..31d0272c --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionPromptPayDisplayQrCodeTest.swift @@ -0,0 +1,38 @@ +// +// STPIntentActionPromptPayDisplayQrCodeTest.swift +// StripeiOSTests +// +// Created by Nick Porter on 9/12/23. +// + +import Foundation +@testable@_spi(STP) import Stripe + +class STPIntentActionPromptPayDisplayQrCodeTest: XCTestCase { + func testActionHostedUrl() throws { + let testJSONString = """ + { + "promptpay_display_qr_code": { + "hosted_instructions_url": "stripe.com/test/promptpay/qr", + }, + "type": "promptpay_display_qr_code" + } + """ + guard + let testJSONData = testJSONString.data(using: .utf8), + let json = try? JSONSerialization.jsonObject( + with: testJSONData, + options: .allowFragments + ) as? [AnyHashable: Any], + let nextAction = STPIntentAction.decodedObject(fromAPIResponse: json), + let promptPayDisplayQrCode = nextAction.promptPayDisplayQrCode + else { + XCTFail() + return + } + XCTAssertEqual( + promptPayDisplayQrCode.hostedInstructionsURL, + URL(string: "stripe.com/test/promptpay/qr") + ) + } +} diff --git a/Stripe/StripeiOSTests/STPIntentActionTest.swift b/Stripe/StripeiOSTests/STPIntentActionTest.swift new file mode 100644 index 00000000..4aa2b028 --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionTest.swift @@ -0,0 +1,136 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPIntentActionTest.m +// StripeiOS Tests +// +// Created by Daniel Jackson on 11/7/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripePayments + +class STPIntentActionTest: XCTestCase { + func testDecodedObjectFromAPIResponseRedirectToURL() { + + let decode: (([AnyHashable: Any]?) -> STPIntentAction?) = { dict in + return .decodedObject(fromAPIResponse: dict) + } + + XCTAssertNil(decode(nil)) + XCTAssertNil(decode([:])) + XCTAssertNil( + decode([ + "redirect_to_url": [ + "url": "http://stripe.com" + ], + ]), + "fails without type") + + let missingDetails = decode( + [ + "type": "redirect_to_url" + ]) + XCTAssertNotNil(missingDetails) + XCTAssertEqual( + missingDetails!.type, + .unknown, + "Type becomes unknown if the redirect_to_url details are missing") + + let badURL = decode( + [ + "type": "redirect_to_url", + "redirect_to_url": [ + "url": "not a url" + ], + ]) + XCTAssertNotNil(badURL) + XCTAssertEqual( + badURL!.type, + .unknown, + "Type becomes unknown if the redirect_to_url details don't have a valid URL") + + let missingReturnURL = decode( + [ + "type": "redirect_to_url", + "redirect_to_url": [ + "url": "https://stripe.com/" + ], + ]) + XCTAssertNotNil(missingReturnURL) + XCTAssertEqual( + missingReturnURL!.type, + .redirectToURL, + "Missing return_url won't prevent it from decoding") + XCTAssertNotNil(missingReturnURL?.redirectToURL?.url) + XCTAssertEqual( + missingReturnURL?.redirectToURL?.url, + URL(string: "https://stripe.com/")) + XCTAssertNil(missingReturnURL?.redirectToURL?.returnURL) + + let badReturnURL = decode( + [ + "type": "redirect_to_url", + "redirect_to_url": [ + "url": "https://stripe.com/", + "return_url": "not a url", + ], + ]) + XCTAssertNotNil(badReturnURL) + XCTAssertEqual( + badReturnURL!.type, + .redirectToURL, + "invalid return_url won't prevent it from decoding") + XCTAssertNotNil(badReturnURL?.redirectToURL?.url) + XCTAssertEqual( + badReturnURL?.redirectToURL?.url, + URL(string: "https://stripe.com/")) + XCTAssertNil(badReturnURL?.redirectToURL?.returnURL) + + let complete = decode( + [ + "type": "redirect_to_url", + "redirect_to_url": [ + "url": "https://stripe.com/", + "return_url": "my-app://payment-complete", + ], + ]) + XCTAssertNotNil(complete) + XCTAssertEqual(complete?.type, .redirectToURL) + XCTAssertNotNil(complete?.redirectToURL?.url) + XCTAssertEqual( + complete?.redirectToURL?.url, + URL(string: "https://stripe.com/")) + XCTAssertNotNil(complete?.redirectToURL?.returnURL) + XCTAssertEqual( + complete?.redirectToURL?.returnURL, + URL(string: "my-app://payment-complete")) + XCTAssertFalse(complete!.redirectToURL!.followRedirects) + XCTAssertFalse(complete!.redirectToURL!.useWebAuthSession) + + let withFlags = decode( + [ + "type": "redirect_to_url", + "redirect_to_url": [ + "url": "https://stripe.com/redirect?useWebAuthSession=true&followRedirectsInSDK=true", + "return_url": "my-app://payment-complete", + ], + ]) + XCTAssertNotNil(withFlags) + XCTAssertEqual(withFlags?.type, .redirectToURL) + XCTAssertNotNil(withFlags?.redirectToURL?.url) + XCTAssertTrue(withFlags!.redirectToURL!.followRedirects) + XCTAssertTrue(withFlags!.redirectToURL!.useWebAuthSession) + + // Don't observe flags on non-Stripe URLs + let withNonStripeFlags = decode( + [ + "type": "redirect_to_url", + "redirect_to_url": [ + "url": "https://example.com/redirect?useWebAuthSession=true&followRedirectsInSDK=true", + "return_url": "my-app://payment-complete", + ], + ]) + XCTAssertFalse(withNonStripeFlags!.redirectToURL!.followRedirects) + XCTAssertFalse(withNonStripeFlags!.redirectToURL!.useWebAuthSession) + } +} diff --git a/Stripe/StripeiOSTests/STPIntentActionTypeTest.swift b/Stripe/StripeiOSTests/STPIntentActionTypeTest.swift new file mode 100644 index 00000000..e1879c45 --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionTypeTest.swift @@ -0,0 +1,48 @@ +// +// STPIntentActionTypeTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 9/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPIntentActionTypeTest: XCTestCase { + + func testTypeFromString() { + XCTAssertEqual( + STPIntentActionType(string: "redirect_to_url"), + STPIntentActionType.redirectToURL + ) + XCTAssertEqual( + STPIntentActionType(string: "REDIRECT_TO_URL"), + STPIntentActionType.redirectToURL + ) + + XCTAssertEqual( + STPIntentActionType(string: "use_stripe_sdk"), + STPIntentActionType.useStripeSDK + ) + XCTAssertEqual( + STPIntentActionType(string: "USE_STRIPE_SDK"), + STPIntentActionType.useStripeSDK + ) + + XCTAssertEqual( + STPIntentActionType(string: "garbage"), + STPIntentActionType.unknown + ) + XCTAssertEqual( + STPIntentActionType(string: "GARBAGE"), + STPIntentActionType.unknown + ) + } + +} diff --git a/Stripe/StripeiOSTests/STPIntentActionWeChatPayRedirectToAppTest.swift b/Stripe/StripeiOSTests/STPIntentActionWeChatPayRedirectToAppTest.swift new file mode 100644 index 00000000..5e476ad1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPIntentActionWeChatPayRedirectToAppTest.swift @@ -0,0 +1,44 @@ +// +// STPIntentActionWeChatPayRedirectToAppTest.swift +// StripeiOS Tests +// +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPIntentActionWeChatPayRedirectToAppTest: XCTestCase { + func testActionNativeURL() throws { + let testJSONString = """ + { + "wechat_pay_redirect_to_ios_app": { + "native_url": "weixin://app/value:wx12345a1234b1234c/pay/?package=Sign=WXPay&appid=wx12345a1234b1234c&partnerid=123456789&prepayid=wx12345a1234b1234c&noncestr=12345×tamp=12345&sign=12341234", + }, + "type": "wechat_pay_redirect_to_ios_app" + } + """ + guard + let testJSONData = testJSONString.data(using: .utf8), + let json = try? JSONSerialization.jsonObject( + with: testJSONData, + options: .allowFragments + ) as? [AnyHashable: Any], + let nextAction = STPIntentAction.decodedObject(fromAPIResponse: json), + let weChatPayRedirectToApp = nextAction.weChatPayRedirectToApp + else { + XCTFail() + return + } + XCTAssertEqual( + weChatPayRedirectToApp.nativeURL, + URL( + string: + "weixin://app/value:wx12345a1234b1234c/pay/?package=Sign=WXPay&appid=wx12345a1234b1234c&partnerid=123456789&prepayid=wx12345a1234b1234c&noncestr=12345×tamp=12345&sign=12341234" + ) + ) + } +} diff --git a/Stripe/StripeiOSTests/STPLabeledFormTextFieldViewSnapshotTests.swift b/Stripe/StripeiOSTests/STPLabeledFormTextFieldViewSnapshotTests.swift new file mode 100644 index 00000000..5f8b56d3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPLabeledFormTextFieldViewSnapshotTests.swift @@ -0,0 +1,24 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPLabeledFormTextFieldViewSnapshotTests.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCaseCore +import StripeCoreTestUtils +@testable @_spi(STP) import StripePaymentsUI + +class STPLabeledFormTextFieldViewSnapshotTests: STPSnapshotTestCase { + func testAppearance() { + let formTextField = STPFormTextField() + formTextField.placeholder = "A placeholder" + formTextField.placeholderColor = UIColor.lightGray + let labeledFormField = STPLabeledFormTextFieldView(formLabel: "Test Label", textField: formTextField) + labeledFormField.formBackgroundColor = UIColor.white + labeledFormField.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) + STPSnapshotVerifyView(labeledFormField, identifier: "STPLabeledFormTextFieldView.defaultAppearance") + } +} diff --git a/Stripe/StripeiOSTests/STPLabeledMultiFormTextFieldViewSnapshotTests.swift b/Stripe/StripeiOSTests/STPLabeledMultiFormTextFieldViewSnapshotTests.swift new file mode 100644 index 00000000..e6116225 --- /dev/null +++ b/Stripe/StripeiOSTests/STPLabeledMultiFormTextFieldViewSnapshotTests.swift @@ -0,0 +1,41 @@ +// +// STPLabeledMultiFormTextFieldViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPLabeledMultiFormTextFieldViewSnapshotTests: STPSnapshotTestCase { + + func testAppearance() { + let formTextField1 = STPFormTextField() + formTextField1.placeholder = "Placeholder 1" + formTextField1.placeholderColor = UIColor.lightGray + + let formTextField2 = STPFormTextField() + formTextField2.placeholder = "Placeholder 2" + formTextField2.placeholderColor = UIColor.lightGray + + let labeledFormField = STPLabeledMultiFormTextFieldView( + formLabel: "Test Label", + firstTextField: formTextField1, + secondTextField: formTextField2 + ) + labeledFormField.formBackgroundColor = UIColor.white + labeledFormField.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 62.0) + STPSnapshotVerifyView( + labeledFormField, + identifier: "STPLabeledMultiFormTextFieldView.defaultAppearance" + ) + } +} diff --git a/Stripe/StripeiOSTests/STPMandateCustomerAcceptanceParamsTest.swift b/Stripe/StripeiOSTests/STPMandateCustomerAcceptanceParamsTest.swift new file mode 100644 index 00000000..66f1e0d5 --- /dev/null +++ b/Stripe/StripeiOSTests/STPMandateCustomerAcceptanceParamsTest.swift @@ -0,0 +1,44 @@ +// +// STPMandateCustomerAcceptanceParamsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPMandateCustomerAcceptanceParamsTest: XCTestCase { + func testRootObjectName() { + XCTAssertEqual(STPMandateCustomerAcceptanceParams.rootObjectName(), "customer_acceptance") + } + + func testEncoding() { + let onlineParams = STPMandateOnlineParams(ipAddress: "", userAgent: "") + onlineParams.inferFromClient = NSNumber(value: true) + var params = STPMandateCustomerAcceptanceParams(type: .online, onlineParams: onlineParams)! + + var paramsAsDict = STPFormEncoder.dictionary(forObject: params) + var expected = [ + "customer_acceptance": [ + "type": "online", + "online": [ + "infer_from_client": NSNumber(value: true) + ], + ], + ] + XCTAssertEqual(paramsAsDict as NSDictionary, expected as NSDictionary) + + params = STPMandateCustomerAcceptanceParams(type: .offline, onlineParams: nil)! + paramsAsDict = STPFormEncoder.dictionary(forObject: params) + expected = [ + "customer_acceptance": [ + "type": "offline" + ], + ] + XCTAssertEqual(paramsAsDict as NSDictionary, expected as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPMandateDataParamsTest.swift b/Stripe/StripeiOSTests/STPMandateDataParamsTest.swift new file mode 100644 index 00000000..587d8f17 --- /dev/null +++ b/Stripe/StripeiOSTests/STPMandateDataParamsTest.swift @@ -0,0 +1,42 @@ +// +// STPMandateDataParamsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPMandateDataParamsTest: XCTestCase { + func testRootObjectName() { + XCTAssertEqual(STPMandateDataParams.rootObjectName(), "mandate_data") + } + + func testEncoding() { + let onlineParams = STPMandateOnlineParams(ipAddress: "", userAgent: "") + onlineParams.inferFromClient = NSNumber(value: true) + let customerAcceptanceParams = STPMandateCustomerAcceptanceParams( + type: .online, + onlineParams: onlineParams + )! + + let params = STPMandateDataParams(customerAcceptance: customerAcceptanceParams) + + let paramsAsDict = STPFormEncoder.dictionary(forObject: params) + let expected = [ + "mandate_data": [ + "customer_acceptance": [ + "type": "online", + "online": [ + "infer_from_client": true + ], + ], + ], + ] + XCTAssertEqual(paramsAsDict as NSDictionary, expected as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPMandateOnlineParamsTest.swift b/Stripe/StripeiOSTests/STPMandateOnlineParamsTest.swift new file mode 100644 index 00000000..990e02b2 --- /dev/null +++ b/Stripe/StripeiOSTests/STPMandateOnlineParamsTest.swift @@ -0,0 +1,40 @@ +// +// STPMandateOnlineParamsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPMandateOnlineParamsTest: XCTestCase { + func testRootObjectName() { + XCTAssertEqual(STPMandateOnlineParams.rootObjectName(), "online") + } + + func testEncoding() { + var params = STPMandateOnlineParams(ipAddress: "test_ip_address", userAgent: "a_user_agent") + var paramsAsDict = STPFormEncoder.dictionary(forObject: params) + var expected: [String: AnyHashable] = [ + "online": [ + "ip_address": "test_ip_address", + "user_agent": "a_user_agent", + ], + ] + XCTAssertEqual(paramsAsDict as NSDictionary, expected as NSDictionary) + + params = STPMandateOnlineParams(ipAddress: "", userAgent: "") + params.inferFromClient = NSNumber(value: true) + paramsAsDict = STPFormEncoder.dictionary(forObject: params) + expected = [ + "online": [ + "infer_from_client": NSNumber(value: true) + ], + ] + XCTAssertEqual(paramsAsDict as NSDictionary, expected as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPNumericDigitInputTextFormatterTests.swift b/Stripe/StripeiOSTests/STPNumericDigitInputTextFormatterTests.swift new file mode 100644 index 00000000..ea41c2f3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPNumericDigitInputTextFormatterTests.swift @@ -0,0 +1,157 @@ +// +// STPNumericDigitInputTextFormatterTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPNumericDigitInputTextFormatterTests: XCTestCase { + + func testDisallowsNonDigits() { + let formatter = STPNumericDigitInputTextFormatter() + XCTAssertFalse( + formatter.isAllowedInput("a", to: "", at: NSRange(location: 0, length: 1)), + "Shouldn't allow non-digit in empty string" + ) + XCTAssertFalse( + formatter.isAllowedInput("1a", to: "", at: NSRange(location: 0, length: 1)), + "Shouldn't allow digit + non-digit in empty string" + ) + XCTAssertFalse( + formatter.isAllowedInput("a", to: "1", at: NSRange(location: 0, length: 1)), + "Shouldn't allow non-digit in digit string" + ) + XCTAssertFalse( + formatter.isAllowedInput("1a", to: "1", at: NSRange(location: 0, length: 1)), + "Shouldn't allow digit + non-digit in digit string" + ) + XCTAssertFalse( + formatter.isAllowedInput(" ", to: "1", at: NSRange(location: 0, length: 1)), + "Shouldn't allow spaces" + ) + // for now we only validate the input, not the result + XCTAssertTrue( + formatter.isAllowedInput("1", to: "a", at: NSRange(location: 0, length: 1)), + "Should allow digit added to non-digit string" + ) + } + + func testAllowsDigits() { + let formatter = STPNumericDigitInputTextFormatter() + XCTAssertTrue( + formatter.isAllowedInput("1", to: "", at: NSRange(location: 0, length: 1)), + "Should allow digit in empty string" + ) + XCTAssertTrue( + formatter.isAllowedInput("2", to: "1", at: NSRange(location: 0, length: 1)), + "Should allow digit insert at beginning of string" + ) + XCTAssertTrue( + formatter.isAllowedInput("3", to: "1", at: NSRange(location: 1, length: 1)), + "Should allow digit insert at end of string" + ) + XCTAssertTrue( + formatter.isAllowedInput("45", to: "1", at: NSRange(location: 0, length: 1)), + "Should allow multi-digit insert" + ) + } + + func testFormattingCharacterSet() { + let formatter = STPNumericDigitInputTextFormatter( + allowedFormattingCharacterSet: CharacterSet(charactersIn: "xy") + ) + XCTAssertTrue( + formatter.isAllowedInput("x", to: "", at: NSRange(location: 0, length: 1)), + "Should allow formatting character in empty string" + ) + XCTAssertFalse( + formatter.isAllowedInput("xa", to: "", at: NSRange(location: 0, length: 1)), + "Shouldn't allow formatting + non-formatting in empty string" + ) + XCTAssertTrue( + formatter.isAllowedInput("x", to: "1", at: NSRange(location: 0, length: 1)), + "Should allow formatting character in digit string" + ) + XCTAssertTrue( + formatter.isAllowedInput("1x", to: "1", at: NSRange(location: 0, length: 1)), + "Should allow digit + formatting in digit string" + ) + XCTAssertTrue( + formatter.isAllowedInput("xxxxyyy", to: "1", at: NSRange(location: 0, length: 6)), + "Should allow multiple formatting in digit string" + ) + } + + // MARK: - Inherited Tests + func testAllowsDeletion() { + let formatter = STPNumericDigitInputTextFormatter() + let textField = UITextField() + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 2), + replacementString: "" + ), + "Should allow deletion on empty" + ) + textField.text = "12" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 2), + replacementString: "" + ), + "Should allow full deletion" + ) + textField.text = "12345" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 4, length: 1), + replacementString: "" + ), + "Should allow partial deletion at end" + ) + textField.text = "12345" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 3, length: 1), + replacementString: "" + ), + "Should allow partial deletion in middle" + ) + textField.text = "12345" + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 1), + replacementString: "" + ), + "Should allow partial deletion at beginning" + ) + } + + func testAllowsInitialSpaceForAutofill() { + let formatter = STPNumericDigitInputTextFormatter() + let textField = UITextField() + textField.textContentType = .nickname + XCTAssertTrue( + formatter.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 0), + replacementString: " " + ) + ) + } + +} diff --git a/Stripe/StripeiOSTests/STPNumericStringValidatorTests.swift b/Stripe/StripeiOSTests/STPNumericStringValidatorTests.swift new file mode 100644 index 00000000..8f5cdccd --- /dev/null +++ b/Stripe/StripeiOSTests/STPNumericStringValidatorTests.swift @@ -0,0 +1,49 @@ +// +// STPNumericStringValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPNumericStringValidatorTests: XCTestCase { + func testNumberSanitization() { + let tests = [ + ["4242424242424242", "4242424242424242"], + ["XXXXXX", ""], + ["424242424242424X", "424242424242424"], + ["X4242", "4242"], + ["4242 4242 4242 4242", "4242424242424242"], + ["123-456-", "123456"], + ] + for test in tests { + XCTAssertEqual(STPNumericStringValidator.sanitizedNumericString(for: test[0]), test[1]) + } + } + + func testIsStringNumeric() { + let tests = [ + ["4242424242424242", NSNumber(value: true)], + ["XXXXXX", NSNumber(value: false)], + ["424242424242424X", NSNumber(value: false)], + ["X4242", NSNumber(value: false)], + ["4242 4242 4242 4242", NSNumber(value: false)], + ["123-456-", NSNumber(value: false)], + [" 1", NSNumber(value: false)], + ["", NSNumber(value: true)], + ] + for test in tests { + let first = STPNumericStringValidator.isStringNumeric(test[0] as! String) + let second = (test[1] as! NSNumber).boolValue + XCTAssertEqual(first, second) + } + } +} diff --git a/Stripe/StripeiOSTests/STPPIIFunctionalTest.swift b/Stripe/StripeiOSTests/STPPIIFunctionalTest.swift new file mode 100644 index 00000000..e8d6a54d --- /dev/null +++ b/Stripe/StripeiOSTests/STPPIIFunctionalTest.swift @@ -0,0 +1,46 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPIIFunctionalTest.m +// Stripe +// +// Created by Charles Scalesse on 1/8/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPIIFunctionalTest: STPNetworkStubbingTestCase { + func testCreatePersonallyIdentifiableInformationToken() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "PII creation") + + client.createToken(withPersonalIDNumber: "0123456789") { token, error in + expectation.fulfill() + XCTAssertNil(error, "error should be nil \(String(describing: error?.localizedDescription))") + XCTAssertNotNil(token, "token should not be nil") + XCTAssertNotNil(token?.tokenId) + XCTAssertEqual(token?.type, .PII) + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testSSNLast4Token() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let expectation = self.expectation(description: "PII creation") + + client.createToken(withSSNLast4: "1234") { token, error in + expectation.fulfill() + XCTAssertNil(error, "error should be nil \(String(describing: error?.localizedDescription))") + XCTAssertNotNil(token, "token should not be nil") + XCTAssertNotNil(token?.tokenId) + XCTAssertEqual(token?.type, .PII) + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentCardTextFieldKVOTest.m b/Stripe/StripeiOSTests/STPPaymentCardTextFieldKVOTest.m new file mode 100644 index 00000000..b7a65d02 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentCardTextFieldKVOTest.m @@ -0,0 +1,83 @@ +// +// STPPaymentCardTextFieldKVOTest.m +// Stripe +// +// Created by Jack Flintermann on 8/26/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +@import UIKit; +@import XCTest; +@import OCMock; +@import StripeCoreTestUtils; +@import StripePaymentsObjcTestUtils; + +@interface STPPaymentCardTextField (Testing) +@property (nonatomic, readwrite, weak) UIImageView *brandImageView; +@property (nonatomic, readwrite, weak) STPFormTextField *numberField; +@property (nonatomic, readwrite, weak) STPFormTextField *expirationField; +@property (nonatomic, readwrite, weak) STPFormTextField *cvcField; +@property (nonatomic, readwrite, weak) STPFormTextField *postalCodeField; +@property (nonatomic, readonly, weak) STPFormTextField *currentFirstResponderField; +@property (nonatomic, copy) NSNumber *focusedTextFieldForLayout; ++ (UIImage *)cvcImageForCardBrand:(STPCardBrand)cardBrand; ++ (UIImage *)brandImageForCardBrand:(STPCardBrand)cardBrand; +@end + +@interface STPPaymentCardTextFieldKVOUITests : XCTestCase +@property (nonatomic) UIWindow *window; +@property (nonatomic) STPPaymentCardTextField *sut; +@end + +@implementation STPPaymentCardTextFieldKVOUITests + ++ (void)setUp { + [super setUp]; + [[STPAPIClient sharedClient] setPublishableKey:STPTestingDefaultPublishableKey]; +} + +- (void)setUp { + [super setUp]; + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + STPPaymentCardTextField *textField = [[STPPaymentCardTextField alloc] initWithFrame:self.window.bounds]; + [self.window addSubview:textField]; + XCTAssertTrue([textField.numberField canBecomeFirstResponder], @"text field cannot become first responder"); + self.sut = textField; +} + +- (void)testIsValidKVO { + id observer = OCMClassMock([UIViewController class]); + self.sut.numberField.text = @"4242424242424242"; + self.sut.expirationField.text = @"10/50"; + self.sut.postalCodeField.text = @"90210"; + XCTAssertFalse(self.sut.isValid); + + NSString *expectedKeyPath = @"sut.isValid"; + [self addObserver:observer forKeyPath:expectedKeyPath options:NSKeyValueObservingOptionNew context:nil]; + XCTestExpectation *exp = [self expectationWithDescription:@"observeValue"]; + OCMStub([observer observeValueForKeyPath:[OCMArg any] ofObject:[OCMArg any] change:[OCMArg any] context:nil]) + .andDo(^(NSInvocation *invocation) { + NSString *keyPath; + NSDictionary *change; + [invocation getArgument:&keyPath atIndex:2]; + [invocation getArgument:&change atIndex:4]; + if ([keyPath isEqualToString:expectedKeyPath]) { + if ([change[@"new"] boolValue]) { + [exp fulfill]; + [self removeObserver:observer forKeyPath:@"sut.isValid"]; + } + } + }); + + self.sut.cvcField.text = @"123"; + + [self waitForExpectationsWithTimeout:TestConstants.STPTestingNetworkRequestTimeout handler:nil]; +} + +- (void)testPaymentCardTextFieldCanSetPreferredBrands { + STPPaymentCardTextField *textField = [[STPPaymentCardTextField alloc] initWithFrame:self.window.bounds]; + [textField setPreferredNetworks:@[[NSNumber numberWithInt:STPCardBrandVisa]]]; + XCTAssertEqual([[[textField preferredNetworks] firstObject] intValue], STPCardBrandVisa); +} + +@end diff --git a/Stripe/StripeiOSTests/STPPaymentCardTextFieldTest.swift b/Stripe/StripeiOSTests/STPPaymentCardTextFieldTest.swift new file mode 100644 index 00000000..14b7ec9d --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentCardTextFieldTest.swift @@ -0,0 +1,1291 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentCardTextFieldTest.m +// Stripe +// +// Created by Jack Flintermann on 8/26/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +@testable @_spi(STP) import StripePayments +@testable @_spi(STP) import StripePaymentsUI +import UIKit +import XCTest + +/// Class that implements STPPaymentCardTextFieldDelegate and uses a block for each delegate method. +class PaymentCardTextFieldBlockDelegate: NSObject, STPPaymentCardTextFieldDelegate { + var didChange: ((STPPaymentCardTextField) -> Void)? + var willEndEditingForReturn: ((STPPaymentCardTextField) -> Void)? + var didEndEditing: ((STPPaymentCardTextField) -> Void)? + // add more properties for other delegate methods as this test needs them + + func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) { + if let didChange { + didChange(textField) + } + } + + func paymentCardTextFieldWillEndEditing(forReturn textField: STPPaymentCardTextField) { + if let willEndEditingForReturn { + willEndEditingForReturn(textField) + } + } + + func paymentCardTextFieldDidEndEditing(_ textField: STPPaymentCardTextField) { + if let didEndEditing { + didEndEditing(textField) + } + } +} + +class STPPaymentCardTextFieldTest: XCTestCase { + override class func setUp() { + super.setUp() + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + } + + func testIntrinsicContentSize() { + let textField = STPPaymentCardTextField() + + let iOS8SystemFont = UIFont(name: "HelveticaNeue", size: 18) + textField.font = iOS8SystemFont! + XCTAssertEqual(textField.intrinsicContentSize.height, 44, accuracy: 0.1) + XCTAssertEqual(textField.intrinsicContentSize.width, 241, accuracy: 0.1) + + let iOS9SystemFont = UIFont.systemFont(ofSize: 18) + textField.font = iOS9SystemFont + XCTAssertEqual(textField.intrinsicContentSize.height, 44, accuracy: 0.1) + XCTAssertEqual(textField.intrinsicContentSize.width, 253, accuracy: 1.0) + + textField.font = UIFont(name: "Avenir", size: 44)! + XCTAssertEqual(textField.intrinsicContentSize.height, 62, accuracy: 0.1) + XCTAssertEqual(textField.intrinsicContentSize.width, 472, accuracy: 0.1) + } + + func testSetCard_numberUnknown() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "1" + card.number = number + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.errorImage(for: .unknown)!.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, 0) + XCTAssertEqual(sut.cvcField.text!.count, 0) + XCTAssertNil(sut.currentFirstResponderField()) + } + + func testSetCard_expiration() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData! == imgData) + } + XCTAssertEqual(sut.numberField.text?.count, Int(0)) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text?.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_CVC() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let cvc = "123" + card.cvc = cvc + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_updatesCVCValidity() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "378282246310005" + sut.cvcField.text = "1234" + sut.expirationField.text = "10/50" + XCTAssertTrue(sut.cvcField.validText) + sut.numberField.text = "4242424242424242" + XCTAssertFalse(sut.cvcField.validText) + } + + func testSetCard_numberVisa() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "424242" + card.number = number + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertNotNil(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.placeholder, "CVC") + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberVisaInvalid() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "4242111111111111" + card.number = number + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.errorImage(for: .visa)!.pngData() + + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + } + + func testSetCard_withCBCInfo() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "424242" + card.number = number + card.networks = .init(preferred: "visa") + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + XCTAssertEqual(sut.paymentMethodParams.card!.networks!.preferred, "visa") + } + + func testSetCard_numberAmex() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "378282" + card.number = number + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .amex)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.placeholder, "CVC") + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberAmexInvalid() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "378282246311111" + card.number = number + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.errorImage(for: .amex)!.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + } + + func testSetCard_numberAndExpiration() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_partialNumberAndExpiration() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "424242" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberAndCVC() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "378282246310005" + let cvc = "123" + card.number = number + card.cvc = cvc + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .amex)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_expirationAndCVC() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let cvc = "123" + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_completeCardCountryWithoutPostal() { + let sut = STPPaymentCardTextField() + sut.countryCode = "BZ" + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertTrue(sut.isValid) + } + + func testSetCard_completeCardNoPostal() { + let sut = STPPaymentCardTextField() + sut.postalCodeEntryEnabled = false + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertTrue(sut.isValid) + } + + func testSetCard_completeCard() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.address = STPPaymentMethodAddress() + billingDetails.address!.postalCode = "90210" + let params = STPPaymentMethodParams(card: card, billingDetails: billingDetails, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertTrue(sut.isValid) + } + + func testSetCard_empty() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "4242424242424242" + sut.cvcField.text = "123" + sut.expirationField.text = "10/50" + let card = STPPaymentMethodCardParams() + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSettingTextUpdatesViewModelText() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "4242424242424242" + XCTAssertEqual(sut.viewModel.cardNumber, sut.numberField.text) + + sut.cvcField.text = "123" + XCTAssertEqual(sut.viewModel.cvc, sut.cvcField.text) + + sut.expirationField.text = "10/50" + XCTAssertEqual(sut.viewModel.rawExpiration, sut.expirationField.text) + XCTAssertEqual(sut.viewModel.expirationMonth, "10") + XCTAssertEqual(sut.viewModel.expirationYear, "50") + } + + func testSettingTextUpdatesCardParams() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "4242424242424242" + sut.cvcField.text = "123" + sut.expirationField.text = "10/50" + sut.postalCodeField.text = "90210" + + let card = sut.paymentMethodParams.card + XCTAssertNotNil(card) + XCTAssertEqual(card?.number, "4242424242424242") + XCTAssertEqual(card?.cvc, "123") + XCTAssertEqual(card?.expMonth?.intValue ?? 0, 10) + XCTAssertEqual(card?.expYear?.intValue ?? 0, 50) + XCTAssertEqual(sut.paymentMethodParams.billingDetails!.address!.postalCode, "90210") + } + + func testSettingBillingDetailsRetainsBillingDetails() { + let sut = STPPaymentCardTextField() + let params = STPPaymentMethodCardParams() + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Test test" + + sut.paymentMethodParams = STPPaymentMethodParams(card: params, billingDetails: billingDetails, metadata: nil) + let actual = sut.paymentMethodParams + + XCTAssertEqual("Test test", actual.billingDetails!.name) + } + + func testSettingMetadataRetainsMetadata() { + let sut = STPPaymentCardTextField() + let params = STPPaymentMethodCardParams() + sut.paymentMethodParams = STPPaymentMethodParams(card: params, billingDetails: nil, metadata: [ + "hello": "test", + ]) + let actual = sut.paymentMethodParams + + XCTAssertEqual([ + "hello": "test", + ], actual.metadata) + } + + func testSettingPostalCodeUpdatesCardParams() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "4242424242424242" + sut.cvcField.text = "123" + sut.expirationField.text = "10/50" + sut.postalCodeField.text = "90210" + + let params = sut.paymentMethodParams.card + XCTAssertNotNil(params) + XCTAssertEqual(params?.number, "4242424242424242") + XCTAssertEqual(params?.cvc, "123") + XCTAssertEqual(params?.expMonth?.intValue ?? 0, 10) + XCTAssertEqual(params?.expYear?.intValue ?? 0, 50) + } + + func testEmptyPostalCodeVendsNilAddress() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "4242424242424242" + sut.cvcField.text = "123" + sut.expirationField.text = "10/50" + + XCTAssertNil(sut.paymentMethodParams.billingDetails?.address?.postalCode) + let params = sut.paymentMethodParams.card + XCTAssertNotNil(params) + XCTAssertEqual(params?.number, "4242424242424242") + XCTAssertEqual(params?.cvc, "123") + XCTAssertEqual(params?.expMonth?.intValue ?? 0, 10) + XCTAssertEqual(params?.expYear?.intValue ?? 0, 50) + } + + func testAccessingCardParamsDuringSettingCardParams() { + let delegate = PaymentCardTextFieldBlockDelegate() + delegate.didChange = { textField in + // delegate reads the `cardParams` for any reason it wants + _ = textField.paymentMethodParams.card + } + let sut = STPPaymentCardTextField() + sut.delegate = delegate + + let params = STPPaymentMethodCardParams() + params.number = "4242424242424242" + params.cvc = "123" + + sut.paymentMethodParams = STPPaymentMethodParams(card: params, billingDetails: nil, metadata: nil) + let actual = sut.paymentMethodParams.card + + XCTAssertEqual("4242424242424242", actual!.number) + XCTAssertEqual("123", actual!.cvc) + } + + func testSetCardParamsCopiesObject() { + let sut = STPPaymentCardTextField() + let params = STPPaymentMethodCardParams() + + params.number = "4242424242424242" // legit + sut.paymentMethodParams = STPPaymentMethodParams(card: params, billingDetails: nil, metadata: nil) + + // fetching `sut.cardParams` returns a copy, so edits happen to caller's copy + sut.paymentMethodParams.card!.number = "number 1" + + // `sut` copied `params` (& `params.address`) when set, so edits to original don't show up + params.number = "number 2" + + XCTAssertEqual("4242424242424242", sut.paymentMethodParams.card!.number) + + XCTAssertNotEqual("number 1", sut.paymentMethodParams.card!.number, "return value from cardParams cannot be edited inline") + + XCTAssertNotEqual("number 2", sut.paymentMethodParams.card!.number, "caller changed their copy after setCardParams:") + } + + // MARK: - paymentMethodParams + + func testSetCard_numberUnknown_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "1" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.errorImage(for: .unknown)!.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + } + + func testSetCard_expiration_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_CVC_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let cvc = "123" + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberVisa_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "424242" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.placeholder, "CVC") + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberVisaInvalid_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "4242111111111111" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.errorImage(for: .visa)!.pngData() + + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + } + + func testSetCard_numberAmex_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "378282" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .amex)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.placeholder, "CVC") + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberAmexInvalid_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "378282246311111" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.errorImage(for: .amex)!.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + } + + func testSetCard_numberAndExpiration_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_partialNumberAndExpiration_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "424242" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_numberAndCVC_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "378282246310005" + let cvc = "123" + card.number = number + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .amex)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_expirationAndCVC_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let cvc = "123" + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testSetCard_completeCardCountryWithoutPostal_pm() { + let sut = STPPaymentCardTextField() + sut.countryCode = "BZ" + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertTrue(sut.isValid) + } + + func testSetCard_completeCardNoPostal_pm() { + let sut = STPPaymentCardTextField() + sut.postalCodeEntryEnabled = false + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertTrue(sut.isValid) + } + + func testSetCard_completeCard_pm() { + let sut = STPPaymentCardTextField() + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: STPPaymentMethodBillingDetails(postalCode: "90210", countryCode: "US"), metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertNil(sut.currentFirstResponderField()) + let isvalid = sut.isValid + XCTAssertTrue(isvalid) + + let paymentMethodParams = sut.paymentMethodParams + XCTAssertNotNil(paymentMethodParams) + + let sutCardParams = paymentMethodParams.card + XCTAssertNotNil(sutCardParams) + + XCTAssertEqual(sutCardParams?.number, card.number) + XCTAssertEqual(sutCardParams?.expMonth, card.expMonth) + XCTAssertEqual(sutCardParams?.expYear, card.expYear) + XCTAssertEqual(sutCardParams?.cvc, card.cvc) + + let sutBillingDetails = paymentMethodParams.billingDetails + XCTAssertNotNil(sutBillingDetails) + + let sutAddress = sutBillingDetails?.address + XCTAssertNotNil(sutAddress) + + XCTAssertEqual(sutAddress?.postalCode, "90210") + XCTAssertEqual(sutAddress?.country, "US") + } + + func testSetCard_empty_pm() { + let sut = STPPaymentCardTextField() + sut.numberField.text = "4242424242424242" + sut.cvcField.text = "123" + sut.expirationField.text = "10/50" + let card = STPPaymentMethodCardParams() + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertNil(sut.currentFirstResponderField()) + XCTAssertFalse(sut.isValid) + } + + func testUsesPreferredNetworks() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let sut = STPPaymentCardTextField() + sut.cbcEnabledOverride = true + sut.preferredNetworks = [.visa] + let card = STPPaymentMethodCardParams() + card.number = "4973019750239993" + card.expMonth = 12 + card.expYear = 43 + card.cvc = "123" + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + let exp = expectation(description: "Wait for CBC load") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + XCTAssertEqual(sut.viewModel.cbcController.selectedBrand, .visa) + exp.fulfill() + } + waitForExpectations(timeout: 3.0) + } + + func testOBOCBC() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let sut = STPPaymentCardTextField() + sut.onBehalfOf = "acct_abc123" + XCTAssertEqual(sut.viewModel.cbcController.onBehalfOf, "acct_abc123") + } + + func testFourDigitCVCNotAllowedUnknownCBCCard() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let sut = STPPaymentCardTextField() + sut.cbcEnabledOverride = true + sut.preferredNetworks = [.visa] + let card = STPPaymentMethodCardParams() + card.number = "4973019750239993" + card.expMonth = 12 + card.expYear = 43 + card.cvc = "1234" + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + sut.paymentMethodParams = params + XCTAssertFalse(sut.isValid) + } +} + +// N.B. It is eexpected for setting the card params to generate API response errors +// because we are calling to the card metadata service without configuration STPAPIClient +class STPPaymentCardTextFieldUITests: XCTestCase { + var window: UIWindow! + var sut: STPPaymentCardTextField! + + override class func setUp() { + super.setUp() + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + } + + override func setUp() { + super.setUp() + window = UIWindow(frame: UIScreen.main.bounds) + let textField = STPPaymentCardTextField(frame: window.bounds) + window?.addSubview(textField) + XCTAssertTrue(textField.numberField.canBecomeFirstResponder, "text field cannot become first responder") + sut = textField + } + + // MARK: - UI Tests + + func testSetCard_allFields_whileEditingNumber() { + XCTAssertTrue(sut.numberField.becomeFirstResponder(), "text field is not first responder") + let card = STPPaymentMethodCardParams() + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.address = STPPaymentMethodAddress() + billingDetails.address!.postalCode = "90210" + let number = "4242424242424242" + let cvc = "123" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + card.cvc = cvc + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: billingDetails, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .visa)?.pngData() + + XCTAssertNil(sut.focusedTextFieldForLayout) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text, cvc) + XCTAssertEqual(sut.postalCode, "90210") + XCTAssertFalse(sut.isFirstResponder, "after `setCardParams:`, if all fields are valid, should resign firstResponder") + XCTAssertTrue(sut.isValid) + } + + func testSetCard_partialNumberAndExpiration_whileEditingExpiration() { + XCTAssertTrue(sut.expirationField.becomeFirstResponder(), "text field is not first responder") + let card = STPPaymentMethodCardParams() + let number = "42" + card.number = number + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 50) + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.cvcImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.CVC.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text, "10/50") + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertTrue(sut.cvcField.isFirstResponder, "after `setCardParams:`, when firstResponder becomes valid, first invalid field should become firstResponder") + XCTAssertFalse(sut.isValid) + } + + func testSetCard_number_whileEditingCVC() { + XCTAssertTrue(sut.cvcField.becomeFirstResponder(), "text field is not first responder") + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.cvcImage(for: .visa)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.CVC.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData == imgData) + } + XCTAssertEqual(sut.numberField.text, number) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertTrue(sut.cvcField.isFirstResponder, "after `setCardParams:`, if firstResponder is invalid, it should remain firstResponder") + XCTAssertFalse(sut.isValid) + } + + func testSetCard_empty_whileEditingNumber() { + sut.numberField.text = "4242424242424242" + sut.cvcField.text = "123" + sut.expirationField.text = "10/50" + XCTAssertTrue(sut.numberField.becomeFirstResponder(), "text field is not first responder") + let card = STPPaymentMethodCardParams() + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + + let imgData = sut.brandImageView.image?.pngData() + let expectedImgData = STPPaymentCardTextField.brandImage(for: .unknown)?.pngData() + + XCTAssertNotNil(sut.focusedTextFieldForLayout) + XCTAssertTrue(sut.focusedTextFieldForLayout?.intValue ?? 0 == STPCardFieldType.number.rawValue) + if let imgData { + XCTAssertTrue(expectedImgData! == imgData) + } + XCTAssertEqual(sut.numberField.text!.count, Int(0)) + XCTAssertEqual(sut.expirationField.text!.count, Int(0)) + XCTAssertEqual(sut.cvcField.text!.count, Int(0)) + XCTAssertTrue(sut.numberField.isFirstResponder, "after `setCardParams:` that clears the text fields, the first invalid field should become firstResponder") + XCTAssertFalse(sut.isValid) + } + + func testBecomeFirstResponder() { + sut.postalCodeEntryEnabled = false + XCTAssertTrue(sut.canBecomeFirstResponder) + XCTAssertTrue(sut.becomeFirstResponder()) + XCTAssertTrue(sut.isFirstResponder) + + XCTAssertEqual(sut.numberField, sut.currentFirstResponderField()) + + sut.becomeFirstResponder() + XCTAssertEqual( + sut.numberField, + sut.currentFirstResponderField(), + "Repeated calls to becomeFirstResponder should not change the firstResponder") + + sut.numberField.text = """ + 4242\ + 4242\ + 4242\ + 4242 + """ + + // Don't unit test auto-advance from number field here because we don't know the cache state + + XCTAssertTrue(sut.cvcField.becomeFirstResponder()) + XCTAssertEqual( + sut.cvcField, + sut.currentFirstResponderField(), + "We don't block other fields from becoming firstResponder") + + XCTAssertTrue(sut.becomeFirstResponder()) + XCTAssertEqual( + sut.cvcField, + sut.currentFirstResponderField(), + "Calling becomeFirstResponder does not change the currentFirstResponder") + + sut.expirationField.text = "10/50" + sut.cvcField.text = "123" + + sut.resignFirstResponder() + XCTAssertTrue(sut.canBecomeFirstResponder) + XCTAssertTrue(sut.becomeFirstResponder()) + + XCTAssertEqual( + sut.cvcField, + sut.currentFirstResponderField(), + "When all fields are valid, the last one should be the preferred firstResponder") + + sut.postalCodeEntryEnabled = true + XCTAssertFalse(sut.isValid) + + sut.resignFirstResponder() + XCTAssertTrue(sut.becomeFirstResponder()) + XCTAssertEqual( + sut.postalCodeField, + sut.currentFirstResponderField(), + "When postalCodeEntryEnabled=YES, it should become firstResponder after other fields are valid") + + sut.expirationField.text = "" + sut.resignFirstResponder() + XCTAssertTrue(sut.becomeFirstResponder()) + XCTAssertEqual( + sut.expirationField, + sut.currentFirstResponderField(), + "Moves firstResponder back to expiration, because it's not valid anymore") + + sut.expirationField.text = "10/50" + sut.postalCodeField.text = "90210" + + sut.resignFirstResponder() + XCTAssertTrue(sut.becomeFirstResponder()) + XCTAssertEqual( + sut.postalCodeField, + sut.currentFirstResponderField(), + "When all fields are valid, the last one should be the preferred firstResponder") + } + + func testShouldReturnCyclesThroughFields() { + let delegate = PaymentCardTextFieldBlockDelegate() + delegate.willEndEditingForReturn = { _ in + XCTFail("Did not expect editing to end in this test") + } + sut.delegate = delegate + + sut.becomeFirstResponder() + XCTAssertTrue(sut.numberField.isFirstResponder) + + XCTAssertFalse(sut.numberField.delegate!.textFieldShouldReturn!(sut.numberField), "shouldReturn = NO") + XCTAssertTrue(sut.expirationField.isFirstResponder, "with side effect to move 1st responder to next field") + + XCTAssertFalse(sut.expirationField.delegate!.textFieldShouldReturn!(sut.expirationField), "shouldReturn = NO") + XCTAssertTrue(sut.cvcField.isFirstResponder, "with side effect to move 1st responder to next field") + + XCTAssertFalse(sut.cvcField.delegate!.textFieldShouldReturn!(sut.cvcField), "shouldReturn = NO") + XCTAssertTrue(sut.postalCodeField.isFirstResponder, "with side effect to move 1st responder to next field") + + XCTAssertFalse(sut.postalCodeField.delegate!.textFieldShouldReturn!(sut.postalCodeField), "shouldReturn = NO") + XCTAssertTrue(sut.numberField.isFirstResponder, "with side effect to move 1st responder from last field to first invalid field") + } + + func testShouldReturnCyclesThroughFieldsWithoutPostal() { + let delegate = PaymentCardTextFieldBlockDelegate() + delegate.willEndEditingForReturn = { _ in + XCTFail("Did not expect editing to end in this test") + } + sut.delegate = delegate + sut.postalCodeEntryEnabled = false + + sut.becomeFirstResponder() + XCTAssertTrue(sut.numberField.isFirstResponder) + + XCTAssertFalse(sut.numberField.delegate!.textFieldShouldReturn!(sut.numberField), "shouldReturn = NO") + + XCTAssertTrue(sut.expirationField.isFirstResponder, "with side effect to move 1st responder to next field") + + XCTAssertFalse(sut.expirationField.delegate!.textFieldShouldReturn!(sut.expirationField), "shouldReturn = NO") + XCTAssertTrue(sut.cvcField.isFirstResponder, "with side effect to move 1st responder to next field") + + XCTAssertFalse(sut.cvcField.delegate!.textFieldShouldReturn!(sut.cvcField), "shouldReturn = NO") + XCTAssertTrue(sut.numberField.isFirstResponder, "with side effect to move 1st responder from last field to first invalid field") + } + + func testShouldReturnDismissesWhenValidNoPostalCode() { + var hasReturned = false + var didEnd = false + + sut.postalCodeEntryEnabled = false + sut.paymentMethodParams = STPPaymentMethodParams(card: STPFixtures.paymentMethodCardParams(), billingDetails: nil, metadata: nil) + + let delegate = PaymentCardTextFieldBlockDelegate() + delegate.willEndEditingForReturn = { _ in + XCTAssertFalse(didEnd, "willEnd is called before didEnd") + XCTAssertFalse(hasReturned, "willEnd is only called once") + hasReturned = true + } + + delegate.didEndEditing = { _ in + XCTAssertTrue(hasReturned, "didEndEditing should be called after willEnd") + XCTAssertFalse(didEnd, "didEnd is only called once") + didEnd = true + } + + sut.delegate = delegate + sut.becomeFirstResponder() + XCTAssertTrue(sut.cvcField.isFirstResponder, "when textfield is filled out, default first responder is the last field") + + XCTAssertFalse(hasReturned, "willEndEditingForReturn delegate method should not have been called yet") + + XCTAssertFalse(sut.cvcField.delegate!.textFieldShouldReturn!(sut.cvcField), "shouldReturn = NO") + + XCTAssertNil(sut.currentFirstResponderField(), "Should have resigned first responder") + XCTAssertTrue(hasReturned, "delegate method has been invoked") + XCTAssertTrue(didEnd, "delegate method has been invoked") + } + + func testShouldReturnDismissesWhenValid() { + var hasReturned = false + var didEnd = false + + sut.paymentMethodParams = STPPaymentMethodParams(card: STPFixtures.paymentMethodCardParams(), billingDetails: nil, metadata: nil) + sut.postalCodeField.text = "90210" + let delegate = PaymentCardTextFieldBlockDelegate() + delegate.willEndEditingForReturn = { _ in + XCTAssertFalse(didEnd, "willEnd is called before didEnd") + XCTAssertFalse(hasReturned, "willEnd is only called once") + hasReturned = true + } + + delegate.didEndEditing = { _ in + XCTAssertTrue(hasReturned, "didEndEditing should be called after willEnd") + XCTAssertFalse(didEnd, "didEnd is only called once") + didEnd = true + } + + sut.delegate = delegate + sut.becomeFirstResponder() + XCTAssertTrue(sut.postalCodeField.isFirstResponder, "when textfield is filled out, default first responder is the last field") + + XCTAssertFalse(hasReturned, "willEndEditingForReturn delegate method should not have been called yet") + XCTAssertFalse(sut.postalCodeField.delegate!.textFieldShouldReturn!(sut.postalCodeField), "shouldReturn = NO") + + XCTAssertNil(sut.currentFirstResponderField(), "Should have resigned first responder") + XCTAssertTrue(hasReturned, "delegate method has been invoked") + XCTAssertTrue(didEnd, "delegate method has been invoked") + } + + func testValueUpdatesWhenDeletingOnEmptyField() { + let card = STPPaymentMethodCardParams() + let number = "4242424242424242" + card.number = number + sut.paymentMethodParams = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + var hasChanged = false + let delegate = PaymentCardTextFieldBlockDelegate() + delegate.didChange = { textField in + XCTAssertEqual(textField.numberField.text, "424242424242424") + XCTAssertEqual(textField.cardNumber, "424242424242424") + XCTAssertFalse(hasChanged, "didChange delegate method should not have been called yet") + hasChanged = true + } + + sut.delegate = delegate + sut.becomeFirstResponder() + sut.deleteBackward() + XCTAssertEqual(sut.numberField.text, "424242424242424") + XCTAssertEqual(sut.cardNumber, "424242424242424") + XCTAssertTrue(hasChanged, "delegate method has been invoked") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentCardTextFieldTestsSwift.swift b/Stripe/StripeiOSTests/STPPaymentCardTextFieldTestsSwift.swift new file mode 100644 index 00000000..c914d4dd --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentCardTextFieldTestsSwift.swift @@ -0,0 +1,67 @@ +// +// STPPaymentCardTextFieldTestsSwift.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 8/24/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentCardTextFieldTestsSwift: XCTestCase { + + func testClearMaintainsPostalCodeEntryEnabled() { + let textField = STPPaymentCardTextField() + let postalCodeEntryDefaultEnabled = textField.postalCodeEntryEnabled + textField.clear() + XCTAssertEqual( + postalCodeEntryDefaultEnabled, + textField.postalCodeEntryEnabled, + "clear overrode default postalCodeEntryEnabled value" + ) + + // -- + textField.postalCodeEntryEnabled = false + textField.clear() + XCTAssertFalse( + textField.postalCodeEntryEnabled, + "clear overrode custom postalCodeEntryEnabled false value" + ) + + // -- + textField.postalCodeEntryEnabled = true + // The ORs in this test are to handle if these tests are run in an environment + // where the locale doesn't require postal codes, in which case the calculated + // value for postalCodeEntryEnabled can be different than the value set + // (this is a legacy API). + let stillTrueOrRequestedButNoPostal = + textField.postalCodeEntryEnabled + || (textField.viewModel.postalCodeRequested + && STPPostalCodeValidator.postalCodeIsRequired( + forCountryCode: textField.viewModel.postalCodeCountryCode + )) + XCTAssertTrue( + stillTrueOrRequestedButNoPostal, + "clear overrode custom postalCodeEntryEnabled true value" + ) + + } + + func testPostalCodeIsValidWhenExpirationIsNot() { + let cardTextField = STPPaymentCardTextField() + + // Old expiration date + cardTextField.expirationField.text = "10/10" + XCTAssertFalse(cardTextField.expirationField.validText) + + cardTextField.postalCode = "10001" + cardTextField.formTextFieldTextDidChange(cardTextField.postalCodeField) + XCTAssertTrue(cardTextField.postalCodeField.validText) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentCardTextFieldViewModelTest.swift b/Stripe/StripeiOSTests/STPPaymentCardTextFieldViewModelTest.swift new file mode 100644 index 00000000..b10a63af --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentCardTextFieldViewModelTest.swift @@ -0,0 +1,112 @@ +// +// STPPaymentCardTextFieldViewModelTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 7/16/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentCardTextFieldViewModelTest: XCTestCase { + var viewModel: STPPaymentCardTextFieldViewModel? + + override func setUp() { + super.setUp() + viewModel = STPPaymentCardTextFieldViewModel(brandUpdateHandler: {}) + } + + func testCardNumber() { + let tests = [ + ["", ""], + ["4242", "4242"], + ["4242424242424242", "4242424242424242"], + ["4242 4242 4242 4242", "4242424242424242"], + ["4242xxx4242", "42424242"], + ["12345678901234567890", "1234567890123456789"], + ] + for test in tests { + viewModel?.cardNumber = test[0] + XCTAssertEqual(viewModel?.cardNumber, test[1]) + } + } + + func testRawExpiration() { + // swiftlint:disable:next large_tuple + let tests: [(String, String, String, String, STPCardValidationState)] = [ + ("", "", "", "", .incomplete), + ("12/25", "12/25", "12", "25", .valid), + ("1225", "12/25", "12", "25", .valid), + ("1", "1", "1", "", .incomplete), + ("2", "02/", "02", "", .incomplete), + ("12", "12/", "12", "", .incomplete), + ("12/2", "12/2", "12", "2", .incomplete), + ("99/23", "99", "99", "23", .invalid), + ("10/12", "10/12", "10", "12", .invalid), + ("12*25", "12/25", "12", "25", .valid), + ("12/*", "12/", "12", "", .incomplete), + ("*", "", "", "", .incomplete), + ] + for test in tests { + viewModel?.rawExpiration = test.0 + XCTAssertEqual(viewModel?.rawExpiration, test.1) + XCTAssertEqual(viewModel?.expirationMonth, test.2) + XCTAssertEqual(viewModel?.expirationYear, test.3) + XCTAssertEqual(viewModel?.validationStateForExpiration(), test.4) + } + } + + func testCVC() { + let tests = [["1", "1"], ["1234", "1234"], ["12345", "1234"], ["1x", "1"]] + for test in tests { + viewModel?.cvc = test[0] + XCTAssertEqual(viewModel?.cvc, test[1]) + } + } + + func testValidity() { + viewModel?.cardNumber = "4242424242424242" + viewModel?.rawExpiration = "12/24" + viewModel?.cvc = "123" + XCTAssertTrue(viewModel!.isValid) + + viewModel?.cvc = "12" + XCTAssertFalse(viewModel!.isValid) + } + + func testCompressedCardNumber() { + viewModel?.cardNumber = nil + // Should use default placeholder + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "4242") + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: "1234567812345678"), "5678") + + viewModel?.cardNumber = "424212345678" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "5678") + viewModel?.cardNumber = "42421234567" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "567") + viewModel?.cardNumber = "4242123456" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "56") + viewModel?.cardNumber = "424212345" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "5") + viewModel?.cardNumber = "42421234" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "1234") + + viewModel?.cardNumber = "12" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "12") + + viewModel?.cardNumber = "36227206271667" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "1667") + viewModel?.cardNumber = "3622720627166" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "166") + viewModel?.cardNumber = "36227206271" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "1") + viewModel?.cardNumber = "3622720627" + XCTAssertEqual(viewModel?.compressedCardNumber(withPlaceholder: nil), "720627") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentHandlerFunctionalTest.m b/Stripe/StripeiOSTests/STPPaymentHandlerFunctionalTest.m new file mode 100644 index 00000000..699a7ca4 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentHandlerFunctionalTest.m @@ -0,0 +1,136 @@ +// +// STPPaymentHandlerFunctionalTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/14/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import +@import Stripe; +#import +#import + +@import StripePaymentsObjcTestUtils; + +@interface STPPaymentHandlerFunctionalTest : XCTestCase +@property (nonatomic) id presentingViewController; +@property (nonatomic) id applicationMock; +@end + +@interface STPPaymentHandler (Test) +- (BOOL)_canPresentWithAuthenticationContext:(id)authenticationContext error:(NSError **)error; +@end + +@implementation STPPaymentHandlerFunctionalTest + +- (void)setUp { + self.presentingViewController = OCMClassMock([UIViewController class]); + // Mock UIApplication.shared, which is otherwise not available in XCTestCase, to always call its completion block with @NO (i.e. it couldn't open a native app with the URL) + self.applicationMock = OCMClassMock([UIApplication class]); + OCMStub([self.applicationMock sharedApplication]).andReturn(self.applicationMock); + OCMStub([self.applicationMock openURL:[OCMArg any] + options:[OCMArg any] + completionHandler:([OCMArg invokeBlockWithArgs:@NO, nil])]); + [STPAPIClient sharedClient].publishableKey = STPTestingDefaultPublishableKey; +} + +// N.B. Test mode alipay PaymentIntent's never have a native redirect so we can't test that here +- (void)testAlipayOpensWebviewAfterNativeURLUnavailable { + __block NSString *clientSecret = @"pi_123_secret_456"; + + id apiClient = OCMPartialMock(STPAPIClient.sharedClient); + NSMutableDictionary *paymentIntentJSON = [[STPTestUtils jsonNamed:@"PaymentIntent"] mutableCopy]; + paymentIntentJSON[@"payment_method"] = [STPTestUtils jsonNamed:STPTestJSONPaymentMethodCard]; + STPPaymentIntent *paymentIntent = [STPPaymentIntent decodedObjectFromAPIResponse:paymentIntentJSON]; + + OCMStub([apiClient confirmPaymentIntentWithParams:[OCMArg any] expand:[OCMArg any] completion:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + void (^handler)(STPPaymentIntent *paymentIntent, __unused NSError * _Nullable error); + [invocation getArgument:&handler atIndex:4]; + handler(paymentIntent, nil); + }); + + OCMStub([apiClient retrievePaymentIntentWithClientSecret:[OCMArg any] expand:[OCMArg any] completion:[OCMArg any]]).andDo(^(NSInvocation *invocation) { + void (^handler)(STPPaymentIntent *paymentIntent, __unused NSError * _Nullable error); + [invocation getArgument:&handler atIndex:4]; + handler(paymentIntent, nil); + }); + + id paymentHandler = OCMPartialMock(STPPaymentHandler.sharedHandler); + OCMStub([paymentHandler apiClient]).andReturn(apiClient); + + // Simulate the safari VC finishing after presenting it + OCMStub([self.presentingViewController presentViewController:[OCMArg any] animated:YES completion:[OCMArg any]]).andDo(^(__unused NSInvocation *_) { + [paymentHandler safariViewControllerDidFinish:self.presentingViewController]; + }); + + STPPaymentIntentParams *confirmParams = [[STPPaymentIntentParams alloc] initWithClientSecret:clientSecret]; + confirmParams.paymentMethodOptions = [STPConfirmPaymentMethodOptions new]; + confirmParams.paymentMethodOptions.alipayOptions = [STPConfirmAlipayOptions new]; + confirmParams.paymentMethodParams = [STPPaymentMethodParams paramsWithAlipay:[STPPaymentMethodAlipayParams new] billingDetails:nil metadata:nil]; + confirmParams.returnURL = @"foo://bar"; + + XCTestExpectation *e = [self expectationWithDescription:@""]; + [paymentHandler confirmPayment:confirmParams withAuthenticationContext:self completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * __unused _, __unused NSError * _Nullable error) { + // ...shouldn't attempt to open the native URL (ie the alipay app) + OCMReject([self.applicationMock openURL:[OCMArg any] + options:[OCMArg any] + completionHandler:[OCMArg isNotNil]]); + // ...and then open UIViewController + OCMVerify([self.presentingViewController presentViewController:[OCMArg any] animated:YES completion:[OCMArg any]]); + + // ...and since we didn't actually authenticate, the final state is canceled + XCTAssertEqual(status, STPPaymentHandlerActionStatusCanceled); + [e fulfill]; + }]; + [self waitForExpectationsWithTimeout:4 handler:nil]; + [paymentHandler stopMocking]; // paymentHandler is a singleton, so we need to manually call `stopMocking` +} + +- (void)test_oxxo_payment_intent_server_side_confirmation { + // OXXO is interesting b/c the PI status after handling next actions is requires_action, not succeeded. + id paymentHandler = OCMPartialMock(STPPaymentHandler.sharedHandler); + + // Simulate the safari VC finishing after presenting it + OCMStub([self.presentingViewController presentViewController:[OCMArg any] animated:YES completion:[OCMArg any]]).andDo(^(__unused NSInvocation *_) { + [paymentHandler safariViewControllerDidFinish:self.presentingViewController]; + }); + + STPAPIClient *apiClient = [[STPAPIClient alloc] initWithPublishableKey: STPTestingMEXPublishableKey]; + [STPAPIClient sharedClient].publishableKey = STPTestingMEXPublishableKey; + + STPPaymentMethodBillingDetails *billingDetails = [STPPaymentMethodBillingDetails new]; + billingDetails.name = @"Test Customer"; + billingDetails.email = @"test@example.com"; + + XCTestExpectation *e = [self expectationWithDescription:@""]; + STPPaymentMethodParams *params = [[STPPaymentMethodParams alloc] initWithOxxo:[STPPaymentMethodOXXOParams new] billingDetails:billingDetails metadata:nil]; + [apiClient createPaymentMethodWithParams:params completion:^(STPPaymentMethod * paymentMethod, NSError * error) { + XCTAssertNil(error); + NSDictionary *pi_params = @{ + @"confirm": @"true", + @"payment_method_types": @[@"oxxo"], + @"currency": @"mxn", + @"amount": @1099, + @"payment_method": paymentMethod.stripeId, + @"return_url": @"foo://z" + }; + [[STPTestingAPIClient new] createPaymentIntentWithParams:pi_params account:@"mex" apiVersion:nil completion:^(NSString * clientSecret, NSError * error2) { + XCTAssertNil(error2); + [paymentHandler handleNextActionForPayment:clientSecret withAuthenticationContext:self returnURL:@"foo://z" completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * error3) { + XCTAssertNil(error3); + XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusRequiresAction); + XCTAssertEqual(status, STPPaymentHandlerActionStatusSucceeded); + [e fulfill]; + }]; + }]; + }]; + [self waitForExpectationsWithTimeout:4 handler:nil]; + [paymentHandler stopMocking]; // paymentHandler is a singleton, so we need to manually call `stopMocking` +} + +- (UIViewController *)authenticationPresentingViewController { + return self.presentingViewController; +} + +@end diff --git a/Stripe/StripeiOSTests/STPPaymentHandlerFunctionalTest.swift b/Stripe/StripeiOSTests/STPPaymentHandlerFunctionalTest.swift new file mode 100644 index 00000000..92956ac8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentHandlerFunctionalTest.swift @@ -0,0 +1,413 @@ +// +// STPPaymentHandlerFunctionalTest.swift +// StripeiOSTests +// +// Created by Yuki Tokuhiro on 4/24/23. +// + +@testable import Stripe +@_spi(STP) @testable import StripeCore +@_spi(STP) @testable import StripePayments +@_spi(STP) @testable import StripePaymentsTestUtils +import XCTest + +// You can add tests in here for payment methods that don't require customer actions (i.e. don't open webviews for customer authentication). +// If they require customer action, use STPPaymentHandlerFunctionalTest.m instead +final class STPPaymentHandlerFunctionalSwiftTest: STPNetworkStubbingTestCase, STPAuthenticationContext { + // MARK: - STPAuthenticationContext + func authenticationPresentingViewController() -> UIViewController { + return UIViewController() + } + + // MARK: - PaymentIntent tests + + func test_card_payment_intent_server_side_confirmation() { + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let e = self.expectation(description: "") + apiClient.createPaymentMethod(with: ._testValidCardValue()) { paymentMethod, error in + guard let paymentMethod = paymentMethod else { + XCTFail(String(describing: error)) + return + } + STPTestingAPIClient.shared().createPaymentIntent(withParams: [ + "confirm": "true", + "payment_method_types": ["card"], + "currency": "usd", + "payment_method": paymentMethod.stripeId, + "return_url": "foo://z", + ]) { clientSecret, error in + guard let clientSecret = clientSecret else { + XCTFail(String(describing: error)) + return + } + let sut = STPPaymentHandler(apiClient: apiClient) + // Note: `waitForExpectations` can deadlock if this test is async. When we can use Xcode 14.3, we can switch this test to async and use fulfillment(of:) instead of waitForExpectations + sut.handleNextAction(forPayment: clientSecret, with: self, returnURL: "foo://z") { status, intent, _ in + XCTAssertEqual(sut.apiClient, apiClient) // Reference sut in the closure so it doesn't get deallocated + XCTAssertEqual(intent?.status, .succeeded) + XCTAssertEqual(status, .succeeded) + e.fulfill() + } + } + } + self.waitForExpectations(timeout: 10) + } + + func test_sepa_debit_payment_intent_server_side_confirmation() { + // SEPA Debit is a good payment method to test here because + // - it's a "delayed" or "asynchronous" payment method + // - it doesn't require customer actions (we can't simulate customer actions in XCTestCase) + + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "SEPA Test Customer" + billingDetails.email = "test@example.com" + + let sepaDebitDetails = STPPaymentMethodSEPADebitParams() + sepaDebitDetails.iban = "DE89370400440532013000" + + let e = self.expectation(description: "") + apiClient.createPaymentMethod(with: .init(sepaDebit: sepaDebitDetails, billingDetails: billingDetails, metadata: nil)) { paymentMethod, error in + guard let paymentMethod = paymentMethod else { + XCTFail(String(describing: error)) + return + } + STPTestingAPIClient.shared().createPaymentIntent(withParams: [ + "confirm": "true", + "payment_method_types": ["sepa_debit"], + "currency": "eur", + "payment_method": paymentMethod.stripeId, + "return_url": "foo://z", + "mandate_data": [ + "customer_acceptance": [ + "type": "online", + "online": [ + "user_agent": "123", + "ip_address": "172.18.117.125", + ], + ], + ], + ]) { clientSecret, error in + guard let clientSecret = clientSecret else { + XCTFail(String(describing: error)) + return + } + let sut = STPPaymentHandler(apiClient: apiClient) + // Note: `waitForExpectations` can deadlock if this test is async. When we can use Xcode 14.3, we can switch this test to async and use fulfillment(of:) instead of waitForExpectations + sut.handleNextAction(forPayment: clientSecret, with: self, returnURL: "foo://z") { status, intent, _ in + XCTAssertEqual(sut.apiClient, apiClient) // Reference sut in the closure so it doesn't get deallocated + XCTAssertEqual(intent?.status, .processing) + XCTAssertEqual(status, .succeeded) + e.fulfill() + } + } + } + self.waitForExpectations(timeout: 10) + } + + // MARK: - SetupIntent tests + + func test_card_setup_intent_server_side_confirmation() { + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let e = self.expectation(description: "") + apiClient.createPaymentMethod(with: ._testValidCardValue()) { paymentMethod, error in + guard let paymentMethod = paymentMethod else { + XCTFail(String(describing: error)) + return + } + STPTestingAPIClient.shared().createSetupIntent(withParams: [ + "confirm": "true", + "payment_method_types": ["card"], + "payment_method": paymentMethod.stripeId, + "return_url": "foo://z", + ]) { clientSecret, error in + guard let clientSecret = clientSecret else { + XCTFail(String(describing: error)) + return + } + let sut = STPPaymentHandler(apiClient: apiClient) + // Note: `waitForExpectations` can deadlock if this test is async. When we can use Xcode 14.3, we can switch this test to async and use fulfillment(of:) instead of waitForExpectations + sut.handleNextAction(forSetupIntent: clientSecret, with: self, returnURL: "foo://z") { status, intent, _ in + XCTAssertEqual(sut.apiClient, apiClient) // Reference sut in the closure so it doesn't get deallocated + XCTAssertEqual(intent?.status, .succeeded) + XCTAssertEqual(status, .succeeded) + e.fulfill() + } + } + } + self.waitForExpectations(timeout: 10) + } + + func test_sepa_debit_setup_intent_server_side_confirmation() { + // SEPA Debit is a good payment method to test here because + // - it's a "delayed" or "asynchronous" payment method + // - it doesn't require customer actions (we can't simulate customer actions in XCTestCase) + + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "SEPA Test Customer" + billingDetails.email = "test@example.com" + + let sepaDebitDetails = STPPaymentMethodSEPADebitParams() + sepaDebitDetails.iban = "DE89370400440532013000" + + let e = self.expectation(description: "") + apiClient.createPaymentMethod(with: .init(sepaDebit: sepaDebitDetails, billingDetails: billingDetails, metadata: nil)) { paymentMethod, error in + guard let paymentMethod = paymentMethod else { + XCTFail() + return + } + STPTestingAPIClient.shared().createSetupIntent(withParams: [ + "confirm": "true", + "payment_method_types": ["sepa_debit"], + "payment_method": paymentMethod.stripeId, + "return_url": "foo://z", + "mandate_data": [ + "customer_acceptance": [ + "type": "online", + "online": [ + "user_agent": "123", + "ip_address": "172.18.117.125", + ], + ], + ], + ]) { clientSecret, error in + guard let clientSecret = clientSecret else { + XCTFail("\(String(describing: error))") + return + } + let sut = STPPaymentHandler(apiClient: apiClient) + // Note: `waitForExpectations` can deadlock if this test is async. When we can use Xcode 14.3, we can switch this test to async and use fulfillment(of:) instead of waitForExpectations + sut.handleNextAction(forSetupIntent: clientSecret, with: self, returnURL: "foo://z") { status, intent, _ in + XCTAssertEqual(sut.apiClient, apiClient) // Reference sut in the closure so it doesn't get deallocated + XCTAssertEqual(intent?.status, .succeeded) // Note: I think this should be .processing, but testmode disagrees + XCTAssertEqual(status, .succeeded) + e.fulfill() + } + } + } + self.waitForExpectations(timeout: 10) + } + + // MARK: - Test payment handler sends analytics + + func test_confirm_payment_intent_sends_analytic() { + // Confirming a hardcoded already-confirmed PI with invalid params... + let paymentIntentParams = STPPaymentIntentParams(clientSecret: "pi_3P20wFFY0qyl6XeW0dSOQ6W7_secret_9V8GkrCOt1MEW8SBmAaGnmT6A", paymentMethodType: .card) + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.confirmPayment(paymentIntentParams, with: self) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "pi_3P20wFFY0qyl6XeW0dSOQ6W7") + XCTAssertEqual(firstAnalytic?["payment_method_type"] as? String, "card") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmFinished.rawValue) + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "pi_3P20wFFY0qyl6XeW0dSOQ6W7") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["payment_method_type"] as? String, "card") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "invalid_request_error") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "payment_intent_unexpected_state") + XCTAssertTrue((lastAnalytic?["request_id"] as? String)!.starts(with: "req_")) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_confirm_payment_intent_savedpm_sends_analytic() { + // Confirming a hardcoded already-confirmed PI with invalid params... + let paymentIntentParams = STPPaymentIntentParams(clientSecret: "pi_3P20wFFY0qyl6XeW0dSOQ6W7_secret_9V8GkrCOt1MEW8SBmAaGnmT6A") + paymentIntentParams.paymentMethodId = "pm_123" + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.confirmPayment(paymentIntentParams, with: self) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "pi_3P20wFFY0qyl6XeW0dSOQ6W7") + XCTAssertEqual(firstAnalytic?["payment_method_id"] as? String, "pm_123") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmFinished.rawValue) + XCTAssertEqual(lastAnalytic?["payment_method_id"] as? String, "pm_123") + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "pi_3P20wFFY0qyl6XeW0dSOQ6W7") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "invalid_request_error") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "resource_missing") + XCTAssertTrue((lastAnalytic?["request_id"] as? String)!.starts(with: "req_")) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_confirm_setup_intent_sends_analytic() { + let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: "seti_1P1xLBFY0qyl6XeWc7c2LrMK_secret_PrgithiYFFPH0NVGP1BK7Oy9OU3mrDT", paymentMethodType: .card) + // Confirming a hardcoded already-confirmed SI with invalid params... + setupIntentParams.paymentMethodParams = STPPaymentMethodParams(type: .card) + + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.confirmSetupIntent(setupIntentParams, with: self) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "seti_1P1xLBFY0qyl6XeWc7c2LrMK") + XCTAssertEqual(firstAnalytic?["payment_method_type"] as? String, "card") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmFinished.rawValue) + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "seti_1P1xLBFY0qyl6XeWc7c2LrMK") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["payment_method_type"] as? String, "card") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "invalid_request_error") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "parameter_missing") + XCTAssertTrue((lastAnalytic?["request_id"] as? String)!.starts(with: "req_")) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_confirm_setup_intent_savedpm_sends_analytic() { + let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: "seti_1P1xLBFY0qyl6XeWc7c2LrMK_secret_PrgithiYFFPH0NVGP1BK7Oy9OU3mrDT") + // Confirming a hardcoded already-confirmed SI with invalid params... + setupIntentParams.paymentMethodID = "pm_123" + + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.confirmSetupIntent(setupIntentParams, with: self) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "seti_1P1xLBFY0qyl6XeWc7c2LrMK") + XCTAssertEqual(firstAnalytic?["payment_method_id"] as? String, "pm_123") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmFinished.rawValue) + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "seti_1P1xLBFY0qyl6XeWc7c2LrMK") + XCTAssertEqual(lastAnalytic?["payment_method_id"] as? String, "pm_123") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "invalid_request_error") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "resource_missing") + XCTAssertTrue((lastAnalytic?["request_id"] as? String)!.starts(with: "req_")) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_handle_next_action_payment_intent_sends_analytic() { + // Calling handleNextAction(forPayment:) with an invalid PI client secret... + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.handleNextAction(forPayment: "pi_3P232pFY0qyl6XeW0FFRtE0A_secret_foo", with: self, returnURL: nil) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "pi_3P232pFY0qyl6XeW0FFRtE0A") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionFinished.rawValue) + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "pi_3P232pFY0qyl6XeW0FFRtE0A") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "invalid_request_error") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "payment_intent_invalid_parameter") + XCTAssertTrue((lastAnalytic?["request_id"] as? String)!.starts(with: "req_")) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_handle_next_action_2_payment_intent_sends_analytic() { + // Calling handleNextAction(for:) with a STPPaymentIntent w/ an unknown next action... + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentIntent = STPFixtures.paymentIntent(paymentMethodTypes: ["card"], status: .requiresAction, paymentMethod: STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard), nextAction: .unknown) + + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.handleNextAction(for: paymentIntent, with: self, returnURL: nil) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "123") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionFinished.rawValue) + XCTAssertEqual(lastAnalytic?["payment_method_id"] as? String, "pm_123456789") + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "123") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "STPPaymentHandlerErrorDomain") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "unsupportedAuthenticationErrorCode") + XCTAssertEqual(lastAnalytic?["error_details"] as? [String: String], [ + "NSLocalizedDescription": "There was an unexpected error -- try again in a few seconds", + "com.stripe.lib:ErrorMessageKey": "Unknown authentication action type", + ]) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_handle_next_action_setup_intent_sends_analytic() { + // Calling handleNextAction(forSetupIntent:) with an invalid SI client secret... + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.handleNextAction(forSetupIntent: "seti_3P232pFY0qyl6XeW0FFRtE0A_secret_foo", with: self, returnURL: nil) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "seti_3P232pFY0qyl6XeW0FFRtE0A") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionFinished.rawValue) + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "seti_3P232pFY0qyl6XeW0FFRtE0A") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "invalid_request_error") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "resource_missing") + XCTAssertTrue((lastAnalytic?["request_id"] as? String)!.starts(with: "req_")) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } + + func test_handle_next_action_2_setup_intent_sends_analytic() { + // Calling handleNextAction(for:) with a STPSetupIntent w/ an unknown next action... + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + var siJSON = STPTestUtils.jsonNamed("SetupIntent")! + siJSON[jsonDict: "next_action"]!["type"] = "foo" + siJSON["payment_method"] = STPTestUtils.jsonNamed("CardPaymentMethod")! + let setupIntent = STPSetupIntent.decodedObject(fromAPIResponse: siJSON)! + + let paymentHandler = STPPaymentHandler(apiClient: STPAPIClient(publishableKey: STPTestingDefaultPublishableKey)) + let analyticsClient = STPAnalyticsClient() + paymentHandler.analyticsClient = analyticsClient + paymentHandler.handleNextAction(for: setupIntent, with: self, returnURL: nil) { (_, _, _) in + // ...should send these analytics + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, "seti_123456789") + XCTAssertEqual(firstAnalytic?["payment_method_id"] as? String, "pm_123456789") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerHandleNextActionFinished.rawValue) + XCTAssertEqual(lastAnalytic?["payment_method_id"] as? String, "pm_123456789") + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, "seti_123456789") + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "STPPaymentHandlerErrorDomain") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "unsupportedAuthenticationErrorCode") + XCTAssertEqual(lastAnalytic?["error_details"] as? [String: String], [ + "NSLocalizedDescription": "There was an unexpected error -- try again in a few seconds", + "com.stripe.lib:ErrorMessageKey": "Unknown authentication action type", + ]) + paymentHandlerExpectation.fulfill() + } + waitForExpectations(timeout: 10) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentHandlerRefreshTests.swift b/Stripe/StripeiOSTests/STPPaymentHandlerRefreshTests.swift new file mode 100644 index 00000000..b5fcd7b2 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentHandlerRefreshTests.swift @@ -0,0 +1,151 @@ +// +// STPPaymentHandlerRefreshTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 5/13/24. +// + +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import Stripe3DS2 +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentHandlerRefreshTests: XCTestCase { + + func testPaymentIntentShouldHitRefreshEndpoint() { + let shouldRefresh: [STPPaymentMethodType] = [.cashApp] + + for paymentMethodType in STPPaymentMethodType.allCases { + let paymentMethodDict: [AnyHashable: Any] = [ + "id": "pm_test", + "type": paymentMethodType.identifier, + ] + let paymentIntent = STPFixtures.paymentIntent(paymentMethodTypes: [paymentMethodType.identifier], + status: .requiresAction, + paymentMethod: paymentMethodDict, + nextAction: .useStripeSDK) + + let apiClientMock = STPAPIClientMock() + let currentAction = STPPaymentHandlerPaymentIntentActionParams.makeTestable(apiClient: apiClientMock, + paymentMethodTypes: [paymentMethodType.identifier], + paymentIntent: paymentIntent) + + let paymentHandler = STPPaymentHandler(apiClient: apiClientMock) + paymentHandler._retrieveAndCheckIntentForCurrentAction(currentAction: currentAction) + + let requiresRefresh = shouldRefresh.contains(paymentMethodType) + XCTAssertEqual(apiClientMock.refreshPaymentIntentCalled, requiresRefresh, + "\(paymentMethodType.displayName) should \(requiresRefresh ? "" : "not ")hit the refresh endpoint when using a PaymentIntent") + XCTAssertEqual(apiClientMock.retrievePaymentIntentCalled, !requiresRefresh, + "\(paymentMethodType.displayName) should \(requiresRefresh ? "" : "not ")hit the retrieve endpoint when using a PaymentIntent") + } + } + + func testSetupIntentShouldHitRefreshEndpoint() { + let shouldRefresh: [STPPaymentMethodType] = [.cashApp] + + for paymentMethodType in STPPaymentMethodType.allCases { + let paymentMethodDict: [AnyHashable: Any] = [ + "id": "pm_test", + "type": paymentMethodType.identifier, + ] + + let setupIntent = STPFixtures.setupIntent(paymentMethodTypes: [paymentMethodType.identifier], + status: .requiresAction, + paymentMethod: paymentMethodDict, + nextAction: .useStripeSDK) + + let apiClientMock = STPAPIClientMock() + let currentAction = STPPaymentHandlerSetupIntentActionParams.makeTestable(apiClient: apiClientMock, + paymentMethodTypes: [paymentMethodType.identifier], + setupIntent: setupIntent) + + let paymentHandler = STPPaymentHandler(apiClient: apiClientMock) + paymentHandler._retrieveAndCheckIntentForCurrentAction(currentAction: currentAction) + + let requiresRefresh = shouldRefresh.contains(paymentMethodType) + XCTAssertEqual(apiClientMock.refreshSetupIntentCalled, requiresRefresh, + "\(paymentMethodType.displayName) should \(requiresRefresh ? "" : "not ")hit the refresh endpoint when using a SetupIntent") + XCTAssertEqual(apiClientMock.retrieveSetupIntentCalled, !requiresRefresh, + "\(paymentMethodType.displayName) should \(requiresRefresh ? "" : "not ")hit the retrieve endpoint when using a SetupIntent") + } + } +} + +// MARK: - Mocks and helpers + +class STPAPIClientMock: STPAPIClient { + var refreshPaymentIntentCalled = false + var refreshSetupIntentCalled = false + var retrievePaymentIntentCalled = false + var retrieveSetupIntentCalled = false + + override func refreshPaymentIntent(withClientSecret secret: String, completion: @escaping STPPaymentIntentCompletionBlock) { + refreshPaymentIntentCalled = true + } + + override func refreshSetupIntent(withClientSecret secret: String, completion: @escaping STPSetupIntentCompletionBlock) { + refreshSetupIntentCalled = true + } + + override func retrievePaymentIntent( + withClientSecret secret: String, + expand: [String]?, + completion: @escaping STPPaymentIntentCompletionBlock + ) { + retrievePaymentIntentCalled = true + } + + override func retrieveSetupIntent( + withClientSecret secret: String, + expand: [String]?, + completion: @escaping STPSetupIntentCompletionBlock + ) { + retrieveSetupIntentCalled = true + } +} + +extension STPPaymentHandlerPaymentIntentActionParams { + static func makeTestable(apiClient: STPAPIClient, + paymentMethodTypes: [String], + paymentIntent: STPPaymentIntent) -> STPPaymentHandlerPaymentIntentActionParams { + + return .init(apiClient: apiClient, + authenticationContext: STPAuthenticationContextMock(), + threeDSCustomizationSettings: .init(), + paymentIntent: paymentIntent, + returnURL: nil) { _, _, _ in + // no-op + } + } +} + +extension STPPaymentHandlerSetupIntentActionParams { + static func makeTestable(apiClient: STPAPIClient, + paymentMethodTypes: [String], + setupIntent: STPSetupIntent) -> STPPaymentHandlerSetupIntentActionParams { + + return .init(apiClient: apiClient, + authenticationContext: STPAuthenticationContextMock(), + threeDSCustomizationSettings: .init(), + setupIntent: setupIntent, + returnURL: nil) { _, _, _ in + // no-op + } + } +} + +class STPAuthenticationContextMock: NSObject, STPAuthenticationContext { + func authenticationPresentingViewController() -> UIViewController { + return UIViewController() + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentHandlerStubbedMockedFilesTests.swift b/Stripe/StripeiOSTests/STPPaymentHandlerStubbedMockedFilesTests.swift new file mode 100644 index 00000000..e8577932 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentHandlerStubbedMockedFilesTests.swift @@ -0,0 +1,453 @@ +// +// STPPaymentHandlerStubbedMockedFilesTests.swift +// StripeiOS Tests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentHandlerStubbedMockedFilesTests: APIStubbedTestCase, STPAuthenticationContext { + func testCallConfirmAfterpay_Redirect_thenCanceled() { + let nextActionData = """ + { + "redirect_to_url": { + "return_url": "payments-example://stripe-redirect", + "url": "https://hooks.stripe.com/afterpay_clearpay/acct_123/pa_nonce_321/redirect" + }, + "type": "redirect_to_url" + } + """ + let paymentMethod = """ + { + "id": "pm_123123123123123", + "object": "payment_method", + "afterpay_clearpay": {}, + "billing_details": { + "address": { + "city": "San Francisco", + "country": "AT", + "line1": "510 Townsend St.", + "line2": "", + "postal_code": "94102", + "state": null + }, + "email": "foo@bar.com", + "name": "Jane Doe", + "phone": null + }, + "created": 1658187899, + "customer": null, + "livemode": false, + "type": "afterpay_clearpay" + } + """ + let paymentHandler = stubbedPaymentHandler(formSpecProvider: formSpecProvider()) + stubConfirm( + fileMock: .paymentIntentResponse, + responseCallback: { data in + self.replaceData( + data: data, + variables: [ + "": nextActionData, + "": paymentMethod, + "": "\"requires_action\"", + ] + ) + } + ) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: "pi_123456_secret_654321") + paymentIntentParams.returnURL = "payments-example://stripe-redirect" + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + afterpayClearpay: STPPaymentMethodAfterpayClearpayParams(), + billingDetails: STPPaymentMethodBillingDetails(), + metadata: nil + ) + paymentIntentParams.paymentMethodParams?.afterpayClearpay = + STPPaymentMethodAfterpayClearpayParams() + let didRedirect = expectation(description: "didRedirect") + paymentHandler._redirectShim = { redirectTo, returnToURL, isStandardRedirect in + XCTAssertEqual( + redirectTo.absoluteString, + "https://hooks.stripe.com/afterpay_clearpay/acct_123/pa_nonce_321/redirect" + ) + XCTAssertEqual(returnToURL?.absoluteString, "payments-example://stripe-redirect") + XCTAssert(isStandardRedirect) + didRedirect.fulfill() + } + let expectConfirmWasCanceled = expectation(description: "didCancel") + paymentHandler.confirmPayment(paymentIntentParams, with: self) { + status, + _, + _ in + if case .canceled = status { + expectConfirmWasCanceled.fulfill() + } + } + guard XCTWaiter.wait(for: [didRedirect], timeout: 2.0) != .timedOut else { + XCTFail("Unable to redirect") + return + } + + // Test the cancel case + stubRetrievePaymentIntent( + fileMock: .paymentIntentResponse, + responseCallback: { data in + self.replaceData( + data: data, + variables: [ + "": nextActionData, + "": paymentMethod, + "": "\"requires_action\"", + ] + ) + } + ) + paymentHandler._retrieveAndCheckIntentForCurrentAction() + wait(for: [expectConfirmWasCanceled], timeout: 2.0) + } + + func testCallConfirmAfterpay_Redirect_thenSucceeded() { + let nextActionData = """ + { + "redirect_to_url": { + "return_url": "payments-example://stripe-redirect", + "url": "https://hooks.stripe.com/afterpay_clearpay/acct_123/pa_nonce_321/redirect" + }, + "type": "redirect_to_url" + } + """ + let paymentMethodData = """ + { + "id": "pm_123123123123123", + "object": "payment_method", + "afterpay_clearpay": {}, + "billing_details": { + "address": { + "city": "San Francisco", + "country": "AT", + "line1": "510 Townsend St.", + "line2": "", + "postal_code": "94102", + "state": null + }, + "email": "foo@bar.com", + "name": "Jane Doe", + "phone": null + }, + "created": 1658187899, + "customer": null, + "livemode": false, + "type": "afterpay_clearpay" + } + """ + let paymentHandler = stubbedPaymentHandler(formSpecProvider: formSpecProvider()) + stubConfirm( + fileMock: .paymentIntentResponse, + responseCallback: { data in + self.replaceData( + data: data, + variables: [ + "": nextActionData, + "": paymentMethodData, + "": "\"requires_action\"", + ] + ) + } + ) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: "pi_123456_secret_654321") + paymentIntentParams.returnURL = "payments-example://stripe-redirect" + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + afterpayClearpay: STPPaymentMethodAfterpayClearpayParams(), + billingDetails: STPPaymentMethodBillingDetails(), + metadata: nil + ) + paymentIntentParams.paymentMethodParams?.afterpayClearpay = + STPPaymentMethodAfterpayClearpayParams() + let didRedirect = expectation(description: "didRedirect") + paymentHandler._redirectShim = { redirectTo, returnToURL, isStandardRedirect in + XCTAssertEqual( + redirectTo.absoluteString, + "https://hooks.stripe.com/afterpay_clearpay/acct_123/pa_nonce_321/redirect" + ) + XCTAssertEqual(returnToURL?.absoluteString, "payments-example://stripe-redirect") + XCTAssert(isStandardRedirect) + didRedirect.fulfill() + } + confirmPaymentWithSucceed(nextActionData: nextActionData, + paymentMethodData: paymentMethodData, + didRedirect: didRedirect, + paymentHandler: paymentHandler, + paymentIntentParams: paymentIntentParams) + } + + func testCallConfirmAfterpay_Redirect() { + let formSpecProvider = formSpecProvider() + let paymentHandler = stubbedPaymentHandler(formSpecProvider: formSpecProvider) + + // Override it with a spec that doesn't define a next action so that we force the SDK to default behavior + let updatedSpecJson = + """ + [{ + "type": "affirm", + "async": false, + "fields": [ + { + "type": "name" + } + ] + }] + """.data(using: .utf8)! + let formSpec = try! JSONSerialization.jsonObject(with: updatedSpecJson) as! [NSDictionary] + XCTAssert(formSpecProvider.loadFrom(formSpec)) + guard formSpecProvider.formSpec(for: "affirm") != nil else { + XCTFail() + return + } + + let nextActionData = """ + { + "redirect_to_url": { + "return_url": "payments-example://stripe-redirect", + "url": "https://hooks.stripe.com/affirm/acct_123/pa_nonce_321/redirect" + }, + "type": "redirect_to_url" + } + """ + let paymentMethodData = """ + { + "id": "pm_123123123123123", + "object": "payment_method", + "afterpay_clearpay": {}, + "billing_details": { + "address": { + "city": "San Francisco", + "country": "AT", + "line1": "510 Townsend St.", + "line2": "", + "postal_code": "94102", + "state": null + }, + "email": "foo@bar.com", + "name": "Jane Doe", + "phone": null + }, + "created": 1658187899, + "customer": null, + "livemode": false, + "type": "afterpay_clearpay" + } + """ + stubConfirm( + fileMock: .paymentIntentResponse, + responseCallback: { data in + self.replaceData( + data: data, + variables: [ + "": nextActionData, + "": paymentMethodData, + "": "\"requires_action\"", + ] + ) + } + ) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: "pi_123456_secret_654321") + paymentIntentParams.returnURL = "payments-example://stripe-redirect" + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + afterpayClearpay: STPPaymentMethodAfterpayClearpayParams(), + billingDetails: STPPaymentMethodBillingDetails(), + metadata: nil + ) + paymentIntentParams.paymentMethodParams?.afterpayClearpay = + STPPaymentMethodAfterpayClearpayParams() + let didRedirect = expectation(description: "didRedirect") + paymentHandler._redirectShim = { redirectTo, returnToURL, isStandardRedirect in + XCTAssertEqual( + redirectTo.absoluteString, + "https://hooks.stripe.com/affirm/acct_123/pa_nonce_321/redirect" + ) + XCTAssertEqual(returnToURL?.absoluteString, "payments-example://stripe-redirect") + XCTAssert(isStandardRedirect) + didRedirect.fulfill() + } + confirmPaymentWithSucceed(nextActionData: nextActionData, + paymentMethodData: paymentMethodData, + didRedirect: didRedirect, + paymentHandler: paymentHandler, + paymentIntentParams: paymentIntentParams) + } + + func testCallConfirmBlikSucceeds() { + let nextActionData = """ + { + "type": "blik_authorize" + } + """ + let paymentMethodData = """ + { + "id": "pm_123123123123123", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "blik": {}, + "created": 1658187899, + "customer": null, + "livemode": false, + "type": "blik" + } + """ + let paymentHandler = stubbedPaymentHandler(formSpecProvider: formSpecProvider()) + let paymentIntentParams = STPPaymentIntentParams(clientSecret: "pi_123456_secret_654321") + paymentIntentParams.returnURL = "payments-example://stripe-redirect" + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + blik: STPPaymentMethodBLIKParams(), + billingDetails: nil, + metadata: nil + ) + + stubRetrievePaymentIntent( + fileMock: .paymentIntentResponse, + responseCallback: { data in + self.replaceData( + data: data, + variables: [ + "": nextActionData, + "": paymentMethodData, + "": "\"requires_action\"", + ] + ) + } + ) + + let expectConfirmSucceeded = expectation(description: "didSucceed") + paymentHandler.confirmPayment( + paymentIntentParams, + with: self) { status, _, _ in + if case .succeeded = status { + expectConfirmSucceeded.fulfill() + } + } + waitForExpectations(timeout: 2.0) + } + + private func confirmPaymentWithSucceed( + nextActionData: String, + paymentMethodData: String, + didRedirect: XCTestExpectation, + paymentHandler: STPPaymentHandler, + paymentIntentParams: STPPaymentIntentParams + ) { + let expectConfirmSucceeded = expectation(description: "didSucceed") + paymentHandler.confirmPayment(paymentIntentParams, with: self) { + status, + _, + _ in + if case .succeeded = status { + expectConfirmSucceeded.fulfill() + } + } + + guard XCTWaiter.wait(for: [didRedirect], timeout: 2.0) != .timedOut else { + XCTFail("Unable to redirect") + return + } + + // Test status as succeeded + stubRetrievePaymentIntent( + fileMock: .paymentIntentResponse, + responseCallback: { data in + self.replaceData( + data: data, + variables: [ + "": nextActionData, + "": paymentMethodData, + "": "\"succeeded\"", + ] + ) + } + ) + paymentHandler._retrieveAndCheckIntentForCurrentAction() + wait(for: [expectConfirmSucceeded], timeout: 2.0) + } + + private func formSpecProvider() -> FormSpecProvider { + let expectation = expectation(description: "Load Specs") + let formSpecProvider = FormSpecProvider() + formSpecProvider.load { _ in + expectation.fulfill() + } + wait(for: [expectation], timeout: 5.0) + return formSpecProvider + } + + private func stubbedPaymentHandler(formSpecProvider: FormSpecProvider) -> STPPaymentHandler { + return STPPaymentHandler(apiClient: stubbedAPIClient()) + } + + private func replaceData(data: Data, variables: [String: String]) -> Data { + var template = String(data: data, encoding: .utf8)! + for (templateKey, templateValue) in variables { + let translated = template.replacingOccurrences(of: templateKey, with: templateValue) + template = translated + } + return template.data(using: .utf8)! + } + + private func stubConfirm(fileMock: FileMock, responseCallback: ((Data) -> Data)? = nil) { + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/confirm") ?? false + } response: { _ in + let mockResponseData = try! fileMock.data() + let data = responseCallback?(mockResponseData) ?? mockResponseData + return HTTPStubsResponse(data: data, statusCode: 200, headers: nil) + } + } + private func stubRetrievePaymentIntent( + fileMock: FileMock, + responseCallback: ((Data) -> Data)? = nil + ) { + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/payment_intents") ?? false + } response: { _ in + let mockResponseData = try! fileMock.data() + let data = responseCallback?(mockResponseData) ?? mockResponseData + return HTTPStubsResponse(data: data, statusCode: 200, headers: nil) + } + } +} +extension STPPaymentHandlerStubbedMockedFilesTests { + func authenticationPresentingViewController() -> UIViewController { + return UIViewController() + } +} + +public class ClassForBundle {} +@_spi(STP) public enum FileMock: String, MockData { + public typealias ResponseType = StripeFile + public var bundle: Bundle { return Bundle(for: ClassForBundle.self) } + + case paymentIntentResponse = "MockFiles/paymentIntentResponse" +} diff --git a/Stripe/StripeiOSTests/STPPaymentHandlerTests.swift b/Stripe/StripeiOSTests/STPPaymentHandlerTests.swift new file mode 100644 index 00000000..8053ecd0 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentHandlerTests.swift @@ -0,0 +1,287 @@ +// +// STPPaymentHandlerTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import Stripe3DS2 +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentHandlerStubbedTests: STPNetworkStubbingTestCase { + override func setUp() { + self.recordingMode = false + super.setUp() + } + + func testCanPresentErrorsAreReported() { + let createPaymentIntentExpectation = expectation( + description: "createPaymentIntentExpectation" + ) + var retrievedClientSecret: String? + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { + (createdPIClientSecret, _) in + if let createdPIClientSecret = createdPIClientSecret { + retrievedClientSecret = createdPIClientSecret + createPaymentIntentExpectation.fulfill() + } else { + XCTFail() + } + } + wait(for: [createPaymentIntentExpectation], timeout: 8) // STPTestingNetworkRequestTimeout + guard let clientSecret = retrievedClientSecret, + let currentYear = Calendar.current.dateComponents([.year], from: Date()).year + else { + XCTFail() + return + } + + let expiryYear = NSNumber(value: currentYear + 2) + let expiryMonth = NSNumber(1) + + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4000000000003220" + cardParams.expYear = expiryYear + cardParams.expMonth = expiryMonth + cardParams.cvc = "123" + + let address = STPPaymentMethodAddress() + address.postalCode = "12345" + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.address = address + + let paymentMethodParams = STPPaymentMethodParams.paramsWith( + card: cardParams, + billingDetails: billingDetails, + metadata: nil + ) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) + paymentIntentParams.paymentMethodParams = paymentMethodParams + + // STPTestingDefaultPublishableKey + STPAPIClient.shared.publishableKey = "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6" + + let paymentHandlerExpectation = expectation(description: "paymentHandlerExpectation") + STPPaymentHandler.shared().checkCanPresentInTest = true + let analyticsClient = STPAnalyticsClient() + STPPaymentHandler.sharedHandler.analyticsClient = analyticsClient + STPPaymentHandler.shared().confirmPayment(paymentIntentParams, with: self) { + (status, paymentIntent, error) in + let firstAnalytic = analyticsClient._testLogHistory.first + XCTAssertEqual(firstAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmStarted.rawValue) + XCTAssertEqual(firstAnalytic?["intent_id"] as? String, paymentIntentParams.stripeId) + XCTAssertEqual(firstAnalytic?["payment_method_type"] as? String, "card") + let lastAnalytic = analyticsClient._testLogHistory.last + XCTAssertEqual(lastAnalytic?["event"] as? String, STPAnalyticEvent.paymentHandlerConfirmFinished.rawValue) + XCTAssertEqual(lastAnalytic?["intent_id"] as? String, paymentIntentParams.stripeId) + XCTAssertEqual(lastAnalytic?["status"] as? String, "failed") + XCTAssertEqual(lastAnalytic?["payment_method_type"] as? String, "card") + XCTAssertEqual(lastAnalytic?["error_type"] as? String, "STPPaymentHandlerErrorDomain") + XCTAssertEqual(lastAnalytic?["error_code"] as? String, "requiresAuthenticationContextErrorCode") + XCTAssertEqual(lastAnalytic?[jsonDict: "error_details"]?["com.stripe.lib:ErrorMessageKey"] as? String, "authenticationPresentingViewController is not in the window hierarchy. You should probably return the top-most view controller instead.") + XCTAssertTrue(status == .failed) + XCTAssertNotNil(paymentIntent) + XCTAssertNotNil(error) + XCTAssertEqual( + error?.userInfo[STPError.errorMessageKey] as? String, + "authenticationPresentingViewController is not in the window hierarchy. You should probably return the top-most view controller instead." + ) + paymentHandlerExpectation.fulfill() + } + // 2*STPTestingNetworkRequestTimeout payment handler needs to make an ares for this + // test in addition to fetching the payment intent + wait(for: [paymentHandlerExpectation], timeout: 2 * 8) + } +} + +class STPPaymentHandlerTests: APIStubbedTestCase { + + func testPaymentHandlerRetriesWithBackoff() { + let oldMaxRetries = StripeAPI.maxRetries + StripeAPI.maxRetries = 1 + STPPaymentHandler.sharedHandler.apiClient = stubbedAPIClient() + + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("3ds2/authenticate") ?? false + } response: { _ in + let jsonText = """ + { + "state": "challenge_required", + "livemode": "false", + "ares" : { + "dsTransID": "4e4750e7-6ab5-45a4-accf-9c668ed3b5a7", + "acsTransID": "fa695a82-a48c-455d-9566-a652058dda27", + "p_messageVersion": "1.0.5", + "acsOperatorID": "acsOperatorUL", + "sdkTransID": "D77EB83F-F317-4E29-9852-EBAAB55515B7", + "eci": "00", + "dsReferenceNumber": "3DS_LOA_DIS_PPFU_020100_00010", + "acsReferenceNumber": "3DS_LOA_ACS_PPFU_020100_00009", + "threeDSServerTransID": "fc7a39de-dc41-4b65-ba76-a322769b2efc", + "messageVersion": "2.2.0", + "authenticationValue": "AABBCCDDEEFFAABBCCDDEEFFAAA=", + "messageType": "pArs", + "transStatus": "C", + "acsChallengeMandated": "NO" + } + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("3ds2/challenge_complete") ?? false + } response: { _ in + let errorResponse = [ + "error": + [ + "message": "This is intentionally failing for this test.", + "type": "invalid_request_error", + ], + ] + return HTTPStubsResponse(jsonObject: errorResponse, statusCode: 400, headers: nil) + } + + // Stub the fetch SetupIntent request, which should be called after the failed challenge_complete + let fetchedSetupIntentExpectation = expectation(description: "Fetched SetupIntent") + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("setup_intents/seti_123") ?? false + } response: { _ in + fetchedSetupIntentExpectation.fulfill() + return HTTPStubsResponse(jsonObject: STPTestUtils.jsonNamed("SetupIntent")!, statusCode: 400, headers: nil) + } + + let paymentHandlerExpectation = expectation( + description: "paymentHandlerFinished" + ) + var inProgress = true + + // Meaningless cert, generated for this test + // Expires 3/2/2121: Apologies to future engineers! + let cert = """ + MIIBijCB9AIBATANBgkqhkiG9w0BAQUFADANMQswCQYDVQQGEwJVUzAgFw0yMTAz + MjYxODQyNDVaGA8yMTIxMDMwMjE4NDI0NVowDTELMAkGA1UEBhMCVVMwgZ8wDQYJ + KoZIhvcNAQEBBQADgY0AMIGJAoGBAL6rIW6t+8eo1exqhvYt8H1vM+TyHNNychlD + hILw745yXZQAy9ByRG3euYEydE3SFINgWBCUuwWmkNfsZUW7Uci1PBMglBFHJrE8 + 8ZvtuJgnPkqmu97a9JkyROiaqAmqoMDP95HiZG5i3a1E/QPpPyYA3VJ/El17Qqkl + aHN32qzjAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAUhxbGQ5sQMDUqFTvibU7RzqL + dTaFhdjTDBu5YeIbXXUrJSG2AydXRq7OacRksnQhvNYXimfcgfse46XQG7rKUCfj + kbazRiRxMZylTz8zbePAFcVq6zxJ+RBVrv51D+/JgbCcQ50nZiocllR0J9UL8CKZ + obaUC2OjBbSuCZwF8Ig= + """ + let rootCA = """ + MIIBkDCB+gIJAJ3pmjFOkxTXMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAYTAlVT + MB4XDTIxMDMyNjE4NDEzMVoXDTIyMDMyNjE4NDEzMVowDTELMAkGA1UEBhMCVVMw + gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKmFDGPV77Fk/wgUMwbxjQk+bpUY + cTjNBsjK3xMaUWeE17Sry6IguO1iWaXVey9YJ1Dm83PNO/5i9nHh3gmFhEJmc55T + g+0tZQigjTcs5/BfmWtrfPYIWqKvIJqkkHrIEJnwavAS5OFGyDArHLwUtsgJbDmW + tIeQg3EH/8BSWR0BAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEATY2aQvZZJLPgUr1/ + oDvRy6KZ6p7n3+jXF8DNvVOIaQRD4Ndk5NfStteIT5XvzfmD6QqpG3nlJ6Wy3oSP + 03KvO4GWIyP9cuP/QLaEmxJIYKwPrdxLkUHFfzyy8tN54xOWPxN4Up9gVN6pSdVk + KWrsPfhPs3G57wir370Q69lV/8A= + """ + let iauss = STPIntentActionUseStripeSDK( + encryptionInfo: [ + "certificate": cert, + "directory_server_id": "0000000000", + "root_certificate_authorities": [rootCA], + ], + directoryServerName: "none", + directoryServerKeyID: "none", + serverTransactionID: "none", + threeDSSourceID: "none", + publishableKeyOverride: nil, + threeDS2IntentOverride: nil, + allResponseFields: [:] + ) + let action = STPIntentAction( + type: .useStripeSDK, + redirectToURL: nil, + alipayHandleRedirect: nil, + useStripeSDK: iauss, + oxxoDisplayDetails: nil, + weChatPayRedirectToApp: nil, + boletoDisplayDetails: nil, + verifyWithMicrodeposits: nil, + cashAppRedirectToApp: nil, + payNowDisplayQrCode: nil, + konbiniDisplayDetails: nil, + promptPayDisplayQrCode: nil, + swishHandleRedirect: nil, + multibancoDisplayDetails: nil, + allResponseFields: [:] + ) + let setupIntent = STPSetupIntent( + stripeID: "test", + clientSecret: "seti_123_secret_123", + created: Date(), + customerID: nil, + stripeDescription: nil, + livemode: false, + nextAction: action, + paymentMethodID: "test", + paymentMethod: nil, + paymentMethodOptions: nil, + paymentMethodTypes: [], + status: .requiresAction, + usage: .none, + lastSetupError: nil, + allResponseFields: [:] + ) + + // We expect this request to retry a few times with exponential backoff before calling the completion handler. + STPPaymentHandler.sharedHandler._handleNextAction( + for: setupIntent, + with: self, + returnURL: nil + ) { (status, _, _) in + XCTAssertEqual(status, .failed) + inProgress = false + paymentHandlerExpectation.fulfill() + } + + let checkedStillInProgress = expectation( + description: "Checked that we're still in progress after 2s" + ) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { + // Make sure we're still in progress after 2 seconds + // This shows that we're retrying the 3DS2 request a few times + // while applying an appropriate amount of backoff. + XCTAssertEqual(inProgress, true) + checkedStillInProgress.fulfill() + } + + wait(for: [paymentHandlerExpectation, checkedStillInProgress, fetchedSetupIntentExpectation], timeout: 60) + STPPaymentHandler.sharedHandler.apiClient = STPAPIClient.shared + StripeAPI.maxRetries = oldMaxRetries + } +} + +extension STPPaymentHandlerTests: STPAuthenticationContext { + func authenticationPresentingViewController() -> UIViewController { + return UIViewController() + } +} + +extension STPPaymentHandlerStubbedTests: STPAuthenticationContext { + func authenticationPresentingViewController() -> UIViewController { + return UIViewController() + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentIntentEnumsTest.swift b/Stripe/StripeiOSTests/STPPaymentIntentEnumsTest.swift new file mode 100644 index 00000000..32ed6d52 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentIntentEnumsTest.swift @@ -0,0 +1,220 @@ +// +// STPPaymentIntentEnumsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 9/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentIntentEnumsTest: XCTestCase { + + func textStatusFromString() { + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "requires_payment_method"), + .requiresPaymentMethod + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "REQUIRES_PAYMENT_METHOD"), + .requiresPaymentMethod + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "requires_confirmation"), + .requiresConfirmation + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "REQUIRES_CONFIRMATION"), + .requiresConfirmation + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "requires_action"), + .requiresAction + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "REQUIRES_ACTION"), + .requiresAction + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "processing"), + .processing + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "PROCESSING"), + .processing + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "succeeded"), + .succeeded + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "SUCCEEDED"), + .succeeded + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "requires_capture"), + .requiresCapture + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "REQUIRES_CAPTURE"), + .requiresCapture + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "canceled"), + .canceled + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "CANCELED"), + .canceled + ) + + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "garbage"), + .unknown + ) + XCTAssertEqual( + STPPaymentIntentStatus.status(from: "GARBAGE"), + .unknown + ) + } + + func testStringFromStatus() { + + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .requiresPaymentMethod), + "requires_payment_method" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .requiresConfirmation), + "requires_confirmation" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .requiresAction), + "requires_action" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .processing), + "processing" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .succeeded), + "succeeded" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .requiresCapture), + "requires_capture" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .canceled), + "canceled" + ) + XCTAssertEqual( + STPPaymentIntentStatus.string(from: .unknown), + "unknown" + ) + } + + func testCaptureMethodFromString() { + XCTAssertEqual( + STPPaymentIntentCaptureMethod.captureMethod(from: "manual"), + .manual + ) + XCTAssertEqual( + STPPaymentIntentCaptureMethod.captureMethod(from: "MANUAL"), + .manual + ) + + XCTAssertEqual( + STPPaymentIntentCaptureMethod.captureMethod(from: "automatic"), + .automatic + ) + XCTAssertEqual( + STPPaymentIntentCaptureMethod.captureMethod(from: "AUTOMATIC"), + .automatic + ) + + XCTAssertEqual( + STPPaymentIntentCaptureMethod.captureMethod(from: "garbage"), + .unknown + ) + XCTAssertEqual( + STPPaymentIntentCaptureMethod.captureMethod(from: "GARBAGE"), + .unknown + ) + } + + func testConfirmationMethodFromString() { + XCTAssertEqual( + STPPaymentIntentConfirmationMethod.confirmationMethod(from: "automatic"), + .automatic + ) + XCTAssertEqual( + STPPaymentIntentConfirmationMethod.confirmationMethod(from: "AUTOMATIC"), + .automatic + ) + + XCTAssertEqual( + STPPaymentIntentConfirmationMethod.confirmationMethod(from: "manual"), + .manual + ) + XCTAssertEqual( + STPPaymentIntentConfirmationMethod.confirmationMethod(from: "MANUAL"), + .manual + ) + + XCTAssertEqual( + STPPaymentIntentConfirmationMethod.confirmationMethod(from: "garbage"), + .unknown + ) + XCTAssertEqual( + STPPaymentIntentConfirmationMethod.confirmationMethod(from: "GARBAGE"), + .unknown + ) + } + + func testSetupFutureUsageFromString() { + XCTAssertEqual( + STPPaymentIntentSetupFutureUsage(string: "on_session"), + .onSession + ) + XCTAssertEqual( + STPPaymentIntentSetupFutureUsage(string: "ON_SESSION"), + .onSession + ) + + XCTAssertEqual( + STPPaymentIntentSetupFutureUsage(string: "off_session"), + .offSession + ) + XCTAssertEqual( + STPPaymentIntentSetupFutureUsage(string: "OFF_SESSION"), + .offSession + ) + + XCTAssertEqual( + STPPaymentIntentSetupFutureUsage(string: "garbage"), + .unknown + ) + XCTAssertEqual( + STPPaymentIntentSetupFutureUsage(string: "GARBAGE"), + .unknown + ) + } + + func testStringConvertible() { + XCTAssertEqual(String(describing: STPPaymentIntentStatus.requiresAction), "requiresAction") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentIntentFunctionalTest.swift b/Stripe/StripeiOSTests/STPPaymentIntentFunctionalTest.swift new file mode 100644 index 00000000..ee2bdcbf --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentIntentFunctionalTest.swift @@ -0,0 +1,1585 @@ +// +// STPPaymentIntentFunctionalTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentIntentFunctionalTest: STPNetworkStubbingTestCase { + func testCreatePaymentIntentWithTestingServer() { + let expectation = self.expectation(description: "PaymentIntent create.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: nil) { clientSecret, error in + XCTAssertNotNil(clientSecret) + XCTAssertNil(error) + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreatePaymentIntentWithInvalidCurrency() { + let expectation = self.expectation(description: "PaymentIntent create.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: [ + "payment_method_types": ["bancontact"], + ]) { clientSecret, error in + XCTAssertNil(clientSecret) + XCTAssertNotNil(error) + let errorString = (error! as NSError).userInfo[STPError.errorMessageKey] as! String + XCTAssertTrue(errorString.hasPrefix("Error creating PaymentIntent: The currency provided (usd) is invalid. Payments with bancontact support the following currencies: eur.")) + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRetrievePreviousCreatedPaymentIntent() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent retrieve") + + client.retrievePaymentIntent( + withClientSecret: "pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe") { paymentIntent, error in + XCTAssertNil(error) + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, "pi_1GGCGfFY0qyl6XeWbSAsh2hn") + XCTAssertEqual(paymentIntent?.amount, 100) + XCTAssertEqual(paymentIntent?.currency, "usd") + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNil(paymentIntent?.sourceId) + XCTAssertNil(paymentIntent?.paymentMethodId) + XCTAssertEqual(paymentIntent?.status, .canceled) + XCTAssertEqual(paymentIntent?.setupFutureUsage, STPPaymentIntentSetupFutureUsage.none) + XCTAssertNil(paymentIntent?.perform(NSSelectorFromString("nextSourceAction"))) + // #pragma clang diagnostic pop + XCTAssertNil(paymentIntent!.nextAction) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRetrieveWithWrongSecret() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent retrieve") + + client.retrievePaymentIntent( + withClientSecret: "pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_bad-secret") { paymentIntent, error in + XCTAssertNil(paymentIntent) + + XCTAssertNotNil(error) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain) + XCTAssertEqual((error as NSError?)?.code, STPErrorCode.invalidRequestError.rawValue) + XCTAssertEqual( + (error as NSError?)?.userInfo[STPError.errorParameterKey] as! String, + "clientSecret") + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRetrieveMismatchedPublishableKey() { + // Given an API Client with a publishable key for a test account A... + let client = STPAPIClient(publishableKey: "pk_test_51JtgfQKG6vc7r7YCU0qQNOkDaaHrEgeHgGKrJMNfuWwaKgXMLzPUA1f8ZlCNPonIROLOnzpUnJK1C1xFH3M3Mz8X00Q6O4GfUt") + let expectation = self.expectation(description: "Payment Intent retrieve") + + // ...retrieving a PI attached to a *different* account + client.retrievePaymentIntent( + withClientSecret: "pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe") { paymentIntent, error in + // ...should fail. + XCTAssertNil(paymentIntent) + + XCTAssertNotNil(error) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain) + XCTAssertEqual((error as NSError?)?.code, STPErrorCode.invalidRequestError.rawValue) + XCTAssertEqual( + (error as NSError?)?.userInfo[STPError.errorParameterKey] as! String, + "intent") + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmCanceledPaymentIntentFails() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let params = STPPaymentIntentParams(clientSecret: "pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe") + params.sourceParams = cardSourceParams() + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNil(paymentIntent) + + XCTAssertNotNil(error) + XCTAssertEqual((error as NSError?)?.domain, STPError.stripeDomain) + XCTAssertEqual((error as NSError?)?.code, STPErrorCode.invalidRequestError.rawValue) + let errorString = (error! as NSError).userInfo[STPError.errorMessageKey] as! String + XCTAssertTrue(errorString.hasPrefix("This PaymentIntent's source could not be updated because it has a status of canceled. You may only update the source of a PaymentIntent with one of the following statuses: requires_payment_method, requires_confirmation, requires_action."), + "Expected error message to complain about status being canceled. Actual msg: \(errorString)" + ) + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmPaymentIntentWith3DSCardSucceeds() { + + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + params.sourceParams = cardSourceParams() + // returnURL must be passed in while confirming (not creation time) + params.returnURL = "example-app-scheme://authorized" + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, params.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + + // sourceParams is the 3DS-required test card + XCTAssertEqual(paymentIntent?.status, .requiresAction) + + // STPRedirectContext is relying on receiving returnURL + XCTAssertNotNil(paymentIntent!.nextAction!.redirectToURL!.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + // Test deprecated property still works too + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + XCTAssertNotNil(paymentIntent?.nextSourceAction?.authorizeWithURL?.returnURL) + XCTAssertEqual( + paymentIntent?.nextSourceAction?.authorizeWithURL?.returnURL, + URL(string: "example-app-scheme://authorized")) + // #pragma clang diagnostic pop + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds() { + + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4000000000003220" + cardParams.expMonth = NSNumber(value: 7) + cardParams.expYear = NSNumber(value: Calendar.current.component(.year, from: Date()) + 5) + cardParams.cvc = "123" + + let billingDetails = STPPaymentMethodBillingDetails() + + params.paymentMethodParams = STPPaymentMethodParams( + card: cardParams, + billingDetails: billingDetails, + metadata: nil) + // returnURL must be passed in while confirming (not creation time) + params.returnURL = "example-app-scheme://authorized" + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, params.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // sourceParams is the 3DS-required test card + XCTAssertEqual(paymentIntent?.status, .requiresAction) + + // STPRedirectContext is relying on receiving returnURL + + XCTAssertNotNil(paymentIntent!.nextAction!.redirectToURL!.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + // Going to log all the fields so that you, the developer manually running this test, can inspect them + if let allResponseFields = paymentIntent?.allResponseFields { + print("Confirmed PaymentIntent: \(allResponseFields)") + } + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmPaymentIntentWithShippingDetailsSucceeds() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4242424242424242" + cardParams.expMonth = NSNumber(value: 7) + cardParams.expYear = NSNumber(value: Calendar.current.component(.year, from: Date()) + 5) + cardParams.cvc = "123" + + let billingDetails = STPPaymentMethodBillingDetails() + + params.paymentMethodParams = STPPaymentMethodParams( + card: cardParams, + billingDetails: billingDetails, + metadata: nil) + + let addressParams = STPPaymentIntentShippingDetailsAddressParams(line1: "123 Main St") + addressParams.line2 = "Apt 2" + addressParams.city = "San Francisco" + addressParams.state = "CA" + addressParams.country = "US" + addressParams.postalCode = "94106" + params.shipping = STPPaymentIntentShippingDetailsParams(address: addressParams, name: "Jane") + params.shipping?.carrier = "UPS" + params.shipping?.phone = "555-555-5555" + params.shipping?.trackingNumber = "123abc" + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, params.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // Address + XCTAssertEqual(paymentIntent?.shipping!.address!.line1, "123 Main St") + XCTAssertEqual(paymentIntent?.shipping!.address!.line2, "Apt 2") + XCTAssertEqual(paymentIntent?.shipping!.address!.city, "San Francisco") + XCTAssertEqual(paymentIntent?.shipping!.address!.state, "CA") + XCTAssertEqual(paymentIntent?.shipping!.address!.country, "US") + XCTAssertEqual(paymentIntent?.shipping!.address!.postalCode, "94106") + + XCTAssertEqual(paymentIntent?.shipping!.name, "Jane") + XCTAssertEqual(paymentIntent?.shipping!.carrier, "UPS") + XCTAssertEqual(paymentIntent?.shipping!.phone, "555-555-5555") + XCTAssertEqual(paymentIntent?.shipping!.trackingNumber, "123abc") + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmCardWithoutNetworkParam() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4242424242424242" + cardParams.expMonth = NSNumber(value: 7) + cardParams.expYear = NSNumber(value: Calendar.current.component(.year, from: Date()) + 5) + cardParams.cvc = "123" + + let billingDetails = STPPaymentMethodBillingDetails() + + params.paymentMethodParams = STPPaymentMethodParams( + card: cardParams, + billingDetails: billingDetails, + metadata: nil) + + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, params.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .succeeded) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmCardWithNetworkParam() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4242424242424242" + cardParams.expMonth = NSNumber(value: 7) + cardParams.expYear = NSNumber(value: Calendar.current.component(.year, from: Date()) + 5) + cardParams.cvc = "123" + + let billingDetails = STPPaymentMethodBillingDetails() + + params.paymentMethodParams = STPPaymentMethodParams( + card: cardParams, + billingDetails: billingDetails, + metadata: nil) + + let cardOptions = STPConfirmCardOptions() + cardOptions.network = "visa" + let paymentMethodOptions = STPConfirmPaymentMethodOptions() + paymentMethodOptions.cardOptions = cardOptions + params.paymentMethodOptions = paymentMethodOptions + + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, params.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .succeeded) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmCardWithInvalidNetworkParam() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let params = STPPaymentIntentParams(clientSecret: clientSecret!) + let cardParams = STPPaymentMethodCardParams() + cardParams.number = "4242424242424242" + cardParams.expMonth = NSNumber(value: 7) + cardParams.expYear = NSNumber(value: Calendar.current.component(.year, from: Date()) + 5) + + let billingDetails = STPPaymentMethodBillingDetails() + + params.paymentMethodParams = STPPaymentMethodParams( + card: cardParams, + billingDetails: billingDetails, + metadata: nil) + + let cardOptions = STPConfirmCardOptions() + cardOptions.network = "fake_network" + let paymentMethodOptions = STPConfirmPaymentMethodOptions() + paymentMethodOptions.cardOptions = cardOptions + params.paymentMethodOptions = paymentMethodOptions + + client.confirmPaymentIntent( + with: params) { paymentIntent, error in + XCTAssertNotNil(error, "Confirming with invalid network should result in an error") + + XCTAssertNil(paymentIntent) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - AU BECS Debit + + func testConfirmAUBECSDebitPaymentIntent() { + + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "currency": "aud", + "amount": NSNumber(value: 2000), + "payment_method_types": ["au_becs_debit"], + ], + account: "au") { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let becsParams = STPPaymentMethodAUBECSDebitParams() + becsParams.bsbNumber = "000000" // Stripe test bank + becsParams.accountNumber = "000123456" // test account + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + billingDetails.email = "jrosen@example.com" + + let params = STPPaymentMethodParams( + aubecsDebit: becsParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = params + + let client = STPAPIClient(publishableKey: STPTestingAUPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // AU BECS Debit should be in Processing + XCTAssertEqual(paymentIntent?.status, .processing) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Przelewy24 + + func testConfirmPaymentIntentWithPrzelewy24() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["p24"], + "currency": "eur", + ]) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let przelewy24Params = STPPaymentMethodPrzelewy24Params() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.email = "email@email.com" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + przelewy24: przelewy24Params, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + paymentIntentParams.returnURL = "example-app-scheme://authorized" + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // Przelewy24 requires a redirect + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertNotNil(paymentIntent!.nextAction!.redirectToURL!.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Bancontact + + func testConfirmPaymentIntentWithBancontact() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["bancontact"], + "currency": "eur", + ]) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let bancontact = STPPaymentMethodBancontactParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + bancontact: bancontact, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + paymentIntentParams.returnURL = "example-app-scheme://authorized" + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // Bancontact requires a redirect + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertNotNil(paymentIntent!.nextAction!.redirectToURL!.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - OXXO + + func testConfirmPaymentIntentWithOXXO() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["oxxo"], + "amount": NSNumber(value: 2000), + "currency": "mxn", + ], + account: "mex") { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingMEXPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let oxxo = STPPaymentMethodOXXOParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + billingDetails.email = "email@email.com" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + oxxo: oxxo, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // OXXO requires display the voucher as next step + let oxxoDisplayDetails = paymentIntent!.nextAction!.allResponseFields["oxxo_display_details"] as? [AnyHashable: Any] + XCTAssertNotNil(oxxoDisplayDetails?["expires_after"]) + XCTAssertNotNil(oxxoDisplayDetails?["number"]) + XCTAssertEqual(paymentIntent?.status, .requiresAction) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - EPS + + func testConfirmPaymentIntentWithEPS() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["eps"], + "currency": "eur", + ]) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let epsParams = STPPaymentMethodEPSParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + eps: epsParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + paymentIntentParams.returnURL = "example-app-scheme://authorized" + + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // EPS requires a redirect + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertNotNil(paymentIntent!.nextAction!.redirectToURL!.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Alipay + + func testConfirmAlipayPaymentIntent() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "currency": "usd", + "amount": NSNumber(value: 2000), + "payment_method_types": ["alipay"], + ]) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let params = STPPaymentMethodParams(alipay: STPPaymentMethodAlipayParams(), billingDetails: nil, metadata: nil) + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = params + paymentIntentParams.returnURL = "foo://bar" + paymentIntentParams.paymentMethodOptions = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions!.alipayOptions = STPConfirmAlipayOptions() + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .alipayHandleRedirect) + XCTAssertNotNil(paymentIntent!.nextAction) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - GrabPay + + func testConfirmPaymentIntentWithGrabPay() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["grabpay"], + "currency": "sgd", + ], + account: "sg") { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingSGPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let grabpay = STPPaymentMethodGrabPayParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + grabPay: grabpay, + billingDetails: billingDetails, + metadata: nil) + paymentIntentParams.returnURL = "example-app-scheme://authorized" + + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // GrabPay requires a redirect + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertNotNil(paymentIntent!.nextAction?.redirectToURL?.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - PayPal + + func testConfirmPaymentIntentWithPayPal() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["paypal"], + "currency": "eur", + ], + account: "be") { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingBEPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let payPal = STPPaymentMethodPayPalParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + payPal: payPal, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + paymentIntentParams.returnURL = "example-app-scheme://authorized" + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // PayPal requires a redirect + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertNotNil(paymentIntent!.nextAction!.redirectToURL!.returnURL) + XCTAssertEqual( + paymentIntent!.nextAction!.redirectToURL!.returnURL, + URL(string: "example-app-scheme://authorized")) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - BLIK + + func testConfirmPaymentIntentWithBLIK() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["blik"], + "currency": "pln", + "amount": NSNumber(value: 1000), + ], + account: "be") { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingBEPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let blik = STPPaymentMethodBLIKParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + blik: blik, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + let options = STPConfirmPaymentMethodOptions() + options.blikOptions = STPConfirmBLIKOptions(code: "123456") + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + // Blik transitions to requires_action until the customer authorizes the transaction or 1 minute passes + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .BLIKAuthorize) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Affirm + + func testConfirmPaymentIntentWithAffirm() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["affirm"], + "currency": "usd", + "amount": NSNumber(value: 6000), + ]) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + let affirm = STPPaymentMethodAffirmParams() + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + affirm: affirm, + metadata: [ + "test_key": "test_value", + ]) + + let addressParams = STPPaymentIntentShippingDetailsAddressParams(line1: "123 Main St") + addressParams.line2 = "Apt 2" + addressParams.city = "San Francisco" + addressParams.state = "CA" + addressParams.country = "US" + addressParams.postalCode = "94106" + paymentIntentParams.shipping = STPPaymentIntentShippingDetailsParams(address: addressParams, name: "Jane Doe") + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent( + with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - MobilePay + + func testConfirmPaymentIntentWithMobilePay() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["mobilepay"], + "currency": "dkk", + "amount": NSNumber(value: 6000), + ], + account: "fr" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + mobilePay: STPPaymentMethodMobilePayParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Amazon Pay + + func testConfirmPaymentIntentWithAmazonPay() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["amazon_pay"], + "currency": "usd", + "amount": NSNumber(value: 6000), + ], + account: "us" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + amazonPay: STPPaymentMethodAmazonPayParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Alma + + func testConfirmPaymentIntentWithAlma() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["alma"], + "currency": "eur", + "amount": NSNumber(value: 6000), + ], + account: "fr" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + alma: STPPaymentMethodAlmaParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Sunbit + + func testConfirmPaymentIntentWithSunbit() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["sunbit"], + "currency": "usd", + "amount": NSNumber(value: 6000), + ], + account: "us" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + sunbit: STPPaymentMethodSunbitParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Billie + + func testConfirmPaymentIntentWithBillie() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["billie"], + "currency": "eur", + "amount": NSNumber(value: 6000), + ], + account: "de" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDEPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + billie: STPPaymentMethodBillieParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Satispay + + func testConfirmPaymentIntentWithSatispay() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["satispay"], + "currency": "eur", + "amount": NSNumber(value: 6000), + ], + account: "it" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingITPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + satispay: STPPaymentMethodSatispayParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Crypto + + func testConfirmPaymentIntentWithCrypto() { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["crypto"], + "currency": "usd", + "amount": NSNumber(value: 100), + ], + account: "us" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret!) + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + crypto: STPPaymentMethodCryptoParams(), + billingDetails: nil, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent!.livemode) + XCTAssertNotNil(paymentIntent?.paymentMethodId) + + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent!.nextAction?.type, .redirectToURL) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - Multibanco + + func testConfirmPaymentIntentWithMultibanco() throws { + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["multibanco"], + "currency": "eur", + "amount": NSNumber(value: 6000), + ], + account: "us" + ) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + clientSecret = createdClientSecret + createExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Payment Intent confirm") + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: try XCTUnwrap(clientSecret)) + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.email = "tester@example.com" + + paymentIntentParams.paymentMethodParams = STPPaymentMethodParams( + multibanco: STPPaymentMethodMultibancoParams(), + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ] + ) + + let options = STPConfirmPaymentMethodOptions() + paymentIntentParams.paymentMethodOptions = options + paymentIntentParams.returnURL = "example-app-scheme://unused" + client.confirmPaymentIntent(with: paymentIntentParams) { paymentIntent, error in + guard let paymentIntent = paymentIntent else { + XCTFail() + return + } + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + XCTAssertEqual(paymentIntent.stripeId, paymentIntentParams.stripeId) + XCTAssertFalse(paymentIntent.livemode) + XCTAssertNotNil(paymentIntent.paymentMethodId) + + XCTAssertEqual(paymentIntent.status, .requiresAction) + XCTAssertEqual(paymentIntent.nextAction?.type, .multibancoDisplayDetails) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - US Bank Account + func createAndConfirmPaymentIntentWithUSBankAccount( + paymentMethodOptions: STPConfirmUSBankAccountOptions? = nil, + completion: @escaping (String?) -> Void + ) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + var clientSecret: String? + let createPIExpectation = expectation(description: "Create PaymentIntent") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["us_bank_account"], + "currency": "usd", + "amount": 1000, + ], + account: nil + ) { intentClientSecret, error in + XCTAssertNil(error) + XCTAssertNotNil(intentClientSecret) + clientSecret = intentClientSecret + createPIExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + guard let clientSecret = clientSecret else { + XCTFail("Failed to create PaymentIntent") + return + } + + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .checking + usBankAccountParams.accountHolderType = .individual + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let paymentMethodParams = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) + paymentIntentParams.paymentMethodParams = paymentMethodParams + if let paymentMethodOptions = paymentMethodOptions { + let pmo = STPConfirmPaymentMethodOptions() + pmo.usBankAccountOptions = paymentMethodOptions + paymentIntentParams.paymentMethodOptions = pmo + } + + let confirmPIExpectation = expectation(description: "Confirm PaymentIntent") + client.confirmPaymentIntent(with: paymentIntentParams, expand: ["payment_method"]) { + paymentIntent, + error in + XCTAssertNil(error) + XCTAssertNotNil(paymentIntent) + XCTAssertNotNil(paymentIntent?.paymentMethod) + XCTAssertNotNil(paymentIntent?.paymentMethod?.usBankAccount) + XCTAssertEqual(paymentIntent?.paymentMethod?.usBankAccount?.last4, "6789") + XCTAssertEqual(paymentIntent?.status, .requiresAction) + XCTAssertEqual(paymentIntent?.nextAction?.type, .verifyWithMicrodeposits) + if let paymentMethodOptions = paymentMethodOptions { + XCTAssertEqual( + paymentIntent?.paymentMethodOptions?.usBankAccount?.setupFutureUsage, + paymentMethodOptions.setupFutureUsage + ) + } + confirmPIExpectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + completion(clientSecret) + } + + func testConfirmPaymentIntentWithUSBankAccount_verifyWithAmounts() { + createAndConfirmPaymentIntentWithUSBankAccount { [self] clientSecret in + guard let clientSecret = clientSecret else { + XCTFail("Failed to create PaymentIntent") + return + } + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let verificationExpectation = expectation(description: "Verify with microdeposits") + client.verifyPaymentIntentWithMicrodeposits( + clientSecret: clientSecret, + firstAmount: 32, + secondAmount: 45 + ) { paymentIntent, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.status, .processing) + verificationExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + } + + func testConfirmPaymentIntentWithUSBankAccount_verifyWithDescriptorCode() { + createAndConfirmPaymentIntentWithUSBankAccount { [self] clientSecret in + guard let clientSecret = clientSecret else { + XCTFail("Failed to create PaymentIntent") + return + } + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let verificationExpectation = expectation(description: "Verify with microdeposits") + client.verifyPaymentIntentWithMicrodeposits( + clientSecret: clientSecret, + descriptorCode: "SM11AA" + ) { paymentIntent, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentIntent) + XCTAssertEqual(paymentIntent?.status, .processing) + verificationExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + } + + func testConfirmUSBankAccountWithPaymentMethodOptions() { + createAndConfirmPaymentIntentWithUSBankAccount( + paymentMethodOptions: STPConfirmUSBankAccountOptions(setupFutureUsage: .offSession) + ) { clientSecret in + XCTAssertNotNil(clientSecret) + } + } + + // MARK: - Helpers + + func cardSourceParams() -> STPSourceParams { + let card = STPCardParams() + card.number = "4000 0000 0000 3220" // Test 3DS required card + card.expMonth = 7 + card.expYear = UInt(Calendar.current.component(.year, from: Date()) + 5) + card.currency = "usd" + card.cvc = "123" + + return .cardParams(withCard: card) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentIntentLastPaymentErrorTest.swift b/Stripe/StripeiOSTests/STPPaymentIntentLastPaymentErrorTest.swift new file mode 100644 index 00000000..208b9a2d --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentIntentLastPaymentErrorTest.swift @@ -0,0 +1,60 @@ +// +// STPPaymentIntentLastPaymentErrorTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 9/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentIntentLastPaymentErrorTest: XCTestCase { + + func testErrorType() { + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "api_connection_error"), + .apiConnection + ) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "API_CONNECTION_ERROR"), + .apiConnection + ) + XCTAssertEqual(STPPaymentIntentLastPaymentErrorType(string: "api_error"), .api) + XCTAssertEqual(STPPaymentIntentLastPaymentErrorType(string: "API_ERROR"), .api) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "authentication_error"), + .authentication + ) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "AUTHENTICATION_ERROR"), + .authentication + ) + XCTAssertEqual(STPPaymentIntentLastPaymentErrorType(string: "card_error"), .card) + XCTAssertEqual(STPPaymentIntentLastPaymentErrorType(string: "CARD_ERROR"), .card) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "idempotency_error"), + .idempotency + ) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "IDEMPOTENCY_ERROR"), + .idempotency + ) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "invalid_request_error"), + .invalidRequest + ) + XCTAssertEqual( + STPPaymentIntentLastPaymentErrorType(string: "INVALID_REQUEST_ERROR"), + .invalidRequest + ) + XCTAssertEqual(STPPaymentIntentLastPaymentErrorType(string: "rate_limit_error"), .rateLimit) + XCTAssertEqual(STPPaymentIntentLastPaymentErrorType(string: "RATE_LIMIT_ERROR"), .rateLimit) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentIntentParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentIntentParamsTest.swift new file mode 100644 index 00000000..ba390eaf --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentIntentParamsTest.swift @@ -0,0 +1,215 @@ +// +// STPPaymentIntentParamsTest.swift +// StripeiOS Tests +// +// Created by Daniel Jackson on 7/5/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentIntentParamsTest: XCTestCase { + func testInit() { + for params in [ + STPPaymentIntentParams(clientSecret: "secret"), + STPPaymentIntentParams(), + STPPaymentIntentParams(), + ] { + XCTAssertNotNil(params) + XCTAssertNotNil(params.clientSecret) + XCTAssertNotNil(params.additionalAPIParameters) + XCTAssertEqual(params.additionalAPIParameters.count, 0) + + XCTAssertNil(params.stripeId, "invalid secrets, no stripeId") + XCTAssertNil(params.sourceParams) + XCTAssertNil(params.sourceId) + XCTAssertNil(params.receiptEmail) + XCTAssertNil(params.perform(NSSelectorFromString("saveSourceToCustomer"))) + XCTAssertNil(params.savePaymentMethod) + XCTAssertNil(params.returnURL) + XCTAssertNil(params.setupFutureUsage) + XCTAssertNil(params.useStripeSDK) + XCTAssertNil(params.mandateData) + XCTAssertNil(params.paymentMethodOptions) + XCTAssertNil(params.shipping) + } + } + + func testDescription() { + let params = STPPaymentIntentParams() + XCTAssertNotNil(params.description) + } + + // MARK: Deprecated Property + + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + func testReturnURLRenaming() { + let params = STPPaymentIntentParams() + + XCTAssertNil(params.returnURL) + XCTAssertNil(params.perform(NSSelectorFromString("returnUrl"))) + + params.returnURL = "set via new name" + XCTAssertEqual(params.perform(NSSelectorFromString("returnUrl")).takeUnretainedValue() as? NSString, "set via new name") + + params.perform(NSSelectorFromString("setReturnUrl:"), with: "set via old name") + XCTAssertEqual(params.returnURL, "set via old name") + } + + func testSaveSourceToCustomerRenaming() { + let params = STPPaymentIntentParams() + + XCTAssertNil(params.perform(NSSelectorFromString("saveSourceToCustomer"))) + XCTAssertNil(params.savePaymentMethod) + + params.savePaymentMethod = NSNumber(value: false) + XCTAssertEqual(params.perform(NSSelectorFromString("saveSourceToCustomer")).takeUnretainedValue() as? NSNumber, NSNumber(value: false)) + + params.perform(NSSelectorFromString("setSaveSourceToCustomer:"), with: NSNumber(value: true)) + XCTAssertEqual(params.savePaymentMethod, NSNumber(value: true)) + } + + func testDefaultMandateData() { + let params = STPPaymentIntentParams() + + // no configuration should have no mandateData + XCTAssertNil(params.mandateData) + + params.paymentMethodParams = STPPaymentMethodParams() + + params.paymentMethodParams!.rawTypeString = "card" + // card type should have no default mandateData + XCTAssertNil(params.mandateData) + + for type in ["sepa_debit", "au_becs_debit", "bacs_debit"] { + params.mandateData = nil + params.paymentMethodParams!.rawTypeString = type + // Mandate-required type should have mandateData + XCTAssertNotNil(params.mandateData) + XCTAssertEqual( + params.mandateData!.customerAcceptance.onlineParams!.inferFromClient, + NSNumber(value: true) + ) + + params.mandateData = STPMandateDataParams( + customerAcceptance: STPMandateCustomerAcceptanceParams( + type: .offline, + onlineParams: nil + )! + ) + // Default behavior should not override custom setting + XCTAssertNotNil(params.mandateData) + XCTAssertNil(params.mandateData!.customerAcceptance.onlineParams) + } + } + + // #pragma clang diagnostic pop + + // MARK: STPFormEncodable Tests + func testRootObjectName() { + XCTAssertNil(STPPaymentIntentParams.rootObjectName()) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let params = STPPaymentIntentParams() + + let mapping = STPPaymentIntentParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(params.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(formFieldName.count > 0) + } + + XCTAssertEqual( + mapping.values.count, + NSSet(array: (mapping as NSDictionary).allValues).count + ) + } + + func testCopy() { + let params = STPPaymentIntentParams(clientSecret: "test_client_secret") + params.paymentMethodParams = STPPaymentMethodParams() + params.paymentMethodId = "test_payment_method_id" + params.savePaymentMethod = NSNumber(value: true) + params.returnURL = "fake://testing_only" + params.setupFutureUsage = STPPaymentIntentSetupFutureUsage( + rawValue: Int(truncating: NSNumber(value: 1)) + ) + params.useStripeSDK = NSNumber(value: true) + params.mandateData = STPMandateDataParams( + customerAcceptance: STPMandateCustomerAcceptanceParams( + type: .offline, + onlineParams: nil + )! + ) + params.paymentMethodOptions = STPConfirmPaymentMethodOptions() + params.additionalAPIParameters = [ + "other_param": "other_value" + ] + params.shipping = STPPaymentIntentShippingDetailsParams( + address: STPPaymentIntentShippingDetailsAddressParams(line1: ""), + name: "" + ) + + let paramsCopy = params.copy() as! STPPaymentIntentParams + XCTAssertEqual(params.clientSecret, paramsCopy.clientSecret) + XCTAssertEqual(params.paymentMethodId, paramsCopy.paymentMethodId) + + // assert equal, not equal objects, because this is a shallow copy + XCTAssertEqual(params.paymentMethodParams, paramsCopy.paymentMethodParams) + XCTAssertEqual(params.mandateData, paramsCopy.mandateData) + XCTAssertEqual(params.shipping, paramsCopy.shipping) + + XCTAssertEqual(params.setupFutureUsage, STPPaymentIntentSetupFutureUsage.none) + XCTAssertEqual(params.savePaymentMethod, paramsCopy.savePaymentMethod) + XCTAssertEqual(params.returnURL, paramsCopy.returnURL) + XCTAssertEqual(params.useStripeSDK, paramsCopy.useStripeSDK) + XCTAssertEqual(params.paymentMethodOptions, paramsCopy.paymentMethodOptions) + XCTAssertEqual( + params.additionalAPIParameters as NSDictionary, + paramsCopy.additionalAPIParameters as NSDictionary + ) + + } + + func testClientSecretValidation() { + XCTAssertFalse( + STPPaymentIntentParams.isClientSecretValid("pi_12345"), + "'pi_12345' is not a valid client secret." + ) + XCTAssertFalse( + STPPaymentIntentParams.isClientSecretValid("pi_12345_secret_"), + "'pi_12345_secret_' is not a valid client secret." + ) + XCTAssertFalse( + STPPaymentIntentParams.isClientSecretValid( + "pi_a1b2c3_secret_x7y8z9pi_a1b2c3_secret_x7y8z9" + ), + "'pi_a1b2c3_secret_x7y8z9pi_a1b2c3_secret_x7y8z9' is not a valid client secret." + ) + XCTAssertFalse( + STPPaymentIntentParams.isClientSecretValid("seti_a1b2c3_secret_x7y8z9"), + "'seti_a1b2c3_secret_x7y8z9' is not a valid client secret." + ) + + XCTAssertTrue( + STPPaymentIntentParams.isClientSecretValid("pi_a1b2c3_secret_x7y8z9"), + "'pi_a1b2c3_secret_x7y8z9' is a valid client secret." + ) + XCTAssertTrue( + STPPaymentIntentParams.isClientSecretValid( + "pi_1CkiBMLENEVhOs7YMtUehLau_secret_s4O8SDh7s6spSmHDw1VaYPGZA" + ), + "'pi_1CkiBMLENEVhOs7YMtUehLau_secret_s4O8SDh7s6spSmHDw1VaYPGZA' is a valid client secret." + ) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentIntentTest.swift b/Stripe/StripeiOSTests/STPPaymentIntentTest.swift new file mode 100644 index 00000000..4f5bdfbe --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentIntentTest.swift @@ -0,0 +1,162 @@ +// +// STPPaymentIntentTest.swift +// StripeiOS Tests +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentIntentTest: XCTestCase { + func testIdentifierFromSecret() { + XCTAssertEqual( + STPPaymentIntent.id(fromClientSecret: "pi_123_secret_XYZ"), + "pi_123" + ) + XCTAssertEqual( + STPPaymentIntent.id( + fromClientSecret: "pi_123_secret_RandomlyContains_secret_WhichIsFine" + ), + "pi_123" + ) + + XCTAssertNil(STPPaymentIntent.id(fromClientSecret: "")) + XCTAssertNil(STPPaymentIntent.id(fromClientSecret: "po_123_secret_HasBadPrefix")) + XCTAssertNil(STPPaymentIntent.id(fromClientSecret: "MissingSentinalForSplitting")) + } + + // MARK: - Description Tests + func testDescription() { + let paymentIntent = STPFixtures.paymentIntent() + + XCTAssertNotNil(paymentIntent) + let desc = paymentIntent.description + XCTAssertTrue(desc.contains(NSStringFromClass(type(of: paymentIntent).self))) + XCTAssertGreaterThan((desc.count), 500, "Custom description should be long") + } + + // MARK: - STPAPIResponseDecodable Tests + func testDecodedObjectFromAPIResponseRequiredFields() { + let fullJson = STPTestUtils.jsonNamed(STPTestJSONPaymentIntent) + + XCTAssertNotNil( + STPPaymentIntent.decodedObject(fromAPIResponse: fullJson), + "can decode with full json" + ) + + let requiredFields = ["id", "client_secret", "amount", "currency", "livemode", "status"] + + for field in requiredFields { + var partialJson = fullJson + + XCTAssertNotNil(partialJson?[field]) + partialJson?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentIntent.decodedObject(fromAPIResponse: partialJson)) + } + } + + func testDecodedObjectFromAPIResponseMapping() { + let paymentIntentJson = STPTestUtils.jsonNamed("PaymentIntent")! + let paymentIntent = STPPaymentIntent.decodedObject(fromAPIResponse: paymentIntentJson)! + + XCTAssertEqual(paymentIntent.stripeId, "pi_1Cl15wIl4IdHmuTbCWrpJXN6") + XCTAssertEqual( + paymentIntent.clientSecret, + "pi_1Cl15wIl4IdHmuTbCWrpJXN6_secret_EkKtQ7Sg75hLDFKqFG8DtWcaK" + ) + XCTAssertEqual(paymentIntent.amount, 2345) + XCTAssertEqual(paymentIntent.canceledAt, Date(timeIntervalSince1970: 1_530_911_045)) + XCTAssertEqual(paymentIntent.captureMethod, .manual) + XCTAssertEqual(paymentIntent.confirmationMethod, .automatic) + XCTAssertEqual(paymentIntent.created, Date(timeIntervalSince1970: 1_530_911_040)) + XCTAssertEqual(paymentIntent.currency, "usd") + XCTAssertEqual(paymentIntent.stripeDescription, "My Sample PaymentIntent") + XCTAssertFalse(paymentIntent.livemode) + XCTAssertEqual(paymentIntent.receiptEmail, "danj@example.com") + + // Deprecated: `nextSourceAction` & `authorizeWithURL` should just be aliases for `nextAction` & `redirectToURL` + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + XCTAssertEqual( + paymentIntent.nextAction, + paymentIntent.nextAction, + "Should be the same object." + ) + XCTAssertEqual( + paymentIntent.nextAction!.redirectToURL!, + paymentIntent.nextAction!.redirectToURL, + "Should be the same object." + ) + // #pragma clang diagnostic pop + + // nextAction + XCTAssertNotNil(paymentIntent.nextAction) + XCTAssertEqual(paymentIntent.nextAction!.type, .redirectToURL) + XCTAssertNotNil(paymentIntent.nextAction!.redirectToURL) + XCTAssertNotNil(paymentIntent.nextAction!.redirectToURL!.url) + let returnURL = paymentIntent.nextAction!.redirectToURL!.returnURL + XCTAssertNotNil(returnURL) + XCTAssertEqual(returnURL, URL(string: "payments-example://stripe-redirect")) + let url = paymentIntent.nextAction!.redirectToURL!.url + XCTAssertNotNil(url) + + XCTAssertEqual( + url, + URL( + string: + "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk" + ) + ) + XCTAssertEqual(paymentIntent.sourceId, "src_1Cl1AdIl4IdHmuTbseiDWq6m") + XCTAssertEqual(paymentIntent.status, .requiresAction) + XCTAssertEqual(paymentIntent.setupFutureUsage, .none) + + XCTAssertEqual( + paymentIntent.paymentMethodTypes, + [NSNumber(value: STPPaymentMethodType.card.rawValue)] + ) + + // lastPaymentError + + XCTAssertNotNil(paymentIntent.lastPaymentError) + XCTAssertEqual( + paymentIntent.lastPaymentError!.code, + "payment_intent_authentication_failure" + ) + XCTAssertEqual( + paymentIntent.lastPaymentError!.docURL, + "https://stripe.com/docs/error-codes#payment-intent-authentication-failure" + ) + XCTAssertEqual( + paymentIntent.lastPaymentError!.message, + "The provided PaymentMethod has failed authentication. You can provide payment_method_data or a new PaymentMethod to attempt to fulfill this PaymentIntent again." + ) + XCTAssertNotNil(paymentIntent.lastPaymentError!.paymentMethod) + XCTAssertEqual(paymentIntent.lastPaymentError!.type, .invalidRequest) + + // Shipping + XCTAssertNotNil(paymentIntent.shipping) + XCTAssertEqual(paymentIntent.shipping!.carrier, "USPS") + XCTAssertEqual(paymentIntent.shipping!.name, "Dan") + XCTAssertEqual(paymentIntent.shipping!.phone, "1-415-555-1234") + XCTAssertEqual(paymentIntent.shipping!.trackingNumber, "xyz123abc") + XCTAssertNotNil(paymentIntent.shipping!.address) + XCTAssertEqual(paymentIntent.shipping!.address!.city, "San Francisco") + XCTAssertEqual(paymentIntent.shipping!.address!.country, "USA") + XCTAssertEqual(paymentIntent.shipping!.address!.line1, "123 Main St") + XCTAssertEqual(paymentIntent.shipping!.address!.line2, "Apt 456") + XCTAssertEqual(paymentIntent.shipping!.address!.postalCode, "94107") + XCTAssertEqual(paymentIntent.shipping!.address!.state, "CA") + + XCTAssertEqual( + paymentIntent.allResponseFields as NSDictionary, + paymentIntentJson as NSDictionary + ) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAUBECSDebitParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAUBECSDebitParamsTests.swift new file mode 100644 index 00000000..8541bab6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAUBECSDebitParamsTests.swift @@ -0,0 +1,56 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodAUBECSDebitParamsTests.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/4/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodAUBECSDebitParamsTests: STPNetworkStubbingTestCase { + func testCreateAUBECSPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingAUPublishableKey) + let becsParams = STPPaymentMethodAUBECSDebitParams() + becsParams.bsbNumber = "000000" // Stripe test bank + becsParams.accountNumber = "000123456" // test account + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + billingDetails.email = "jrosen@example.com" + + let params = STPPaymentMethodParams( + aubecsDebit: becsParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method AU BECS Debit create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating AU BECS Debit PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create AU BECS Debit PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .AUBECSDebit, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.email, "jrosen@example.com") + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + + // AU BECS Debit + XCTAssertEqual(paymentMethod?.auBECSDebit!.bsbNumber, "000000") + XCTAssertEqual(paymentMethod?.auBECSDebit!.last4, "3456") + XCTAssertNotNil(paymentMethod?.auBECSDebit!.fingerprint, "Missing fingerprint") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAUBECSDebitTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAUBECSDebitTests.swift new file mode 100644 index 00000000..198332ee --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAUBECSDebitTests.swift @@ -0,0 +1,85 @@ +// +// STPPaymentMethodAUBECSDebitTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/4/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +private var kAUBECSDebitPaymentIntentClientSecret = + "pi_1GaRLjF7QokQdxByYgFPQEi0_secret_z76otRQH2jjOIEQYsA9vxhuKn" +class STPPaymentMethodAUBECSDebitTests: STPNetworkStubbingTestCase { + private(set) var auBECSDebitJSON: [AnyHashable: Any]? + + func _retrieveAUBECSDebitJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let auBECSDebitJSON = auBECSDebitJSON { + completion(auBECSDebitJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingAUPublishableKey) + client.retrievePaymentIntent( + withClientSecret: kAUBECSDebitPaymentIntentClientSecret, + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + auBECSDebitJSON = paymentIntent?.paymentMethod?.auBECSDebit?.allResponseFields + completion(auBECSDebitJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + _retrieveAUBECSDebitJSON({ json in + let auBECSDebit = STPPaymentMethodAUBECSDebit.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(auBECSDebit, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + + func testFailWithoutRequired() { + var retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + _retrieveAUBECSDebitJSON({ json in + var auBECSDebitJSON = json + auBECSDebitJSON?["bsb_number"] = nil + XCTAssertNil( + STPPaymentMethodAUBECSDebit.decodedObject(fromAPIResponse: auBECSDebitJSON), + "Should not intialize with missing `bsb_number`" + ) + retrieveJSON.fulfill() + }) + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + + retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + _retrieveAUBECSDebitJSON({ json in + var auBECSDebitJSON = json + auBECSDebitJSON?["last4"] = nil + XCTAssertNil( + STPPaymentMethodAUBECSDebit.decodedObject(fromAPIResponse: auBECSDebitJSON), + "Should not intialize with missing `last4`" + ) + retrieveJSON.fulfill() + }) + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + + retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + _retrieveAUBECSDebitJSON({ json in + var auBECSDebitJSON = json + auBECSDebitJSON?["fingerprint"] = nil + XCTAssertNil( + STPPaymentMethodAUBECSDebit.decodedObject(fromAPIResponse: auBECSDebitJSON), + "Should not intialize with missing `fingerprint`" + ) + retrieveJSON.fulfill() + }) + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAddressTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodAddressTest.swift new file mode 100644 index 00000000..da7d044a --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAddressTest.swift @@ -0,0 +1,34 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodAddressTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/6/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodAddressTest: XCTestCase { + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = (STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["billing_details"] as! [AnyHashable: Any])["address"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodAddress.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPPaymentMethodAddress.decodedObject(fromAPIResponse: (STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["billing_details"] as? [AnyHashable: Any])!["address"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = (STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["billing_details"] as? [AnyHashable: Any])!["address"] as? [AnyHashable: Any] + let address = STPPaymentMethodAddress.decodedObject(fromAPIResponse: response) + XCTAssertEqual(address?.city, "München") + XCTAssertEqual(address?.country, "DE") + XCTAssertEqual(address?.postalCode, "80337") + XCTAssertEqual(address?.line1, "Marienplatz") + XCTAssertEqual(address?.line2, "8") + XCTAssertEqual(address?.state, "Bayern") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAffirmParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodAffirmParamsTest.swift new file mode 100644 index 00000000..4651065a --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAffirmParamsTest.swift @@ -0,0 +1,43 @@ +// +// STPPaymentMethodAffirmParamsTest.swift +// StripeiOS Tests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodAffirmParamsTests: STPNetworkStubbingTestCase { + + func testCreateAffirmPaymentMethod() throws { + let affirmParams = STPPaymentMethodAffirmParams() + + let params = STPPaymentMethodParams( + affirm: affirmParams, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Affirm create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .affirm, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.affirm, "The `affirm` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAffirmTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAffirmTests.swift new file mode 100644 index 00000000..f36d6e8c --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAffirmTests.swift @@ -0,0 +1,47 @@ +// +// STPPaymentMethodAffirmTests.swift +// StripeiOS Tests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodAffirmTests: STPNetworkStubbingTestCase { + + static let affirmPaymentIntentClientSecret = + "pi_3KUFbTFY0qyl6XeW1oDBbiQk_secret_8kdpLx37oa5WMrI2xoXThCK9s" + + func _retrieveAffirmJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.affirmPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + let affirmJson = paymentIntent?.paymentMethod?.affirm?.allResponseFields + XCTAssertNotNil(paymentIntent?.paymentMethod?.affirm) + completion(affirmJson ?? [:]) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveAffirmJSON({ json in + let affirm = STPPaymentMethodAffirm.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(affirm, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAfterpayClearpayParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodAfterpayClearpayParamsTest.swift new file mode 100644 index 00000000..fad741a1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAfterpayClearpayParamsTest.swift @@ -0,0 +1,59 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodAfterpayClearpayParamsTest.m +// StripeiOS Tests +// +// Created by Ali Riaz on 1/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodAfterpayClearpayParamsTest: STPNetworkStubbingTestCase { + func testCreateAfterpayClearpayPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let afterpayClearpayParams = STPPaymentMethodAfterpayClearpayParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + billingDetails.email = "jrosen@example.com" + billingDetails.address = STPPaymentMethodAddress() + billingDetails.address!.line1 = "510 Townsend St." + billingDetails.address!.postalCode = "94102" + billingDetails.address!.country = "US" + + let params = STPPaymentMethodParams( + afterpayClearpay: afterpayClearpayParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method AfterpayClearpay create") + + client.createPaymentMethod(with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating AfterpayClearpay Payment Method") + XCTAssertNotNil(paymentMethod, "Failed to create AfterpayClearpay PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .afterpayClearpay, "Incorrect PaymentMethod type") + XCTAssertNil(paymentMethod?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.email, "jrosen@example.com") + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.line1, "510 Townsend St.") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.postalCode, "94102") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.country, "US") + + XCTAssertNotNil(paymentMethod?.afterpayClearpay, "") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAfterpayClearpayTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodAfterpayClearpayTest.swift new file mode 100644 index 00000000..a28dbd61 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAfterpayClearpayTest.swift @@ -0,0 +1,38 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodAfterpayClearpayTest.m +// StripeiOS Tests +// +// Created by Ali Riaz on 1/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodAfterpayClearpayTest: STPNetworkStubbingTestCase { + var afterpayJSON: [AnyHashable: Any]? + + func _retrieveAfterpayJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let afterpayJSON { + completion(afterpayJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent(withClientSecret: "pi_1HbSAfFY0qyl6XeWRnlezJ7K_secret_t6Ju9Z0hxOvslawK34uC1Wm2b", expand: ["payment_method"]) { paymentIntent, _ in + self.afterpayJSON = paymentIntent?.paymentMethod?.afterpayClearpay?.allResponseFields + completion(self.afterpayJSON) + } + } + } + + func testCorrectParsing() { + let jsonExpectation = XCTestExpectation(description: "Fetch Afterpay Clearpay JSON") + _retrieveAfterpayJSON({ json in + let afterpay = STPPaymentMethodAfterpayClearpay.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(afterpay, "Failed to decode JSON") + jsonExpectation.fulfill() + }) + wait(for: [jsonExpectation], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAlmaParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAlmaParamsTests.swift new file mode 100644 index 00000000..275e334b --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAlmaParamsTests.swift @@ -0,0 +1,45 @@ +// +// STPPaymentMethodAlmaParamsTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 3/27/24. +// + +import Foundation +import StripeCoreTestUtils +import StripePaymentsTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodAlmaParamsTests: STPNetworkStubbingTestCase { + + func testCreateAlmaPaymentMethod() throws { + let almaParams = STPPaymentMethodAlmaParams() + + let params = STPPaymentMethodParams( + alma: almaParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Alma create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .alma, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.alma, "The `alma` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAlmaTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAlmaTests.swift new file mode 100644 index 00000000..aeaceae1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAlmaTests.swift @@ -0,0 +1,41 @@ +// +// STPPaymentMethodAlmaTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 3/27/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPaymentMethodAlmaTests: STPNetworkStubbingTestCase { + + static let almaPaymentIntentClientSecret = "pi_3Oz1AfKG6vc7r7YC0VaP6KiE_secret_SxVptpJ5PaAceAYCGetQh8FVv" + + func _retrieveAlmaJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.almaPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["alma"]) + let almaJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.alma?.allResponseFields) + completion(almaJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveAlmaJSON({ json in + let alma = STPPaymentMethodAlma.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(alma, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAmazonPayParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAmazonPayParamsTests.swift new file mode 100644 index 00000000..0ecdd00d --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAmazonPayParamsTests.swift @@ -0,0 +1,45 @@ +// +// STPPaymentMethodAmazonPayParamsTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 2/21/24. +// + +import Foundation +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodAmazonPayParamsTests: STPNetworkStubbingTestCase { + + func testCreateAmazonPayPaymentMethod() throws { + let amazonPayParams = STPPaymentMethodAmazonPayParams() + + let params = STPPaymentMethodParams( + amazonPay: amazonPayParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Amazon Pay create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .amazonPay, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.amazonPay, "The `amazonPay` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodAmazonPayTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodAmazonPayTests.swift new file mode 100644 index 00000000..82b29857 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodAmazonPayTests.swift @@ -0,0 +1,41 @@ +// +// STPPaymentMethodAmazonPayTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 2/21/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPaymentMethodAmazonPayTests: STPNetworkStubbingTestCase { + + static let amazonPayPaymentIntentClientSecret = "pi_3OmQQ0FY0qyl6XeW0H4X6eI0_secret_BerPIzUf8vFy1KXG53iYvX2Zb" + + func _retrieveAmazonPayJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.amazonPayPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["amazon_pay"]) + let amazonPayJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.amazonPay?.allResponseFields) + completion(amazonPayJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveAmazonPayJSON({ json in + let amazonPay = STPPaymentMethodAmazonPay.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(amazonPay, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBacsDebitTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodBacsDebitTest.swift new file mode 100644 index 00000000..ceeb58b5 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBacsDebitTest.swift @@ -0,0 +1,36 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodBacsDebitTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 1/28/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodBacsDebitTest: XCTestCase { + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let paymentMethodJSON = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodBacsDebit) + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = paymentMethodJSON?["bacs_debit"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodBacsDebit.decodedObject(fromAPIResponse: response)) + } + + let paymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: paymentMethodJSON) + XCTAssertNotNil(paymentMethod) + XCTAssertNotNil(paymentMethod?.bacsDebit) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodBacsDebit)["bacs_debit"] as? [AnyHashable: Any] + let bacs = STPPaymentMethodBacsDebit.decodedObject(fromAPIResponse: response) + XCTAssertEqual(bacs?.fingerprint, "9eMbmctOrd8i7DYa") + XCTAssertEqual(bacs?.last4, "2345") + XCTAssertEqual(bacs?.sortCode, "108800") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBancontactParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodBancontactParamsTests.swift new file mode 100644 index 00000000..01b705e4 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBancontactParamsTests.swift @@ -0,0 +1,50 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodBancontactParamsTests.m +// StripeiOS Tests +// +// Created by Vineet Shah on 4/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodBancontactParamsTests: STPNetworkStubbingTestCase { + func testCreateBancontactPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let bancontactParams = STPPaymentMethodBancontactParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + + let params = STPPaymentMethodParams( + bancontact: bancontactParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method Bancontact create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating Bancontact PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create Bancontact PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .bancontact, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jane Doe") + + // Bancontact Details + XCTAssertNotNil(paymentMethod?.bancontact, "Missing Bancontact") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBancontactTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodBancontactTests.swift new file mode 100644 index 00000000..50be8c06 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBancontactTests.swift @@ -0,0 +1,45 @@ +// +// STPPaymentMethodBancontactTests.swift +// StripeiOS Tests +// +// Created by Vineet Shah on 4/29/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodBancontactTests: STPNetworkStubbingTestCase { + private(set) var bancontactJSON: [AnyHashable: Any]? + + func _retrieveBancontactJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let bancontactJSON = bancontactJSON { + completion(bancontactJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1GdPnbFY0qyl6XeW8Ezvxe87_secret_Fxi2EZBQ0nInHumvvezcTRWF4", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + bancontactJSON = paymentIntent?.paymentMethod?.bancontact?.allResponseFields + completion(bancontactJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let expectation = self.expectation(description: "Retrieve payment intent") + _retrieveBancontactJSON({ json in + let bancontact = STPPaymentMethodBancontact.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(bancontact, "Failed to decode JSON") + expectation.fulfill() + }) + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBillieParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodBillieParamsTests.swift new file mode 100644 index 00000000..0b20095e --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBillieParamsTests.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodBillieParamsTests.swift +// StripeiOSTests +// +// Created by Eric Geniesse on 6/28/24. +// + +import Foundation +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodBillieParamsTests: XCTestCase { + + func testCreateBilliePaymentMethod() throws { + let billieParams = STPPaymentMethodBillieParams() + + let params = STPPaymentMethodParams( + billie: billieParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Billie create") + + let client = STPAPIClient(publishableKey: STPTestingDEPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .billie, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.billie, "The `billie` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBillieTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodBillieTests.swift new file mode 100644 index 00000000..dea803fc --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBillieTests.swift @@ -0,0 +1,39 @@ +// +// STPPaymentMethodBillieTests.swift +// StripeiOSTests +// +// Created by Eric Geniesse on 6/28/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import XCTest + +class STPPaymentMethodBillieTests: XCTestCase { + + static let billiePaymentIntentClientSecret = "pi_3PWj22Alz2yHYCNZ0vnWGOMN_secret_8eUe1QkN5OVMY0323P4rsYPvv" + + func _retrieveBillieJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDEPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.billiePaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["billie"]) + let billieJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.billie?.allResponseFields) + completion(billieJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveBillieJSON({ json in + let billie = STPPaymentMethodBillie.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(billie, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBillingDetailsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodBillingDetailsTest.swift new file mode 100644 index 00000000..92c6546a --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBillingDetailsTest.swift @@ -0,0 +1,34 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodBillingDetailsTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/6/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodBillingDetailsTest: XCTestCase { + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["billing_details"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodBillingDetails.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPPaymentMethodBillingDetails.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["billing_details"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["billing_details"] as? [AnyHashable: Any] + let billingDetails = STPPaymentMethodBillingDetails.decodedObject(fromAPIResponse: response) + XCTAssertEqual(billingDetails?.email, "jenny@example.com") + XCTAssertEqual(billingDetails?.name, "jenny") + XCTAssertEqual(billingDetails?.phone, "+15555555555") + XCTAssertNotNil(billingDetails?.address) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBillingDetailsTests+Link.swift b/Stripe/StripeiOSTests/STPPaymentMethodBillingDetailsTests+Link.swift new file mode 100644 index 00000000..10b6bd75 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBillingDetailsTests+Link.swift @@ -0,0 +1,62 @@ +// +// STPPaymentMethodBillingDetailsTests.swift +// StripeiOSTests +// +// Created by Eduardo Urias on 2/27/23. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@testable import StripePaymentSheet +import XCTest + +// Link mapping tests +final class STPPaymentMethodBillingDetailsTests: XCTestCase { + func testConsumersAPIParamsMapping() { + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Name" + billingDetails.email = "Email" + billingDetails.phone = "Phone" + billingDetails.address = STPPaymentMethodAddress() + billingDetails.address?.line1 = "Line 1" + billingDetails.address?.line2 = "" + billingDetails.address?.city = "City" + billingDetails.address?.state = "State" + billingDetails.address?.country = "Country" + + let params = billingDetails.consumersAPIParams + XCTAssertEqual(params["name"] as? String, "Name") + XCTAssertEqual(params["line_1"] as? String, "Line 1") + XCTAssertNil(params["line_2"]) + XCTAssertEqual(params["locality"] as? String, "City") + XCTAssertEqual(params["administrative_area"] as? String, "State") + XCTAssertEqual(params["country_code"] as? String, "Country") + + XCTAssertNil(params["email"]) + XCTAssertNil(params["phone"]) + XCTAssertNil(params["line1"]) + XCTAssertNil(params["line2"]) + XCTAssertNil(params["city"]) + XCTAssertNil(params["state"]) + XCTAssertNil(params["country"]) + } + + func testCreateLinkBillingAddress() { + let billingAddress = BillingAddress( + name: "Name", + line1: "Line 1", + line2: "Line 2", + city: "City", + state: "State", + countryCode: "Country" + ) + let billingDetails = STPPaymentMethodBillingDetails.init(billingAddress: billingAddress, email: "email@email.com")! + XCTAssertEqual(billingDetails.name, "Name") + XCTAssertEqual(billingDetails.address?.line1, "Line 1") + XCTAssertEqual(billingDetails.address?.line2, "Line 2") + XCTAssertEqual(billingDetails.address?.city, "City") + XCTAssertEqual(billingDetails.address?.state, "State") + XCTAssertEqual(billingDetails.address?.country, "Country") + XCTAssertEqual(billingDetails.email, "email@email.com") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBoletoParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodBoletoParamsTests.swift new file mode 100644 index 00000000..b67a74ab --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBoletoParamsTests.swift @@ -0,0 +1,72 @@ +// +// STPPaymentMethodBoletoParamsTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 9/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodBoletoParamsTests: STPNetworkStubbingTestCase { + + func testCreateBoletoPaymentMethod() throws { + let boletoParams = STPPaymentMethodBoletoParams() + boletoParams.taxID = "00.000.000/0001-91" + + let address = STPPaymentMethodAddress() + address.line1 = "Av. Do Brasil 1374" + address.city = "Sao Paulo" + address.state = "SP" + address.postalCode = "01310100" + address.country = "BR" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Diaz" + billingDetails.email = "jane@example.com" + billingDetails.address = address + + let params = STPPaymentMethodParams( + boleto: boletoParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Boleto create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .boleto, "Incorrect PaymentMethod type") + + XCTAssertEqual( + paymentMethod?.billingDetails?.name, + "Jane Diaz", + "Billing name should match the name provided during creation" + ) + + XCTAssertEqual( + paymentMethod?.billingDetails?.email, + "jane@example.com", + "Billing email should match the name provided during creation" + ) + + XCTAssertNotNil(paymentMethod?.boleto, "The `boleto` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodBoletoTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodBoletoTests.swift new file mode 100644 index 00000000..a72f4cdb --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodBoletoTests.swift @@ -0,0 +1,54 @@ +// +// STPPaymentMethodBoletoTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 9/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodBoletoTests: STPNetworkStubbingTestCase { + + private(set) var boletoJSON: [AnyHashable: Any]? + + static let boletoPaymentIntentClientSecret = + "pi_3JYFj9JQVROkWvqT0d2HYaTk_secret_c2PniS4q2A7XhZ9mbFwOTpN08" + + func _retrieveBoletoJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let boletoJSON = boletoJSON { + completion(boletoJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingBRPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.boletoPaymentIntentClientSecret, + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + boletoJSON = paymentIntent?.paymentMethod?.boleto?.allResponseFields + completion(boletoJSON ?? [:]) + } + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveBoletoJSON({ json in + let boleto = STPPaymentMethodBoleto.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(boleto, "Failed to decode JSON") + XCTAssertEqual(boleto?.taxID, "00.000.000/0001-91", "It must properly decode `taxID`") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCardChecksTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodCardChecksTest.swift new file mode 100644 index 00000000..164dde11 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCardChecksTest.swift @@ -0,0 +1,45 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodCardChecksTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/5/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class STPPaymentMethodCardChecksTest: XCTestCase { + func testDecodedObjectFromAPIResponse() { + let response = [ + "address_line1_check": NSNull(), + "address_postal_code_check": NSNull(), + "cvc_check": NSNull(), + ] + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var mutableResponse = response + mutableResponse.removeValue(forKey: field) + XCTAssertNil(STPPaymentMethodCardChecks.decodedObject(fromAPIResponse: mutableResponse)) + } + let checks = STPPaymentMethodCardChecks.decodedObject(fromAPIResponse: response) + XCTAssertNotNil(checks) + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + XCTAssertEqual(checks?.addressLine1Check, .unknown) + XCTAssertEqual(checks?.addressPostalCodeCheck, .unknown) + XCTAssertEqual(checks?.cvcCheck, .unknown) + // #pragma clang diagnostic pop + } + + func testCheckResultFromString() { + XCTAssertEqual(STPPaymentMethodCardChecks.checkResult(from: "pass"), .pass) + XCTAssertEqual(STPPaymentMethodCardChecks.checkResult(from: "failed"), .failed) + XCTAssertEqual(STPPaymentMethodCardChecks.checkResult(from: "unavailable"), .unavailable) + XCTAssertEqual(STPPaymentMethodCardChecks.checkResult(from: "unchecked"), .unchecked) + XCTAssertEqual(STPPaymentMethodCardChecks.checkResult(from: "unknown_string"), .unknown) + XCTAssertEqual(STPPaymentMethodCardChecks.checkResult(from: nil), .unknown) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCardParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodCardParamsTest.swift new file mode 100644 index 00000000..b684214e --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCardParamsTest.swift @@ -0,0 +1,99 @@ +// +// STPPaymentMethodCardParamsTest.swift +// StripeiOS Tests +// +// Created by David Estes on 2/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodCardParamsTest: XCTestCase { + func testEqualityCheck() { + let params1 = STPPaymentMethodCardParams() + params1.number = "4242424242424242" + params1.cvc = "123" + params1.expYear = 22 + params1.expMonth = 12 + params1.networks = .init(preferred: "visa") + let params2 = STPPaymentMethodCardParams() + params2.number = "4242424242424242" + params2.cvc = "123" + params2.expYear = 22 + params2.expMonth = 12 + params2.networks = .init(preferred: "visa") + XCTAssertEqual(params1, params2) + params1.additionalAPIParameters["test"] = "bla" + XCTAssertNotEqual(params1, params2) + params2.additionalAPIParameters["test"] = "bla" + XCTAssertEqual(params1, params2) + } + + func testCardParamsFromPaymentMethodParams() { + let pmCardParams = STPPaymentMethodCardParams() + pmCardParams.number = "4242424242424242" + pmCardParams.cvc = "123" + pmCardParams.expYear = 22 + pmCardParams.expMonth = 12 + let addressParams = STPPaymentMethodBillingDetails() + addressParams.name = "Tester McTestington" + let address = STPPaymentMethodAddress() + address.line1 = "123 Fake St" + address.line2 = "Apt 123" + address.city = "City" + address.state = "NY" + address.country = "US" + address.postalCode = "12345" + addressParams.address = address + let pmParams = STPPaymentMethodParams( + card: pmCardParams, + billingDetails: addressParams, + metadata: nil + ) + let cardParams = STPCardParams(paymentMethodParams: pmParams) + XCTAssertEqual(cardParams.number, "4242424242424242") + XCTAssertEqual(cardParams.cvc, "123") + XCTAssertEqual(cardParams.expYear, 22) + XCTAssertEqual(cardParams.expMonth, 12) + XCTAssertEqual(cardParams.name, "Tester McTestington") + XCTAssertEqual(cardParams.addressLine1, "123 Fake St") + XCTAssertEqual(cardParams.addressLine2, "Apt 123") + XCTAssertEqual(cardParams.addressCity, "City") + XCTAssertEqual(cardParams.addressState, "NY") + XCTAssertEqual(cardParams.addressCountry, "US") + XCTAssertEqual(cardParams.addressZip, "12345") + } + + func testPropertyNamesToFormFieldsMapping() { + // Test for STPPaymentMethodCardParams + let cardParams = STPPaymentMethodCardParams() + + let cardParamsExpectedMapping = [ + "number": "number", + "expMonth": "exp_month", + "expYear": "exp_year", + "cvc": "cvc", + "token": "token", + "networks": "networks", + ] + + let cardParamsMapping = type(of: cardParams).propertyNamesToFormFieldNamesMapping() + + XCTAssertEqual(cardParamsMapping, cardParamsExpectedMapping) + + // Test for STPPaymentMethodCardNetworksParams + let networksParams = STPPaymentMethodCardNetworksParams() + + let networksParamsExpectedMapping = [ + "preferred": "preferred", + ] + + let networksParamsMapping = type(of: networksParams).propertyNamesToFormFieldNamesMapping() + + XCTAssertEqual(networksParamsMapping, networksParamsExpectedMapping) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCardTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodCardTest.swift new file mode 100644 index 00000000..b3d15f66 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCardTest.swift @@ -0,0 +1,93 @@ +// +// STPPaymentMethodCardTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/6/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI +private let kCardPaymentIntentClientSecret = + "pi_1H5J4RFY0qyl6XeWFTpgue7g_secret_1SS59M0x65qWMaX2wEB03iwVE" + +class STPPaymentMethodCardTest: STPNetworkStubbingTestCase { + private(set) var cardJSON: [AnyHashable: Any]? + + func _retrieveCardJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let cardJSON = cardJSON { + completion(cardJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: kCardPaymentIntentClientSecret, + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + cardJSON = paymentIntent?.paymentMethod?.card?.allResponseFields + completion(cardJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + _retrieveCardJSON({ json in + let card = STPPaymentMethodCard.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(card, "Failed to decode JSON") + retrieveJSON.fulfill() + XCTAssertEqual(card?.brand, .visa) + XCTAssertEqual(card?.country, "US") + XCTAssertNotNil(card?.checks) + XCTAssertEqual(card?.expMonth, 7) + XCTAssertEqual(card?.expYear, 2021) + XCTAssertEqual(card?.funding, "credit") + XCTAssertEqual(card?.last4, "4242") + XCTAssertNotNil(card?.threeDSecureUsage) + XCTAssertEqual(card?.threeDSecureUsage?.supported, true) + XCTAssertNotNil(card?.networks) + XCTAssertEqual(card?.networks?.available, ["visa"]) + XCTAssertNil(card?.networks?.preferred) + }) + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = + STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)?["card"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodCard.decodedObject(fromAPIResponse: response)) + } + let json = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)?["card"] + let decoded = STPPaymentMethodCard.decodedObject( + fromAPIResponse: json as? [AnyHashable: Any] + ) + XCTAssertNotNil(decoded) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = + STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)?["card"] as? [AnyHashable: Any] + let card = STPPaymentMethodCard.decodedObject(fromAPIResponse: response) + XCTAssertEqual(card?.brand, .visa) + XCTAssertEqual(card?.country, "US") + XCTAssertNotNil(card?.checks) + XCTAssertEqual(card?.expMonth, 8) + XCTAssertEqual(card?.expYear, 2020) + XCTAssertEqual(card?.funding, "credit") + XCTAssertEqual(card?.last4, "4242") + XCTAssertEqual(card?.fingerprint, "6gVyxfIhqc8Z0g0X") + XCTAssertNotNil(card?.threeDSecureUsage) + XCTAssertEqual(card?.threeDSecureUsage?.supported, true) + XCTAssertEqual(card?.displayBrand, "cartes_bancaires") + XCTAssertNotNil(card?.wallet) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCardWalletMasterpassTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodCardWalletMasterpassTest.swift new file mode 100644 index 00000000..7054d01b --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCardWalletMasterpassTest.swift @@ -0,0 +1,21 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodCardWalletMasterpassTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/9/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodCardWalletMasterpassTest: XCTestCase { + func testDecodedObjectFromAPIResponseMapping() { + // We reuse the visa checkout JSON because it's identical to the masterpass version + let response = ((STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["card"] as! [AnyHashable: Any])["wallet"] as! [AnyHashable: Any])["visa_checkout"] as? [AnyHashable: Any] + let masterpass = STPPaymentMethodCardWalletMasterpass.decodedObject(fromAPIResponse: response) + XCTAssertNotNil(masterpass) + XCTAssertEqual(masterpass?.name, "Jenny") + XCTAssertEqual(masterpass?.email, "jenny@example.com") + XCTAssertNotNil(masterpass?.billingAddress) + XCTAssertNotNil(masterpass?.shippingAddress) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCardWalletTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodCardWalletTest.swift new file mode 100644 index 00000000..4058033c --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCardWalletTest.swift @@ -0,0 +1,40 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodCardWalletTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/9/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments + +class STPPaymentMethodCardWalletTest: XCTestCase { + // MARK: - STPPaymentMethodCardWalletType Tests + + func testTypeFromString() { + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "amex_express_checkout"), .amexExpressCheckout) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "AMEX_EXPRESS_CHECKOUT"), .amexExpressCheckout) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "apple_pay"), .applePay) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "APPLE_PAY"), .applePay) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "google_pay"), .googlePay) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "GOOGLE_PAY"), .googlePay) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "masterpass"), .masterpass) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "MASTERPASS"), .masterpass) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "samsung_pay"), .samsungPay) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "SAMSUNG_PAY"), .samsungPay) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "visa_checkout"), .visaCheckout) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "VISA_CHECKOUT"), .visaCheckout) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "link"), .link) + XCTAssertEqual(STPPaymentMethodCardWallet.type(from: "LINK"), .link) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseMapping() { + let response = (STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["card"] as! [AnyHashable: Any]) ["wallet"] as? [AnyHashable: Any] + let wallet = STPPaymentMethodCardWallet.decodedObject(fromAPIResponse: response) + XCTAssertNotNil(wallet) + XCTAssertEqual(wallet?.type, .visaCheckout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCardWalletVisaCheckoutTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodCardWalletVisaCheckoutTest.swift new file mode 100644 index 00000000..c389521f --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCardWalletVisaCheckoutTest.swift @@ -0,0 +1,20 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodCardWalletVisaCheckoutTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/9/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodCardWalletVisaCheckoutTest: XCTestCase { + func testDecodedObjectFromAPIResponseMapping() { + let response = ((STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard)["card"] as! [AnyHashable: Any])["wallet"] as! [AnyHashable: Any])["visa_checkout"] as? [AnyHashable: Any] + let visaCheckout = STPPaymentMethodCardWalletVisaCheckout.decodedObject(fromAPIResponse: response) + XCTAssertNotNil(visaCheckout) + XCTAssertEqual(visaCheckout?.name, "Jenny") + XCTAssertEqual(visaCheckout?.email, "jenny@example.com") + XCTAssertNotNil(visaCheckout?.billingAddress) + XCTAssertNotNil(visaCheckout?.shippingAddress) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCashAppParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodCashAppParamsTests.swift new file mode 100644 index 00000000..b7eb3d08 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCashAppParamsTests.swift @@ -0,0 +1,45 @@ +// +// STPPaymentMethodCashAppParamsTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 1/4/23. +// + +import Foundation +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodCashAppParamsTests: STPNetworkStubbingTestCase { + + func testCreateCashAppPaymentMethod() throws { + let cashAppParams = STPPaymentMethodCashAppParams() + + let params = STPPaymentMethodParams( + cashApp: cashAppParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Cash App create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .cashApp, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.cashApp, "The `cashApp` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCashAppTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodCashAppTests.swift new file mode 100644 index 00000000..2b0a621f --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCashAppTests.swift @@ -0,0 +1,41 @@ +// +// STPPaymentMethodCashAppTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 1/4/23. +// + +@testable import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPaymentMethodCashAppTests: STPNetworkStubbingTestCase { + + static let cashAppPaymentIntentClientSecret = "pi_3MMa4NFY0qyl6XeW1FM3HOts_secret_b4HQ5YksK3mfe7zZaxBlWCark" + + func _retrieveCashAppJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.cashAppPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["cashapp"]) + let cashAppJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.cashApp?.allResponseFields) + completion(cashAppJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveCashAppJSON({ json in + let cashApp = STPPaymentMethodCashApp.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(cashApp, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCryptoParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodCryptoParamsTests.swift new file mode 100644 index 00000000..1f3b1203 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCryptoParamsTests.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodCryptoParamsTests.swift +// StripeiOSTests +// +// Created by Eric Zhang on 11/20/24. +// + +import Foundation +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodCryptoParamsTests: XCTestCase { + + func testCreateCryptoPaymentMethod() throws { + let cryptoParams = STPPaymentMethodCryptoParams() + + let params = STPPaymentMethodParams( + crypto: cryptoParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Crypto create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .crypto, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.crypto, "The `crypto` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodCryptoTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodCryptoTests.swift new file mode 100644 index 00000000..6ef45f36 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodCryptoTests.swift @@ -0,0 +1,17 @@ +// +// STPPaymentMethodCryptoTests.swift +// StripeiOSTests +// +// Created by Eric Zhang on 11/20/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import XCTest + +class STPPaymentMethodCryptoTests: XCTestCase { + func testObjectDecoding() { + let crypto = STPPaymentMethodCrypto.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("CryptoPaymentMethod") as? [AnyHashable: Any]) + XCTAssertNotNil(crypto, "Failed to decode JSON") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodEPSParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodEPSParamsTests.swift new file mode 100644 index 00000000..96b68c96 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodEPSParamsTests.swift @@ -0,0 +1,51 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodEPSParamsTests.m +// StripeiOS Tests +// +// Created by Shengwei Wu on 5/15/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodEPSParamsTests: STPNetworkStubbingTestCase { + func testCreateEPSPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let epsParams = STPPaymentMethodEPSParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + let params = STPPaymentMethodParams( + eps: epsParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method EPS create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating EPS PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create EPS PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .EPS, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + + // EPS Details + XCTAssertNotNil(paymentMethod?.eps, "Missing eps") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodEPSTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodEPSTests.swift new file mode 100644 index 00000000..ce20092b --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodEPSTests.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodEPSTests.swift +// StripeiOS Tests +// +// Created by Shengwei Wu on 5/15/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodEPSTests: STPNetworkStubbingTestCase { + private(set) var epsJSON: [AnyHashable: Any]? + + func _retrieveEPSJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let epsJSON = epsJSON { + completion(epsJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1Gj0rqFY0qyl6XeWrug30CPz_secret_tKyf8QOKtiIrE3NSEkWCkBbyy", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + epsJSON = paymentIntent?.paymentMethod?.eps?.allResponseFields + completion(epsJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let jsonExpectation = XCTestExpectation(description: "Fetch EPS JSON") + _retrieveEPSJSON({ json in + let eps = STPPaymentMethodEPS.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(eps, "Failed to decode JSON") + jsonExpectation.fulfill() + }) + wait(for: [jsonExpectation], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodFPXTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodFPXTest.swift new file mode 100644 index 00000000..81a19c9c --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodFPXTest.swift @@ -0,0 +1,35 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodFPXTest.m +// StripeiOS Tests +// +// Created by David Estes on 8/26/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodFPXTest: XCTestCase { + func exampleJson() -> [AnyHashable: Any]? { + return [ + "bank": "maybank2u", + ] + } + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = exampleJson() + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodFPX.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPPaymentMethodFPX.decodedObject(fromAPIResponse: exampleJson())) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = exampleJson() + let fpx = STPPaymentMethodFPX.decodedObject(fromAPIResponse: response) + XCTAssertEqual(fpx?.bankIdentifierCode, "maybank2u") + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodFunctionalTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodFunctionalTest.swift new file mode 100644 index 00000000..a54071f6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodFunctionalTest.swift @@ -0,0 +1,369 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodFunctionalTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/6/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Stripe +import StripeCoreTestUtils +@_spi(STP) import StripePayments +@testable @_spi(CustomerSessionBetaAccess) import StripePaymentSheet +@testable import StripePaymentsTestUtils + +class STPPaymentMethodFunctionalTest: STPNetworkStubbingTestCase { + func testCreateCardPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let card = STPPaymentMethodCardParams() + card.number = "4242424242424242" + card.expMonth = NSNumber(value: 10) + card.expYear = NSNumber(value: 2028) + card.cvc = "100" + + let billingAddress = STPPaymentMethodAddress() + billingAddress.city = "San Francisco" + billingAddress.country = "US" + billingAddress.line1 = "150 Townsend St" + billingAddress.line2 = "4th Floor" + billingAddress.postalCode = "94103" + billingAddress.state = "CA" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.address = billingAddress + billingDetails.email = "email@email.com" + billingDetails.name = "Isaac Asimov" + billingDetails.phone = "555-555-5555" + + let params = STPPaymentMethodParams( + card: card, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + let expectation = self.expectation(description: "Payment Method Card create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertNotNil(paymentMethod?.stripeId) + XCTAssertNotNil(paymentMethod?.created) + XCTAssertFalse(paymentMethod!.liveMode) + XCTAssertEqual(paymentMethod?.type, .card) + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.email, "email@email.com") + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Isaac Asimov") + XCTAssertEqual(paymentMethod?.billingDetails!.phone, "555-555-5555") + + // Billing Details Address + XCTAssertEqual(paymentMethod?.billingDetails!.address!.line1, "150 Townsend St") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.line2, "4th Floor") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.city, "San Francisco") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.country, "US") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.state, "CA") + XCTAssertEqual(paymentMethod?.billingDetails!.address!.postalCode, "94103") + + // Card + XCTAssertEqual(paymentMethod?.card!.brand, .visa) + XCTAssertEqual(paymentMethod?.card!.checks!.cvcCheck, .unknown) + XCTAssertEqual(paymentMethod?.card!.checks!.addressLine1Check, .unknown) + XCTAssertEqual(paymentMethod?.card!.checks!.addressPostalCodeCheck, .unknown) + XCTAssertEqual(paymentMethod?.card!.country, "US") + XCTAssertEqual(paymentMethod?.card!.expMonth, 10) + XCTAssertEqual(paymentMethod?.card!.expYear, 2028) + XCTAssertEqual(paymentMethod?.card!.funding, "credit") + XCTAssertEqual(paymentMethod?.card!.last4, "4242") + XCTAssertTrue(paymentMethod!.card!.threeDSecureUsage!.supported) + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testUpdateCardPaymentMethod() async throws { + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + + // A hardcoded test Customer + let testCustomerID = "cus_PTf9mhkFv9ZGXl" + + // Create a new EK for the Customer + let customerAndEphemeralKey = try await STPTestingAPIClient.shared().fetchCustomerAndEphemeralKey(customerID: testCustomerID, merchantCountry: "fr") + + // Create a new payment method + let paymentMethod = try await client.createPaymentMethod(with: ._testCardValue(), additionalPaymentUserAgentValues: []) + + // Attach the payment method to the customer + try await client.attachPaymentMethod(paymentMethod.stripeId, + customerID: customerAndEphemeralKey.customer, + ephemeralKeySecret: customerAndEphemeralKey.ephemeralKeySecret) + + // Update the expiry year for the card by 1 year + let card = STPPaymentMethodCardParams() + card.expYear = (paymentMethod.card!.expYear + 1) as NSNumber + + let params = STPPaymentMethodUpdateParams(card: card, billingDetails: nil) + + let updatedPaymentMethod = try await client.updatePaymentMethod(with: paymentMethod.stripeId, + paymentMethodUpdateParams: params, + ephemeralKeySecret: customerAndEphemeralKey.ephemeralKeySecret) + + // Verify + XCTAssertEqual(updatedPaymentMethod.card!.expYear, (paymentMethod.card!.expYear + 1)) + + // Clean up, detach the payment method as a customer can only have 400 payment methods saved + try await client.detachPaymentMethod(paymentMethod.stripeId, + fromCustomerUsing: customerAndEphemeralKey.ephemeralKeySecret) + } + + func testMulitpleCardCreationWithCustomerSessionAndMultiDelete() async throws { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + // Create a new customer and new key + let customerAndEphemeralKey = try await STPTestingAPIClient.shared().fetchCustomerAndEphemeralKey(customerID: nil, merchantCountry: nil) + + // Create a new payment method 1 + let paymentMethod1 = try await client.createPaymentMethod(with: ._testCardValue(), additionalPaymentUserAgentValues: []) + + // Attach the payment method 1 to the customer + try await client.attachPaymentMethod(paymentMethod1.stripeId, + customerID: customerAndEphemeralKey.customer, + ephemeralKeySecret: customerAndEphemeralKey.ephemeralKeySecret) + + // Create a new payment method 2 + let paymentMethod2 = try await client.createPaymentMethod(with: ._testCardValue(), additionalPaymentUserAgentValues: []) + + // Attach the payment method 2 to the customer + try await client.attachPaymentMethod(paymentMethod2.stripeId, + customerID: customerAndEphemeralKey.customer, + ephemeralKeySecret: customerAndEphemeralKey.ephemeralKeySecret) + + // Element/Sessions endpoint should de-dupe payment methods with CustomerSesssion + let cscs = try await STPTestingAPIClient.shared().fetchCustomerAndCustomerSessionClientSecret(customerID: customerAndEphemeralKey.customer, + merchantCountry: nil) + var configuration = PaymentSheet.Configuration() + configuration.customer = PaymentSheet.CustomerConfiguration(id: cscs.customer, customerSessionClientSecret: cscs.customerSessionClientSecret) + let elementSession = try await client.retrieveDeferredElementsSession( + withIntentConfig: .init(mode: .payment(amount: 5000, currency: "usd", setupFutureUsage: .offSession, captureMethod: .automatic), + confirmHandler: { _, _, _ in + // no-op + }), + clientDefaultPaymentMethod: paymentMethod2.stripeId, + configuration: configuration) + + // Requires FF: elements_enable_read_allow_redisplay, to return "1", otherwise 0 + XCTAssertEqual(elementSession.customer?.paymentMethods.count, 1) + XCTAssertEqual(elementSession.customer?.paymentMethods.first?.stripeId, paymentMethod2.stripeId) + XCTAssertEqual(elementSession.customer?.defaultPaymentMethod, paymentMethod2.stripeId) + + // Official endpoint should have two payment methods + let fetchedPaymentMethods = try await fetchPaymentMethods(client: client, customerAndEphemeralKey: customerAndEphemeralKey) + XCTAssertEqual(fetchedPaymentMethods.count, 2) + + // Clean up, detach both payment methods + try await client.detachPaymentMethodRemoveDuplicates(paymentMethod2.stripeId, + customerId: customerAndEphemeralKey.customer, + fromCustomerUsing: customerAndEphemeralKey.ephemeralKeySecret) + + let reFetchedPaymentMethods = try await fetchPaymentMethods(client: client, customerAndEphemeralKey: customerAndEphemeralKey) + XCTAssertEqual(reFetchedPaymentMethods.count, 0) + } + + func testCreateBacsPaymentMethod() { + let client = STPAPIClient(publishableKey: "pk_test_z6Ct4bpx0NUjHii0rsi4XZBf00jmM8qA28") + + let bacs = STPPaymentMethodBacsDebitParams() + bacs.sortCode = "108800" + bacs.accountNumber = "00012345" + + let billingAddress = STPPaymentMethodAddress() + billingAddress.city = "London" + billingAddress.country = "GB" + billingAddress.line1 = "Stripe, 7th Floor The Bower Warehouse" + billingAddress.postalCode = "EC1V 9NR" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.address = billingAddress + billingDetails.email = "email@email.com" + billingDetails.name = "Isaac Asimov" + billingDetails.phone = "555-555-5555" + + let params = STPPaymentMethodParams(bacsDebit: bacs, billingDetails: billingDetails, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod( + with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .bacsDebit) + + // Bacs Debit + XCTAssertEqual(paymentMethod!.bacsDebit!.fingerprint, "UkSG0HfCGxxrja1H") + XCTAssertEqual(paymentMethod!.bacsDebit!.last4, "2345") + XCTAssertEqual(paymentMethod!.bacsDebit!.sortCode, "108800") + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateAlipayPaymentMethod() { + let client = STPAPIClient(publishableKey: "pk_test_JBVAMwnBuzCdmsgN34jfxbU700LRiPqVit") + + let params = STPPaymentMethodParams(alipay: STPPaymentMethodAlipayParams(), billingDetails: nil, metadata: nil) + + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod( + with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .alipay) + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateBLIKPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let params = STPPaymentMethodParams(blik: STPPaymentMethodBLIKParams(), billingDetails: nil, metadata: nil) + + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod( + with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .blik + ) + XCTAssertNotNil(paymentMethod?.blik) + expectation.fulfill() + } + + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateMobilePayPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + let params = STPPaymentMethodParams(mobilePay: STPPaymentMethodMobilePayParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .mobilePay) + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateAmazonPayPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let params = STPPaymentMethodParams(amazonPay: STPPaymentMethodAmazonPayParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .amazonPay) + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateAlmaPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + let params = STPPaymentMethodParams(alma: STPPaymentMethodAlmaParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .alma) + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateSunbitPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let params = STPPaymentMethodParams(sunbit: STPPaymentMethodSunbitParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .sunbit) + XCTAssertNotNil(paymentMethod?.sunbit, "The `sunbit` property must be populated") + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateBilliePaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDEPublishableKey) + let params = STPPaymentMethodParams(billie: STPPaymentMethodBillieParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .billie) + XCTAssertNotNil(paymentMethod?.billie, "The `billie` property must be populated") + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateSatispayPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingITPublishableKey) + let params = STPPaymentMethodParams(satispay: STPPaymentMethodSatispayParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .satispay) + XCTAssertNotNil(paymentMethod?.satispay, "The `satispay` property must be populated") + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateCryptoPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let params = STPPaymentMethodParams(crypto: STPPaymentMethodCryptoParams(), billingDetails: nil, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .crypto) + XCTAssertNotNil(paymentMethod?.crypto, "The `crypto` property must be populated") + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func testCreateMultibancoPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.email = "tester@example.com" + let params = STPPaymentMethodParams(alma: STPPaymentMethodAlmaParams(), billingDetails: billingDetails, metadata: nil) + let expectation = self.expectation(description: "Payment Method create") + client.createPaymentMethod(with: params) { paymentMethod, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod) + XCTAssertEqual(paymentMethod?.type, .alma) + expectation.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + } + + func fetchPaymentMethods(client: STPAPIClient, + customerAndEphemeralKey: STPTestingAPIClient.CreateEphemeralKeyResponse) async throws -> [STPPaymentMethod] { + try await withCheckedThrowingContinuation { continuation in + client.listPaymentMethods(forCustomer: customerAndEphemeralKey.customer, + using: customerAndEphemeralKey.ephemeralKeySecret, + types: [.card]) { paymentMethods, error in + guard let paymentMethods, error == nil else { + continuation.resume(throwing: error!) + return + } + continuation.resume(returning: paymentMethods) + } + } + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodGiropayParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodGiropayParamsTests.swift new file mode 100644 index 00000000..4df28201 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodGiropayParamsTests.swift @@ -0,0 +1,50 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodGiropayParamsTests.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 4/21/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodGiropayParamsTests: STPNetworkStubbingTestCase { + func testCreateGiropayPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let giropayParams = STPPaymentMethodGiropayParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + let params = STPPaymentMethodParams( + giropay: giropayParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method giropay create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating giropay PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create giropay PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .giropay, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + + // giropay Details + XCTAssertNotNil(paymentMethod?.giropay, "Missing giropay") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodGiropayTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodGiropayTests.swift new file mode 100644 index 00000000..4bdd9854 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodGiropayTests.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodGiropayTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 4/21/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodGiropayTests: STPNetworkStubbingTestCase { + private(set) var giropayJSON: [AnyHashable: Any]? + + func _retrieveGiropayDebitJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let giropayJSON = giropayJSON { + completion(giropayJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1GfsdtFY0qyl6XeWLnepIXCI_secret_bLJRSeSY7fBjDXnwh9BUKilMW", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + giropayJSON = paymentIntent?.paymentMethod?.giropay?.allResponseFields + completion(giropayJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let jsonExpectation = XCTestExpectation(description: "Fetch Giropay JSON") + _retrieveGiropayDebitJSON({ json in + let giropay = STPPaymentMethodGiropay.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(giropay, "Failed to decode JSON") + jsonExpectation.fulfill() + }) + wait(for: [jsonExpectation], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodGrabPayParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodGrabPayParamsTest.swift new file mode 100644 index 00000000..f4c37e7d --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodGrabPayParamsTest.swift @@ -0,0 +1,51 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodGrabPayParamsTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 7/21/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodGrabPayParamsTest: STPNetworkStubbingTestCase { + func testCreateGrabPayPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingSGPublishableKey) + let grabPayParams = STPPaymentMethodGrabPayParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + let params = STPPaymentMethodParams( + grabPay: grabPayParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method GrabPay create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating GrabPay PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create GrabPay PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .grabPay, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + + // GrabPay Details + XCTAssertNotNil(paymentMethod?.grabPay, "Missing grabPay") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodKlarnaParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodKlarnaParamsTests.swift new file mode 100644 index 00000000..2e42ec46 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodKlarnaParamsTests.swift @@ -0,0 +1,72 @@ +// +// STPPaymentMethodKlarnaParamsTests.swift +// StripeiOS Tests +// +// Created by Nick Porter on 10/20/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import StripeCoreTestUtils +import StripePaymentsTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodKlarnaParamsTests: STPNetworkStubbingTestCase { + + func testCreateKlarnaPaymentMethod() throws { + let klarnaParams = STPPaymentMethodKlarnaParams() + + let address = STPPaymentMethodAddress() + address.line1 = "55 John St" + address.line2 = "#3B" + address.city = "New York" + address.state = "NY" + address.postalCode = "10002" + address.country = "US" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "John Smith" + billingDetails.email = "foo@example.com" + billingDetails.address = address + + let params = STPPaymentMethodParams( + klarna: klarnaParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Klarna create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .klarna, "Incorrect PaymentMethod type") + + XCTAssertEqual( + paymentMethod?.billingDetails?.name, + "John Smith", + "Billing name should match the name provided during creation" + ) + + XCTAssertEqual( + paymentMethod?.billingDetails?.email, + "foo@example.com", + "Billing email should match the name provided during creation" + ) + + XCTAssertNotNil(paymentMethod?.klarna, "The `klarna` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodKlarnaTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodKlarnaTests.swift new file mode 100644 index 00000000..553d5552 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodKlarnaTests.swift @@ -0,0 +1,47 @@ +// +// STPPaymentMethodKlarnaTests.swift +// StripeiOS Tests +// +// Created by Nick Porter on 10/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodKlarnaTests: STPNetworkStubbingTestCase { + + static let klarnaPaymentIntentClientSecret = + "pi_3Jn3kUFY0qyl6XeW0mCp95UD_secret_28aNjjd1zsySFWvGoSzgcR5Qw" + + func _retrieveKlarnaJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.klarnaPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + let klarnaJson = paymentIntent?.paymentMethod?.klarna?.allResponseFields + completion(klarnaJson ?? [:]) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveKlarnaJSON({ json in + let klarna = STPPaymentMethodKlarna.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(klarna, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodMobilePayParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodMobilePayParamsTests.swift new file mode 100644 index 00000000..8b28b9b7 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodMobilePayParamsTests.swift @@ -0,0 +1,43 @@ +// +// STPPaymentMethodMobilePayParamsTests.swift +// StripeiOSTests +// + +import Foundation +import StripeCoreTestUtils +import StripePaymentsTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodMobilePayParamsTests: STPNetworkStubbingTestCase { + + func testCreateMobilePayPaymentMethod() throws { + let mobilePayParams = STPPaymentMethodMobilePayParams() + + let params = STPPaymentMethodParams( + mobilePay: mobilePayParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method MobilePay create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .mobilePay, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.mobilePay, "The `mobilepay` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodMobilePayTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodMobilePayTests.swift new file mode 100644 index 00000000..5361dcc6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodMobilePayTests.swift @@ -0,0 +1,39 @@ +// +// STPPaymentMethodMobilePayTests.swift +// StripeiOSTests +// + +@testable import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPaymentMethodMobilePayTests: STPNetworkStubbingTestCase { + + static let mobilePayPaymentIntentClientSecret = "pi_3PGVQJKG6vc7r7YC1Xs7oiWw_secret_5cqzEtQ059azmV1GmkLRA7Lvt" + + func _retrieveMobilePayJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.mobilePayPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["mobilepay"]) + let mobilePayJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.mobilePay?.allResponseFields) + completion(mobilePayJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveMobilePayJSON({ json in + let mobilePay = STPPaymentMethodMobilePay.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(mobilePay, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodMultibancoParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodMultibancoParamsTests.swift new file mode 100644 index 00000000..528144ba --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodMultibancoParamsTests.swift @@ -0,0 +1,47 @@ +// +// STPPaymentMethodMultibancoParamsTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 4/22/24. +// + +import Foundation +import StripeCoreTestUtils +import StripePaymentsTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodMultibancoParamsTests: STPNetworkStubbingTestCase { + + func testCreateMultibancoPaymentMethod() throws { + let multibancoParams = STPPaymentMethodMultibancoParams() + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + multibanco: multibancoParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Multibanco create") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .multibanco, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.multibanco, "The `multibanco` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodMultibancoTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodMultibancoTests.swift new file mode 100644 index 00000000..20f70b13 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodMultibancoTests.swift @@ -0,0 +1,41 @@ +// +// STPPaymentMethodMultibancoTests.swift +// StripeiOSTests +// +// Created by Nick Porter on 4/22/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPaymentMethodMultibancoTests: STPNetworkStubbingTestCase { + + static let multibancoPaymentIntentClientSecret = "pi_3P8R5lFY0qyl6XeW0byterUo_secret_seOTE1wwZqkjBte83FjHalgsW" + + func _retrieveMultibancoJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.multibancoPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["multibanco"]) + let multibancoJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.multibanco?.allResponseFields) + completion(multibancoJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveMultibancoJSON({ json in + let multibanco = STPPaymentMethodMultibanco.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(multibanco, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodNetBankingParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodNetBankingParamsTest.swift new file mode 100644 index 00000000..6ec5fb2a --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodNetBankingParamsTest.swift @@ -0,0 +1,47 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodNetBankingParamsTest.m +// StripeiOS +// +// Created by Anirudh Bhargava on 11/19/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodNetBankingParamsTests: STPNetworkStubbingTestCase { + func testCreateNetBankingPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingINPublishableKey) + let netbankingParams = STPPaymentMethodNetBankingParams() + netbankingParams.bank = "icici" + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + let params = STPPaymentMethodParams( + netBanking: netbankingParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method NetBanking create") + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + XCTAssertNil(error, "Unexpected error creating NetBanking PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create NetBanking PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .netBanking, "Incorrect PaymentMethod type") + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + // UPI Details + XCTAssertNotNil(paymentMethod?.netBanking, "Missing NetBanking") + XCTAssertEqual(paymentMethod?.netBanking!.bank, "icici") + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodNetBankingTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodNetBankingTests.swift new file mode 100644 index 00000000..a98ef0fe --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodNetBankingTests.swift @@ -0,0 +1,45 @@ +// +// STPPaymentMethodNetBankingTests.swift +// StripeiOS Tests +// +// Created by Anirudh Bhargava on 11/19/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodNetBankingTests: STPNetworkStubbingTestCase { + private(set) var netbankingJSON: [AnyHashable: Any]? + + func _retrieveNetBankingJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let netbankingJSON = netbankingJSON { + completion(netbankingJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingINPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1HoPqsBte6TMTRd4jX0PwrFa_secret_ThiIwyssre9qjJ6gtmghC21fk", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + netbankingJSON = paymentIntent?.paymentMethod?.netBanking?.allResponseFields + completion(netbankingJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let jsonExpectation = XCTestExpectation(description: "Fetch NetBanking JSON") + _retrieveNetBankingJSON({ json in + let netbanking = STPPaymentMethodNetBanking.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(netbanking, "Failed to decode JSON") + jsonExpectation.fulfill() + }) + wait(for: [jsonExpectation], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodOXXOParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodOXXOParamsTests.swift new file mode 100644 index 00000000..5c4c5de8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodOXXOParamsTests.swift @@ -0,0 +1,53 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodOXXOParamsTests.m +// StripeiOS Tests +// +// Created by Polo Li on 6/16/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodOXXOParamsTests: STPNetworkStubbingTestCase { + func testCreateOXXOPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let oxxoParams = STPPaymentMethodOXXOParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + billingDetails.email = "test@test.com" + + let params = STPPaymentMethodParams( + oxxo: oxxoParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method OXXO create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating OXXO PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create OXXO PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .OXXO, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails?.name, "Jane Doe") + XCTAssertEqual(paymentMethod?.billingDetails?.email, "test@test.com") + + // OXXO Details + XCTAssertNotNil(paymentMethod?.oxxo, "Missing OXXO") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodOXXOTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodOXXOTests.swift new file mode 100644 index 00000000..276d5829 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodOXXOTests.swift @@ -0,0 +1,38 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodOXXOTests.m +// StripeiOS Tests +// +// Created by Polo Li on 6/16/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodOXXOTests: STPNetworkStubbingTestCase { + private(set) var oxxoJSON: [AnyHashable: Any]? + + func _retrieveOXXOJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let oxxoJSON { + completion(oxxoJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingMEXPublishableKey) + client.retrievePaymentIntent(withClientSecret: "pi_1GvAdyHNG4o8pO5l0dr078gf_secret_h0tJE5mSX9BPEkmpKSh93jBXi", expand: ["payment_method"]) { paymentIntent, _ in + self.oxxoJSON = paymentIntent?.paymentMethod?.oxxo?.allResponseFields + completion(self.oxxoJSON) + } + } + } + + func testCorrectParsing() { + let expectation = self.expectation(description: "Retrieve payment intent") + _retrieveOXXOJSON({ json in + let oxxo = STPPaymentMethodOXXO.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(oxxo, "Failed to decode JSON") + expectation.fulfill() + }) + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodOptionsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodOptionsTest.swift new file mode 100644 index 00000000..8c0ac4f3 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodOptionsTest.swift @@ -0,0 +1,133 @@ +// +// STPPaymentMethodOptionsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 4/8/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodOptionsTest: STPNetworkStubbingTestCase { + + func testUSBankAccountOptions_PaymentIntent() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let verificationMethods = [ + "skip", + "automatic", + "instant", + "microdeposits", + "instant_or_skip", + ] + for verificationMethod in verificationMethods { + var clientSecret: String? + let createPIExpectation = expectation(description: "Create PaymentIntent") + STPTestingAPIClient.shared.createPaymentIntent( + withParams: [ + "payment_method_types": ["us_bank_account"], + "payment_method_options": [ + "us_bank_account": ["verification_method": verificationMethod] + ], + "currency": "usd", + "amount": 1000, + ], + account: nil + ) { intentClientSecret, error in + XCTAssertNil(error) + XCTAssertNotNil(intentClientSecret) + clientSecret = intentClientSecret + createPIExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + guard let clientSecret = clientSecret else { + XCTFail("Failed to create PaymentIntent") + continue + } + + let retrievePIExpectation = expectation(description: "Retrieve PaymentIntent") + client.retrievePaymentIntent(withClientSecret: clientSecret) { paymentIntent, error in + XCTAssertNil(error) + XCTAssertNotNil(paymentIntent) + + XCTAssertNotNil( + paymentIntent?.paymentMethodOptions?.usBankAccount?.verificationMethod + ) + XCTAssertEqual( + paymentIntent?.paymentMethodOptions?.usBankAccount?.verificationMethod, + STPPaymentMethodOptions.USBankAccount.VerificationMethod( + rawValue: verificationMethod + ) + ) + retrievePIExpectation.fulfill() + + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + } + + func testUSBankAccountOptions_SetupIntent() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let verificationMethods = [ + "skip", + "automatic", + "instant", + "microdeposits", + "instant_or_skip", + ] + for verificationMethod in verificationMethods { + var clientSecret: String? + let createPIExpectation = expectation(description: "Create SetupIntent") + STPTestingAPIClient.shared.createSetupIntent( + withParams: [ + "payment_method_types": ["us_bank_account"], + "payment_method_options": [ + "us_bank_account": ["verification_method": verificationMethod] + ], + ], + account: nil + ) { intentClientSecret, error in + XCTAssertNil(error) + XCTAssertNotNil(intentClientSecret) + clientSecret = intentClientSecret + createPIExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + guard let clientSecret = clientSecret else { + XCTFail("Failed to create SetupIntent") + continue + } + + let retrievePIExpectation = expectation(description: "Retrieve PaymentIntent") + client.retrieveSetupIntent(withClientSecret: clientSecret) { setupIntent, error in + XCTAssertNil(error) + XCTAssertNotNil(setupIntent) + + XCTAssertNotNil( + setupIntent?.paymentMethodOptions?.usBankAccount?.verificationMethod + ) + XCTAssertEqual( + setupIntent?.paymentMethodOptions?.usBankAccount?.verificationMethod, + STPPaymentMethodOptions.USBankAccount.VerificationMethod( + rawValue: verificationMethod + ) + ) + retrievePIExpectation.fulfill() + + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodParamsTest.swift new file mode 100644 index 00000000..934dbacc --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodParamsTest.swift @@ -0,0 +1,33 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodParamsTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodParamsTest: XCTestCase { + // MARK: STPFormEncodable Tests + + func testRootObjectName() { + XCTAssertNil(STPPaymentMethodParams.rootObjectName()) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let params = STPPaymentMethodParams() + + let mapping = STPPaymentMethodParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(params.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(formFieldName.count > 0) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodPayPalParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodPayPalParamsTests.swift new file mode 100644 index 00000000..e52efa50 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodPayPalParamsTests.swift @@ -0,0 +1,50 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodPayPalParamsTests.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/7/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodPayPalParamsTests: STPNetworkStubbingTestCase { + func testCreatePayPalPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let payPalParams = STPPaymentMethodPayPalParams() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + + let params = STPPaymentMethodParams( + payPal: payPalParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method PayPal create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating PayPal PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create PayPal PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .payPal, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jane Doe") + + // PayPal Details + XCTAssertNotNil(paymentMethod?.payPal, "Missing PayPal") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodPayPalTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodPayPalTests.swift new file mode 100644 index 00000000..cd9319fa --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodPayPalTests.swift @@ -0,0 +1,37 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodPayPalTests.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/7/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodPayPalTests: STPNetworkStubbingTestCase { + var payPalJSON: [AnyHashable: Any]? + + func _retrievePayPalJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let payPalJSON { + completion(payPalJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent(withClientSecret: "pi_1HcI17FY0qyl6XeWcFAAbZCw_secret_oAZ9OCoeyIg8EPeBEdF96ZJOT", expand: ["payment_method"]) { paymentIntent, _ in + self.payPalJSON = paymentIntent?.lastPaymentError?.paymentMethod?.payPal?.allResponseFields + completion(self.payPalJSON) + } + } + } + + func testCorrectParsing() { + let expectation = self.expectation(description: "Retrieve payment intent") + _retrievePayPalJSON({ json in + let payPal = STPPaymentMethodPayPal.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(payPal, "Failed to decode JSON") + expectation.fulfill() + }) + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodPrzelewy24ParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodPrzelewy24ParamsTests.swift new file mode 100644 index 00000000..ce537a9d --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodPrzelewy24ParamsTests.swift @@ -0,0 +1,51 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodPrzelewy24ParamsTests.m +// StripeiOS Tests +// +// Created by Vineet Shah on 4/23/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodPrzelewy24ParamsTests: STPNetworkStubbingTestCase { + func testCreatePrzelewy24PaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let przelewy24Params = STPPaymentMethodPrzelewy24Params() + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.email = "email@email.com" + + let params = STPPaymentMethodParams( + przelewy24: przelewy24Params, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method Przelewy24 create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating Przelewy24 PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create Przelewy24 PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .przelewy24, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.email, "email@email.com") + + // Przelewy24 Details + XCTAssertNotNil(paymentMethod?.przelewy24, "Missing Przelewy24") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodPrzelewy24Tests.swift b/Stripe/StripeiOSTests/STPPaymentMethodPrzelewy24Tests.swift new file mode 100644 index 00000000..887ebb06 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodPrzelewy24Tests.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodPrzelewy24Tests.swift +// StripeiOS Tests +// +// Created by Vineet Shah on 4/23/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodPrzelewy24Tests: STPNetworkStubbingTestCase { + private(set) var przelewy24JSON: [AnyHashable: Any]? + + func _retrievePrzelewy24JSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let przelewy24JSON = przelewy24JSON { + completion(przelewy24JSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1GciHFFY0qyl6XeWp9RdhmFF_secret_rFeERcidL1O5o1lwQUcIjLEZz", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + przelewy24JSON = paymentIntent?.paymentMethod?.przelewy24?.allResponseFields + completion(przelewy24JSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let expectation = self.expectation(description: "Retrieve payment intent") + _retrievePrzelewy24JSON({ json in + let przelewy24 = STPPaymentMethodPrzelewy24.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(przelewy24, "Failed to decode JSON") + expectation.fulfill() + }) + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodRevolutPayParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodRevolutPayParamsTests.swift new file mode 100644 index 00000000..2712f72c --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodRevolutPayParamsTests.swift @@ -0,0 +1,43 @@ +// +// STPPaymentMethodRevolutPayParamsTests.swift +// StripeiOSTests +// + +import Foundation +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodRevolutPayParamsTests: STPNetworkStubbingTestCase { + + func testCreateRevolutPayPaymentMethod() throws { + let revolutPayParams = STPPaymentMethodRevolutPayParams() + + let params = STPPaymentMethodParams( + revolutPay: revolutPayParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Revolut Pay create") + + let client = STPAPIClient(publishableKey: STPTestingGBPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .revolutPay, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.revolutPay, "The `revolut_pay` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodRevolutPayTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodRevolutPayTests.swift new file mode 100644 index 00000000..68e47a23 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodRevolutPayTests.swift @@ -0,0 +1,39 @@ +// +// STPPaymentMethodRevolutPayTests.swift +// StripeiOSTests +// + +@testable import Stripe +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +class STPPaymentMethodRevolutPayTests: STPNetworkStubbingTestCase { + + static let revolutPayPaymentIntentClientSecret = "pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6" + + func _retrieveRevolutPayJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingGBPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.revolutPayPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["revolut_pay"]) + let revolutPayJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.revolutPay?.allResponseFields) + completion(revolutPayJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveRevolutPayJSON({ json in + let revolutPay = STPPaymentMethodRevolutPay.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(revolutPay, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSEPADebitTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodSEPADebitTest.swift new file mode 100644 index 00000000..9454c33a --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSEPADebitTest.swift @@ -0,0 +1,39 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodSEPADebitTest.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodSEPADebitTest: XCTestCase { + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodSEPADebit.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPPaymentMethodSEPADebit.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any] + let sepaDebit = STPPaymentMethodSEPADebit.decodedObject(fromAPIResponse: response) + + XCTAssertEqual(sepaDebit?.bankCode, "37040044") + XCTAssertEqual(sepaDebit?.branchCode, "a_branch") + XCTAssertEqual(sepaDebit?.country, "DE") + XCTAssertEqual(sepaDebit?.fingerprint, "NxdSyRegc9PsMkWy") + XCTAssertEqual(sepaDebit?.last4, "3001") + XCTAssertEqual(sepaDebit?.mandate, "NXDSYREGC9PSMKWY") + + XCTAssertEqual(sepaDebit!.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSatispayTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodSatispayTests.swift new file mode 100644 index 00000000..4dccb425 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSatispayTests.swift @@ -0,0 +1,40 @@ +// +// STPPaymentMethodSatispayTests.swift +// StripeiOSTests +// +// Created by Eric Geniesse on 7/1/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import XCTest +@testable@_spi(STP) import StripePaymentsTestUtils + +class STPPaymentMethodSatispayTests: STPNetworkStubbingTestCase { + + static let satispayPaymentIntentClientSecret = "pi_3PXrkJIFbdis1OxT0XLmWug3_secret_oZfKhPPuB4KNqR4H3f34ZgDvY" + + func _retrieveSatispayJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingITPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.satispayPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["satispay"]) + let satispayJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.satispay?.allResponseFields) + completion(satispayJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveSatispayJSON({ json in + let satispay = STPPaymentMethodSatispay.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(satispay, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSofortParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodSofortParamsTests.swift new file mode 100644 index 00000000..f96ce155 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSofortParamsTests.swift @@ -0,0 +1,52 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodSofortParamsTests.m +// StripeiOS Tests +// +// Created by David Estes on 8/7/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodSofortParamsTests: STPNetworkStubbingTestCase { + func testCreateSofortPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let sofortParams = STPPaymentMethodSofortParams() + sofortParams.country = "DE" + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + let params = STPPaymentMethodParams( + sofort: sofortParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method Sofort create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating Sofort PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create Sofort PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .sofort, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + + // Sofort Details + XCTAssertNotNil(paymentMethod?.sofort, "Missing Sofort") + XCTAssertEqual(paymentMethod?.sofort!.country, "DE") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSofortTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodSofortTests.swift new file mode 100644 index 00000000..cafa0534 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSofortTests.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodSofortTests.swift +// StripeiOS Tests +// +// Created by David Estes on 8/7/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// +import StripeCoreTestUtils +import StripePaymentsTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodSofortTests: STPNetworkStubbingTestCase { + private(set) var sofortJSON: [AnyHashable: Any]? + + func _retrieveSofortJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let sofortJSON = sofortJSON { + completion(sofortJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1HDdfSFY0qyl6XeWjk7ogYVV_secret_5ikjoct7F271A4Bp6t7HkHwUo", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + sofortJSON = paymentIntent?.paymentMethod?.sofort?.allResponseFields + completion(sofortJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let jsonExpectation = XCTestExpectation(description: "Fetch Sofort JSON") + _retrieveSofortJSON({ json in + let sofort = STPPaymentMethodSofort.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(sofort, "Failed to decode JSON") + jsonExpectation.fulfill() + }) + wait(for: [jsonExpectation], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSunbitTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodSunbitTests.swift new file mode 100644 index 00000000..87c32c9b --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSunbitTests.swift @@ -0,0 +1,39 @@ +// +// STPPaymentMethodSunbitTests.swift +// StripeiOSTests +// +// Created by Eric Geniesse on 6/27/24. +// + +@testable import Stripe +import StripeCoreTestUtils +import XCTest + +class STPPaymentMethodSunbitTests: XCTestCase { + + static let sunbitPaymentIntentClientSecret = "pi_3PXmrrFY0qyl6XeW1KiGqDLP_secret_zAZk6ZD3cLPwb2lB6wTcLAhLU" + + func _retrieveSunbitJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.sunbitPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + XCTAssertNotNil(paymentIntent?.paymentMethod?.allResponseFields["sunbit"]) + let sunbitJson = try? XCTUnwrap(paymentIntent?.paymentMethod?.sunbit?.allResponseFields) + completion(sunbitJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveSunbitJSON({ json in + let sunbit = STPPaymentMethodSunbit.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(sunbit, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSwishParamsTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodSwishParamsTests.swift new file mode 100644 index 00000000..c67b09c2 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSwishParamsTests.swift @@ -0,0 +1,40 @@ +// +// STPPaymentMethodSwishParamsTests.swift +// StripeiOSTests +// +// Created by Eduardo Urias on 9/21/23. +// + +import Foundation +@testable @_spi(STP) import StripeCoreTestUtils +@testable @_spi(STP) import StripePayments +import StripePaymentsTestUtils + +class STPPaymentMethodSwishParamsTests: STPNetworkStubbingTestCase { + + func testCreateSwishPaymentMethod() throws { + let swishParams = STPPaymentMethodSwishParams() + + let params = STPPaymentMethodParams( + swish: swishParams, + billingDetails: nil, + metadata: nil + ) + + let exp = expectation(description: "Payment Method Swish create") + + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + client.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + exp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .swish, "Incorrect PaymentMethod type") + XCTAssertNotNil(paymentMethod?.swish, "The `swish` property must be populated") + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodSwishTests.swift b/Stripe/StripeiOSTests/STPPaymentMethodSwishTests.swift new file mode 100644 index 00000000..f7c2fbc4 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodSwishTests.swift @@ -0,0 +1,42 @@ +// +// STPPaymentMethodSwishTests.swift +// StripeiOSTests +// +// Created by Eduardo Urias on 9/21/23. +// + +import Foundation +import StripePaymentsTestUtils + +@testable @_spi(STP) import StripeCoreTestUtils +@testable @_spi(STP) import StripePayments + +class STPPaymentMethodSwishTests: STPNetworkStubbingTestCase { + + static let swishPaymentIntentClientSecret = + "pi_3Nsu6oKG6vc7r7YC1FJJPNjg_secret_wQtTkgmjgOMSqN7lje5RCtzrm" + + func _retrieveSwishJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingFRPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.swishPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + let swishJson = paymentIntent?.paymentMethod?.swish?.allResponseFields + completion(swishJson) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + _retrieveSwishJSON({ json in + let klarna = STPPaymentMethodSwish.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(klarna, "Failed to decode JSON") + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodTest.swift new file mode 100644 index 00000000..99bd2660 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodTest.swift @@ -0,0 +1,167 @@ +// +// STPPaymentMethodTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/6/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodTest: XCTestCase { + // MARK: - STPPaymentMethodType Tests + func testTypeFromString() { + XCTAssertEqual( + STPPaymentMethod.type(from: "au_becs_debit"), + STPPaymentMethodType.AUBECSDebit + ) + XCTAssertEqual( + STPPaymentMethod.type(from: "AU_BECS_DEBIT"), + STPPaymentMethodType.AUBECSDebit + ) + XCTAssertEqual(STPPaymentMethod.type(from: "BACS_DEBIT"), STPPaymentMethodType.bacsDebit) + XCTAssertEqual(STPPaymentMethod.type(from: "bacs_debit"), STPPaymentMethodType.bacsDebit) + XCTAssertEqual(STPPaymentMethod.type(from: "BACS_DEBIT"), STPPaymentMethodType.bacsDebit) + XCTAssertEqual(STPPaymentMethod.type(from: "card"), STPPaymentMethodType.card) + XCTAssertEqual(STPPaymentMethod.type(from: "CARD"), STPPaymentMethodType.card) + XCTAssertEqual(STPPaymentMethod.type(from: "ideal"), STPPaymentMethodType.iDEAL) + XCTAssertEqual(STPPaymentMethod.type(from: "IDEAL"), STPPaymentMethodType.iDEAL) + XCTAssertEqual(STPPaymentMethod.type(from: "fpx"), STPPaymentMethodType.FPX) + XCTAssertEqual(STPPaymentMethod.type(from: "FPX"), STPPaymentMethodType.FPX) + XCTAssertEqual(STPPaymentMethod.type(from: "sepa_debit"), STPPaymentMethodType.SEPADebit) + XCTAssertEqual(STPPaymentMethod.type(from: "SEPA_DEBIT"), STPPaymentMethodType.SEPADebit) + XCTAssertEqual(STPPaymentMethod.type(from: "multibanco"), STPPaymentMethodType.multibanco) + XCTAssertEqual( + STPPaymentMethod.type(from: "card_present"), + STPPaymentMethodType.cardPresent + ) + XCTAssertEqual( + STPPaymentMethod.type(from: "CARD_PRESENT"), + STPPaymentMethodType.cardPresent + ) + XCTAssertEqual(STPPaymentMethod.type(from: "unknown_string"), STPPaymentMethodType.unknown) + } + + func testTypesFromStrings() { + let rawTypes = [ + "card", + "ideal", + "card_present", + "fpx", + "sepa_debit", + "bacs_debit", + "au_becs_debit", + "multibanco", + ] + let expectedTypes: [STPPaymentMethodType] = [ + .card, + .iDEAL, + .cardPresent, + .FPX, + .SEPADebit, + .bacsDebit, + .AUBECSDebit, + .multibanco, + ] + XCTAssertEqual(STPPaymentMethod.paymentMethodTypes(from: rawTypes), expectedTypes) + } + + func testStringFromType() { + let values: [STPPaymentMethodType] = [ + .card, + .iDEAL, + .cardPresent, + .FPX, + .SEPADebit, + .bacsDebit, + .AUBECSDebit, + .OXXO, + .alipay, + .payPal, + .giropay, + .multibanco, + .unknown, + ] + for type in values { + let string = STPPaymentMethod.string(from: type) + + switch type { + case .card: + XCTAssertEqual(string, "card") + case .iDEAL: + XCTAssertEqual(string, "ideal") + case .cardPresent: + XCTAssertEqual(string, "card_present") + case .FPX: + XCTAssertEqual(string, "fpx") + case .SEPADebit: + XCTAssertEqual(string, "sepa_debit") + case .bacsDebit: + XCTAssertEqual(string, "bacs_debit") + case .AUBECSDebit: + XCTAssertEqual(string, "au_becs_debit") + case .giropay: + XCTAssertEqual(string, "giropay") + case .przelewy24: + XCTAssertEqual(string, "p24") + case .bancontact: + XCTAssertEqual(string, "bancontact") + case .EPS: + XCTAssertEqual(string, "eps") + case .OXXO: + XCTAssertEqual(string, "oxxo") + case .sofort: + XCTAssertEqual(string, "sofort") + case .alipay: + XCTAssertEqual(string, "alipay") + case .payPal: + XCTAssertEqual(string, "paypal") + case .grabPay: + XCTAssertEqual(string, "grabpay") + case .multibanco: + XCTAssertEqual(string, "multibanco") + case .unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - STPAPIResponseDecodable Tests + func testDecodedObjectFromAPIResponseRequiredFields() { + let fullJson = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard) + + XCTAssertNotNil( + STPPaymentMethod.decodedObject(fromAPIResponse: fullJson), + "can decode with full json" + ) + + let requiredFields = ["id"] + + for field in requiredFields { + var partialJson = fullJson + + XCTAssertNotNil(partialJson?[field]) + partialJson?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentIntent.decodedObject(fromAPIResponse: partialJson)) + } + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed(STPTestJSONPaymentMethodCard) + let paymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: response) + XCTAssertEqual(paymentMethod?.stripeId, "pm_123456789") + XCTAssertEqual(paymentMethod?.created, Date(timeIntervalSince1970: 123_456_789)) + XCTAssertEqual(paymentMethod!.liveMode, false) + XCTAssertEqual(paymentMethod?.type, .card) + XCTAssertNotNil(paymentMethod?.billingDetails) + XCTAssertNotNil(paymentMethod?.card) + XCTAssertNil(paymentMethod?.customerId) + XCTAssertEqual(paymentMethod!.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodThreeDSecureUsageTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodThreeDSecureUsageTest.swift new file mode 100644 index 00000000..37627145 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodThreeDSecureUsageTest.swift @@ -0,0 +1,27 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodThreeDSecureUsageTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/5/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import XCTest + +class STPPaymentMethodThreeDSecureUsageTest: XCTestCase { + func testDecodedObjectFromAPIResponse() { + let response = [ + "supported": NSNumber(value: true) + ] + let requiredFields = ["supported"] + + for field in requiredFields { + var mutableResponse = response + mutableResponse.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodThreeDSecureUsage.decodedObject(fromAPIResponse: mutableResponse)) + } + XCTAssertNotNil(STPPaymentMethodThreeDSecureUsage.decodedObject(fromAPIResponse: response)) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodUPIParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodUPIParamsTest.swift new file mode 100644 index 00000000..e46cbc8c --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodUPIParamsTest.swift @@ -0,0 +1,52 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodUPIParamsTest.m +// StripeiOS Tests +// +// Created by Anirudh Bhargava on 11/6/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils + +class STPPaymentMethodUPIParamsTests: STPNetworkStubbingTestCase { + func testCreateUPIPaymentMethod() { + let client = STPAPIClient(publishableKey: STPTestingINPublishableKey) + let upiParams = STPPaymentMethodUPIParams() + upiParams.vpa = "somevpa@hdfcbank" + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + + let params = STPPaymentMethodParams( + upi: upiParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value" + ]) + + let expectation = self.expectation(description: "Payment Method UPI create") + + client.createPaymentMethod( + with: params) { paymentMethod, error in + expectation.fulfill() + + XCTAssertNil(error, "Unexpected error creating UPI PaymentMethod") + XCTAssertNotNil(paymentMethod, "Failed to create UPI PaymentMethod") + XCTAssertNotNil(paymentMethod?.stripeId, "Missing stripeId") + XCTAssertNotNil(paymentMethod?.created, "Missing created") + XCTAssertFalse(paymentMethod!.liveMode, "Incorrect livemode") + XCTAssertEqual(paymentMethod?.type, .UPI, "Incorrect PaymentMethod type") + + // Billing Details + XCTAssertEqual(paymentMethod?.billingDetails!.name, "Jenny Rosen") + + // UPI Details + XCTAssertNotNil(paymentMethod?.upi, "Missing UPI") + XCTAssertEqual(paymentMethod?.upi!.vpa, "somevpa@hdfcbank") + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodUPITests.swift b/Stripe/StripeiOSTests/STPPaymentMethodUPITests.swift new file mode 100644 index 00000000..304e57dd --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodUPITests.swift @@ -0,0 +1,45 @@ +// +// STPPaymentMethodUPITests.swift +// StripeiOS Tests +// +// Created by Anirudh Bhargava on 11/10/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodUPITests: STPNetworkStubbingTestCase { + private(set) var upiJSON: [AnyHashable: Any]? + + func _retrieveUPIJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + if let upiJSON = upiJSON { + completion(upiJSON) + } else { + let client = STPAPIClient(publishableKey: STPTestingINPublishableKey) + client.retrievePaymentIntent( + withClientSecret: "pi_1HlYxxBte6TMTRd48W66zjTJ_secret_TgB7p7e7aTRbr22UT6N6KNrSm", + expand: ["payment_method"] + ) { [self] paymentIntent, _ in + upiJSON = paymentIntent?.paymentMethod?.upi?.allResponseFields + completion(upiJSON ?? [:]) + } + } + } + + func testCorrectParsing() { + let jsonExpectation = XCTestExpectation(description: "Fetch UPI JSON") + _retrieveUPIJSON({ json in + let upi = STPPaymentMethodUPI.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(upi, "Failed to decode JSON") + jsonExpectation.fulfill() + }) + wait(for: [jsonExpectation], timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountParamsStubbedTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountParamsStubbedTest.swift new file mode 100644 index 00000000..e58023d6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountParamsStubbedTest.swift @@ -0,0 +1,302 @@ +// +// STPPaymentMethodUSBankAccountParamsStubbedTest.swift +// StripeiOS Tests +// +// Created by John Woo on 3/24/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeApplePay +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet + +class STPPaymentMethodUSBankAccountParamsStubbedTest: APIStubbedTestCase { + func testus_bank_account_withoutNetworks() { + let stubbedApiClient = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/payment_methods") ?? false + } response: { _ in + let jsonText = """ + { + "id": "pm_1KgvOfFY0qyl6XeW7RfvDvxE", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": "tester@example.com", + "name": "iOS CI Tester", + "phone": null + }, + "created": 1648146513, + "customer": null, + "livemode": false, + "type": "us_bank_account", + "us_bank_account": { + "account_holder_type": "individual", + "account_type": "checking", + "bank_name": "STRIPE TEST BANK", + "fingerprint": "ickfX9sbxIyAlbuh", + "last4": "6789", + "linked_account": null, + "routing_number": "110000000" + } + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .checking + usBankAccountParams.accountHolderType = .individual + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + stubbedApiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .individual, + "`accountHolderType` should be individual" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .checking, + "`accountType` should be checking" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + XCTAssertNil(paymentMethod?.usBankAccount?.networks) + exp.fulfill() + + } + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + func testus_bank_account_networksInPayloadWithPreferred() { + + let stubbedApiClient = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/payment_methods") ?? false + } response: { _ in + let jsonText = """ + { + "id": "pm_1KgvOfFY0qyl6XeW7RfvDvxE", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": "tester@example.com", + "name": "iOS CI Tester", + "phone": null + }, + "created": 1648146513, + "customer": null, + "livemode": false, + "type": "us_bank_account", + "us_bank_account": { + "account_holder_type": "individual", + "account_type": "checking", + "bank_name": "STRIPE TEST BANK", + "fingerprint": "ickfX9sbxIyAlbuh", + "last4": "6789", + "linked_account": null, + "networks": { + "preferred": "ach", + "supported": [ + "ach" + ] + }, + "routing_number": "110000000" + } + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .checking + usBankAccountParams.accountHolderType = .individual + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + + stubbedApiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .individual, + "`accountHolderType` should be individual" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .checking, + "`accountType` should be checking" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + XCTAssertEqual(paymentMethod?.usBankAccount?.networks?.preferred, "ach") + XCTAssertEqual(paymentMethod?.usBankAccount?.networks?.supported.count, 1) + XCTAssertEqual(paymentMethod?.usBankAccount?.networks?.supported.first, "ach") + exp.fulfill() + + } + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + func testus_bank_account_networksInPayloadWithoutPreferred() { + + let stubbedApiClient = stubbedAPIClient() + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/payment_methods") ?? false + } response: { _ in + let jsonText = """ + { + "id": "pm_1KgvOfFY0qyl6XeW7RfvDvxE", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": "tester@example.com", + "name": "iOS CI Tester", + "phone": null + }, + "created": 1648146513, + "customer": null, + "livemode": false, + "type": "us_bank_account", + "us_bank_account": { + "account_holder_type": "individual", + "account_type": "checking", + "bank_name": "STRIPE TEST BANK", + "fingerprint": "ickfX9sbxIyAlbuh", + "last4": "6789", + "linked_account": null, + "networks": { + "supported": [ + "ach" + ] + }, + "routing_number": "110000000" + } + } + """ + return HTTPStubsResponse( + data: jsonText.data(using: .utf8)!, + statusCode: 200, + headers: nil + ) + } + + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .checking + usBankAccountParams.accountHolderType = .individual + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + + stubbedApiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .individual, + "`accountHolderType` should be individual" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .checking, + "`accountType` should be checking" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + XCTAssertNil(paymentMethod?.usBankAccount?.networks?.preferred) + XCTAssertEqual(paymentMethod?.usBankAccount?.networks?.supported.count, 1) + XCTAssertEqual(paymentMethod?.usBankAccount?.networks?.supported.first, "ach") + exp.fulfill() + + } + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountParamsTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountParamsTest.swift new file mode 100644 index 00000000..fa872ddc --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountParamsTest.swift @@ -0,0 +1,235 @@ +// +// STPPaymentMethodUSBankAccountParamsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodUSBankAccountParamsTest: STPNetworkStubbingTestCase { + + var apiClient: STPAPIClient! + override func setUp() { + super.setUp() + apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + } + + func testCreateUSBankAccountPaymentMethod_checking_individual() { + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .checking + XCTAssertEqual(usBankAccountParams.accountTypeString, "checking") + usBankAccountParams.accountHolderType = .individual + XCTAssertEqual(usBankAccountParams.accountHolderTypeString, "individual") + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + + apiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .individual, + "`accountHolderType` should be individual" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .checking, + "`accountType` should be checking" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + + exp.fulfill() + + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + func testCreateUSBankAccountPaymentMethod_savings_company() { + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .savings + XCTAssertEqual(usBankAccountParams.accountTypeString, "savings") + usBankAccountParams.accountHolderType = .company + XCTAssertEqual(usBankAccountParams.accountHolderTypeString, "company") + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + + apiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .company, + "`accountHolderType` should be company" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .savings, + "`accountType` should be savings" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + + exp.fulfill() + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + func testCreateUSBankAccountPaymentMethod_checking_individual_set_with_string() { + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountTypeString = "checking" + XCTAssertEqual(usBankAccountParams.accountType, .checking) + usBankAccountParams.accountHolderTypeString = "individual" + XCTAssertEqual(usBankAccountParams.accountHolderType, .individual) + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + + apiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .individual, + "`accountHolderType` should be individual" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .checking, + "`accountType` should be checking" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + + exp.fulfill() + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + func testCreateUSBankAccountPaymentMethod_savings_company_set_with_string() { + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountTypeString = "savings" + XCTAssertEqual(usBankAccountParams.accountType, .savings) + usBankAccountParams.accountHolderTypeString = "company" + XCTAssertEqual(usBankAccountParams.accountHolderType, .company) + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let params = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let exp = expectation(description: "Payment Method US Bank Account create") + + apiClient.createPaymentMethod(with: params) { + (paymentMethod: STPPaymentMethod?, error: Error?) in + + XCTAssertNil(error) + XCTAssertNotNil(paymentMethod, "Payment method should be populated") + XCTAssertEqual(paymentMethod?.type, .USBankAccount, "Incorrect PaymentMethod type") + XCTAssertNotNil( + paymentMethod?.usBankAccount, + "The `usBankAccount` property must be populated" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountHolderType, + .company, + "`accountHolderType` should be company" + ) + XCTAssertEqual( + paymentMethod?.usBankAccount?.accountType, + .savings, + "`accountType` should be savings" + ) + XCTAssertEqual(paymentMethod?.usBankAccount?.last4, "6789", "`last4` should be 6789") + + exp.fulfill() + } + + self.waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + + func testEncodingWithLinkAccountSessionID() { + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.linkAccountSessionID = "las_test" + let encoded = STPFormEncoder.dictionary(forObject: usBankAccountParams) + XCTAssertEqual( + (encoded["us_bank_account"] as? [AnyHashable: Any])?["link_account_session"] as? String, + "las_test" + ) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountTest.swift new file mode 100644 index 00000000..33460d6c --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodUSBankAccountTest.swift @@ -0,0 +1,53 @@ +// +// STPPaymentMethodUSBankAccountTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPaymentMethodUSBankAccountTest: STPNetworkStubbingTestCase { + + static let usBankAccountPaymentIntentClientSecret = + "pi_3KhHLqFY0qyl6XeW1X2ZMsOT_secret_k5bOLoKJEW8ZhQFpokL0OrpbU" + + func retrieveUSBankAccountJSON(_ completion: @escaping ([AnyHashable: Any]?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + client.retrievePaymentIntent( + withClientSecret: Self.usBankAccountPaymentIntentClientSecret, + expand: ["payment_method"] + ) { paymentIntent, _ in + let klarnaJson = paymentIntent?.paymentMethod?.usBankAccount?.allResponseFields + completion(klarnaJson ?? [:]) + } + } + + func testObjectDecoding() { + let retrieveJSON = XCTestExpectation(description: "Retrieve JSON") + + retrieveUSBankAccountJSON({ json in + let usBankAccount = STPPaymentMethodUSBankAccount.decodedObject(fromAPIResponse: json) + XCTAssertNotNil(usBankAccount, "Failed to decode JSON") + XCTAssertEqual(usBankAccount?.last4, "6789") + XCTAssertEqual(usBankAccount?.routingNumber, "110000000") + XCTAssertEqual(usBankAccount?.bankName, "STRIPE TEST BANK") + XCTAssertEqual(usBankAccount?.accountHolderType, .individual) + XCTAssertEqual(usBankAccount?.accountType, .checking) + XCTAssertNotNil(usBankAccount?.fingerprint) + retrieveJSON.fulfill() + }) + + wait(for: [retrieveJSON], timeout: STPTestingNetworkRequestTimeout) + } + +} diff --git a/Stripe/StripeiOSTests/STPPaymentMethodiDEALTest.swift b/Stripe/StripeiOSTests/STPPaymentMethodiDEALTest.swift new file mode 100644 index 00000000..24f7cc65 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPaymentMethodiDEALTest.swift @@ -0,0 +1,37 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPPaymentMethodiDEALTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 3/9/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +class STPPaymentMethodiDEALTest: XCTestCase { + func exampleJson() -> [AnyHashable: Any]? { + return [ + "bank": "Rabobank", + "bic": "RABONL2U", + ] + } + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = exampleJson() + response?.removeValue(forKey: field) + + XCTAssertNil(STPPaymentMethodiDEAL.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPPaymentMethodiDEAL.decodedObject(fromAPIResponse: exampleJson())) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = exampleJson() + let ideal = STPPaymentMethodiDEAL.decodedObject(fromAPIResponse: response) + XCTAssertEqual(ideal?.bankName, "Rabobank") + XCTAssertEqual(ideal?.bankIdentifierCode, "RABONL2U") + } +} diff --git a/Stripe/StripeiOSTests/STPPhoneNumberValidatorTest.swift b/Stripe/StripeiOSTests/STPPhoneNumberValidatorTest.swift new file mode 100644 index 00000000..66ffe633 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPhoneNumberValidatorTest.swift @@ -0,0 +1,92 @@ +// +// STPPhoneNumberValidatorTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 3/22/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +private let kUSCountryCode = "US" +private let kUKCountryCode = "UK" +class STPPhoneNumberValidatorTest: XCTestCase { + func testFormattedSanitizedPhoneNumberForString() { + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "55", + forCountryCode: kUSCountryCode + ), + "55" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "555", + forCountryCode: kUSCountryCode + ), + "(555) " + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "55555", + forCountryCode: kUSCountryCode + ), + "(555) 55" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "A-55555", + forCountryCode: kUSCountryCode + ), + "(555) 55" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "5555555", + forCountryCode: kUSCountryCode + ), + "(555) 555-5" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "5555555555", + forCountryCode: kUSCountryCode + ), + "(555) 555-5555" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "5555555555123", + forCountryCode: kUSCountryCode + ), + "(555) 555-5555" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: "5555555555123", + forCountryCode: kUKCountryCode + ), + "5555555555123" + ) + } + + func testFormattedRedactedPhoneNumberForString() { + XCTAssertEqual( + STPPhoneNumberValidator.formattedRedactedPhoneNumber( + for: "+1******1234", + forCountryCode: kUSCountryCode + ), + "+1 (•••) •••-1234" + ) + XCTAssertEqual( + STPPhoneNumberValidator.formattedRedactedPhoneNumber( + for: "+86******1234", + forCountryCode: kUKCountryCode + ), + "+86 ••••••1234" + ) + } +} diff --git a/Stripe/StripeiOSTests/STPPinManagementServiceFunctionalTest.swift b/Stripe/StripeiOSTests/STPPinManagementServiceFunctionalTest.swift new file mode 100644 index 00000000..8f011b14 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPinManagementServiceFunctionalTest.swift @@ -0,0 +1,270 @@ +// +// STPPinManagementServiceFunctionalTest.swift +// StripeiOS Tests +// +// Created by Arnaud Cavailhez on 4/29/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import PassKit +import XCTest + +import OHHTTPStubs +import OHHTTPStubsSwift +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class TestEphemeralKeyProvider: NSObject, STPIssuingCardEphemeralKeyProvider { + func createIssuingCardKey( + withAPIVersion apiVersion: String, + completion: STPJSONResponseCompletionBlock + ) { + print("apiVersion \(apiVersion)") + let response = + [ + "id": "ephkey_token", + "object": "ephemeral_key", + "associated_objects": [ + [ + "type": "issuing.card", + "id": "ic_token", + ], + ], + "created": NSNumber(value: 1_556_656_558), + "expires": NSNumber(value: 1_556_660_158), + "livemode": NSNumber(value: true), + "secret": "ek_live_secret", + ] as [String: Any] + completion(response, nil) + } +} + +class STPPinManagementServiceFunctionalTest: APIStubbedTestCase { + + func testRetrievePin() { + let keyProvider = TestEphemeralKeyProvider() + let service = STPPinManagementService(keyProvider: keyProvider) + + let expectation = self.expectation(description: "Received PIN") + + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/v1/issuing/cards/ic_token/pin") ?? false + } response: { _ in + let pinResponseJSON = """ + { + "pin" : "2345", + "object" : "issuing.card_pin", + "card" : { + "id" : "ic_token", + "last4" : "1234", + "livemode" : true, + "shipping" : null, + "metadata" : { + + }, + "brand" : "Visa", + "authorization_controls" : { + "max_approvals" : null, + "currency" : null, + "allowed_categories" : null, + "spending_limits" : null, + "blocked_categories" : null, + "max_amount" : null + }, + "type" : "virtual", + "cardholder" : { + "id" : "ich_token", + "livemode" : true, + "phone_number" : "+1415", + "metadata" : { + + }, + "authorization_controls" : { + "blocked_categories" : [ + + ], + "spending_limits" : [ + + ], + "allowed_categories" : [ + + ] + }, + "type" : "individual", + "object" : "issuing.cardholder", + "billing" : { + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "123", + "city" : "San Francisco", + "line1" : "510 Townsend St", + "postal_code" : "94103" + }, + "name" : "Arnaud Cavailhez" + }, + "created" : 1536780742, + "is_default" : false, + "email" : "acavailhez@stripe.com", + "name" : "Arnaud Cavailhez", + "status" : "active" + }, + "object" : "issuing.card", + "exp_month" : 9, + "exp_year" : 2021, + "created" : 1536781947, + "currency" : "usd", + "name" : "Arnaud Cavailhez", + "status" : "active" + } + } + """ + return HTTPStubsResponse(data: pinResponseJSON.data(using: .utf8)!, statusCode: 200, headers: nil) + } + + service.retrievePin( + "ic_token", + verificationId: "iv_token", + oneTimeCode: "123456" + ) { cardPin, status, error in + if error == nil && status == .success && (cardPin?.pin == "2345") { + expectation.fulfill() + } + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testUpdatePin() { + let keyProvider = TestEphemeralKeyProvider() + let service = STPPinManagementService(keyProvider: keyProvider) + + let expectation = self.expectation(description: "Received PIN") + + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/v1/issuing/cards/ic_token/pin") ?? false + } response: { _ in + let pinResponseJSON = """ + { + "pin" : "3456", + "object" : "issuing.card_pin", + "card" : { + "id" : "ic_token", + "last4" : "1234", + "livemode" : true, + "replacement_for" : null, + "metadata" : { + + }, + "brand" : "Visa", + "shipping" : null, + "authorization_controls" : { + "max_approvals" : null, + "currency" : null, + "allowed_categories" : null, + "spending_limits" : null, + "blocked_categories" : null, + "max_amount" : null + }, + "replacement_reason" : null, + "type" : "virtual", + "cardholder" : { + "id" : "ich_token", + "livemode" : true, + "phone_number" : "+1415", + "metadata" : { + + }, + "authorization_controls" : { + "blocked_categories" : [ + + ], + "spending_limits" : [ + + ], + "allowed_categories" : [ + + ] + }, + "type" : "individual", + "object" : "issuing.cardholder", + "billing" : { + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "123", + "city" : "San Francisco", + "line1" : "510 Townsend St", + "postal_code" : "94103" + }, + "name" : "Arnaud Cavailhez" + }, + "created" : 1536780742, + "is_default" : false, + "email" : "acavailhez@stripe.com", + "name" : "Arnaud Cavailhez", + "status" : "active" + }, + "object" : "issuing.card", + "exp_month" : 9, + "exp_year" : 2021, + "created" : 1536781947, + "currency" : "usd", + "name" : "Arnaud Cavailhez", + "status" : "active" + } + } + """ + return HTTPStubsResponse(data: pinResponseJSON.data(using: .utf8)!, statusCode: 200, headers: nil) + } + + service.updatePin( + "ic_token", + newPin: "3456", + verificationId: "iv_token", + oneTimeCode: "123-456" + ) { cardPin, status, error in + if error == nil && status == .success && (cardPin?.pin == "3456") { + expectation.fulfill() + } + } + waitForExpectations(timeout: 5.0, handler: nil) + } + + func testRetrievePinWithError() { + let keyProvider = TestEphemeralKeyProvider() + let service = STPPinManagementService(keyProvider: keyProvider) + + let expectation = self.expectation(description: "Received Error") + + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/v1/issuing/cards/ic_token/pin") ?? false + } response: { _ in + let pinResponseJSON = """ + { + "error" : { + "message" : "Verification challenge does not exist or is already redeemed", + "type" : "invalid_request_error", + "code" : "already_redeemed" + } + } + """ + return HTTPStubsResponse(data: pinResponseJSON.data(using: .utf8)!, statusCode: 400, headers: nil) + } + + service.retrievePin( + "ic_token", + verificationId: "iv_token", + oneTimeCode: "123456" + ) { _, status, _ in + if status == .errorVerificationAlreadyRedeemed { + expectation.fulfill() + } + } + waitForExpectations(timeout: 5.0, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldFormatterTests.swift b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldFormatterTests.swift new file mode 100644 index 00000000..5a664d9e --- /dev/null +++ b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldFormatterTests.swift @@ -0,0 +1,58 @@ +// +// STPPostalCodeInputTextFieldFormatterTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/30/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPostalCodeInputTextFieldFormatterTests: XCTestCase { + + func testIsAllowedInput() { + let formatter = STPPostalCodeInputTextFieldFormatter() + formatter.countryCode = "US" + XCTAssertTrue(formatter.isAllowedInput("10002", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("21218", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("10002-1234", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("100021234", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("ABC10002", to: "", at: NSRange(location: 0, length: 0))) + + XCTAssertFalse(formatter.isAllowedInput("1", to: "100021234", at: NSRange(location: 10, length: 0))) + XCTAssertFalse(formatter.isAllowedInput("1", to: "10002", at: NSRange(location: 4, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("1", to: "1000", at: NSRange(location: 4, length: 0))) + + formatter.countryCode = "UK" + XCTAssertTrue(formatter.isAllowedInput("10002-1234", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("100021234", to: "", at: NSRange(location: 0, length: 0))) + XCTAssertTrue(formatter.isAllowedInput("ABC10002", to: "", at: NSRange(location: 0, length: 0))) + } + + func testFormattedString() { + let formatter = STPPostalCodeInputTextFieldFormatter() + formatter.countryCode = "US" + + XCTAssertEqual( + NSAttributedString(string: ""), + formatter.formattedText(from: "- ", with: [:]) + ) + XCTAssertEqual( + NSAttributedString(string: "10002"), + formatter.formattedText(from: "10002-1234", with: [:]) + ) + + formatter.countryCode = "UK" + XCTAssertEqual( + NSAttributedString(string: "A B C D E F G"), + formatter.formattedText(from: " a b c d e f g", with: [:]) + ) + } + +} diff --git a/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldSnapshotTests.swift b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldSnapshotTests.swift new file mode 100644 index 00000000..90801be6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldSnapshotTests.swift @@ -0,0 +1,73 @@ +// +// STPPostalCodeInputTextFieldSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/30/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPostalCodeInputTextFieldSnapshotTests: STPSnapshotTestCase { + + func testEmpty() { + let field = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + field.sizeToFit() + field.frame.size.width = 200 + + STPSnapshotVerifyView(field) + } + + func testIncomplete() { + let field = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + field.sizeToFit() + field.frame.size.width = 200 + field.countryCode = "US" + field.text = "1" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testValidUS() { + let field = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + field.sizeToFit() + field.frame.size.width = 200 + field.countryCode = "US" + field.text = "12345" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testValidUK() { + let field = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + field.sizeToFit() + field.frame.size.width = 200 + field.countryCode = "UK" + field.text = "abcdef" + field.textDidChange() + + STPSnapshotVerifyView(field) + } + + func testInvalid() { + let field = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + field.sizeToFit() + field.frame.size.width = 200 + field.countryCode = "US" + field.text = "12-3456789" + field.textDidChange() + // manually set because the formatter prevents setting invalid text + field.validator.validationState = .invalid(errorMessage: nil) + + STPSnapshotVerifyView(field) + } +} diff --git a/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldTests.swift b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldTests.swift new file mode 100644 index 00000000..d9ce772a --- /dev/null +++ b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldTests.swift @@ -0,0 +1,69 @@ +// +// STPPostalCodeInputTextFieldTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 9/3/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPostalCodeInputTextFieldTests: XCTestCase { + + func testClearingInvalidPostalCodeAfterCountryChange() { + let postalCodeField = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + postalCodeField.countryCode = "UK" + postalCodeField.text = "DL12" // valid UK post code, invalid US ZIP Code + + // Change country + postalCodeField.countryCode = "US" + + XCTAssertEqual( + postalCodeField.text, + "", + "Postal code field should clear its value if no longer valid after country change" + ) + } + + func testPreservingValidPostalCodeAfterCountryChange() { + let postalCodeField = STPPostalCodeInputTextField(postalCodeRequirement: .standard) + postalCodeField.countryCode = "US" + postalCodeField.text = "10010" // valid US and HR ZIP/postal code + + // Change country + postalCodeField.countryCode = "HR" + + XCTAssertEqual( + postalCodeField.text, + "10010", + "Postal code field should preserve its value if it is still valid after country change" + ) + } + + func testChangeToNonRequiredPostalCodeIsValid() { + let postalCodeField = STPPostalCodeInputTextField(postalCodeRequirement: .upe) + // given that the postal code field is empty... + + // when + postalCodeField.countryCode = "US" + if case .incomplete = postalCodeField.validationState { + // pass + } else { + XCTFail("Empty postal code should be incomplete for US") + } + + // when + postalCodeField.countryCode = "FR" + if case .valid = postalCodeField.validationState { + // pass + } else { + XCTFail("Empty postal code should be valid for non-required country") + } + } +} diff --git a/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldValidatorTests.swift b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldValidatorTests.swift new file mode 100644 index 00000000..edee1d92 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPostalCodeInputTextFieldValidatorTests.swift @@ -0,0 +1,79 @@ +// +// STPPostalCodeInputTextFieldValidatorTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/30/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPostalCodeInputTextFieldValidatorTests: XCTestCase { + + func testValidation() { + let validator = STPPostalCodeInputTextFieldValidator(postalCodeRequirement: .standard) + validator.countryCode = "US" + + validator.inputValue = nil + XCTAssertEqual( + STPValidatedInputState.incomplete(description: nil), + validator.validationState + ) + + validator.inputValue = "" + XCTAssertEqual( + STPValidatedInputState.incomplete(description: nil), + validator.validationState + ) + + validator.inputValue = "1234" + XCTAssertEqual( + STPValidatedInputState.incomplete(description: "Your ZIP is incomplete."), + validator.validationState + ) + + validator.inputValue = "12345" + XCTAssertEqual(STPValidatedInputState.valid(message: nil), validator.validationState) + + validator.inputValue = "12345678" + XCTAssertEqual( + STPValidatedInputState.incomplete(description: "Your ZIP is incomplete."), + validator.validationState + ) + + validator.inputValue = "123456789" + XCTAssertEqual(STPValidatedInputState.valid(message: nil), validator.validationState) + + validator.inputValue = "12345-6789" + XCTAssertEqual(STPValidatedInputState.valid(message: nil), validator.validationState) + + validator.inputValue = "12-3456789" + XCTAssertEqual( + STPValidatedInputState.invalid(errorMessage: "Your ZIP is invalid."), + validator.validationState + ) + + validator.inputValue = "12345-" + XCTAssertEqual( + STPValidatedInputState.incomplete(description: "Your ZIP is incomplete."), + validator.validationState + ) + + validator.inputValue = "hi" + XCTAssertEqual( + STPValidatedInputState.invalid(errorMessage: "Your ZIP is invalid."), + validator.validationState + ) + + validator.countryCode = "UK" + validator.inputValue = "hi" + XCTAssertEqual(STPValidatedInputState.valid(message: nil), validator.validationState) + } + +} diff --git a/Stripe/StripeiOSTests/STPPostalCodeValidatorTest.swift b/Stripe/StripeiOSTests/STPPostalCodeValidatorTest.swift new file mode 100644 index 00000000..04c47d73 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPostalCodeValidatorTest.swift @@ -0,0 +1,103 @@ +// +// STPPostalCodeValidatorTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 4/14/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPPostalCodeValidatorTest: XCTestCase { + func testValidUSPostalCodes() { + let codes = ["10002", "10002-1234", "100021234", "21218"] + for code in codes { + XCTAssertEqual( + STPPostalCodeValidator.validationState( + forPostalCode: code, + countryCode: "US" + ), + .valid + ) + } + } + + func testInvalidUSPostalCodes() { + let codes = ["100A03", "12345-12345", "1234512345", "$$$$$", "foo"] + for code in codes { + XCTAssertEqual( + STPPostalCodeValidator.validationState( + forPostalCode: code, + countryCode: "US" + ), + .invalid + ) + } + } + + func testIncompleteUSPostalCodes() { + let codes = ["", "123", "12345-", "12345-12"] + for code in codes { + XCTAssertEqual( + STPPostalCodeValidator.validationState( + forPostalCode: code, + countryCode: "US" + ), + .incomplete + ) + } + } + + func testValidGenericPostalCodes() { + let codes = ["ABC10002", "10002-ABCD", "ABCDE"] + for code in codes { + XCTAssertEqual( + STPPostalCodeValidator.validationState( + forPostalCode: code, + countryCode: "UK" + ), + .valid + ) + } + } + + func testIncompleteGenericPostalCodes() { + let codes = [""] + for code in codes { + XCTAssertEqual( + STPPostalCodeValidator.validationState( + forPostalCode: code, + countryCode: "UK" + ), + .incomplete + ) + } + } + + func testPostalCodeIsRequiredForUPE_nil() { + XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequiredForUPE(forCountryCode: nil)) + } + + func testPostalCodeIsRequiredForUPE_empty() { + XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequiredForUPE(forCountryCode: "")) + } + + func testPostalCodeIsRequiredForUPE_CA() { + XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequiredForUPE(forCountryCode: "CA")) + } + + func testPostalCodeIsRequiredForUPE_GB() { + XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequiredForUPE(forCountryCode: "GB")) + } + + func testPostalCodeIsRequiredForUPE_US() { + XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequiredForUPE(forCountryCode: "CA")) + } + + func testPostalCodeIsRequiredForUPE_DK() { + XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequiredForUPE(forCountryCode: "DK")) + } +} diff --git a/Stripe/StripeiOSTests/STPPushProvisioningDetailsFunctionalTest.swift b/Stripe/StripeiOSTests/STPPushProvisioningDetailsFunctionalTest.swift new file mode 100644 index 00000000..6ccf1578 --- /dev/null +++ b/Stripe/StripeiOSTests/STPPushProvisioningDetailsFunctionalTest.swift @@ -0,0 +1,73 @@ +// +// STPPushProvisioningDetailsFunctionalTest.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 11/30/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import Stripe + +import OHHTTPStubs +import OHHTTPStubsSwift +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +import StripeCoreTestUtils +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPPushProvisioningDetailsFunctionalTest: APIStubbedTestCase { + + func testRetrievePushProvisioningDetails() { + // this API requires a secret key - replace the key below if you need to re-record the network traffic. + let client = STPAPIClient(publishableKey: "pk_test_REPLACEME") + let cardId = "ic_1C0Xig4JYtv6MPZK91WoXa9u" + let cert1 = + "MIID/TCCA6OgAwIBAgIIGM2CpiS9WyYwCgYIKoZIzj0EAwIwgYAxNDAyBgNVBAMMK0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zIENBIC0gRzIxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xODA2MDEyMjE0MTVaFw0yMDA2MzAyMjE0MTVaMGwxMjAwBgNVBAMMKWVjYy1jcnlwdG8tc2VydmljZXMtZW5jaXBoZXJtZW50X1VDNi1QUk9EMRQwEgYDVQQLDAtpT1MgU3lzdGVtczETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASzCVyQGX3syyW2aI6nyfNQe+vjjzjU4rLO0ZiWiVZZSmEzYfACFI8tuDFiDLv9XWrHEeX0/yNtGVjwAzpanWb/o4ICGDCCAhQwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSEtoTMOoZichZZlOgao71I3zrfCzBHBggrBgEFBQcBAQQ7MDkwNwYIKwYBBQUHMAGGK2h0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtYXBwbGV3d2RyY2EyMDUwggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxld3dkcmNhMi5jcmwwHQYDVR0OBBYEFI5aYtQKaJCRpvI1Dgh+Ra4x2iCrMA4GA1UdDwEB/wQEAwIDKDASBgkqhkiG92NkBicBAf8EAgUAMAoGCCqGSM49BAMCA0gAMEUCIAY/9gwN/KAAw3EtW3NyeX1UVM3fO+wVt0cbeHL8eM/mAiEAppLm5O/2Ox8uHkxI4U/kU5vDhJA21DRbzm2rsYN+EcQ=" + let cert2 = + "MIIC9zCCAnygAwIBAgIIb+/Y9emjp+4wCgYIKoZIzj0EAwIwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNTA2MjM0MzI0WhcNMjkwNTA2MjM0MzI0WjCBgDE0MDIGA1UEAwwrQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ0EgLSBHMjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3fC3BkvP3XMEE8RDiQOTgPte9nStQmFSWAImUxnIYyIHCVJhysTZV+9tJmiLdJGMxPmAaCj8CWjwENrp0C7JGqOB9zCB9DBGBggrBgEFBQcBAQQ6MDgwNgYIKwYBBQUHMAGGKmh0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDQtYXBwbGVyb290Y2FnMzAdBgNVHQ4EFgQUhLaEzDqGYnIWWZToGqO9SN863wswDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS7sN6hWDOImqSKmd6+veuv2sskqzA3BgNVHR8EMDAuMCygKqAohiZodHRwOi8vY3JsLmFwcGxlLmNvbS9hcHBsZXJvb3RjYWczLmNybDAOBgNVHQ8BAf8EBAMCAQYwEAYKKoZIhvdjZAYCDwQCBQAwCgYIKoZIzj0EAwIDaQAwZgIxANmxxzHGI/ZPTdDZR8V9GGkRh3En02it4Jtlmr5s3z9GppAJvm6hOyywUYlBPIfSvwIxAPxkUolLPF2/axzCiZgvcq61m6oaCyNUd1ToFUOixRLal1BzfF7QbrJcYlDXUfE6Wg==" + let nonce = "ea85a73a" + let nonceSignature = + "QBfCqTvDhmRcwqxJF3fDqzhXezIpwrpHFcOMw7/DvGVBwpfCuicwwqHCmMKYMD06w754wrjChcObwqjDr8K9wqxxUydQaMOyfsKGZMK4AcKMwqNfwoHDlcKLHsO5w7JqQiHDln7Du8KUNMOnwqpGwq/CqcKswo1Lw7s=" + let certs: [Data] = [ + Data(base64Encoded: cert1, options: [])!, + Data(base64Encoded: cert2, options: [])!, + ] + let expectation = self.expectation(description: "Push provisioning details") + + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/v1/issuing/cards/ic_1C0Xig4JYtv6MPZK91WoXa9u/push_provisioning_details") ?? false + } response: { _ in + let pushDetailsResponseJSON = """ + { + "activation_data" : "TUJQQUMtMS1GSy00MDU1NTEuMS0tVERFQS1FNUVCNUJCQjhGMENDMjdCQjU5MUNCRTdCMTVDN0U2RjBENTQ5RDM1NkZERTRDQUZBNDBGOUNCMTA1MzI5NUQ4N0RBRTk0MTE0QTQyNENDQTY0NDAxRTFCQTExOTRBRDM=", + "object" : "issuing.push_provisioning_details", + "ephemeral_public_key" : "BHdLb5BNpoPrh9Btay8LwQ5oELoziQwJL7HagE3xB5mrbdgLa5iiwogu34Y32\\/xBEviaN31s\\/ONRXSetYT745ig=", + "card" : "ic_1C0Xig4JYtv6MPZK91WoXa9u", + "contents" : "UYeMxRqiYrjVzwqKcCGRVbgFXRspbDIKkWWly8e55caWkHYmO2DNnFtqD3y5S6bvGbe+bkNPCIkT1UzQQChCQOkb0P+cbVDBLaFGaDeJW0Mhca8\\/6GwbUx9lp4H3czqszG504PkiA89dNvnbtwUmlQOpk+B\\/IAMnkaXjD2xUBUtPX9xEr5EvckkDSHHFmpy5rbGfqnWsPbJNPwUiE+6mYbt643DqF9RpmgdFN84DImuMU1W0xshbkN7voq63L\\/6UgTW7liTzWVzKUT36TtaTw5TGKVf1Niqu5CHNu2NpDEnzrvcwUCgphxRVgezJyFfq1NjhZVlGA2nUKZuRvc\\/XjBwdE0fr4Enw5XfHbRQHorpv\\/S2rX4Cmn4VHJE1JDHWK3Wrn4HmMOCH+psVi+T5hPvy6+\\/v+0zRRmGGeFKQEx0soItHiQauN2\\/zO4QoC2DCQOAKGj1KSzqHhTgdxBcBu4TIOQRsIXu6zk1ItenHIdq4thD8vF8m3wgJ8Y3KcG3TwwgbjomxOjO4rX0AA9q2V6w0TXWQ1eWC8WfX11J30Zt\\/SbyYHoU8KrIdM2ANcOvIFHENnUNBcL7AO0+tjv9lVO7M9w7hKMiVJOnDGMeH2OQfnTBOJI8SEHGm2kDRNw\\/5+VGJ7pHA1wZ9y2IS6EvY8IeNhqZ7HdBXhn18X4AWThZqmNvGuZoEU\\/ZlPmfed\\/c0BgqSw5x6K9GuC5G9Nyee3O1E6wETf4Z3goNbFnRYNJ8m+A4DxUKbvhhRSNva4d\\/lRkoNuQj2ztPiLSAPVt1H7Dk3ryeyXCrqprHsjn5T35jXFOlm5whuyeGgeXWt3DUxsYXZGTiohZyU3bZELqW3EUSwjwQ==" + } + """ + return HTTPStubsResponse(data: pushDetailsResponseJSON.data(using: .utf8)!, statusCode: 200, headers: nil) + } + + let params = STPPushProvisioningDetailsParams( + cardId: "ic_1C0Xig4JYtv6MPZK91WoXa9u", + certificates: certs, + nonce: Data(base64Encoded: nonce, options: [])!, + nonceSignature: Data(base64Encoded: nonceSignature, options: [])! + ) + // To re-record this test, get an ephemeral key for the above Issuing card and pass that instead of [STPFixtures ephemeralKey] + let ephemeralKey = STPFixtures.ephemeralKey() + client.retrievePushProvisioningDetails(with: params, ephemeralKey: ephemeralKey) { + details, + error in + expectation.fulfill() + XCTAssertNil(error) + XCTAssert((details?.cardId == cardId)) + XCTAssertEqual(details, details) + } + waitForExpectations(timeout: 5.0, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPRadarSessionFunctionalTest.swift b/Stripe/StripeiOSTests/STPRadarSessionFunctionalTest.swift new file mode 100644 index 00000000..29bfe220 --- /dev/null +++ b/Stripe/StripeiOSTests/STPRadarSessionFunctionalTest.swift @@ -0,0 +1,57 @@ +// +// STPRadarSessionFunctionalTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/20/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +import StripePaymentsTestUtils +@testable@_spi(STP) import StripePaymentsUI + +class STPRadarSessionFunctionalTest: XCTestCase { + func testCreateWithoutInitialFraudDetection() { + // When fraudDetectionData is empty... + FraudDetectionData.shared.reset() + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let exp1 = expectation(description: "Create RadarSession") + client.createRadarSession { session, error in + // ...creates a Radar Session + XCTAssertNil(error) + guard let session = session else { + XCTFail() + return + } + XCTAssertTrue(session.id.count > 0) + exp1.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + + // Now that fraudDetectionData is populated... + XCTAssertNotNil(FraudDetectionData.shared.sid) + XCTAssertNotNil(FraudDetectionData.shared.muid) + XCTAssertNotNil(FraudDetectionData.shared.guid) + + let exp2 = expectation(description: "Create RadarSession again") + client.createRadarSession { session, error in + // ...still creates a Radar Session + XCTAssertNil(error) + guard let session = session else { + XCTFail() + return + } + XCTAssertTrue(session.id.count > 0) + exp2.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPRedirectContextTest.swift b/Stripe/StripeiOSTests/STPRedirectContextTest.swift new file mode 100644 index 00000000..608bd52a --- /dev/null +++ b/Stripe/StripeiOSTests/STPRedirectContextTest.swift @@ -0,0 +1,625 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPRedirectContextTest.m +// Stripe +// +// Created by Ben Guo on 4/6/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import SafariServices +@_spi(STP) @testable import StripeCore +@testable import StripePayments + +class MockUIViewController: UIViewController { + var presentChecker: (UIViewController) -> Bool = { _ in return true } + var presentCalled: Bool = false + var dismiss: () -> Void = { } + override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + presentCalled = true + if !presentChecker(viewControllerToPresent) { + XCTFail("checker failed") + } + super.present(viewControllerToPresent, animated: flag, completion: completion) + } + + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + super.dismiss(animated: flag, completion: completion) + dismiss() + } +} + +class MockUIApplication: UIApplicationProtocol { + var openHandler: (URL, ((Bool) -> Void)?) -> Void = { _, completion in completion?(true) } + var openCalled: Bool = false + + func _open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey: Any], completionHandler completion: ((Bool) -> Void)?) { + openCalled = true + openHandler(url, completion) + } +} + +/* + NOTE: + + If you are adding a test make sure your context unsubscribes from notifications + before your test ends. Otherwise notifications fired from other tests can cause + a reaction in an earlier, completed test and cause strange failures. + + Possible ways to do this: + 1. Your sut should already be calling unsubscribe, verified by OCMVerify + - you're good + 2. Your sut doesn't call unsubscribe as part of the test but it's not explicitly + disallowed - call [sut unsubscribeFromNotifications] at the end of your test + 3. Your sut doesn't call unsubscribe and you explicitly OCMReject it firing + - call [self unsubscribeContext:context] at the end of your test (use + the original context object here and _NOT_ the sut or it will not work). + */ +class STPRedirectContextTest: XCTestCase { + weak var weak_sut: STPRedirectContext? + + /// Use this to unsubscribe a context from notifications without calling + /// `sut.unsubscrbeFromNotifications` if you have OCMReject'd that method and thus + /// can't call it. + /// Note: You MUST pass in the actual context object here and not the mock or the + /// unsubscibe will silently fail. + func unsubscribeContext(_ context: STPRedirectContext?) { + if let context { + NotificationCenter.default.removeObserver( + context, + name: UIApplication.didBecomeActiveNotification, + object: nil) + STPURLCallbackHandler.shared().unregisterListener(context) + } + } + + func testInitWithNonRedirectSourceReturnsNil() { + let source = STPFixtures.cardSource() + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion was called") + } + XCTAssertNil(sut) + } + + func testInitWithConsumedSourceReturnsNil() { + var json = STPTestUtils.jsonNamed(STPTestJSONSourceCard) + json?["status"] = "consumed" + let source = STPSource.decodedObject(fromAPIResponse: json)! + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion was called") + } + XCTAssertNil(sut) + } + + func testInitWithSource() { + let source = STPFixtures.iDEALSource() + var completionCalled = false + let fakeError = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil) + + let sut = STPRedirectContext(source: source) { sourceID, clientSecret, error in + XCTAssertEqual(source.stripeID, sourceID) + XCTAssertEqual(source.clientSecret, clientSecret) + XCTAssertEqual(error! as NSError, fakeError, "Should be the same NSError object passed to completion() below") + completionCalled = true + } + + // Make sure the initWithSource: method pulled out the right values from the Source + XCTAssertNil(sut?.nativeRedirectURL) + XCTAssertEqual(sut?.redirectURL, source.redirect?.url) + XCTAssertEqual(sut?.returnURL, source.redirect?.returnURL) + + // and make sure the completion calls the completion block above + sut?.completion(fakeError) + XCTAssertTrue(completionCalled) + } + + func testInitWithSourceWithNativeURL() { + let source = STPFixtures.alipaySourceWithNativeURL() + var completionCalled = false + let nativeURL = URL(string: source.details?["native_url"] as? String ?? "") + let fakeError = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil) + + let sut = STPRedirectContext(source: source) { sourceID, clientSecret, error in + XCTAssertEqual(source.stripeID, sourceID) + XCTAssertEqual(source.clientSecret, clientSecret) + XCTAssertEqual(error! as NSError, fakeError, "Should be the same NSError object passed to completion() below") + completionCalled = true + } + + // Make sure the initWithSource: method pulled out the right values from the Source + XCTAssertEqual(sut?.nativeRedirectURL, nativeURL) + XCTAssertEqual(sut?.redirectURL, source.redirect?.url) + XCTAssertEqual(sut?.returnURL, source.redirect?.returnURL) + + // and make sure the completion calls the completion block above + sut?.completion(fakeError) + XCTAssertTrue(completionCalled) + } + + func testInitWithPaymentIntent() { + let paymentIntent = STPFixtures.paymentIntent() + var completionCalled = false + let fakeError = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil) + + let sut = STPRedirectContext(paymentIntent: paymentIntent) { clientSecret, error in + XCTAssertEqual(paymentIntent.clientSecret, clientSecret) + XCTAssertEqual(error! as NSError, fakeError, "Should be the same NSError object passed to completion() below") + completionCalled = true + } + + // Make sure the initWithPaymentIntent: method pulled out the right values from the PaymentIntent + XCTAssertNil(sut?.nativeRedirectURL) + XCTAssertEqual( + sut?.redirectURL?.absoluteString, + "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk") + + // `nextSourceAction` & `authorizeWithURL` should just be aliases for `nextAction` & `redirectToURL`, already tested in `STPPaymentIntentTest` + XCTAssertNotNil(paymentIntent.nextAction?.redirectToURL?.returnURL) + XCTAssertEqual(sut?.returnURL, paymentIntent.nextAction?.redirectToURL?.returnURL) + + // and make sure the completion calls the completion block above + XCTAssertNotNil(sut) + sut?.completion(fakeError) + XCTAssertTrue(completionCalled) + } + + func testInitWithPaymentIntentFailures() { + // Note next_action has been renamed to next_source_action in the API, but both still get sent down in the 2015-10-12 API + let unusedCompletion: ((String?, Error?) -> Void) = { _, _ in + XCTFail("should not be constructed, definitely not completed") + } + + let create: (([AnyHashable: Any]) -> STPRedirectContext?) = { json in + let paymentIntent = STPPaymentIntent.decodedObject(fromAPIResponse: json)! + return STPRedirectContext( + paymentIntent: paymentIntent, + completion: unusedCompletion) + } + + var json = STPTestUtils.jsonNamed(STPTestJSONPaymentIntent)! + XCTAssertNotNil(create(json), "before mutation of json, creation should succeed") + + json["status"] = "processing" + XCTAssertNil(create(json), "not created with wrong status") + json["status"] = "requires_action" + + json[jsonDict: "next_action"]?["type"] = "not_redirect_to_url" + XCTAssertNil(create(json), "not created with wrong next_action.type") + json[jsonDict: "next_action"]?["type"] = "redirect_to_url" + + let correctURL = json[jsonDict: "next_action"]?[jsonDict: "redirect_to_url"]?["url"] as? String + json[jsonDict: "next_action"]?[jsonDict: "redirect_to_url"]?["url"] = "not a valid URL" + XCTAssertNil(create(json), "not created with an invalid URL in next_action.redirect_to_url.url") + json[jsonDict: "next_action"]?[jsonDict: "redirect_to_url"]?["url"] = correctURL ?? "" + + let correctReturnURL = json[jsonDict: "next_action"]?[jsonDict: "redirect_to_url"]?["return_url"] as? String + json[jsonDict: "next_action"]?[jsonDict: "redirect_to_url"]?["return_url"] = "not a url" + XCTAssertNil(create(json), "not created with invalid returnUrl") + json[jsonDict: "next_action"]?[jsonDict: "redirect_to_url"]?["return_url"] = correctReturnURL ?? "" + + XCTAssertNotNil(create(json), "works again when everything is back to normal") + } + + /// After starting a SafariViewController redirect flow, + /// when a DidBecomeActive notification is posted, RedirectContext's completion + /// block and dismiss method should _NOT_ be called. + func testSafariViewControllerRedirectFlow_activeNotification() { + let mockVC = MockUIViewController() + mockVC.presentChecker = { vc in + if vc is SFSafariViewController { + return true + } + return false + } + + let source = STPFixtures.iDEALSource() + + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion called") + }! + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) + unsubscribeContext(sut) + XCTAssertFalse(sut._unsubscribeFromNotificationsCalled) + XCTAssertFalse(sut._dismissPresentedViewControllerCalled) + XCTAssertTrue(mockVC.presentCalled) + } + + /// After starting a SafariViewController redirect flow, + /// when the shared URLCallbackHandler is called with a valid URL, + /// RedirectContext's completion block and dismiss method should be called. + func testSafariViewControllerRedirectFlow_callbackHandlerCalledValidURL() { + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + mockVC.presentChecker = { vc in + if vc is SFSafariViewController { + let url = source.redirect!.returnURL + let comps = NSURLComponents(url: url, resolvingAgainstBaseURL: false)! + comps.stp_queryItemsDictionary = + [ + "source": source.stripeID, + "client_secret": source.clientSecret!, + ] + STPURLCallbackHandler.shared().handleURLCallback(comps.url!) + return true + } + return false + } + let exp = expectation(description: "completion") + let sut = STPRedirectContext(source: source) { sourceID, clientSecret, error in + XCTAssertEqual(sourceID, source.stripeID) + XCTAssertEqual(clientSecret, source.clientSecret) + XCTAssertNil(error) + exp.fulfill() + }! + XCTAssertEqual(source.redirect?.returnURL, sut.returnURL) + sut._handleRedirectCompletionWithErrorHook = { shouldDismissViewController in + if shouldDismissViewController { + sut.safariViewControllerDidCompleteDismissal(SFSafariViewController(url: URL(string: "https://www.stripe.com")!)) + } + } + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + XCTAssertTrue(sut._unsubscribeFromNotificationsCalled) + XCTAssertTrue(sut._dismissPresentedViewControllerCalled) + XCTAssertTrue(mockVC.presentCalled) + waitForExpectations(timeout: 2, handler: nil) + } + + /// After starting a SafariViewController redirect flow, + /// when the shared URLCallbackHandler is called with an invalid URL, + /// RedirectContext's completion block and dismiss method should not be called. + func testSafariViewControllerRedirectFlow_callbackHandlerCalledInvalidURL() { + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion called") + }! + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + + mockVC.presentChecker = { vc in + if vc is SFSafariViewController { + let url = URL(string: "my-app://some_path")! + XCTAssertNotEqual(url, sut.returnURL) + STPURLCallbackHandler.shared().handleURLCallback(url) + return true + } + return false + } + + XCTAssertFalse(sut._unsubscribeFromNotificationsCalled) + XCTAssertFalse(sut._dismissPresentedViewControllerCalled) + XCTAssertTrue(mockVC.presentCalled) + unsubscribeContext(sut) + } + + /// After starting a SafariViewController redirect flow, + /// when SafariViewController finishes, RedirectContext's completion block + /// should be called. + func testSafariViewControllerRedirectFlow_didFinish() { + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + + let exp = expectation(description: "completion") + let sut: STPRedirectContext = STPRedirectContext(source: source) { sourceID, clientSecret, error in + XCTAssertEqual(sourceID, source.stripeID) + XCTAssertEqual(clientSecret, source.clientSecret) + // because we are manually invoking the dismissal, we report this as a cancelation + let error = error! as NSError + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.cancellationError.rawValue) + exp.fulfill() + }! + + sut._handleRedirectCompletionWithErrorHook = { shouldDismissViewController in + if !shouldDismissViewController { + sut.safariViewControllerDidCompleteDismissal(SFSafariViewController(url: URL(string: "https://www.stripe.com")!)) + } + } + mockVC.presentChecker = { vc in + if vc is SFSafariViewController { + let sfvc = vc as? SFSafariViewController + if let sfvc { + sfvc.delegate?.safariViewControllerDidFinish?(sfvc) + } + return true + } + return false + } + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + + // dismiss should not be called – SafariVC dismisses itself when Done is tapped + XCTAssertFalse(sut._dismissPresentedViewControllerCalled) + XCTAssertTrue(sut._unsubscribeFromNotificationsCalled) + waitForExpectations(timeout: 2, handler: nil) + } + + /// After starting a SafariViewController redirect flow, + /// when SafariViewController fails to load the initial page (on iOS 11+ & without redirects), + /// RedirectContext's completion block and dismiss method should be called. + func testSafariViewControllerRedirectFlow_failedInitialLoad_iOS11Plus() { + + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + let exp = expectation(description: "completion") + let sut = STPRedirectContext(source: source) { sourceID, clientSecret, error in + XCTAssertEqual(sourceID, source.stripeID) + XCTAssertEqual(clientSecret, source.clientSecret) + let expectedError = NSError.stp_genericConnectionError() + XCTAssertEqual(error! as NSError, expectedError) + exp.fulfill() + }! + + sut._handleRedirectCompletionWithErrorHook = { shouldDismissViewController in + if shouldDismissViewController { + sut.safariViewControllerDidCompleteDismissal(SFSafariViewController(url: URL(string: "https://www.stripe.com")!)) + } + } + + mockVC.presentChecker = { vc in + if vc is SFSafariViewController { + let sfvc = vc as? SFSafariViewController + if let sfvc { + sfvc.delegate?.safariViewController?(sfvc, didCompleteInitialLoad: false) + } + return true + } + return false + } + sut.startSafariViewControllerRedirectFlow(from: mockVC) + + XCTAssertTrue(sut._unsubscribeFromNotificationsCalled) + XCTAssertTrue(sut._dismissPresentedViewControllerCalled) + + waitForExpectations(timeout: 2, handler: nil) + } + + /// After starting a SafariViewController redirect flow, + /// when SafariViewController fails to load the initial page (on iOS 11+ after redirecting to non-Stripe page), + /// RedirectContext's completion block should not be called (SFVC keeps loading) + func testSafariViewControllerRedirectFlow_failedInitialLoadAfterRedirect_iOS11Plus() { + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion called") + }! + + XCTAssertFalse(sut._unsubscribeFromNotificationsCalled) // move + XCTAssertFalse(sut._dismissPresentedViewControllerCalled) // move + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + + mockVC.presentChecker = { vc in + if vc is SFSafariViewController { + let sfvc = vc as? SFSafariViewController + // before initial load is done, SFVC was redirected to a non-stripe.com domain + if let sfvc, let url = URL(string: "https://girogate.de") { + sfvc.delegate?.safariViewController?( + sfvc, + initialLoadDidRedirectTo: url) + } + // Tell the delegate that the initial load failed. + // on iOS 11, with the redirect, this is a no-op + if let sfvc { + sfvc.delegate?.safariViewController?(sfvc, didCompleteInitialLoad: false) + } + return true + } + return false + } + unsubscribeContext(sut) + } + + /// After starting a SafariViewController redirect flow, + /// when the RedirectContext is cancelled, its dismiss method should be called. + func testSafariViewControllerRedirectFlow_cancel() { + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion called") + }! + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + sut.cancel() + + XCTAssertTrue(mockVC.presentCalled) + XCTAssertTrue(sut._unsubscribeFromNotificationsCalled) + XCTAssertTrue(sut._dismissPresentedViewControllerCalled) + } + + /// After starting a SafariViewController redirect flow, + /// if no action is taken, nothing should be called. + func testSafariViewControllerRedirectFlow_noAction() { + let mockVC = MockUIViewController() + let source = STPFixtures.iDEALSource() + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion called") + }! + + sut.startSafariViewControllerRedirectFlow(from: mockVC) + XCTAssertTrue(mockVC.presentCalled) + XCTAssertFalse(sut._unsubscribeFromNotificationsCalled) + XCTAssertFalse(sut._dismissPresentedViewControllerCalled) + + unsubscribeContext(sut) + } + + /// After starting a Safari app redirect flow, + /// when a DidBecomeActive notification is posted, RedirectContext's completion + /// block and dismiss method should be called. + func testSafariAppRedirectFlow_activeNotification() { + let source = STPFixtures.iDEALSource() + let exp = expectation(description: "completion") + let sut = STPRedirectContext(source: source) { sourceID, clientSecret, error in + XCTAssertEqual(sourceID, source.stripeID) + XCTAssertEqual(clientSecret, source.clientSecret) + XCTAssertNil(error) + + exp.fulfill() + }! + + sut.startSafariAppRedirectFlow() + NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) + + waitForExpectations(timeout: 2, handler: nil) + XCTAssertTrue(sut._unsubscribeFromNotificationsCalled) + } + + /// After starting a Safari app redirect flow, + /// if no notification is posted, nothing should be called. + func testSafariAppRedirectFlow_noNotification() { + let source = STPFixtures.iDEALSource() + let sut = STPRedirectContext(source: source) { _, _, _ in + XCTFail("completion called") + }! + + sut.startSafariAppRedirectFlow() + XCTAssertFalse(sut._unsubscribeFromNotificationsCalled) + XCTAssertFalse(sut._dismissPresentedViewControllerCalled) + + unsubscribeContext(sut) + } + + /// If a source type that supports native redirect is used and it contains a native + /// url, an app to app redirect should attempt to be initiated. + func testNativeRedirectSupportingSourceFlow_validNativeURL() { + let source = STPFixtures.alipaySourceWithNativeURL() + let sourceURL = URL(string: source.details?["native_url"] as! String)! + + let sut = STPRedirectContext( + source: source) { _, _, _ in + XCTFail("completion called") + }! + + XCTAssertNotNil(sut.nativeRedirectURL) + XCTAssertEqual(sut.nativeRedirectURL, sourceURL) + + let applicationMock = MockUIApplication() + sut.application = applicationMock + applicationMock.openHandler = { url, completion in + XCTAssertTrue(url == sourceURL) + completion?(true) + } + + let mockVC = MockUIViewController() + sut.startRedirectFlow(from: mockVC) + XCTAssertFalse(sut._startSafariAppRedirectFlowCalled) + XCTAssertTrue(applicationMock.openCalled) + sut.unsubscribeFromNotifications() + } + + /// If a source type that supports native redirect is used and it does not + /// contain a native url, standard web view redirect should be attempted + func testNativeRedirectSupportingSourceFlow_invalidNativeURL() { + let source = STPFixtures.alipaySource() + let sut = STPRedirectContext( + source: source) { _, _, _ in + XCTFail("completion called") + }! + XCTAssertNil(sut.nativeRedirectURL) + + let applicationMock = MockUIApplication() + sut.application = applicationMock + + let mockVC = MockUIViewController() + mockVC.presentChecker = { $0 is SFSafariViewController } + sut.startRedirectFlow(from: mockVC) + + let expectation = self.expectation(description: "Waiting 100ms for SafariServices") + + // Hack: Wait ~100ms to call sut back before unsubscribing from notifications. Otherwise the Safari thread doesn't get the unsubscribe request in time and calls the deallocated sut, crashing the app. + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { + expectation.fulfill() + }) + + waitForExpectations(timeout: 1, handler: nil) + XCTAssertFalse(applicationMock.openCalled) + XCTAssertTrue(mockVC.presentCalled) + sut.unsubscribeFromNotifications() + } + + // MARK: - WeChat Pay + + /// If a WeChat source type is used, we should attempt an app redirect. + func testWeChatPaySource_appRedirectSucceeds() { + let source = STPFixtures.weChatPaySource() + let sourceURL = URL(string: source.weChatPayDetails!.weChatAppURL!)! + + let sut = STPRedirectContext( + source: source) { _, _, _ in + XCTFail("completion called") + }! + + XCTAssertNotNil(sut.nativeRedirectURL) + XCTAssertEqual(sut.nativeRedirectURL, sourceURL) + XCTAssertNil(sut.redirectURL) + XCTAssertNotNil(sut.returnURL) + + let applicationMock = MockUIApplication() + applicationMock.openHandler = { url, completion in + XCTAssertEqual(url, sourceURL) + completion?(true) + } + sut.application = applicationMock + let mockVC = MockUIViewController() + sut.startRedirectFlow(from: mockVC) + let expectation = self.expectation(description: "Waiting 100ms for SafariServices") + + // Hack: Wait ~100ms to call sut back before unsubscribing from notifications. Otherwise the Safari thread doesn't get the unsubscribe request in time and calls the deallocated sut, crashing the app. + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { + expectation.fulfill() + }) + + waitForExpectations(timeout: 1, handler: nil) + XCTAssertFalse(sut._startSafariAppRedirectFlowCalled) + XCTAssertFalse(sut.isSafariVCPresented()) + XCTAssertTrue(applicationMock.openCalled) + sut.unsubscribeFromNotifications() + } + + /// If a WeChat source type is used, we should attempt an app redirect. + /// If app redirect fails, expect an error. + func testWeChatPaySource_appRedirectFails() { + let source = STPFixtures.weChatPaySource() + let sourceURL = URL(string: source.weChatPayDetails!.weChatAppURL!)! + + let expectation = self.expectation(description: "Completion block called") + let sut = STPRedirectContext(source: source) { _, _, error in + guard let error = error as? NSError else { + XCTFail() + return + } + XCTAssertEqual(error.domain, STPRedirectContext.STPRedirectContextErrorDomain) + XCTAssertEqual(error.code, STPRedirectContextError.appRedirectError.rawValue) + expectation.fulfill() + }! + + XCTAssertNotNil(sut.nativeRedirectURL) + XCTAssertEqual(sut.nativeRedirectURL, sourceURL) + XCTAssertNil(sut.redirectURL) + XCTAssertNotNil(sut.returnURL) + + let applicationMock = MockUIApplication() + applicationMock.openHandler = { url, completion in + XCTAssertEqual(url, sourceURL) + completion?(false) + } + sut.application = applicationMock + let mockVC = MockUIViewController() + sut.startRedirectFlow(from: mockVC) + let safariWaitExpectation = self.expectation(description: "Waiting 100ms for SafariServices") + + // Hack: Wait ~100ms to call sut back before unsubscribing from notifications. Otherwise the Safari thread doesn't get the unsubscribe request in time and calls the deallocated sut, crashing the app. + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: { + safariWaitExpectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + XCTAssertFalse(sut._startSafariAppRedirectFlowCalled) + XCTAssertFalse(sut.isSafariVCPresented()) + XCTAssertTrue(applicationMock.openCalled) + sut.unsubscribeFromNotifications() + } +} diff --git a/Stripe/StripeiOSTests/STPSetupIntentConfirmParamsTest.swift b/Stripe/StripeiOSTests/STPSetupIntentConfirmParamsTest.swift new file mode 100644 index 00000000..a0ce7fe7 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSetupIntentConfirmParamsTest.swift @@ -0,0 +1,156 @@ +// +// STPSetupIntentConfirmParamsTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 7/15/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPSetupIntentConfirmParamsTest: XCTestCase { + func testInit() { + for params in [ + STPSetupIntentConfirmParams(clientSecret: "secret"), + STPSetupIntentConfirmParams(), + STPSetupIntentConfirmParams(), + ] { + XCTAssertNotNil(params) + XCTAssertNotNil(params.clientSecret) + XCTAssertNotNil(params.additionalAPIParameters) + XCTAssertEqual(params.additionalAPIParameters.count, 0) + XCTAssertNil(params.paymentMethodID) + XCTAssertNil(params.returnURL) + XCTAssertNil(params.useStripeSDK) + XCTAssertNil(params.mandateData) + } + } + + func testDescription() { + let params = STPSetupIntentConfirmParams() + XCTAssertNotNil(params.description) + } + + func testDefaultMandateData() { + let params = STPSetupIntentConfirmParams() + + // no configuration should have no mandateData + XCTAssertNil(params.mandateData) + + params.paymentMethodParams = STPPaymentMethodParams() + + params.paymentMethodParams?.rawTypeString = "card" + // card type should have no default mandateData + XCTAssertNil(params.mandateData) + + for type in ["sepa_debit", "au_becs_debit", "bacs_debit", "bancontact", "ideal", "eps", "sofort", "link", "us_bank_account", "cashapp", "paypal", "revolut_pay", "klarna"] { + params.mandateData = nil + params.paymentMethodParams?.rawTypeString = type + // Mandate-required type should have mandateData + XCTAssertNotNil(params.mandateData) + XCTAssertEqual( + params.mandateData?.customerAcceptance.onlineParams?.inferFromClient, + NSNumber(value: true) + ) + + let customerAcceptance = STPMandateCustomerAcceptanceParams( + type: .offline, + onlineParams: nil + ) + params.mandateData = STPMandateDataParams(customerAcceptance: customerAcceptance!) + // Default behavior should not override custom setting + XCTAssertNotNil(params.mandateData) + XCTAssertNil(params.mandateData?.customerAcceptance.onlineParams) + } + } + + // MARK: STPFormEncodable Tests + func testRootObjectName() { + XCTAssertNil(STPSetupIntentConfirmParams.rootObjectName()) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let params = STPSetupIntentConfirmParams() + + let mapping = STPSetupIntentConfirmParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(params.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(formFieldName.count > 0) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } + + func testCopy() { + let params = STPSetupIntentConfirmParams(clientSecret: "test_client_secret") + params.paymentMethodParams = STPPaymentMethodParams() + params.paymentMethodID = "test_payment_method_id" + params.returnURL = "fake://testing_only" + params.useStripeSDK = NSNumber(value: true) + params.mandateData = STPMandateDataParams( + customerAcceptance: STPMandateCustomerAcceptanceParams( + type: .offline, + onlineParams: nil + )! + ) + params.additionalAPIParameters = [ + "other_param": "other_value" + ] + + let paramsCopy = params.copy() as! STPSetupIntentConfirmParams + XCTAssertEqual(params.clientSecret, paramsCopy.clientSecret) + XCTAssertEqual(params.paymentMethodID, paramsCopy.paymentMethodID) + + // assert equal, not equal objects, because this is a shallow copy + XCTAssertEqual(params.paymentMethodParams, paramsCopy.paymentMethodParams) + XCTAssertEqual(params.mandateData, paramsCopy.mandateData) + + XCTAssertEqual(params.returnURL, paramsCopy.returnURL) + XCTAssertEqual(params.useStripeSDK, paramsCopy.useStripeSDK) + XCTAssertEqual( + params.additionalAPIParameters as NSDictionary, + paramsCopy.additionalAPIParameters as NSDictionary + ) + + } + + func testClientSecretValidation() { + XCTAssertFalse( + STPSetupIntentConfirmParams.isClientSecretValid("seti_12345"), + "'seti_12345' is not a valid client secret." + ) + XCTAssertFalse( + STPSetupIntentConfirmParams.isClientSecretValid("seti_12345_secret_"), + "'seti_12345_secret_' is not a valid client secret." + ) + XCTAssertFalse( + STPSetupIntentConfirmParams.isClientSecretValid( + "seti_a1b2c3_secret_x7y8z9seti_a1b2c3_secret_x7y8z9" + ), + "'seti_a1b2c3_secret_x7y8z9seti_a1b2c3_secret_x7y8z9' is not a valid client secret." + ) + XCTAssertFalse( + STPSetupIntentConfirmParams.isClientSecretValid("pi_a1b2c3_secret_x7y8z9"), + "'pi_a1b2c3_secret_x7y8z9' is not a valid client secret." + ) + + XCTAssertTrue( + STPSetupIntentConfirmParams.isClientSecretValid("seti_a1b2c3_secret_x7y8z9"), + "'seti_a1b2c3_secret_x7y8z9' is a valid client secret." + ) + XCTAssertTrue( + STPSetupIntentConfirmParams.isClientSecretValid( + "seti_1Eq5kyGMT9dGPIDGxiSp4cce_secret_FKlHb3yTI0YZWe4iqghS8ZXqwwMoMmy" + ), + "'seti_1Eq5kyGMT9dGPIDGxiSp4cce_secret_FKlHb3yTI0YZWe4iqghS8ZXqwwMoMmy' is a valid client secret." + ) + } +} diff --git a/Stripe/StripeiOSTests/STPSetupIntentFunctionalTest.swift b/Stripe/StripeiOSTests/STPSetupIntentFunctionalTest.swift new file mode 100644 index 00000000..9f40f320 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSetupIntentFunctionalTest.swift @@ -0,0 +1,262 @@ +// +// STPSetupIntentFunctionalTest.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPSetupIntentFunctionalTestSwift: STPNetworkStubbingTestCase { + + // MARK: - US Bank Account + func createAndConfirmSetupIntentWithUSBankAccount(completion: @escaping (String?) -> Void) { + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + var clientSecret: String? + let createSIExpectation = expectation(description: "Create SetupIntent") + STPTestingAPIClient.shared.createSetupIntent( + withParams: ["payment_method_types": ["us_bank_account"]], + account: nil + ) { intentClientSecret, error in + XCTAssertNil(error) + XCTAssertNotNil(intentClientSecret) + clientSecret = intentClientSecret + createSIExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + guard let clientSecret = clientSecret else { + XCTFail("Failed to create SetupIntent") + return + } + + let usBankAccountParams = STPPaymentMethodUSBankAccountParams() + usBankAccountParams.accountType = .checking + usBankAccountParams.accountHolderType = .individual + usBankAccountParams.accountNumber = "000123456789" + usBankAccountParams.routingNumber = "110000000" + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "iOS CI Tester" + billingDetails.email = "tester@example.com" + + let paymentMethodParams = STPPaymentMethodParams( + usBankAccount: usBankAccountParams, + billingDetails: billingDetails, + metadata: nil + ) + + let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: clientSecret) + setupIntentParams.paymentMethodParams = paymentMethodParams + + let confirmSIExpectation = expectation(description: "Confirm SetupIntent") + client.confirmSetupIntent(with: setupIntentParams, expand: ["payment_method"]) { + setupIntent, + error in + XCTAssertNil(error) + guard let setupIntent else { XCTFail(); return } + XCTAssertNotNil(setupIntent.paymentMethod) + XCTAssertNotNil(setupIntent.paymentMethod?.usBankAccount) + XCTAssertEqual(setupIntent.paymentMethod?.usBankAccount?.last4, "6789") + XCTAssertEqual(setupIntent.status, .requiresAction) + XCTAssertEqual(setupIntent.nextAction?.type, .verifyWithMicrodeposits) + confirmSIExpectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + completion(clientSecret) + } + + func testConfirmSetupIntentWithUSBankAccount_verifyWithAmounts() { + createAndConfirmSetupIntentWithUSBankAccount { [self] clientSecret in + guard let clientSecret = clientSecret else { + XCTFail("Failed to create SetupIntent") + return + } + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let verificationExpectation = expectation(description: "Verify with microdeposits") + client.verifySetupIntentWithMicrodeposits( + clientSecret: clientSecret, + firstAmount: 32, + secondAmount: 45 + ) { setupIntent, error in + XCTAssertNil(error) + XCTAssertNotNil(setupIntent) + XCTAssertEqual(setupIntent?.status, .succeeded) + verificationExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + } + + func testConfirmSetupIntentWithUSBankAccount_verifyWithDescriptorCode() { + createAndConfirmSetupIntentWithUSBankAccount { [self] clientSecret in + guard let clientSecret = clientSecret else { + XCTFail("Failed to create SetupIntent") + return + } + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + + let verificationExpectation = expectation(description: "Verify with microdeposits") + client.verifySetupIntentWithMicrodeposits( + clientSecret: clientSecret, + descriptorCode: "SM11AA" + ) { setupIntent, error in + XCTAssertNil(error) + XCTAssertNotNil(setupIntent) + XCTAssertEqual(setupIntent?.status, .succeeded) + verificationExpectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout) + } + } +} + +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +extension STPSetupIntentFunctionalTestSwift { + func testCreateSetupIntentWithTestingServer() { + let expectation = self.expectation(description: "SetupIntent create.") + STPTestingAPIClient.shared.createSetupIntent( + withParams: nil) { clientSecret, error in + XCTAssertNotNil(clientSecret) + XCTAssertNil(error) + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRetrieveSetupIntentSucceeds() { + // Tests retrieving a previously created SetupIntent succeeds + let setupIntentClientSecret = "seti_1GGCuIFY0qyl6XeWVfbQK6b3_secret_GnoX2tzX2JpvxsrcykRSVna2lrYLKew" + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Setup Intent retrieve") + + client.retrieveSetupIntent( + withClientSecret: setupIntentClientSecret) { setupIntent, error in + XCTAssertNil(error) + guard let setupIntent else { XCTFail(); return } + XCTAssertNotNil(setupIntent) + XCTAssertEqual(setupIntent.stripeID, "seti_1GGCuIFY0qyl6XeWVfbQK6b3") + XCTAssertEqual(setupIntent.clientSecret, setupIntentClientSecret) + XCTAssertEqual(setupIntent.created, Date(timeIntervalSince1970: 1582673622)) + XCTAssertNil(setupIntent.customerID) + XCTAssertNil(setupIntent.stripeDescription) + XCTAssertFalse(setupIntent.livemode) + XCTAssertNil(setupIntent.nextAction) + XCTAssertNil(setupIntent.paymentMethodID) + XCTAssertEqual(setupIntent.paymentMethodTypes, [NSNumber(value: STPPaymentMethodType.card.rawValue)]) + XCTAssertEqual(setupIntent.status, STPSetupIntentStatus.requiresPaymentMethod) + XCTAssertEqual(setupIntent.usage, STPSetupIntentUsage.offSession) + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testConfirmSetupIntentSucceeds() { + + var clientSecret: String? + let createExpectation = self.expectation(description: "Create SetupIntent.") + STPTestingAPIClient.shared.createSetupIntent(withParams: nil) { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "SetupIntent confirm") + let params = STPSetupIntentConfirmParams(clientSecret: clientSecret!) + params.returnURL = "example-app-scheme://authorized" + // Confirm using a card requiring 3DS1 authentication (ie requires next steps) + params.paymentMethodID = "pm_card_authenticationRequired" + client.confirmSetupIntent( + with: params) { setupIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + guard let setupIntent else { XCTFail(); return } + + XCTAssertNotNil(setupIntent) + XCTAssertEqual(setupIntent.stripeID, STPSetupIntent.id(fromClientSecret: params.clientSecret)) + XCTAssertEqual(setupIntent.clientSecret, clientSecret) + XCTAssertFalse(setupIntent.livemode) + + XCTAssertEqual(setupIntent.status, STPSetupIntentStatus.requiresAction) + XCTAssertNotNil(setupIntent.nextAction) + XCTAssertEqual(setupIntent.nextAction?.type, STPIntentActionType.redirectToURL) + XCTAssertEqual(setupIntent.nextAction?.redirectToURL?.returnURL, URL(string: "example-app-scheme://authorized")) + XCTAssertNotNil(setupIntent.paymentMethodID) + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // MARK: - AU BECS Debit + + func testConfirmAUBECSDebitSetupIntent() { + + var clientSecret: String? + let createExpectation = self.expectation(description: "Create PaymentIntent.") + STPTestingAPIClient.shared.createSetupIntent( + withParams: [ + "payment_method_types": ["au_becs_debit"], + ], + account: "au") { createdClientSecret, creationError in + XCTAssertNotNil(createdClientSecret) + XCTAssertNil(creationError) + createExpectation.fulfill() + clientSecret = createdClientSecret + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + XCTAssertNotNil(clientSecret) + + let becsParams = STPPaymentMethodAUBECSDebitParams() + becsParams.bsbNumber = "000000" // Stripe test bank + becsParams.accountNumber = "000123456" // test account + + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jenny Rosen" + billingDetails.email = "jrosen@example.com" + + let params = STPPaymentMethodParams( + aubecsDebit: becsParams, + billingDetails: billingDetails, + metadata: [ + "test_key": "test_value", + ]) + + let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: clientSecret!) + setupIntentParams.paymentMethodParams = params + + let client = STPAPIClient(publishableKey: STPTestingAUPublishableKey) + let expectation = self.expectation(description: "Setup Intent confirm") + + client.confirmSetupIntent( + with: setupIntentParams) { setupIntent, error in + XCTAssertNil(error, "With valid key + secret, should be able to confirm the intent") + guard let setupIntent else { XCTFail(); return } + + XCTAssertNotNil(setupIntent) + XCTAssertEqual(setupIntent.stripeID, STPSetupIntent.id(fromClientSecret: setupIntentParams.clientSecret)) + XCTAssertNotNil(setupIntent.paymentMethodID) + XCTAssertEqual(setupIntent.status, STPSetupIntentStatus.succeeded) + + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/Stripe/StripeiOSTests/STPSetupIntentLastSetupErrorTest.swift b/Stripe/StripeiOSTests/STPSetupIntentLastSetupErrorTest.swift new file mode 100644 index 00000000..6bd56371 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSetupIntentLastSetupErrorTest.swift @@ -0,0 +1,32 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSetupIntentLastSetupErrorTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 8/9/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments + +class STPSetupIntentLastSetupErrorTest: XCTestCase { + func testTypeFromString() { + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "api_connection_error"), STPSetupIntentLastSetupErrorType.apiConnection) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "API_CONNECTION_ERROR"), STPSetupIntentLastSetupErrorType.apiConnection) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "api_error"), STPSetupIntentLastSetupErrorType.API) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "API_ERROR"), STPSetupIntentLastSetupErrorType.API) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "authentication_error"), STPSetupIntentLastSetupErrorType.authentication) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "AUTHENTICATION_ERROR"), STPSetupIntentLastSetupErrorType.authentication) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "card_error"), STPSetupIntentLastSetupErrorType.card) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "CARD_ERROR"), STPSetupIntentLastSetupErrorType.card) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "idempotency_error"), STPSetupIntentLastSetupErrorType.idempotency) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "IDEMPOTENCY_ERROR"), STPSetupIntentLastSetupErrorType.idempotency) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "invalid_request_error"), STPSetupIntentLastSetupErrorType.invalidRequest) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "INVALID_REQUEST_ERROR"), STPSetupIntentLastSetupErrorType.invalidRequest) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "rate_limit_error"), STPSetupIntentLastSetupErrorType.rateLimit) + XCTAssertEqual(STPSetupIntentLastSetupError.type(from: "RATE_LIMIT_ERROR"), STPSetupIntentLastSetupErrorType.rateLimit) + } + // MARK: - STPAPIResponseDecodable Tests + + // STPSetupIntentLastError is a sub-object of STPSetupIntent, see STPSetupIntentTest +} diff --git a/Stripe/StripeiOSTests/STPSetupIntentTest.swift b/Stripe/StripeiOSTests/STPSetupIntentTest.swift new file mode 100644 index 00000000..0d7cb3df --- /dev/null +++ b/Stripe/StripeiOSTests/STPSetupIntentTest.swift @@ -0,0 +1,104 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSetupIntentTest.m +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/27/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// +@_spi(STP) @testable import StripePayments + +class STPSetupIntentTest: XCTestCase { + // MARK: - Description Tests + + func testDescription() { + let setupIntent = STPFixtures.setupIntent() + + XCTAssertNotNil(setupIntent) + let desc = setupIntent.description + XCTAssertTrue(desc.contains(NSStringFromClass(type(of: setupIntent).self))) + XCTAssertGreaterThan(desc.count, 500, "Custom description should be long") + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let fullJson = STPTestUtils.jsonNamed(STPTestJSONSetupIntent) + + XCTAssertNotNil(STPSetupIntent.decodedObject(fromAPIResponse: fullJson), "can decode with full json") + + let requiredFields = [ + "id", + "client_secret", + "livemode", + "status", + ] + + for field in requiredFields { + var partialJson = fullJson! as [AnyHashable: Any] + + XCTAssertNotNil(partialJson[field]) + partialJson.removeValue(forKey: field) + + XCTAssertNil(STPSetupIntent.decodedObject(fromAPIResponse: partialJson)) + } + } + + func testDecodedObjectFromAPIResponseMapping() { + let setupIntentJson = STPTestUtils.jsonNamed("SetupIntent")! + guard let setupIntent = STPSetupIntent.decodedObject(fromAPIResponse: setupIntentJson) else { XCTFail(); return } + + XCTAssertEqual(setupIntent.stripeID, "seti_123456789") + XCTAssertEqual(setupIntent.clientSecret, "seti_123456789_secret_123456789") + XCTAssertEqual(setupIntent.created, Date(timeIntervalSince1970: 123456789)) + XCTAssertEqual(setupIntent.customerID, "cus_123456") + XCTAssertEqual(setupIntent.paymentMethodID, "pm_123456") + XCTAssertEqual(setupIntent.stripeDescription, "My Sample SetupIntent") + XCTAssertFalse(setupIntent.livemode) + // nextAction + XCTAssertNotNil(setupIntent.nextAction) + XCTAssertEqual(setupIntent.nextAction?.type, STPIntentActionType.redirectToURL) + XCTAssertNotNil(setupIntent.nextAction?.redirectToURL) + XCTAssertNotNil(setupIntent.nextAction?.redirectToURL?.url) + let returnURL = setupIntent.nextAction?.redirectToURL?.returnURL + XCTAssertNotNil(returnURL) + XCTAssertEqual(returnURL, URL(string: "payments-example://stripe-redirect")) + let url = setupIntent.nextAction?.redirectToURL?.url + XCTAssertNotNil(url) + + XCTAssertEqual(url, URL(string: "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk")) + XCTAssertEqual(setupIntent.paymentMethodID, "pm_123456") + XCTAssertEqual(setupIntent.status, STPSetupIntentStatus.requiresAction) + XCTAssertEqual(setupIntent.usage, STPSetupIntentUsage.offSession) + + XCTAssertEqual(setupIntent.paymentMethodTypes, [NSNumber(value: STPPaymentMethodType.card.rawValue)]) + + // lastSetupError + + XCTAssertNotNil(setupIntent.lastSetupError) + XCTAssertEqual(setupIntent.lastSetupError?.code, "setup_intent_authentication_failure") + XCTAssertEqual(setupIntent.lastSetupError?.docURL, "https://stripe.com/docs/error-codes#setup-intent-authentication-failure") + XCTAssertEqual(setupIntent.lastSetupError?.message, "The latest attempt to set up the payment method has failed because authentication failed.") + XCTAssertNotNil(setupIntent.lastSetupError?.paymentMethod) + XCTAssertEqual(setupIntent.lastSetupError?.type, STPSetupIntentLastSetupErrorType.invalidRequest) + } + + // MARK: STPSetupIntentStatus extension tests + + func testStringFromStatus() { + let expected: [STPSetupIntentStatus: String] = [ + .requiresPaymentMethod: "requires_payment_method", + .requiresConfirmation: "requires_confirmation", + .requiresAction: "requires_action", + .processing: "processing", + .succeeded: "succeeded", + .canceled: "canceled", + .unknown: "unknown", + ] + + for (status, expectedString) in expected { + let resultString = STPSetupIntentStatus.string(from: status) + XCTAssertEqual(resultString, expectedString, "Expected \(status) to map to string '\(expectedString)', but got '\(resultString)'") + } + } +} diff --git a/Stripe/StripeiOSTests/STPSourceCardDetailsTest.swift b/Stripe/StripeiOSTests/STPSourceCardDetailsTest.swift new file mode 100644 index 00000000..5bddcdc6 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceCardDetailsTest.swift @@ -0,0 +1,116 @@ +// +// STPSourceCardDetailsTest.swift +// StripeiOS Tests +// +// Created by Joey Dong on 6/21/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPSourceCardDetailsTest: XCTestCase { + // MARK: - STPSourceCard3DSecureStatus Tests + func testThreeDSecureStatusFromString() { + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "required"), .required) + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "REQUIRED"), .required) + + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "optional"), .optional) + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "OPTIONAL"), .optional) + + XCTAssertEqual( + STPSourceCardDetails.threeDSecureStatus(from: "not_supported"), + .notSupported + ) + XCTAssertEqual( + STPSourceCardDetails.threeDSecureStatus(from: "NOT_SUPPORTED"), + .notSupported + ) + + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "recommended"), .recommended) + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "RECOMMENDED"), .recommended) + + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "unknown"), .unknown) + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "UNKNOWN"), .unknown) + + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "garbage"), .unknown) + XCTAssertEqual(STPSourceCardDetails.threeDSecureStatus(from: "GARBAGE"), .unknown) + } + + func testStringFromThreeDSecureStatus() { + let values: [STPSourceCard3DSecureStatus] = [ + .required, + .optional, + .notSupported, + .recommended, + .unknown, + ] + + for threeDSecureStatus in values { + let string = STPSourceCardDetails.string(fromThreeDSecureStatus: threeDSecureStatus) + + switch threeDSecureStatus { + case .required: + XCTAssertEqual(string, "required") + case .optional: + XCTAssertEqual(string, "optional") + case .notSupported: + XCTAssertEqual(string, "not_supported") + case .recommended: + XCTAssertEqual(string, "recommended") + case .unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - Description Tests + func testDescription() { + let cardDetails = STPSourceCardDetails.decodedObject( + fromAPIResponse: STPTestUtils.jsonNamed("CardSource")!["card"] as? [AnyHashable: Any] + ) + XCTAssert(cardDetails?.description != nil) + } + + // MARK: - STPAPIResponseDecodable Tests + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = STPTestUtils.jsonNamed("CardSource")?["card"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPSourceCardDetails.decodedObject(fromAPIResponse: response)) + } + + XCTAssert( + (STPSourceCardDetails.decodedObject( + fromAPIResponse: STPTestUtils.jsonNamed("CardSource")!["card"] + as? [AnyHashable: Any] + ) + != nil) + ) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("CardSource")?["card"] as? [AnyHashable: Any] + let cardDetails = STPSourceCardDetails.decodedObject(fromAPIResponse: response)! + + XCTAssertEqual(cardDetails.brand, .visa) + XCTAssertEqual(cardDetails.country, "US") + XCTAssertEqual(cardDetails.expMonth, UInt(12)) + XCTAssertEqual(cardDetails.expYear, UInt(2034)) + XCTAssertEqual(cardDetails.funding, .debit) + XCTAssertEqual(cardDetails.last4, "5556") + XCTAssertEqual(cardDetails.threeDSecure, .notSupported) + + XCTAssertEqual(cardDetails.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceFunctionalTest.swift b/Stripe/StripeiOSTests/STPSourceFunctionalTest.swift new file mode 100644 index 00000000..b95c1d57 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceFunctionalTest.swift @@ -0,0 +1,603 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceFunctionalTest.m +// Stripe +// +// Created by Ben Guo on 1/23/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeCore +@testable import StripeCoreTestUtils +@_spi(STP) @testable import StripePayments +import StripePaymentsTestUtils +import XCTest + +class STPSourceFunctionalTest: STPNetworkStubbingTestCase { + func testCreateSource_bancontact() { + let params = STPSourceParams.bancontactParams( + withAmount: 1099, + name: "Jenny Rosen", + returnURL: "https://shop.example.com/crtABC", + statementDescriptor: "ORDER AT123") + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.bancontact) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_card() { + let card = STPCardParams() + card.number = "4242 4242 4242 4242" + card.expMonth = 6 + card.expYear = 2050 + card.currency = "usd" + card.name = "Jenny Rosen" + card.address.line1 = "123 Fake Street" + card.address.line2 = "Apartment 4" + card.address.city = "New York" + card.address.state = "NY" + card.address.country = "USA" + card.address.postalCode = "10002" + let params = STPSourceParams.cardParams(withCard: card) + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.card) + XCTAssertEqual(source?.cardDetails?.last4, "4242") + XCTAssertEqual(source?.cardDetails?.expMonth, card.expMonth) + XCTAssertEqual(source?.cardDetails?.expYear, card.expYear) + XCTAssertEqual(source?.owner?.name, card.name) + let address = source?.owner?.address + XCTAssertEqual(address?.line1, card.address.line1) + XCTAssertEqual(address?.line2, card.address.line2) + XCTAssertEqual(address?.city, card.address.city) + XCTAssertEqual(address?.state, card.address.state) + XCTAssertEqual(address?.country, card.address.country) + XCTAssertEqual(address?.postalCode, card.address.postalCode) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_ideal() { + let params = STPSourceParams.idealParams( + withAmount: 1099, + name: "Jenny Rosen", + returnURL: "https://shop.example.com/crtABC", + statementDescriptor: "ORDER AT123", + bank: "ing") + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.iDEAL) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertEqual(source?.details?["bank"] as? String, "ing") + XCTAssertEqual(source?.details?["statement_descriptor"] as? String, "ORDER AT123") + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + // #pragma clang diagnostic pop + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_ideal_missingOptionalFields() { + let params = STPSourceParams.idealParams( + withAmount: 1099, + name: nil, + returnURL: "https://shop.example.com/crtABC", + statementDescriptor: nil, + bank: nil) + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.iDEAL) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertNil(source?.owner?.name) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.details?["bank"]) + XCTAssertNil(source?.details?["statement_descriptor"]) + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + // #pragma clang diagnostic pop + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_ideal_emptyOptionalFields() { + let params = STPSourceParams.idealParams( + withAmount: 1099, + name: "", + returnURL: "https://shop.example.com/crtABC", + statementDescriptor: "", + bank: "") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.iDEAL) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertNil(source?.owner?.name) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.details?["bank"]) + XCTAssertNil(source?.details?["statement_descriptor"]) + // #pragma clang diagnostic push + // #pragma clang diagnostic ignored "-Wdeprecated" + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + // #pragma clang diagnostic pop + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_sepaDebit() { + let params = STPSourceParams.sepaDebitParams( + withName: "Jenny Rosen", + iban: "DE89370400440532013000", + addressLine1: "Nollendorfstraße 27", + city: "Berlin", + postalCode: "10777", + country: "DE") + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.SEPADebit) + XCTAssertNil(source?.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertEqual(source?.owner?.address?.city, "Berlin") + XCTAssertEqual(source?.owner?.address?.line1, "Nollendorfstraße 27") + XCTAssertEqual(source?.owner?.address?.country, "DE") + XCTAssertEqual(source?.sepaDebitDetails?.country, "DE") + XCTAssertEqual(source?.sepaDebitDetails?.last4, "3000") + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_sepaDebit_NoAddress() { + let params = STPSourceParams.sepaDebitParams( + withName: "Jenny Rosen", + iban: "DE89370400440532013000", + addressLine1: nil, + city: nil, + postalCode: nil, + country: nil) + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.SEPADebit) + XCTAssertNil(source?.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertNil(source?.owner?.address?.city) + XCTAssertNil(source?.owner?.address?.line1) + XCTAssertNil(source?.owner?.address?.country) + XCTAssertEqual(source?.sepaDebitDetails?.country, "DE") // German IBAN so sepa tells us country here even though we didnt pass it up as owner info + XCTAssertEqual(source?.sepaDebitDetails?.last4, "3000") + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_sofort() { + let params = STPSourceParams.sofortParams( + withAmount: 1099, + returnURL: "https://shop.example.com/crtABC", + country: "DE", + statementDescriptor: "ORDER AT11990") + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.sofort) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + XCTAssertEqual(source?.details?["country"] as? String, "DE") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func skip_testCreateSourceVisaCheckout() { + // The SDK does not have a means of generating Visa Checkout params for testing. Supply your own + // callId, and the correct publishable key, and you can run this test case + // manually after removing the `skip_` prefix. It'll log the source's stripeID, and that + // can be verified in dashboard. + let params = STPSourceParams.visaCheckoutParams(withCallId: "") + let client = STPAPIClient(publishableKey: "pk_") + client.apiURL = URL(string: "https://api.stripe.com/v1") + + let sourceExp = expectation(description: "VCO source created") + client.createSource(with: params) { source, error in + sourceExp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.card) + XCTAssertEqual(source?.flow, STPSourceFlow.none) + XCTAssertEqual(source?.status, STPSourceStatus.chargeable) + XCTAssertEqual(source?.usage, STPSourceUsage.reusable) + XCTAssertTrue(source!.stripeID.hasPrefix("src_")) + if let stripeID = source?.stripeID { + print("Created a VCO source \(stripeID)") + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func skip_testCreateSourceMasterpass() { + // The SDK does not have a means of generating Masterpass params for testing. Supply your own + // cartId & transactionId, and the correct publishable key, and you can run this test case + // manually after removing the `skip_` prefix. It'll log the source's stripeID, and that + // can be verified in dashboard. + let params = STPSourceParams.masterpassParams(withCartId: "", transactionId: "") + let client = STPAPIClient(publishableKey: "pk_") + client.apiURL = URL(string: "https://api.stripe.com/v1") + + let sourceExp = expectation(description: "Masterpass source created") + client.createSource(with: params) { source, error in + sourceExp.fulfill() + + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.card) + XCTAssertEqual(source?.flow, STPSourceFlow.none) + XCTAssertEqual(source?.status, STPSourceStatus.chargeable) + XCTAssertEqual(source?.usage, STPSourceUsage.singleUse) + XCTAssertTrue(source!.stripeID.hasPrefix("src_")) + if let stripeID = source?.stripeID { + print("Created a Masterpass source \(stripeID)") + } + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_alipay() { + let params = STPSourceParams.alipayParams( + withAmount: 1099, + currency: "usd", + returnURL: "https://shop.example.com/crtABC") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Alipay Source creation") + + params.metadata = [ + "foo": "bar", + ] + client.createSource(with: params) { source, error2 in + XCTAssertNil(error2) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.alipay) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_p24() { + let params = STPSourceParams.p24Params( + withAmount: 1099, + currency: "eur", + email: "user@example.com", + name: "Jenny Rosen", + returnURL: "https://shop.example.com/crtABC") + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "P24 Source creation") + + params.metadata = [ + "foo": "bar", + ] + client.createSource(with: params) { source, error2 in + XCTAssertNil(error2) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.P24) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.email, params.owner?["email"] as? String) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + expectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testRetrieveSource_sofort() { + let client = STPAPIClient(publishableKey: "pk_test_vOo1umqsYxSrP5UXfOeL3ecm") + let params = STPSourceParams() + params.type = STPSourceType.sofort + params.amount = NSNumber(value: 1099) + params.currency = "eur" + params.redirect = [ + "return_url": "https://shop.example.com/crtA6B28E1", + ] + params.metadata = [ + "foo": "bar", + ] + params.additionalAPIParameters = [ + "sofort": [ + "country": "DE", + ], + ] + let createExp = expectation(description: "Source creation") + let retrieveExp = expectation(description: "Source retrieval") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + guard let source else { XCTFail(); return } + createExp.fulfill() + client.retrieveSource( + withId: source.stripeID, + clientSecret: source.clientSecret!) { source2, error2 in + XCTAssertNil(error2) + XCTAssertNotNil(source2) + XCTAssertEqual(source, source2) + retrieveExp.fulfill() + } + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_eps() { + let params = STPSourceParams.epsParams( + withAmount: 1099, + name: "Jenny Rosen", + returnURL: "https://shop.example.com/crtABC", + statementDescriptor: "ORDER AT123") + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.EPS) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + XCTAssertEqual(source?.allResponseFields["statement_descriptor"] as! String, "ORDER AT123") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_eps_no_statement_descriptor() { + let params = STPSourceParams.epsParams( + withAmount: 1099, + name: "Jenny Rosen", + returnURL: "https://shop.example.com/crtABC", + statementDescriptor: nil) + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.EPS) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.currency, params.currency) + XCTAssertEqual(source?.owner?.name, params.owner?["name"] as? String) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + XCTAssertNil(source?.allResponseFields["statement_descriptor"]) + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_multibanco() { + let params = STPSourceParams.multibancoParams( + withAmount: 1099, + returnURL: "https://shop.example.com/crtABC", + email: "user@example.com") + params.metadata = [ + "foo": "bar", + ] + + let client = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.multibanco) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/crtABC?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata")), "Metadata is not returned.") + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_klarna() { + let lineItems = [ + STPKlarnaLineItem(itemType: STPKlarnaLineItemType.SKU, itemDescription: "Test Item", quantity: NSNumber(value: 2), totalAmount: NSNumber(value: 500)), + STPKlarnaLineItem(itemType: STPKlarnaLineItemType.tax, itemDescription: "Tax", quantity: NSNumber(value: 1), totalAmount: NSNumber(value: 100)), + ] + let address = STPAddress() + address.line1 = "29 Arlington Avenue" + address.email = "test@example.com" + address.city = "London" + address.postalCode = "N1 7BE" + address.country = "GB" + address.phone = "02012267709" + let dob = STPDateOfBirth() + dob.day = 11 + dob.month = 3 + dob.year = 1952 + let params = STPSourceParams.klarnaParams(withReturnURL: "https://shop.example.com/return", currency: "GBP", purchaseCountry: "GB", items: lineItems, customPaymentMethods: [STPKlarnaPaymentMethods.none], billingAddress: address, billingFirstName: "Arthur", billingLastName: "Dent", billingDOB: dob) + + let client = STPAPIClient(publishableKey: STPTestingGBPublishableKey) + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.klarna) + XCTAssertEqual(source?.amount, NSNumber(value: 600)) + XCTAssertEqual(source?.owner?.address?.line1, address.line1) + XCTAssertEqual(source?.klarnaDetails?.purchaseCountry, "GB") + XCTAssertEqual(source?.redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(source?.redirect?.returnURL, URL(string: "https://shop.example.com/return?redirect_merchant_name=xctest")) + XCTAssertNotNil(source?.redirect?.url) + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + // 7/5/2023: + // Previously, we were allowed to use live keys and test w/ wechat on sources would generated "ios_native_url" + // however, this is no longer possible. Therefore, to get ample test coverage, we will have two tests: + // - testCreateSource_wechatPay_testMode - run in test mode, to ensure we can still call sources + // - testCreateSource_wechatPay_mocked - run a mocked version which is what we would expect in live mode + func testCreateSource_wechatPay_testMode() { + let params = STPSourceParams.wechatPay( + withAmount: 1010, + currency: "usd", + appId: "wxa0df51ec63e578ce", + statementDescriptor: nil) + let client = STPAPIClient(publishableKey: "pk_test_h0JFD5q63mLThM5JVSbrREmR") + let expectation = self.expectation(description: "Source creation") + client.createSource(with: params) { source, error in + XCTAssertNil(error) + XCTAssertNotNil(source) + XCTAssertEqual(source?.type, STPSourceType.weChatPay) + XCTAssertEqual(source?.status, STPSourceStatus.pending) + XCTAssertEqual(source?.amount, params.amount) + XCTAssertNil(source?.redirect) + + let wechat = source?.weChatPayDetails + XCTAssertNotNil(wechat) + // Will not be generated in test mode + // XCTAssertNotNil(wechat.weChatAppURL); + + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateSource_wechatPay_mocked() { + let params = STPSourceParams.wechatPay( + withAmount: 1010, + currency: "usd", + appId: "wxa0df51ec63e578ce", + statementDescriptor: nil) + + let source = STPFixtures.weChatPaySource() + XCTAssertEqual(source.type, STPSourceType.weChatPay) + XCTAssertEqual(source.status, STPSourceStatus.pending) + XCTAssertEqual(source.amount, params.amount) + XCTAssertNil(source.redirect) + + let wechat = source.weChatPayDetails + XCTAssertNotNil(wechat) + XCTAssertNotNil(wechat?.weChatAppURL) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceOwnerTest.swift b/Stripe/StripeiOSTests/STPSourceOwnerTest.swift new file mode 100644 index 00000000..b2d4f930 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceOwnerTest.swift @@ -0,0 +1,57 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceOwnerTest.m +// Stripe +// +// Created by Joey Dong on 6/23/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import XCTest + +class STPSourceOwnerTest: XCTestCase { + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = STPTestUtils.jsonNamed(STPTestJSONSource3DS)["owner"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPSourceOwner.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPSourceOwner.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed(STPTestJSONSource3DS)["owner"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + guard + let response = STPTestUtils.jsonNamed(STPTestJSONSource3DS)["owner"] as? NSDictionary, + let owner = STPSourceOwner.decodedObject(fromAPIResponse: response as? [AnyHashable: Any]) else { + XCTFail() + return + } + + XCTAssertEqual(owner.address?.city, "Pittsburgh") + XCTAssertEqual(owner.address?.country, "US") + XCTAssertEqual(owner.address?.line1, "123 Fake St") + XCTAssertEqual(owner.address?.line2, "Apt 1") + XCTAssertEqual(owner.address?.postalCode, "19219") + XCTAssertEqual(owner.address?.state, "PA") + XCTAssertEqual(owner.email, "jenny.rosen@example.com") + XCTAssertEqual(owner.name, "Jenny Rosen") + XCTAssertEqual(owner.phone, "555-867-5309") + XCTAssertEqual(owner.verifiedAddress?.city, "Pittsburgh") + XCTAssertEqual(owner.verifiedAddress?.country, "US") + XCTAssertEqual(owner.verifiedAddress?.line1, "123 Fake St") + XCTAssertEqual(owner.verifiedAddress?.line2, "Apt 1") + XCTAssertEqual(owner.verifiedAddress?.postalCode, "19219") + XCTAssertEqual(owner.verifiedAddress?.state, "PA") + XCTAssertEqual(owner.verifiedEmail, "jenny.rosen@example.com") + XCTAssertEqual(owner.verifiedName, "Jenny Rosen") + XCTAssertEqual(owner.verifiedPhone, "555-867-5309") + + XCTAssertEqual(owner.allResponseFields as NSDictionary, response) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceParamsTest.swift b/Stripe/StripeiOSTests/STPSourceParamsTest.swift new file mode 100644 index 00000000..0534c5b8 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceParamsTest.swift @@ -0,0 +1,317 @@ +// +// STPSourceParamsTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 1/25/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPSourceParamsTest: XCTestCase { + // MARK: - + func testInit() { + let sourceParams = STPSourceParams() + XCTAssertEqual(sourceParams.rawTypeString, "") + XCTAssertEqual(sourceParams.flow, .unknown) + XCTAssertEqual(sourceParams.usage, .unknown) + XCTAssertEqual(sourceParams.additionalAPIParameters as NSDictionary, [:] as NSDictionary) + } + + func testType() { + let sourceParams = STPSourceParams() + XCTAssertEqual(sourceParams.type, .unknown) + + sourceParams.rawTypeString = "bancontact" + XCTAssertEqual(sourceParams.type, .bancontact) + + sourceParams.rawTypeString = "card" + XCTAssertEqual(sourceParams.type, .card) + + sourceParams.rawTypeString = "giropay" + XCTAssertEqual(sourceParams.type, .giropay) + + sourceParams.rawTypeString = "ideal" + XCTAssertEqual(sourceParams.type, .iDEAL) + + sourceParams.rawTypeString = "sepa_debit" + XCTAssertEqual(sourceParams.type, .SEPADebit) + + sourceParams.rawTypeString = "sofort" + XCTAssertEqual(sourceParams.type, .sofort) + + sourceParams.rawTypeString = "three_d_secure" + XCTAssertEqual(sourceParams.type, .threeDSecure) + + sourceParams.rawTypeString = "alipay" + XCTAssertEqual(sourceParams.type, .alipay) + + sourceParams.rawTypeString = "p24" + XCTAssertEqual(sourceParams.type, .P24) + + sourceParams.rawTypeString = "eps" + XCTAssertEqual(sourceParams.type, .EPS) + + sourceParams.rawTypeString = "multibanco" + XCTAssertEqual(sourceParams.type, .multibanco) + + sourceParams.rawTypeString = "klarna" + XCTAssertEqual(sourceParams.type, .klarna) + + sourceParams.rawTypeString = "unknown" + XCTAssertEqual(sourceParams.type, .unknown) + + sourceParams.rawTypeString = "garbage" + XCTAssertEqual(sourceParams.type, .unknown) + } + + func testSetType() { + let sourceParams = STPSourceParams() + XCTAssertEqual(sourceParams.type, .unknown) + + sourceParams.type = .bancontact + XCTAssertEqual(sourceParams.rawTypeString, "bancontact") + + sourceParams.type = .card + XCTAssertEqual(sourceParams.rawTypeString, "card") + + sourceParams.type = .giropay + XCTAssertEqual(sourceParams.rawTypeString, "giropay") + + sourceParams.type = .iDEAL + XCTAssertEqual(sourceParams.rawTypeString, "ideal") + + sourceParams.type = .SEPADebit + XCTAssertEqual(sourceParams.rawTypeString, "sepa_debit") + + sourceParams.type = .sofort + XCTAssertEqual(sourceParams.rawTypeString, "sofort") + + sourceParams.type = .threeDSecure + XCTAssertEqual(sourceParams.rawTypeString, "three_d_secure") + + sourceParams.type = .alipay + XCTAssertEqual(sourceParams.rawTypeString, "alipay") + + sourceParams.type = .P24 + XCTAssertEqual(sourceParams.rawTypeString, "p24") + + sourceParams.type = .EPS + XCTAssertEqual(sourceParams.rawTypeString, "eps") + + sourceParams.type = .multibanco + XCTAssertEqual(sourceParams.rawTypeString, "multibanco") + + sourceParams.type = .klarna + XCTAssertEqual(sourceParams.rawTypeString, "klarna") + + sourceParams.type = .unknown + XCTAssertNil(sourceParams.rawTypeString) + } + + func testSetTypePreserveUnknownRawTypeString() { + let sourceParams = STPSourceParams() + sourceParams.rawTypeString = "money" + sourceParams.type = .unknown + XCTAssertEqual(sourceParams.rawTypeString, "money") + } + + func testRawTypeString() { + let sourceParams = STPSourceParams() + + // Check defaults to unknown + XCTAssertEqual(sourceParams.type, .unknown) + + // Check changing type sets rawTypeString + sourceParams.type = .card + XCTAssertEqual(sourceParams.rawTypeString, STPSource.string(from: .card)) + + // Check changing to unknown raw string sets type to unknown + sourceParams.rawTypeString = "new_source_type" + XCTAssertEqual(sourceParams.type, .unknown) + + // Check once unknown that setting type to unknown doesnt clobber string + sourceParams.type = .unknown + XCTAssertEqual(sourceParams.rawTypeString, "new_source_type") + + // Check setting string to known type sets type correctly + sourceParams.rawTypeString = STPSource.string(from: .iDEAL) + XCTAssertEqual(sourceParams.type, .iDEAL) + } + + func testFlowString() { + let sourceParams = STPSourceParams() + XCTAssertNil(sourceParams.flowString()) + + sourceParams.flow = .redirect + XCTAssertEqual(sourceParams.flowString(), "redirect") + + sourceParams.flow = .receiver + XCTAssertEqual(sourceParams.flowString(), "receiver") + + sourceParams.flow = .codeVerification + XCTAssertEqual(sourceParams.flowString(), "code_verification") + + sourceParams.flow = .none + XCTAssertEqual(sourceParams.flowString(), "none") + } + + // MARK: - Constructors Tests + func testCardParamsWithCard() { + let card = STPCardParams() + card.number = "4242 4242 4242 4242" + card.cvc = "123" + card.expMonth = 6 + card.expYear = 2024 + card.currency = "usd" + card.name = "Jenny Rosen" + card.address.line1 = "123 Fake Street" + card.address.line2 = "Apartment 4" + card.address.city = "New York" + card.address.state = "NY" + card.address.country = "USA" + card.address.postalCode = "10002" + + let source = STPSourceParams.cardParams(withCard: card) + let sourceCard = source.additionalAPIParameters["card"] as! [String: AnyHashable] + XCTAssertEqual(sourceCard["number"], card.number) + XCTAssertEqual(sourceCard["cvc"], card.cvc) + XCTAssertEqual(sourceCard["exp_month"], NSNumber(value: card.expMonth)) + XCTAssertEqual(sourceCard["exp_year"], NSNumber(value: card.expYear)) + XCTAssertEqual(source.owner!["name"] as? String, card.name) + let sourceAddress = source.owner!["address"] as! [AnyHashable: Any] + XCTAssertEqual(sourceAddress["line1"] as? String, card.address.line1) + XCTAssertEqual(sourceAddress["line2"] as? String, card.address.line2) + XCTAssertEqual(sourceAddress["city"] as? String, card.address.city) + XCTAssertEqual(sourceAddress["state"] as? String, card.address.state) + XCTAssertEqual(sourceAddress["postal_code"] as? String, card.address.postalCode) + XCTAssertEqual(sourceAddress["country"] as? String, card.address.country) + } + + func testParamsWithVisaCheckout() { + let params = STPSourceParams.visaCheckoutParams(withCallId: "12345678") + + XCTAssertEqual(params.type, .card) + let sourceCard = params.additionalAPIParameters["card"] as? [AnyHashable: Any] + XCTAssertNotNil(sourceCard) + let sourceVisaCheckout = sourceCard!["visa_checkout"] as? [AnyHashable: Any] + XCTAssertNotNil(sourceVisaCheckout) + XCTAssertEqual(sourceVisaCheckout!["callid"] as! String, "12345678") + } + + func testParamsWithMasterPass() { + let params = STPSourceParams.masterpassParams( + withCartId: "12345678", + transactionId: "87654321" + ) + + XCTAssertEqual(params.type, .card) + let sourceCard = params.additionalAPIParameters["card"] as? [AnyHashable: Any] + XCTAssertNotNil(sourceCard) + let sourceMasterpass = sourceCard!["masterpass"] as? [AnyHashable: Any] + XCTAssertNotNil(sourceMasterpass) + XCTAssertEqual(sourceMasterpass!["cart_id"] as! String, "12345678") + XCTAssertEqual(sourceMasterpass!["transaction_id"] as! String, "87654321") + } + + func testKlarnaParams() { + let params = STPSourceParams.klarnaParams( + withReturnURL: "return_url", + currency: "USD", + purchaseCountry: "US", + items: [], + customPaymentMethods: [.none], + billingAddress: nil, + billingFirstName: nil, + billingLastName: nil, + billingDOB: nil + ) + + XCTAssertNotNil(params) + } + + // MARK: - Redirect Dictionary Tests + func redirectMerchantNameQueryItemValue(fromURLString urlString: String?) -> String? { + let components = NSURLComponents(string: urlString ?? "") + for item in components!.queryItems! { + if item.name == "redirect_merchant_name" { + return item.value + } + } + return nil + } + + func testRedirectMerchantNameURL() { + var sourceParams = STPSourceParams.sofortParams( + withAmount: 1000, + returnURL: "test://foo?value=baz", + country: "DE", + statementDescriptor: nil + ) + + var params = STPFormEncoder.dictionary(forObject: sourceParams) + // Should be nil because we have no app name in tests + XCTAssertNil( + redirectMerchantNameQueryItemValue( + fromURLString: (params["redirect"] as! [String: AnyHashable])["return_url"] + as? String + ) + ) + + sourceParams.redirectMerchantName = "bar" + params = STPFormEncoder.dictionary(forObject: sourceParams) + XCTAssertEqual( + redirectMerchantNameQueryItemValue( + fromURLString: (params["redirect"] as! [String: AnyHashable])["return_url"] + as? String + ), + "bar" + ) + + sourceParams = STPSourceParams.sofortParams( + withAmount: 1000, + returnURL: "test://foo?redirect_merchant_name=Manual%20Custom%20Name", + country: "DE", + statementDescriptor: nil + ) + sourceParams.redirectMerchantName = "bar" + params = STPFormEncoder.dictionary(forObject: sourceParams) + // Don't override names set by the user directly in the url + XCTAssertEqual( + redirectMerchantNameQueryItemValue( + fromURLString: (params["redirect"] as! [String: AnyHashable])["return_url"] + as? String + ), + "Manual Custom Name" + ) + + } + + // MARK: - STPFormEncodable Tests + func testRootObjectName() { + XCTAssertNil(STPSourceParams.rootObjectName()) + } + + func testPropertyNamesToFormFieldNamesMapping() { + let sourceParams = STPSourceParams() + + let mapping = STPSourceParams.propertyNamesToFormFieldNamesMapping() + + for propertyName in mapping.keys { + XCTAssertFalse(propertyName.contains(":")) + XCTAssert(sourceParams.responds(to: NSSelectorFromString(propertyName))) + } + + for formFieldName in mapping.values { + XCTAssert(formFieldName.count > 0) + } + + XCTAssertEqual(mapping.values.count, Set(mapping.values).count) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceReceiverTest.swift b/Stripe/StripeiOSTests/STPSourceReceiverTest.swift new file mode 100644 index 00000000..16971fb0 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceReceiverTest.swift @@ -0,0 +1,48 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceReceiverTest.m +// Stripe +// +// Created by Joey Dong on 6/26/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import XCTest + +class STPSourceReceiverTest: XCTestCase { + // MARK: - Description Tests + + func testDescription() { + let receiver = STPSourceReceiver.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed(STPTestJSONSource3DS)["receiver"] as? [AnyHashable: Any]) + XCTAssertNotNil(receiver?.description) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = [ + "address", + ] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed(STPTestJSONSource3DS)["receiver"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPSourceReceiver.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPSourceReceiver.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed(STPTestJSONSource3DS)["receiver"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed(STPTestJSONSource3DS)["receiver"] as? [AnyHashable: Any] + let receiver = STPSourceReceiver.decodedObject(fromAPIResponse: response) + + XCTAssertEqual(receiver?.address, "test_1MBhWS3uv4ynCfQXF3xQjJkzFPukr4K56N") + XCTAssertEqual(receiver?.amountCharged, NSNumber(value: 300)) + XCTAssertEqual(receiver?.amountReceived, NSNumber(value: 200)) + XCTAssertEqual(receiver?.amountReturned, NSNumber(value: 100)) + + XCTAssertEqual(receiver!.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceRedirectTest.swift b/Stripe/StripeiOSTests/STPSourceRedirectTest.swift new file mode 100644 index 00000000..7e754144 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceRedirectTest.swift @@ -0,0 +1,100 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceRedirectTest.m +// Stripe +// +// Created by Joey Dong on 6/21/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class STPSourceRedirectTest: XCTestCase { + // MARK: - STPSourceRedirectStatus Tests + + func testStatusFromString() { + XCTAssertEqual(STPSourceRedirect.status(from: "pending"), STPSourceRedirectStatus.pending) + XCTAssertEqual(STPSourceRedirect.status(from: "PENDING"), STPSourceRedirectStatus.pending) + + XCTAssertEqual(STPSourceRedirect.status(from: "succeeded"), STPSourceRedirectStatus.succeeded) + XCTAssertEqual(STPSourceRedirect.status(from: "SUCCEEDED"), STPSourceRedirectStatus.succeeded) + + XCTAssertEqual(STPSourceRedirect.status(from: "failed"), STPSourceRedirectStatus.failed) + XCTAssertEqual(STPSourceRedirect.status(from: "FAILED"), STPSourceRedirectStatus.failed) + + XCTAssertEqual(STPSourceRedirect.status(from: "unknown"), STPSourceRedirectStatus.unknown) + XCTAssertEqual(STPSourceRedirect.status(from: "UNKNOWN"), STPSourceRedirectStatus.unknown) + + XCTAssertEqual(STPSourceRedirect.status(from: "not_required"), STPSourceRedirectStatus.notRequired) + XCTAssertEqual(STPSourceRedirect.status(from: "NOT_REQUIRED"), STPSourceRedirectStatus.notRequired) + + XCTAssertEqual(STPSourceRedirect.status(from: "garbage"), STPSourceRedirectStatus.unknown) + XCTAssertEqual(STPSourceRedirect.status(from: "GARBAGE"), STPSourceRedirectStatus.unknown) + } + + func testStringFromStatus() { + let values = [ + STPSourceRedirectStatus.pending, + STPSourceRedirectStatus.succeeded, + STPSourceRedirectStatus.failed, + STPSourceRedirectStatus.unknown, + ] + + for status in values { + let string = STPSourceRedirect.string(from: status) + + switch status { + case STPSourceRedirectStatus.pending: + XCTAssertEqual(string, "pending") + case STPSourceRedirectStatus.succeeded: + XCTAssertEqual(string, "succeeded") + case STPSourceRedirectStatus.failed: + XCTAssertEqual(string, "failed") + case STPSourceRedirectStatus.notRequired: + XCTAssertEqual(string, "not_required") + case STPSourceRedirectStatus.unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - Description Tests + + func testDescription() { + let redirect = STPSourceRedirect.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("3DSSource")["redirect"] as? [AnyHashable: Any]) + XCTAssertNotNil(redirect?.description) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = [ + "return_url", + "status", + "url", + ] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed("3DSSource")["redirect"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPSourceRedirect.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPSourceRedirect.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("3DSSource")["redirect"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("3DSSource")["redirect"] as? [AnyHashable: Any] + let redirect = STPSourceRedirect.decodedObject(fromAPIResponse: response) + + XCTAssertEqual(redirect?.returnURL, URL(string: "exampleappschema://stripe_callback")) + XCTAssertEqual(redirect?.status, STPSourceRedirectStatus.pending) + XCTAssertEqual(redirect?.url, URL(string: "https://hooks.stripe.com/redirect/authenticate/src_19YlvWAHEMiOZZp1QQlOD79v?client_secret=src_client_secret_kBwCSm6Xz5MQETiJ43hUH8qv")) + + XCTAssertEqual(redirect!.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceSEPADebitDetailsTest.swift b/Stripe/StripeiOSTests/STPSourceSEPADebitDetailsTest.swift new file mode 100644 index 00000000..9fdda789 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceSEPADebitDetailsTest.swift @@ -0,0 +1,49 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceSEPADebitDetails.m +// Stripe +// +// Created by Joey Dong on 6/26/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class STPSourceSEPADebitDetailsTest: XCTestCase { + // MARK: - Description Tests + + func testDescription() { + let sepaDebitDetails = STPSourceSEPADebitDetails.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any]) + XCTAssertNotNil(sepaDebitDetails?.description) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields: [String]? = [] + + for field in requiredFields ?? [] { + var response = STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPSourceSEPADebitDetails.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPSourceSEPADebitDetails.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("SEPADebitSource")["sepa_debit"] as? [AnyHashable: Any] + let sepaDebitDetails = STPSourceSEPADebitDetails.decodedObject(fromAPIResponse: response) + + XCTAssertEqual(sepaDebitDetails?.bankCode, "37040044") + XCTAssertEqual(sepaDebitDetails?.country, "DE") + XCTAssertEqual(sepaDebitDetails?.fingerprint, "NxdSyRegc9PsMkWy") + XCTAssertEqual(sepaDebitDetails?.last4, "3001") + XCTAssertEqual(sepaDebitDetails?.mandateReference, "NXDSYREGC9PSMKWY") + XCTAssertEqual(sepaDebitDetails?.mandateURL, URL(string: "https://hooks.stripe.com/adapter/sepa_debit/file/src_18HgGjHNCLa1Vra6Y9TIP6tU/src_client_secret_XcBmS94nTg5o0xc9MSliSlDW")) + + XCTAssertEqual(sepaDebitDetails!.allResponseFields as NSDictionary, response! as NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPSourceTest.swift b/Stripe/StripeiOSTests/STPSourceTest.swift new file mode 100644 index 00000000..51d35f93 --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceTest.swift @@ -0,0 +1,429 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceTest.m +// Stripe +// +// Created by Ben Guo on 1/24/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments +@testable import StripePaymentsUI +import XCTest + +class STPSourceTest: XCTestCase { + // MARK: - STPSourceType Tests + + func testTypeFromString() { + XCTAssertEqual(STPSource.type(from: "bancontact"), STPSourceType.bancontact) + XCTAssertEqual(STPSource.type(from: "BANCONTACT"), STPSourceType.bancontact) + + XCTAssertEqual(STPSource.type(from: "card"), STPSourceType.card) + XCTAssertEqual(STPSource.type(from: "CARD"), STPSourceType.card) + + XCTAssertEqual(STPSource.type(from: "giropay"), STPSourceType.giropay) + XCTAssertEqual(STPSource.type(from: "GIROPAY"), STPSourceType.giropay) + + XCTAssertEqual(STPSource.type(from: "ideal"), STPSourceType.iDEAL) + XCTAssertEqual(STPSource.type(from: "IDEAL"), STPSourceType.iDEAL) + + XCTAssertEqual(STPSource.type(from: "sepa_debit"), STPSourceType.SEPADebit) + XCTAssertEqual(STPSource.type(from: "SEPA_DEBIT"), STPSourceType.SEPADebit) + + XCTAssertEqual(STPSource.type(from: "sofort"), STPSourceType.sofort) + XCTAssertEqual(STPSource.type(from: "Sofort"), STPSourceType.sofort) + + XCTAssertEqual(STPSource.type(from: "three_d_secure"), STPSourceType.threeDSecure) + XCTAssertEqual(STPSource.type(from: "THREE_D_SECURE"), STPSourceType.threeDSecure) + + XCTAssertEqual(STPSource.type(from: "alipay"), STPSourceType.alipay) + XCTAssertEqual(STPSource.type(from: "ALIPAY"), STPSourceType.alipay) + + XCTAssertEqual(STPSource.type(from: "p24"), STPSourceType.P24) + XCTAssertEqual(STPSource.type(from: "P24"), STPSourceType.P24) + + XCTAssertEqual(STPSource.type(from: "eps"), STPSourceType.EPS) + XCTAssertEqual(STPSource.type(from: "EPS"), STPSourceType.EPS) + + XCTAssertEqual(STPSource.type(from: "multibanco"), STPSourceType.multibanco) + XCTAssertEqual(STPSource.type(from: "MULTIBANCO"), STPSourceType.multibanco) + + XCTAssertEqual(STPSource.type(from: "unknown"), STPSourceType.unknown) + XCTAssertEqual(STPSource.type(from: "UNKNOWN"), STPSourceType.unknown) + + XCTAssertEqual(STPSource.type(from: "garbage"), STPSourceType.unknown) + XCTAssertEqual(STPSource.type(from: "GARBAGE"), STPSourceType.unknown) + } + + func testStringFromType() { + let values = [ + STPSourceType.bancontact, + STPSourceType.card, + STPSourceType.giropay, + STPSourceType.iDEAL, + STPSourceType.SEPADebit, + STPSourceType.sofort, + STPSourceType.threeDSecure, + STPSourceType.alipay, + STPSourceType.P24, + STPSourceType.EPS, + STPSourceType.multibanco, + STPSourceType.unknown, + ] + + for type in values { + let string = STPSource.string(from: type) + + switch type { + case STPSourceType.bancontact: + XCTAssertEqual(string, "bancontact") + case STPSourceType.card: + XCTAssertEqual(string, "card") + case STPSourceType.giropay: + XCTAssertEqual(string, "giropay") + case STPSourceType.iDEAL: + XCTAssertEqual(string, "ideal") + case STPSourceType.SEPADebit: + XCTAssertEqual(string, "sepa_debit") + case STPSourceType.sofort: + XCTAssertEqual(string, "sofort") + case STPSourceType.threeDSecure: + XCTAssertEqual(string, "three_d_secure") + case STPSourceType.alipay: + XCTAssertEqual(string, "alipay") + case STPSourceType.P24: + XCTAssertEqual(string, "p24") + case STPSourceType.EPS: + XCTAssertEqual(string, "eps") + case STPSourceType.multibanco: + XCTAssertEqual(string, "multibanco") + case STPSourceType.weChatPay: + XCTAssertEqual(string, "wechat") + case STPSourceType.klarna: + XCTAssertEqual(string, "klarna") + case STPSourceType.unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - STPSourceFlow Tests + + func testFlowFromString() { + XCTAssertEqual(STPSource.flow(from: "redirect"), .redirect) + XCTAssertEqual(STPSource.flow(from: "REDIRECT"), .redirect) + + XCTAssertEqual(STPSource.flow(from: "receiver"), .receiver) + XCTAssertEqual(STPSource.flow(from: "RECEIVER"), .receiver) + + XCTAssertEqual(STPSource.flow(from: "code_verification"), .codeVerification) + XCTAssertEqual(STPSource.flow(from: "CODE_VERIFICATION"), .codeVerification) + + XCTAssertEqual(STPSource.flow(from: "none"), .none) + XCTAssertEqual(STPSource.flow(from: "NONE"), .none) + + XCTAssertEqual(STPSource.flow(from: "garbage"), .unknown) + XCTAssertEqual(STPSource.flow(from: "GARBAGE"), .unknown) + } + + func testStringFromFlow() { + let values: [STPSourceFlow] = [ + .redirect, + .receiver, + .codeVerification, + .none, + .unknown, + ] + + for flow in values { + let string = STPSource.string(from: flow) + + switch flow { + case .redirect: + XCTAssertEqual(string, "redirect") + case .receiver: + XCTAssertEqual(string, "receiver") + case .codeVerification: + XCTAssertEqual(string, "code_verification") + case .none: + XCTAssertEqual(string, "none") + case .unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - STPSourceStatus Tests + + func testStatusFromString() { + XCTAssertEqual(STPSource.status(from: "pending"), STPSourceStatus.pending) + XCTAssertEqual(STPSource.status(from: "PENDING"), STPSourceStatus.pending) + + XCTAssertEqual(STPSource.status(from: "chargeable"), STPSourceStatus.chargeable) + XCTAssertEqual(STPSource.status(from: "CHARGEABLE"), STPSourceStatus.chargeable) + + XCTAssertEqual(STPSource.status(from: "consumed"), STPSourceStatus.consumed) + XCTAssertEqual(STPSource.status(from: "CONSUMED"), STPSourceStatus.consumed) + + XCTAssertEqual(STPSource.status(from: "canceled"), STPSourceStatus.canceled) + XCTAssertEqual(STPSource.status(from: "CANCELED"), STPSourceStatus.canceled) + + XCTAssertEqual(STPSource.status(from: "failed"), STPSourceStatus.failed) + XCTAssertEqual(STPSource.status(from: "FAILED"), STPSourceStatus.failed) + + XCTAssertEqual(STPSource.status(from: "garbage"), STPSourceStatus.unknown) + XCTAssertEqual(STPSource.status(from: "GARBAGE"), STPSourceStatus.unknown) + } + + func testStringFromStatus() { + let values = [ + STPSourceStatus.pending, + STPSourceStatus.chargeable, + STPSourceStatus.consumed, + STPSourceStatus.canceled, + STPSourceStatus.failed, + STPSourceStatus.unknown, + ] + + for status in values { + let string = STPSource.string(from: status) + + switch status { + case STPSourceStatus.pending: + XCTAssertEqual(string, "pending") + case STPSourceStatus.chargeable: + XCTAssertEqual(string, "chargeable") + case STPSourceStatus.consumed: + XCTAssertEqual(string, "consumed") + case STPSourceStatus.canceled: + XCTAssertEqual(string, "canceled") + case STPSourceStatus.failed: + XCTAssertEqual(string, "failed") + case STPSourceStatus.unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - STPSourceUsage Tests + + func testUsageFromString() { + XCTAssertEqual(STPSource.usage(from: "reusable"), STPSourceUsage.reusable) + XCTAssertEqual(STPSource.usage(from: "REUSABLE"), STPSourceUsage.reusable) + + XCTAssertEqual(STPSource.usage(from: "single_use"), STPSourceUsage.singleUse) + XCTAssertEqual(STPSource.usage(from: "SINGLE_USE"), STPSourceUsage.singleUse) + + XCTAssertEqual(STPSource.usage(from: "garbage"), STPSourceUsage.unknown) + XCTAssertEqual(STPSource.usage(from: "GARBAGE"), STPSourceUsage.unknown) + } + + func testStringFromUsage() { + let values = [ + STPSourceUsage.reusable, + STPSourceUsage.singleUse, + STPSourceUsage.unknown, + ] + + for usage in values { + let string = STPSource.string(from: usage) + + switch usage { + case STPSourceUsage.reusable: + XCTAssertEqual(string, "reusable") + case STPSourceUsage.singleUse: + XCTAssertEqual(string, "single_use") + case STPSourceUsage.unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - Equality Tests + + func testSourceEquals() { + let source1 = STPSource.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("AlipaySource")) + let source2 = STPSource.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("AlipaySource")) + + XCTAssertEqual(source1, source1) + XCTAssertEqual(source1, source2) + + XCTAssertEqual(source1?.hash, source1?.hash) + XCTAssertEqual(source1?.hash, source2?.hash) + } + + // MARK: - Description Tests + + func testDescription() { + let source = STPSource.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("AlipaySource")) + XCTAssertNotNil(source?.description) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = [ + "id", + "livemode", + "status", + "type", + ] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed("AlipaySource") + response?.removeValue(forKey: field) + + XCTAssertNil(STPSource.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPSource.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("AlipaySource"))) + } + + func testDecodingSource_3ds() { + let response = STPTestUtils.jsonNamed(STPTestJSONSource3DS) + let source = STPSource.decodedObject(fromAPIResponse: response) + XCTAssertEqual(source?.stripeID, "src_456") + XCTAssertEqual(source?.amount, NSNumber(value: 1099)) + XCTAssertEqual(source?.clientSecret, "src_client_secret_456") + XCTAssertEqual(source?.created?.timeIntervalSince1970 ?? 0, 1483663790.0, accuracy: 1.0) + XCTAssertEqual(source?.currency, "eur") + XCTAssertEqual(source?.flow, .redirect) + XCTAssertEqual(source?.livemode, false) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata"))) + XCTAssertNotNil(source?.owner) // STPSourceOwnerTest + XCTAssertNotNil(source?.receiver) // STPSourceReceiverTest + XCTAssertNotNil(source?.redirect) // STPSourceRedirectTest + XCTAssertEqual(source?.status, STPSourceStatus.pending) + XCTAssertEqual(source?.type, STPSourceType.threeDSecure) + XCTAssertEqual(source?.usage, STPSourceUsage.singleUse) + XCTAssertNil(source?.verification) + var threedsecure = response?["three_d_secure"] as? [AnyHashable: Any] + threedsecure?.removeValue(forKey: "customer") // should be nil +// XCTAssertEqual(source?.details, threedsecure) + XCTAssertNotNil(source?.details) + XCTAssertNil(source?.cardDetails) // STPSourceCardDetailsTest + XCTAssertNil(source?.sepaDebitDetails) // STPSourceSEPADebitDetailsTest + } + + func testDecodingSource_alipay() { + let response = STPTestUtils.jsonNamed(STPTestJSONSourceAlipay) + let source = STPSource.decodedObject(fromAPIResponse: response) + XCTAssertEqual(source?.stripeID, "src_123") + XCTAssertEqual(source?.amount, NSNumber(value: 1099)) + XCTAssertEqual(source?.clientSecret, "src_client_secret_123") + XCTAssertEqual(source?.created?.timeIntervalSince1970 ?? 0, 1445277809.0, accuracy: 1.0) + XCTAssertEqual(source?.currency, "usd") + XCTAssertEqual(source?.flow, .redirect) + XCTAssertEqual(source?.livemode, true) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata"))) + XCTAssertNotNil(source?.owner) // STPSourceOwnerTest + XCTAssertNil(source?.receiver) // STPSourceReceiverTest + XCTAssertNotNil(source?.redirect) // STPSourceRedirectTest + XCTAssertEqual(source?.status, STPSourceStatus.pending) + XCTAssertEqual(source?.type, STPSourceType.alipay) + XCTAssertEqual(source?.usage, STPSourceUsage.singleUse) + XCTAssertNil(source?.verification) + var alipayResponse = response!["alipay"] as? [AnyHashable: Any] + alipayResponse?.removeValue(forKey: "native_url") // should be nil + alipayResponse?.removeValue(forKey: "statement_descriptor") // should be nil + XCTAssertEqual(source!.details! as NSDictionary, alipayResponse! as NSDictionary) + XCTAssertNil(source?.cardDetails) // STPSourceCardDetailsTest + XCTAssertNil(source?.sepaDebitDetails) // STPSourceSEPADebitDetailsTest + } + + func testDecodingSource_card() { + let response = STPTestUtils.jsonNamed(STPTestJSONSourceCard) + let source = STPSource.decodedObject(fromAPIResponse: response) + XCTAssertEqual(source?.stripeID, "src_123") + XCTAssertNil(source?.amount) + XCTAssertEqual(source?.clientSecret, "src_client_secret_123") + XCTAssertEqual(source?.created?.timeIntervalSince1970 ?? 0, 1483575790.0, accuracy: 1.0) + XCTAssertNil(source?.currency) + XCTAssertEqual(source?.flow, STPSourceFlow.none) + XCTAssertEqual(source?.livemode, false) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata"))) + XCTAssertNotNil(source?.owner) // STPSourceOwnerTest + XCTAssertNil(source?.receiver) // STPSourceReceiverTest + XCTAssertNil(source?.redirect) // STPSourceRedirectTest + XCTAssertEqual(source?.status, STPSourceStatus.chargeable) + XCTAssertEqual(source?.type, STPSourceType.card) + XCTAssertEqual(source?.usage, STPSourceUsage.reusable) + XCTAssertNil(source?.verification) + XCTAssertEqual(source!.details! as NSDictionary, response!["card"] as! NSDictionary) + XCTAssertNotNil(source?.cardDetails) // STPSourceCardDetailsTest + XCTAssertNil(source?.sepaDebitDetails) // STPSourceSEPADebitDetailsTest + } + + func testDecodingSource_ideal() { + let response = STPTestUtils.jsonNamed(STPTestJSONSourceiDEAL) + let source = STPSource.decodedObject(fromAPIResponse: response) + XCTAssertEqual(source?.stripeID, "src_123") + XCTAssertEqual(source?.amount, NSNumber(value: 1099)) + XCTAssertEqual(source?.clientSecret, "src_client_secret_123") + XCTAssertEqual(source?.created?.timeIntervalSince1970 ?? 0, 1445277809.0, accuracy: 1.0) + XCTAssertEqual(source?.currency, "eur") + XCTAssertEqual(source?.flow, .redirect) + XCTAssertEqual(source?.livemode, true) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata"))) + XCTAssertNotNil(source?.owner) // STPSourceOwnerTest + XCTAssertNil(source?.receiver) // STPSourceReceiverTest + XCTAssertNotNil(source?.redirect) // STPSourceRedirectTest + XCTAssertEqual(source?.status, STPSourceStatus.pending) + XCTAssertEqual(source?.type, STPSourceType.iDEAL) + XCTAssertEqual(source?.usage, STPSourceUsage.singleUse) + XCTAssertNil(source?.verification) + XCTAssertEqual(source!.details! as NSDictionary, response!["ideal"] as! NSDictionary) + XCTAssertNil(source?.cardDetails) // STPSourceCardDetailsTest + XCTAssertNil(source?.sepaDebitDetails) // STPSourceSEPADebitDetailsTest + } + + func testDecodingSource_sepa_debit() { + let response = STPTestUtils.jsonNamed(STPTestJSONSourceSEPADebit) + let source = STPSource.decodedObject(fromAPIResponse: response) + XCTAssertEqual(source?.stripeID, "src_18HgGjHNCLa1Vra6Y9TIP6tU") + XCTAssertNil(source?.amount) + XCTAssertEqual(source?.clientSecret, "src_client_secret_XcBmS94nTg5o0xc9MSliSlDW") + XCTAssertEqual(source?.created?.timeIntervalSince1970 ?? 0, 1464803577.0, accuracy: 1.0) + XCTAssertEqual(source?.currency, "eur") + XCTAssertEqual(source?.flow, STPSourceFlow.none) + XCTAssertEqual(source?.livemode, false) + XCTAssertNil(source?.perform(NSSelectorFromString("metadata"))) + XCTAssertEqual(source?.owner?.name, "Jenny Rosen") + XCTAssertNotNil(source?.owner) // STPSourceOwnerTest + XCTAssertNil(source?.receiver) // STPSourceReceiverTest + XCTAssertNil(source?.redirect) // STPSourceRedirectTest + XCTAssertEqual(source?.status, STPSourceStatus.chargeable) + XCTAssertEqual(source?.type, STPSourceType.SEPADebit) + XCTAssertEqual(source?.usage, STPSourceUsage.reusable) + XCTAssertEqual(source?.verification?.attemptsRemaining, NSNumber(value: 5)) + XCTAssertEqual(source?.verification?.status, STPSourceVerificationStatus.pending) + XCTAssertEqual(source!.details! as NSDictionary, response!["sepa_debit"] as! NSDictionary) + XCTAssertNil(source?.cardDetails) // STPSourceCardDetailsTest + XCTAssertNotNil(source?.sepaDebitDetails) // STPSourceSEPADebitDetailsTest + } + + func possibleAPIResponses() -> [[AnyHashable: Any]] { + return [ + STPTestUtils.jsonNamed(STPTestJSONSourceCard), + STPTestUtils.jsonNamed(STPTestJSONSource3DS), + STPTestUtils.jsonNamed(STPTestJSONSourceAlipay), + STPTestUtils.jsonNamed(STPTestJSONSourceBancontact), + STPTestUtils.jsonNamed(STPTestJSONSourceEPS), + STPTestUtils.jsonNamed(STPTestJSONSourceGiropay), + STPTestUtils.jsonNamed(STPTestJSONSourceiDEAL), + STPTestUtils.jsonNamed(STPTestJSONSourceMultibanco), + STPTestUtils.jsonNamed(STPTestJSONSourceP24), + STPTestUtils.jsonNamed(STPTestJSONSourceSEPADebit), + STPTestUtils.jsonNamed(STPTestJSONSourceSofort), + ] + } + +} diff --git a/Stripe/StripeiOSTests/STPSourceVerificationTest.swift b/Stripe/StripeiOSTests/STPSourceVerificationTest.swift new file mode 100644 index 00000000..cce05ead --- /dev/null +++ b/Stripe/StripeiOSTests/STPSourceVerificationTest.swift @@ -0,0 +1,92 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPSourceVerificationTest.m +// Stripe +// +// Created by Joey Dong on 6/21/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class STPSourceVerificationTest: XCTestCase { + // MARK: - STPSourceVerificationStatus Tests + + func testStatusFromString() { + XCTAssertEqual(STPSourceVerification.status(from: "pending"), STPSourceVerificationStatus.pending) + XCTAssertEqual(STPSourceVerification.status(from: "pending"), STPSourceVerificationStatus.pending) + + XCTAssertEqual(STPSourceVerification.status(from: "succeeded"), STPSourceVerificationStatus.succeeded) + XCTAssertEqual(STPSourceVerification.status(from: "SUCCEEDED"), STPSourceVerificationStatus.succeeded) + + XCTAssertEqual(STPSourceVerification.status(from: "failed"), STPSourceVerificationStatus.failed) + XCTAssertEqual(STPSourceVerification.status(from: "FAILED"), STPSourceVerificationStatus.failed) + + XCTAssertEqual(STPSourceVerification.status(from: "unknown"), STPSourceVerificationStatus.unknown) + XCTAssertEqual(STPSourceVerification.status(from: "UNKNOWN"), STPSourceVerificationStatus.unknown) + + XCTAssertEqual(STPSourceVerification.status(from: "garbage"), STPSourceVerificationStatus.unknown) + XCTAssertEqual(STPSourceVerification.status(from: "GARBAGE"), STPSourceVerificationStatus.unknown) + } + + func testStringFromStatus() { + let values = [ + STPSourceVerificationStatus.pending, + STPSourceVerificationStatus.succeeded, + STPSourceVerificationStatus.failed, + STPSourceVerificationStatus.unknown, + ] + + for status in values { + let string = STPSourceVerification.string(from: status) + + switch status { + case STPSourceVerificationStatus.pending: + XCTAssertEqual(string, "pending") + case STPSourceVerificationStatus.succeeded: + XCTAssertEqual(string, "succeeded") + case STPSourceVerificationStatus.failed: + XCTAssertEqual(string, "failed") + case STPSourceVerificationStatus.unknown: + XCTAssertNil(string) + default: + break + } + } + } + + // MARK: - Description Tests + + func testDescription() { + let verification = STPSourceVerification.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("SEPADebitSource")["verification"] as? [AnyHashable: Any]) + XCTAssertNotNil(verification?.description) + } + + // MARK: - STPAPIResponseDecodable Tests + + func testDecodedObjectFromAPIResponseRequiredFields() { + let requiredFields = [ + "status", + ] + + for field in requiredFields { + var response = STPTestUtils.jsonNamed("SEPADebitSource")["verification"] as? [AnyHashable: Any] + response?.removeValue(forKey: field) + + XCTAssertNil(STPSourceVerification.decodedObject(fromAPIResponse: response)) + } + + XCTAssertNotNil(STPSourceVerification.decodedObject(fromAPIResponse: STPTestUtils.jsonNamed("SEPADebitSource")["verification"] as? [AnyHashable: Any])) + } + + func testDecodedObjectFromAPIResponseMapping() { + let response = STPTestUtils.jsonNamed("SEPADebitSource")["verification"] as? [AnyHashable: Any] + let verification = STPSourceVerification.decodedObject(fromAPIResponse: response) + + XCTAssertEqual(verification?.attemptsRemaining, NSNumber(value: 5)) + XCTAssertEqual(verification?.status, STPSourceVerificationStatus.pending) + + XCTAssertEqual(verification?.allResponseFields as? NSDictionary, response as? NSDictionary) + } +} diff --git a/Stripe/StripeiOSTests/STPStackViewWithSeparatorSnapshotTests.swift b/Stripe/StripeiOSTests/STPStackViewWithSeparatorSnapshotTests.swift new file mode 100644 index 00000000..b26e48ce --- /dev/null +++ b/Stripe/StripeiOSTests/STPStackViewWithSeparatorSnapshotTests.swift @@ -0,0 +1,214 @@ +// +// STPStackViewWithSeparatorSnapshotTests.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/23/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) import StripeUICore + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPStackViewWithSeparatorSnapshotTests: STPSnapshotTestCase { + + func embedInRenderableView(_ stackView: StackViewWithSeparator) -> UIView { + let containingView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 400)) + containingView.addSubview(stackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + stackView.leadingAnchor.constraint(equalTo: containingView.leadingAnchor), + containingView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor), + stackView.topAnchor.constraint(equalTo: containingView.topAnchor), + containingView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor), + ]) + containingView.frame.size = containingView.systemLayoutSizeFitting( + UIView.layoutFittingCompressedSize + ) + return containingView + } + + func testHorizontal() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testVertical() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testSingleArrangedSubviewHorizontal() { + let label1 = UILabel() + label1.text = "Label 1" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testSingleArrangedSubviewVertical() { + let label1 = UILabel() + label1.text = "Label 1" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testCustomColorHorizontal() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .red + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testCustomColorVertical() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .red + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testDisabledColor() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + stackView.drawBorder = true + stackView.isUserInteractionEnabled = false + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testCustomBackgroundColor() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + stackView.drawBorder = true + stackView.customBackgroundColor = .green + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testCustomDisabledColor() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + stackView.customBackgroundDisabledColor = .green + stackView.drawBorder = true + stackView.isUserInteractionEnabled = false + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testPartialSeparatorHorizontal() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + stackView.separatorStyle = .partial + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + + func testPartialSeparatorVertical() { + let label1 = UILabel() + label1.text = "Label 1" + let label2 = UILabel() + label2.text = "Label 2" + let label3 = UILabel() + label3.text = "Label 3" + let stackView = StackViewWithSeparator(arrangedSubviews: [label1, label2, label3]) + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.spacing = 1 + stackView.separatorColor = .lightGray + stackView.separatorStyle = .partial + + STPSnapshotVerifyView(embedInRenderableView(stackView)) + } + +} diff --git a/Stripe/StripeiOSTests/STPStringUtilsTest.swift b/Stripe/StripeiOSTests/STPStringUtilsTest.swift new file mode 100644 index 00000000..310ede4a --- /dev/null +++ b/Stripe/StripeiOSTests/STPStringUtilsTest.swift @@ -0,0 +1,142 @@ +// +// STPStringUtilsTest.swift +// StripeiOS Tests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPStringUtilsTest: XCTestCase { + func testParseRangeSingleTagSuccess1() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRange( + from: "Test string", + withTag: "b" + ) { string, range in + XCTAssertTrue(NSEqualRanges(range, NSRange(location: 5, length: 6))) + XCTAssertEqual(string, "Test string") + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func testParseRangeSingleTagSuccess2() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRange( + from: "Test string", + withTag: "b" + ) { string, range in + XCTAssertTrue(NSEqualRanges(range, NSRange(location: 8, length: 10))) + XCTAssertEqual(string, "Test string") + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func testParseRangeSingleTagFailure1() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRange( + from: "Test string", + withTag: "a" + ) { string, range in + XCTAssertEqual(range.location, NSNotFound) + XCTAssertEqual(string, "Test string") + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func testParseRangeSingleTagFailure2() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRange( + from: "Test string", + withTag: "b" + ) { string, range in + XCTAssertEqual(range.location, NSNotFound) + XCTAssertEqual(string, "Test string") + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func testParseRangeMultiTag1() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRanges( + from: "Test string", + withTags: Set(["a", "b", "c"]) + ) { string, tagMap in + XCTAssertTrue(NSEqualRanges(tagMap["a"]!.rangeValue, NSRange(location: 0, length: 4))) + XCTAssertTrue(NSEqualRanges(tagMap["b"]!.rangeValue, NSRange(location: 5, length: 6))) + XCTAssertEqual(tagMap["c"]!.rangeValue.location, NSNotFound) + XCTAssertEqual(string, "Test string") + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func testParseRangeMultiTag2() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRanges(from: "Test string", withTags: Set(["a", "b", "c"])) { + string, + tagMap in + XCTAssertEqual(tagMap["a"]!.rangeValue.location, NSNotFound) + XCTAssertEqual(tagMap["b"]!.rangeValue.location, NSNotFound) + XCTAssertEqual(tagMap["c"]!.rangeValue.location, NSNotFound) + XCTAssertEqual(string, "Test string") + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + func testParseRangeWithOverlappingRanges() { + let exp = self.expectation(description: "Parsed") + STPStringUtils.parseRanges( + from: "Test string", + withTags: Set(["a", "b"]) + ) { string, tagMap in + XCTAssertEqual(string, "Test string") + XCTAssertTrue(tagMap.isEmpty) + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + func testHasOverlappingRanges_singleItem() { + let ranges: [NSValue: String] = [ + NSValue(range: NSRange(location: 0, length: 2)): "a", + ] + XCTAssertFalse(STPStringUtils.hasOverlappingRanges(ranges: ranges)) + } + func testHasOverlappingRanges_nonOverlapping() { + let ranges: [NSValue: String] = [ + NSValue(range: NSRange(location: 0, length: 2)): "a", + NSValue(range: NSRange(location: 2, length: 1)): "b", + ] + XCTAssertFalse(STPStringUtils.hasOverlappingRanges(ranges: ranges)) + } + func testHasOverlappingRanges_overlapping() { + let ranges: [NSValue: String] = [ + NSValue(range: NSRange(location: 0, length: 2)): "a", + NSValue(range: NSRange(location: 1, length: 1)): "b", + ] + XCTAssert(STPStringUtils.hasOverlappingRanges(ranges: ranges)) + } + + func testExpirationDateStrings() { + XCTAssertEqual(STPStringUtils.expirationDateString(from: "12/1995"), "12/95") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "12 / 1995"), "12 / 95") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "12 /1995"), "12 /95") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "1295"), "1295") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "12/95"), "12/95") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "08/2001"), "08/01") + XCTAssertEqual(STPStringUtils.expirationDateString(from: " 08/a 2001"), " 08/a 2001") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "20/2022"), "20/22") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "20/202222"), "20/22") + XCTAssertEqual(STPStringUtils.expirationDateString(from: ""), "") + XCTAssertEqual(STPStringUtils.expirationDateString(from: " "), " ") + XCTAssertEqual(STPStringUtils.expirationDateString(from: "12/"), "12/") + } +} diff --git a/Stripe/StripeiOSTests/STPSwiftFixtures.swift b/Stripe/StripeiOSTests/STPSwiftFixtures.swift new file mode 100644 index 00000000..1005db9b --- /dev/null +++ b/Stripe/StripeiOSTests/STPSwiftFixtures.swift @@ -0,0 +1,34 @@ +// +// STPSwiftFixtures.swift +// StripeiOS Tests +// +// Created by David Estes on 10/2/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +@_exported @testable import StripePaymentsObjcTestUtils + +extension STPFixtures { + /// A customer-scoped ephemeral key that expires in 100 seconds. + class func ephemeralKey() -> STPEphemeralKey { + var response = STPTestUtils.jsonNamed("EphemeralKey") + let interval: TimeInterval = 100 + response!["expires"] = NSNumber(value: Date(timeIntervalSinceNow: interval).timeIntervalSince1970) + return .decodedObject(fromAPIResponse: response)! + } + + /// A customer-scoped ephemeral key that expires in 10 seconds. + class func expiringEphemeralKey() -> STPEphemeralKey { + var response = STPTestUtils.jsonNamed("EphemeralKey") + let interval: TimeInterval = 10 + response!["expires"] = NSNumber(value: Date(timeIntervalSinceNow: interval).timeIntervalSince1970) + return .decodedObject(fromAPIResponse: response)! + } +} diff --git a/Stripe/StripeiOSTests/STPTextFieldDelegateProxyTests.swift b/Stripe/StripeiOSTests/STPTextFieldDelegateProxyTests.swift new file mode 100644 index 00000000..31c53a7c --- /dev/null +++ b/Stripe/StripeiOSTests/STPTextFieldDelegateProxyTests.swift @@ -0,0 +1,37 @@ +// +// STPTextFieldDelegateProxyTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 11/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPTextFieldDelegateProxyTests: XCTestCase { + + func testProxyShouldDeleteLeadingWhitespace() { + let textField = STPFormTextField() + textField.autoFormattingBehavior = .cardNumbers + textField.text = " " // space + + let sut = STPTextFieldDelegateProxy() + + let result = sut.textField( + textField, + shouldChangeCharactersIn: NSRange(location: 0, length: 1), + replacementString: "" + ) + + // Proxy should handle the deletion and return `false`. + XCTAssertFalse(result) + XCTAssertEqual(textField.text, "") + } + +} diff --git a/Stripe/StripeiOSTests/STPThreeDSButtonCustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSButtonCustomizationTest.swift new file mode 100644 index 00000000..85a2e8fa --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSButtonCustomizationTest.swift @@ -0,0 +1,40 @@ +// +// STPThreeDSButtonCustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSButtonCustomizationTest: XCTestCase { + func testPropertiesAreForwarded() { + let customization = STPThreeDSButtonCustomization.defaultSettings(for: .next) + customization.backgroundColor = UIColor.red + customization.cornerRadius = -1 + customization.titleStyle = .lowercase + customization.font = UIFont.italicSystemFont(ofSize: 1) + customization.textColor = UIColor.blue + + let stdsCustomization = customization.buttonCustomization + XCTAssertEqual(UIColor.red, stdsCustomization.backgroundColor) + XCTAssertEqual(stdsCustomization.backgroundColor, customization.backgroundColor) + + XCTAssertEqual(-1, stdsCustomization.cornerRadius, accuracy: 0.1) + XCTAssertEqual(stdsCustomization.cornerRadius, customization.cornerRadius, accuracy: 0.1) + + XCTAssertEqual(.lowercase, stdsCustomization.titleStyle) + XCTAssertEqual(stdsCustomization.titleStyle.rawValue, customization.titleStyle.rawValue) + + XCTAssertEqual(UIFont.italicSystemFont(ofSize: 1), stdsCustomization.font) + XCTAssertEqual(stdsCustomization.font, customization.font) + + XCTAssertEqual(UIColor.blue, stdsCustomization.textColor) + XCTAssertEqual(stdsCustomization.textColor, customization.textColor) + } +} diff --git a/Stripe/StripeiOSTests/STPThreeDSFooterCustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSFooterCustomizationTest.swift new file mode 100644 index 00000000..81447030 --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSFooterCustomizationTest.swift @@ -0,0 +1,45 @@ +// +// STPThreeDSFooterCustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSFooterCustomizationTest: XCTestCase { + func testPropertiesAreForwarded() { + let customization = STPThreeDSFooterCustomization.defaultSettings() + customization.backgroundColor = UIColor.red + customization.chevronColor = UIColor.blue + customization.headingTextColor = UIColor.green + customization.headingFont = UIFont.systemFont(ofSize: 1) + customization.font = UIFont.systemFont(ofSize: 2) + customization.textColor = UIColor.magenta + + let stdsCustomization = customization.footerCustomization + + XCTAssertEqual(UIColor.red, stdsCustomization.backgroundColor) + XCTAssertEqual(stdsCustomization.backgroundColor, customization.backgroundColor) + + XCTAssertEqual(UIColor.blue, stdsCustomization.chevronColor) + XCTAssertEqual(stdsCustomization.chevronColor, customization.chevronColor) + + XCTAssertEqual(UIColor.green, stdsCustomization.headingTextColor) + XCTAssertEqual(stdsCustomization.headingTextColor, customization.headingTextColor) + + XCTAssertEqual(UIFont.systemFont(ofSize: 1), stdsCustomization.headingFont) + XCTAssertEqual(stdsCustomization.headingFont, customization.headingFont) + + XCTAssertEqual(UIFont.systemFont(ofSize: 2), stdsCustomization.font) + XCTAssertEqual(stdsCustomization.font, customization.font) + + XCTAssertEqual(UIColor.magenta, stdsCustomization.textColor) + XCTAssertEqual(stdsCustomization.textColor, customization.textColor) + } +} diff --git a/Stripe/StripeiOSTests/STPThreeDSLabelCustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSLabelCustomizationTest.swift new file mode 100644 index 00000000..bd546dfc --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSLabelCustomizationTest.swift @@ -0,0 +1,37 @@ +// +// STPThreeDSLabelCustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSLabelCustomizationTest: XCTestCase { + func testPropertiesAreForwarded() { + let customization = STPThreeDSLabelCustomization.defaultSettings() + customization.headingFont = UIFont.systemFont(ofSize: 1) + customization.headingTextColor = UIColor.red + customization.font = UIFont.systemFont(ofSize: 2) + customization.textColor = UIColor.blue + + let stdsCustomization = customization.labelCustomization + + XCTAssertEqual(UIFont.systemFont(ofSize: 1), stdsCustomization.headingFont) + XCTAssertEqual(stdsCustomization.headingFont, customization.headingFont) + + XCTAssertEqual(UIColor.red, stdsCustomization.headingTextColor) + XCTAssertEqual(stdsCustomization.headingTextColor, customization.headingTextColor) + + XCTAssertEqual(UIFont.systemFont(ofSize: 2), stdsCustomization.font) + XCTAssertEqual(stdsCustomization.font, customization.font) + + XCTAssertEqual(UIColor.blue, stdsCustomization.textColor) + XCTAssertEqual(stdsCustomization.textColor, customization.textColor) + } +} diff --git a/Stripe/StripeiOSTests/STPThreeDSNavigationBarCustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSNavigationBarCustomizationTest.swift new file mode 100644 index 00000000..1d09877d --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSNavigationBarCustomizationTest.swift @@ -0,0 +1,48 @@ +// +// STPThreeDSNavigationBarCustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSNavigationBarCustomizationTest: XCTestCase { + func testPropertiesAreForwarded() { + let customization = STPThreeDSNavigationBarCustomization.defaultSettings() + customization.font = UIFont.italicSystemFont(ofSize: 1) + customization.textColor = UIColor.blue + customization.barTintColor = UIColor.red + customization.barStyle = UIBarStyle.black + customization.translucent = false + customization.headerText = "foo" + customization.buttonText = "bar" + + let stdsCustomization = customization.navigationBarCustomization + XCTAssertEqual(UIFont.italicSystemFont(ofSize: 1), stdsCustomization.font) + XCTAssertEqual(stdsCustomization.font, customization.font) + + XCTAssertEqual(UIColor.blue, stdsCustomization.textColor) + XCTAssertEqual(stdsCustomization.textColor, customization.textColor) + + XCTAssertEqual(UIColor.red, stdsCustomization.barTintColor) + XCTAssertEqual(stdsCustomization.barTintColor, customization.barTintColor) + + XCTAssertEqual(UIBarStyle.black, stdsCustomization.barStyle) + XCTAssertEqual(stdsCustomization.barStyle, customization.barStyle) + + XCTAssertEqual(false, stdsCustomization.translucent) + XCTAssertEqual(stdsCustomization.translucent, customization.translucent) + + XCTAssertEqual("foo", stdsCustomization.headerText) + XCTAssertEqual(stdsCustomization.headerText, customization.headerText) + + XCTAssertEqual("bar", stdsCustomization.buttonText) + XCTAssertEqual(stdsCustomization.buttonText, customization.buttonText) + } +} diff --git a/Stripe/StripeiOSTests/STPThreeDSSelectionCustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSSelectionCustomizationTest.swift new file mode 100644 index 00000000..344f04e1 --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSSelectionCustomizationTest.swift @@ -0,0 +1,42 @@ +// +// STPThreeDSSelectionCustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSSelectionCustomizationTest: XCTestCase { + func testPropertiesAreForwarded() { + let customization = STPThreeDSSelectionCustomization.defaultSettings() + customization.primarySelectedColor = UIColor.red + customization.secondarySelectedColor = UIColor.blue + customization.unselectedBorderColor = UIColor.brown + customization.unselectedBackgroundColor = UIColor.cyan + + let stdsCustomization = customization.selectionCustomization + XCTAssertEqual(UIColor.red, stdsCustomization.primarySelectedColor) + XCTAssertEqual(stdsCustomization.primarySelectedColor, customization.primarySelectedColor) + + XCTAssertEqual(UIColor.blue, stdsCustomization.secondarySelectedColor) + XCTAssertEqual( + stdsCustomization.secondarySelectedColor, + customization.secondarySelectedColor + ) + + XCTAssertEqual(UIColor.brown, stdsCustomization.unselectedBorderColor) + XCTAssertEqual(stdsCustomization.unselectedBorderColor, customization.unselectedBorderColor) + + XCTAssertEqual(UIColor.cyan, stdsCustomization.unselectedBackgroundColor) + XCTAssertEqual( + stdsCustomization.unselectedBackgroundColor, + customization.unselectedBackgroundColor + ) + } +} diff --git a/Stripe/StripeiOSTests/STPThreeDSTextFieldCustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSTextFieldCustomizationTest.swift new file mode 100644 index 00000000..2bc87ac9 --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSTextFieldCustomizationTest.swift @@ -0,0 +1,48 @@ +// +// STPThreeDSTextFieldCustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSTextFieldCustomizationTest: XCTestCase { + func testPropertiesAreForwarded() { + let customization = STPThreeDSTextFieldCustomization.defaultSettings() + customization.font = UIFont.italicSystemFont(ofSize: 1) + customization.textColor = UIColor.blue + customization.borderWidth = -1 + customization.borderColor = UIColor.red + customization.cornerRadius = -8 + customization.keyboardAppearance = .alert + customization.placeholderTextColor = UIColor.green + + let stdsCustomization = customization.textFieldCustomization + XCTAssertEqual(UIFont.italicSystemFont(ofSize: 1), stdsCustomization.font) + XCTAssertEqual(stdsCustomization.font, customization.font) + + XCTAssertEqual(UIColor.blue, stdsCustomization.textColor) + XCTAssertEqual(stdsCustomization.textColor, customization.textColor) + + XCTAssertEqual(-1, stdsCustomization.borderWidth, accuracy: 0.1) + XCTAssertEqual(stdsCustomization.borderWidth, customization.borderWidth, accuracy: 0.1) + + XCTAssertEqual(UIColor.red, stdsCustomization.borderColor) + XCTAssertEqual(stdsCustomization.borderColor, customization.borderColor) + + XCTAssertEqual(-8, stdsCustomization.cornerRadius, accuracy: 0.1) + XCTAssertEqual(stdsCustomization.cornerRadius, customization.cornerRadius, accuracy: 0.1) + + XCTAssertEqual(UIKeyboardAppearance.alert, stdsCustomization.keyboardAppearance) + XCTAssertEqual(stdsCustomization.keyboardAppearance, customization.keyboardAppearance) + + XCTAssertEqual(UIColor.green, stdsCustomization.placeholderTextColor) + XCTAssertEqual(stdsCustomization.placeholderTextColor, customization.placeholderTextColor) + } +} diff --git a/Stripe/StripeiOSTests/STPThreeDSUICustomizationTest.swift b/Stripe/StripeiOSTests/STPThreeDSUICustomizationTest.swift new file mode 100644 index 00000000..93968d8e --- /dev/null +++ b/Stripe/StripeiOSTests/STPThreeDSUICustomizationTest.swift @@ -0,0 +1,137 @@ +// +// STPThreeDSUICustomizationTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class STPThreeDSUICustomizationTest: XCTestCase { + func testPropertiesPassedThrough() { + let customization = STPThreeDSUICustomization.defaultSettings() + + // Maintains button customization objects + customization.buttonCustomization(for: .next).backgroundColor = UIColor.cyan + customization.buttonCustomization(for: .resend).backgroundColor = UIColor.cyan + customization.buttonCustomization(for: .submit).backgroundColor = UIColor.cyan + customization.buttonCustomization(for: .continue).backgroundColor = UIColor.cyan + customization.buttonCustomization(for: .cancel).backgroundColor = UIColor.cyan + XCTAssertEqual( + customization.uiCustomization.buttonCustomization(for: .next).backgroundColor, + UIColor.cyan + ) + XCTAssertEqual( + customization.uiCustomization.buttonCustomization(for: .resend).backgroundColor, + UIColor.cyan + ) + XCTAssertEqual( + customization.uiCustomization.buttonCustomization(for: .submit).backgroundColor, + UIColor.cyan + ) + XCTAssertEqual( + customization.uiCustomization.buttonCustomization(for: .continue).backgroundColor, + UIColor.cyan + ) + XCTAssertEqual( + customization.uiCustomization.buttonCustomization(for: .cancel).backgroundColor, + UIColor.cyan + ) + + let buttonCustomization = STPThreeDSButtonCustomization.defaultSettings(for: .next) + customization.setButtonCustomization(buttonCustomization, for: .next) + XCTAssertEqual( + customization.uiCustomization.buttonCustomization(for: .next), + buttonCustomization.buttonCustomization + ) + + // Footer + customization.footerCustomization.backgroundColor = UIColor.cyan + XCTAssertEqual( + customization.uiCustomization.footerCustomization.backgroundColor, + UIColor.cyan + ) + + let footerCustomization = STPThreeDSFooterCustomization.defaultSettings() + customization.footerCustomization = footerCustomization + XCTAssertEqual( + customization.uiCustomization.footerCustomization, + footerCustomization.footerCustomization + ) + + // Label + customization.labelCustomization.textColor = UIColor.cyan + XCTAssertEqual(customization.uiCustomization.labelCustomization.textColor, UIColor.cyan) + + let labelCustomization = STPThreeDSLabelCustomization.defaultSettings() + customization.labelCustomization = labelCustomization + XCTAssertEqual( + customization.uiCustomization.labelCustomization, + labelCustomization.labelCustomization + ) + + // Navigation Bar + customization.navigationBarCustomization.textColor = UIColor.cyan + XCTAssertEqual( + customization.uiCustomization.navigationBarCustomization.textColor, + UIColor.cyan + ) + + let navigationBar = STPThreeDSNavigationBarCustomization.defaultSettings() + customization.navigationBarCustomization = navigationBar + XCTAssertEqual( + customization.uiCustomization.navigationBarCustomization, + navigationBar.navigationBarCustomization + ) + + // Selection + customization.selectionCustomization.primarySelectedColor = UIColor.cyan + XCTAssertEqual( + customization.uiCustomization.selectionCustomization.primarySelectedColor, + UIColor.cyan + ) + + let selection = STPThreeDSSelectionCustomization.defaultSettings() + customization.selectionCustomization = selection + XCTAssertEqual( + customization.uiCustomization.selectionCustomization, + selection.selectionCustomization + ) + + // Text Field + customization.textFieldCustomization.textColor = UIColor.cyan + XCTAssertEqual(customization.uiCustomization.textFieldCustomization.textColor, UIColor.cyan) + + let textField = STPThreeDSTextFieldCustomization.defaultSettings() + customization.textFieldCustomization = textField + XCTAssertEqual( + customization.uiCustomization.textFieldCustomization, + textField.textFieldCustomization + ) + + // Other + customization.backgroundColor = UIColor.red + customization.activityIndicatorViewStyle = UIActivityIndicatorView.Style.large + customization.blurStyle = UIBlurEffect.Style.dark + + XCTAssertEqual(UIColor.red, customization.backgroundColor) + XCTAssertEqual(customization.backgroundColor, customization.uiCustomization.backgroundColor) + + XCTAssertEqual( + UIActivityIndicatorView.Style.large, + customization.activityIndicatorViewStyle + ) + XCTAssertEqual( + customization.activityIndicatorViewStyle, + customization.uiCustomization.activityIndicatorViewStyle + ) + + XCTAssertEqual(UIBlurEffect.Style.dark, customization.blurStyle) + XCTAssertEqual(customization.blurStyle, customization.uiCustomization.blurStyle) + } +} diff --git a/Stripe/StripeiOSTests/STPTokenTest.swift b/Stripe/StripeiOSTests/STPTokenTest.swift new file mode 100644 index 00000000..e4c03570 --- /dev/null +++ b/Stripe/StripeiOSTests/STPTokenTest.swift @@ -0,0 +1,67 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPTokenTest.m +// Stripe +// +// Created by Saikat Chakrabarti on 11/9/12. +// +// + +import Stripe +import XCTest + +class STPTokenTest: XCTestCase { + func buildTokenResponse() -> [AnyHashable: Any]? { + let cardDict = [ + "id": "card_123", + "exp_month": "12", + "exp_year": "2013", + "name": "Smerlock Smolmes", + "address_line1": "221A Baker Street", + "address_city": "New York", + "address_state": "NY", + "address_zip": "12345", + "address_country": "US", + "last4": "1234", + "brand": "Visa", + "fingerprint": "Fingolfin", + "country": "JP", + ] + + let tokenDict = [ + "id": "id_for_token", + "object": "token", + "livemode": NSNumber(value: false), + "created": NSNumber(value: 1353025450.0), + "used": NSNumber(value: false), + "card": cardDict, + "type": "card", + ] as [String: Any] + return tokenDict + } + + func testCreatingTokenWithAttributeDictionarySetsAttributes() { + guard + let token = STPToken.decodedObject(fromAPIResponse: buildTokenResponse()), + let timeInterval = token.created?.timeIntervalSince1970 + else { + XCTFail() + return + } + XCTAssertEqual(token.tokenId, "id_for_token") + XCTAssertEqual(token.livemode, false, "Generated token has the correct livemode") + XCTAssertEqual(token.type, STPTokenType.card, "Generated token has incorrect type") + + XCTAssertEqual(timeInterval, 1353025450.0, accuracy: 1.0, "Generated token has the correct created time") + } + + func testCreatingTokenSetsAdditionalResponseFields() { + var tokenResponse = buildTokenResponse() + tokenResponse?["foo"] = "bar" + let token = STPToken.decodedObject(fromAPIResponse: tokenResponse) + let allResponseFields = token?.allResponseFields + XCTAssertEqual(allResponseFields?["foo"] as? String, "bar") + XCTAssertEqual(allResponseFields?["livemode"] as? NSNumber, NSNumber(value: false)) + XCTAssertNil(allResponseFields?["baz"]) + } +} diff --git a/Stripe/StripeiOSTests/STPViewWithSeparatorSnapshotTests.swift b/Stripe/StripeiOSTests/STPViewWithSeparatorSnapshotTests.swift new file mode 100644 index 00000000..a329c80e --- /dev/null +++ b/Stripe/StripeiOSTests/STPViewWithSeparatorSnapshotTests.swift @@ -0,0 +1,28 @@ +// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/ +// +// STPViewWithSeparatorSnapshotTests.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCaseCore +import StripeCoreTestUtils +@testable import StripePaymentsUI + +class STPViewWithSeparatorSnapshotTests: STPSnapshotTestCase { + + func testDefaultAppearance() { + let view = STPViewWithSeparator(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0)) + view.backgroundColor = UIColor.white + STPSnapshotVerifyView(view, identifier: "STPViewWithSeparator.defaultAppearance") + } + + func testHiddenTopSeparator() { + let view = STPViewWithSeparator(frame: CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0)) + view.backgroundColor = UIColor.white + view.topSeparatorHidden = true + STPSnapshotVerifyView(view, identifier: "STPViewWithSeparator.hiddenTopSeparator") + } +} diff --git a/Stripe/StripeiOSTests/ServerErrorMapperTest.swift b/Stripe/StripeiOSTests/ServerErrorMapperTest.swift new file mode 100644 index 00000000..8087ec8b --- /dev/null +++ b/Stripe/StripeiOSTests/ServerErrorMapperTest.swift @@ -0,0 +1,94 @@ +// +// ServerErrorMapperTest.swift +// StripeiOS Tests +// +// Created by Nick Porter on 9/13/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class ServerErrorMapperTest: XCTestCase { + + func testFromMissingPublishableKey() { + let serverErrorMessage = + "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. \'Authorization: Bearer YOUR_SECRET_KEY\'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/." + + XCTAssertTrue( + ServerErrorMapper.mobileErrorMessage( + from: serverErrorMessage, + httpResponse: HTTPURLResponse() + )!.hasPrefix("No valid API key provided. Set `STPAPIClient.shared") + ) + } + + func testFromInvalidPublishableKey() { + let serverErrorMessage = "Invalid API Key provided: pk_test" + + XCTAssertTrue( + ServerErrorMapper.mobileErrorMessage( + from: serverErrorMessage, + httpResponse: HTTPURLResponse() + )!.hasPrefix("No valid API key provided. Set `STPAPIClient.shared") + ) + } + + func testFromInvalidCustEphKey() { + let serverErrorMessage = "Invalid API Key provided: badCustEphKey" + + let url = URL(string: "https://api.stripe.com/v1/payment_methods?customer=")! + let httpResponse = HTTPURLResponse( + url: url, + statusCode: 401, + httpVersion: nil, + headerFields: nil + ) + XCTAssertTrue( + ServerErrorMapper.mobileErrorMessage( + from: serverErrorMessage, + httpResponse: httpResponse + )!.hasPrefix("Invalid customer ephemeral key secret.") + ) + } + + func testFromNoSuchPaymentIntent() { + let serverErrorMessage = "No such payment_intent: pi_123" + + XCTAssertTrue( + ServerErrorMapper.mobileErrorMessage( + from: serverErrorMessage, + httpResponse: HTTPURLResponse() + )!.hasPrefix("No matching PaymentIntent could") + ) + } + + func testFromNoSuchSetupIntent() { + let serverErrorMessage = "No such setup_intent: si_123" + + XCTAssertTrue( + ServerErrorMapper.mobileErrorMessage( + from: serverErrorMessage, + httpResponse: HTTPURLResponse() + )!.hasPrefix("No matching SetupIntent could") + ) + } + + func testFromUnknownErrorMessage() { + let serverErrorMessage = + "This error message is not known to ServerErrorMapper, should return nil." + + XCTAssertNil( + ServerErrorMapper.mobileErrorMessage( + from: serverErrorMessage, + httpResponse: HTTPURLResponse() + ) + ) + } + +} diff --git a/Stripe/StripeiOSTests/StripeErrorTest.swift b/Stripe/StripeiOSTests/StripeErrorTest.swift new file mode 100644 index 00000000..7c42f17e --- /dev/null +++ b/Stripe/StripeiOSTests/StripeErrorTest.swift @@ -0,0 +1,273 @@ +// +// StripeErrorTest.swift +// StripeiOS Tests +// +// Created by Ben Guo on 4/14/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class StripeErrorTest: XCTestCase { + func testEmptyResponse() { + let response: [AnyHashable: Any] = [:] + let error = NSError.stp_error(fromStripeResponse: response) + XCTAssertNil(error) + } + + func testResponseWithUnknownTypeAndNoMessage() { + let response = [ + "error": [ + "type": "foo", + "code": "error_code", + ], + ] + let error = NSError.stp_error(fromStripeResponse: response)! + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.apiError.rawValue) + XCTAssertEqual( + error.userInfo[NSLocalizedDescriptionKey] as! String, + NSError.stp_unexpectedErrorMessage() + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorTypeKey] as! String, + response["error"]!["type"]! + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorCodeKey] as! String, + response["error"]!["code"]! + ) + XCTAssertTrue( + (error.userInfo[STPError.errorMessageKey]! as! String).hasPrefix( + "Could not interpret the error response" + ) + ) + } + + func testAPIError() { + let response = [ + "error": [ + "type": "api_error", + "message": "some message", + ], + ] + let error = NSError.stp_error(fromStripeResponse: response)! + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.apiError.rawValue) + XCTAssertEqual( + error.userInfo[NSLocalizedDescriptionKey] as! String?, + NSError.stp_unexpectedErrorMessage() + ) + XCTAssertEqual( + error.userInfo[STPError.errorMessageKey] as! String?, + response["error"]!["message"] + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorTypeKey] as! String?, + response["error"]!["type"] + ) + } + + func testInvalidRequestErrorMissingParameter() { + let response = [ + "error": [ + "type": "invalid_request_error", + "message": "The payment method `card` requires the parameter: card[exp_year].", + "param": "card[exp_year]", + ], + ] + let error = NSError.stp_error(fromStripeResponse: response)! + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.invalidRequestError.rawValue) + XCTAssertEqual( + error.userInfo[NSLocalizedDescriptionKey] as? String, + NSError.stp_unexpectedErrorMessage() + ) + XCTAssertEqual( + error.userInfo[STPError.errorMessageKey] as? String, + response["error"]!["message"] + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorTypeKey] as? String, + response["error"]!["type"] + ) + XCTAssertEqual(error.userInfo[STPError.errorParameterKey] as! String, "card[expYear]") + } + + func testAuthenticationError() { + // Given an `invalid_request_error` response + let response = [ + "error": [ + "type": "invalid_request_error", + "message": "Invalid API Key provided: pk_test_***************************00", + ], + ] + + // with a `401` HTTP status code + let httpResponse = HTTPURLResponse( + url: URL(string: "https://api.stripe.com/v1/payment_intents")!, + statusCode: 401, + httpVersion: "1.1", + headerFields: nil + ) + + let error = NSError.stp_error(fromStripeResponse: response, httpResponse: httpResponse)! + + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual( + error.code, + STPErrorCode.authenticationError.rawValue, + "`error.code` should be equals to `STPErrorCode.authenticationError`" + ) + } + + func testAuthenticationErrorDueToExpiredKey() { + // Given an `invalid_request_error` response due to an expired key + let response = [ + "error": [ + "code": "api_key_expired", + "type": "invalid_request_error", + "message": "Expired API Key provided: pk_test_***************************00", + ], + ] + + // with a `401` HTTP status code + let httpResponse = HTTPURLResponse( + url: URL(string: "https://api.stripe.com/v1/payment_intents")!, + statusCode: 401, + httpVersion: "1.1", + headerFields: nil + ) + + let error = NSError.stp_error(fromStripeResponse: response, httpResponse: httpResponse)! + + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual( + error.code, + STPErrorCode.authenticationError.rawValue, + "`error.code` should be equals to `STPErrorCode.authenticationError`" + ) + } + + func testInvalidRequestErrorIncorrectNumber() { + let response = [ + "error": [ + "type": "invalid_request_error", + "message": "Your card number is incorrect.", + "code": "incorrect_number", + ], + ] + let error = NSError.stp_error(fromStripeResponse: response)! + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.invalidRequestError.rawValue) + // Error type is not `card_error`, so `NSLocalizedDescription` will be a generic error. + XCTAssertEqual( + error.userInfo[NSLocalizedDescriptionKey] as! String, + NSError.stp_unexpectedErrorMessage() + ) + XCTAssertEqual( + error.userInfo[STPError.cardErrorCodeKey] as! String, + STPError.incorrectNumber + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorTypeKey] as? String, + response["error"]!["type"] + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorCodeKey] as? String, + response["error"]!["code"] + ) + XCTAssertEqual( + error.userInfo[STPError.errorMessageKey] as? String, + response["error"]!["message"] + ) + } + + func testCardErrorIncorrectNumber() { + let response = [ + "error": [ + "type": "card_error", + "message": "Your card number is incorrect.", + "code": "incorrect_number", + ], + ] + let error = NSError.stp_error(fromStripeResponse: response)! + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.cardError.rawValue) + XCTAssertEqual( + error.userInfo[NSLocalizedDescriptionKey] as! String, + "Your card number is incorrect." + ) + XCTAssertEqual( + error.userInfo[STPError.cardErrorCodeKey] as! String, + STPError.incorrectNumber + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorTypeKey] as? String, + response["error"]!["type"] + ) + XCTAssertEqual( + error.userInfo[STPError.stripeErrorCodeKey] as? String, + response["error"]!["code"] + ) + XCTAssertEqual( + error.userInfo[STPError.errorMessageKey] as? String, + response["error"]!["message"] + ) + } + + func testCardDeclinedError() { + let response = [ + "error": [ + "type": "card_error", + "code": "card_declined", + "decline_code": "insufficient_funds", + ], + ] + guard let error = NSError.stp_error(fromStripeResponse: response) else { + XCTFail() + return + } + XCTAssertEqual(error.domain, STPError.stripeDomain) + XCTAssertEqual(error.code, STPErrorCode.cardError.rawValue) + XCTAssertEqual( + error.userInfo[STPError.cardErrorCodeKey] as? String, + STPCardErrorCode.cardDeclined.rawValue + ) + // Response didn't include a message, so a built in message will be used. + XCTAssertEqual( + error.userInfo[NSLocalizedDescriptionKey] as? String, + NSError.stp_cardErrorDeclinedUserMessage() + ) + XCTAssertEqual( + error.userInfo[STPError.stripeDeclineCodeKey] as? String, + "insufficient_funds" + ) + } + + func testErrorAddsRequestIdToUserInfo() { + let response = [ + "error": [ + "type": "card_error", + "code": "card_declined", + "decline_code": "insufficient_funds", + ], + ] + let httpURLResponse = HTTPURLResponse(url: URL(string: "https://api.stripe.com/v1/some_endpoint")!, statusCode: 400, httpVersion: nil, headerFields: ["request-id": "req_123"]) + guard let error = NSError.stp_error(fromStripeResponse: response, httpResponse: httpURLResponse) else { + XCTFail() + return + } + XCTAssertEqual( + error.userInfo[STPError.stripeRequestIDKey] as? String, + "req_123" + ) + } +} diff --git a/Stripe/StripeiOSTests/StripeTests-Prefix.pch b/Stripe/StripeiOSTests/StripeTests-Prefix.pch new file mode 100644 index 00000000..0540b07f --- /dev/null +++ b/Stripe/StripeiOSTests/StripeTests-Prefix.pch @@ -0,0 +1,21 @@ +// +// StripeTests-Prefix.pch +// StripeiOS Tests +// +// Created by David Estes on 10/8/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#ifndef StripeTests_Prefix_pch +#define StripeTests_Prefix_pch + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#import "STPBlocks.h" +@import StripeApplePay; +@import Stripe; +@import StripePayments; +@import StripePaymentsUI; + +#endif /* StripeTests_Prefix_pch */ diff --git a/Stripe/StripeiOSTests/StripeiOS Tests-Bridging-Header.h b/Stripe/StripeiOSTests/StripeiOS Tests-Bridging-Header.h new file mode 100644 index 00000000..ad4027ef --- /dev/null +++ b/Stripe/StripeiOSTests/StripeiOS Tests-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +@import StripePaymentsObjcTestUtils; diff --git a/Stripe/StripeiOSTests/TextFieldElement+IBANTest.swift b/Stripe/StripeiOSTests/TextFieldElement+IBANTest.swift new file mode 100644 index 00000000..01b65d5c --- /dev/null +++ b/Stripe/StripeiOSTests/TextFieldElement+IBANTest.swift @@ -0,0 +1,140 @@ +// +// TextFieldElement+IBANTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/23/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class TextFieldElementIBANTest: XCTestCase { + typealias IBANError = TextFieldElement.IBANError + typealias Error = TextFieldElement.Error + + func testValidation() throws { + let testcases: [String: TextFieldElement.ValidationState] = [ + "": .invalid(Error.empty), + "G": .invalid(IBANError.incomplete), + "GB": .invalid(IBANError.incomplete), + "GB1": .invalid(IBANError.incomplete), + "GB12": .invalid(IBANError.incomplete), + + "1": .invalid(IBANError.shouldStartWithCountryCode), + "12": .invalid(IBANError.shouldStartWithCountryCode), + "Z1": .invalid(IBANError.shouldStartWithCountryCode), + "🤦🏻🇺🇸": .invalid(IBANError.shouldStartWithCountryCode), + + "ZZ": .invalid(IBANError.invalidCountryCode(countryCode: "ZZ")), + + "GB82WEST12345698765432🇺🇸": .invalid(IBANError.invalidFormat), + // https://www.iban.com/testibans + "GB94BARC20201530093459": .invalid(IBANError.invalidFormat), + + "GB33BUKB20201555555555": .valid, + "GB94BARC10201530093459": .valid, + "SK6902000000001933504555": .valid, + "BG09STSA93000021741508": .valid, + "FR1420041010050500013M02606": .valid, + "AT611904300234573201": .valid, + "AT861904300235473202": .valid, + ] + + let config = TextFieldElement.IBANConfiguration(defaultValue: nil) + for (text, expected) in testcases { + let actual = config.validate(text: text, isOptional: false) + XCTAssertTrue( + actual == expected, + "Input \"\(text)\": expected \(expected) but got \(actual)" + ) + } + } + + func testValidateCountryCode() { + let testcases: [String: TextFieldElement.ValidationState] = [ + "": .invalid(IBANError.incomplete), + "A": .invalid(IBANError.incomplete), + "D": .invalid(IBANError.incomplete), + + "ū": .invalid(IBANError.shouldStartWithCountryCode), + "1": .invalid(IBANError.shouldStartWithCountryCode), + ".": .invalid(IBANError.shouldStartWithCountryCode), + + "AT": .valid, + "DE": .valid, + ] + for (test, expected) in testcases { + let actual = TextFieldElement.IBANConfiguration.validateCountryCode(test) + XCTAssertTrue(actual == expected) + } + } + + func testTransformToASCIIDigits() { + let testcases: [String: String] = [ + "": "", + "1234": "1234", + "GB82": "161182", + "AAAA": "10101010", + "ZZZZ": "35353535", + ] + for (test, expected) in testcases { + let actual = TextFieldElement.IBANConfiguration.transformToASCIIDigits(test) + XCTAssertTrue(actual == expected) + } + } + + func testMod97() { + let testcases: [String: Int?] = [ + "0": 0, + "97": 0, + "96": 96, + "00001": 1, + "13985713857180375018375081735081735": 15, + "13985713857180375018375081735081720": 0, + ] + for (test, expected) in testcases { + let actual = TextFieldElement.IBANConfiguration.mod97(test) + XCTAssertTrue(actual == expected) + } + + for _ in 0...100 { + let test = Int.random(in: 0...Int.max) + let actual = TextFieldElement.IBANConfiguration.mod97(String(test)) + XCTAssertTrue(actual == test % 97) + } + } +} + +// MARK: - Helpers + +// TODO(mludowise): These should get migrated to a shared StripeUICoreTestUtils target + +extension TextFieldElement.ValidationState: Equatable { + public static func == ( + lhs: TextFieldElement.ValidationState, + rhs: TextFieldElement.ValidationState + ) -> Bool { + switch (lhs, rhs) { + case (.valid, .valid): + return true + case (.invalid(let lhsError), .invalid(let rhsError)): + return lhsError == rhsError + default: + return false + } + } +} + +func == (lhs: TextFieldValidationError, rhs: TextFieldValidationError) -> Bool { + guard String(describing: lhs) == String(describing: rhs) else { + return false + } + return (lhs as NSError).isEqual(rhs as NSError) +} diff --git a/Stripe/StripeiOSTests/UserDefaults+StripeTest.swift b/Stripe/StripeiOSTests/UserDefaults+StripeTest.swift new file mode 100644 index 00000000..de944c1a --- /dev/null +++ b/Stripe/StripeiOSTests/UserDefaults+StripeTest.swift @@ -0,0 +1,27 @@ +// +// UserDefaults+StripeTest.swift +// StripeiOS Tests +// +// Created by Yuki Tokuhiro on 5/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentsUI + +class UserDefaults_StripeTest: XCTestCase { + func testFraudDetectionData() throws { + let fraudDetectionData = FraudDetectionData( + sid: UUID().uuidString, + muid: UUID().uuidString, + guid: UUID().uuidString, + sidCreationDate: Date() + ) + UserDefaults.standard.fraudDetectionData = fraudDetectionData + XCTAssertEqual(UserDefaults.standard.fraudDetectionData, fraudDetectionData) + } +} diff --git a/Stripe/StripeiOSTests/WalletHeaderViewSnapshotTests.swift b/Stripe/StripeiOSTests/WalletHeaderViewSnapshotTests.swift new file mode 100644 index 00000000..c58c98e5 --- /dev/null +++ b/Stripe/StripeiOSTests/WalletHeaderViewSnapshotTests.swift @@ -0,0 +1,185 @@ +// +// WalletHeaderViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 12/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import Stripe +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet + +class WalletHeaderViewSnapshotTests: STPSnapshotTestCase { + + func testApplePayButton() { + let headerView = PaymentSheetViewController.WalletHeaderView( + options: .applePay, + delegate: nil + ) + verify(headerView) + } + + func testApplePayButtonWithCustomCta() { + let headerView = PaymentSheetViewController.WalletHeaderView( + options: .applePay, + applePayButtonType: .buy, + delegate: nil + ) + verify(headerView) + } + + func testLinkButton() { + let headerView = PaymentSheetViewController.WalletHeaderView( + options: .link, + delegate: nil + ) + verify(headerView) + } + + // Tests UI elements that adapt their color based on the `PaymentSheet.Appearance` + func testAdaptiveElements() { + var darkMode = false + + var appearance = PaymentSheet.Appearance() + appearance.colors.background = UIColor.init(dynamicProvider: { _ in + if darkMode { + return .black + } + + return .white + }) + + appearance.cornerRadius = 0 + let headerView = PaymentSheetViewController.WalletHeaderView( + options: .applePay, + appearance: appearance, + delegate: nil + ) + + verify(headerView, identifier: "Light") + + darkMode = true + headerView.traitCollectionDidChange(nil) + + verify(headerView, identifier: "Dark") + } + + // Tests UI elements that adapt their color based on the `PaymentSheet.Appearance` + func testAdaptiveElementsWithCustomApplePayCta() { + var darkMode = false + + var appearance = PaymentSheet.Appearance() + appearance.colors.background = UIColor.init(dynamicProvider: { _ in + if darkMode { + return .black + } + + return .white + }) + + appearance.cornerRadius = 0 + let headerView = PaymentSheetViewController.WalletHeaderView( + options: .applePay, + appearance: appearance, + applePayButtonType: .buy, + delegate: nil + ) + + verify(headerView, identifier: "Light") + + darkMode = true + headerView.traitCollectionDidChange(nil) + + verify(headerView, identifier: "Dark") + } + + func testAllButtons() { + let headerView = PaymentSheetViewController.WalletHeaderView( + options: [.applePay, .link], + delegate: nil + ) + verify(headerView) + + headerView.showsCardPaymentMessage = true + verify(headerView, identifier: "Card only") + } + + func testAllButtonsWithCustomApplePayCta() { + let headerView = PaymentSheetViewController.WalletHeaderView( + options: [.applePay, .link], + applePayButtonType: .buy, + delegate: nil + ) + verify(headerView) + + headerView.showsCardPaymentMessage = true + verify(headerView, identifier: "Card only") + } + + func testCustomFont() throws { + var appearance = PaymentSheet.Appearance.default + appearance.font.base = try XCTUnwrap(UIFont(name: "AmericanTypewriter", size: 12.0)) + + let headerView = PaymentSheetViewController.WalletHeaderView( + options: [.applePay, .link], + appearance: appearance, + delegate: nil + ) + + verify(headerView) + } + + func testCustomFontScales() throws { + var appearance = PaymentSheet.Appearance.default + appearance.font.base = try XCTUnwrap(UIFont(name: "AmericanTypewriter", size: 12.0)) + appearance.font.sizeScaleFactor = 1.25 + + let headerView = PaymentSheetViewController.WalletHeaderView( + options: [.applePay, .link], + appearance: appearance, + delegate: nil + ) + + verify(headerView) + } + + func testCustomCornerRadius() { + var appearance = PaymentSheet.Appearance.default + appearance.cornerRadius = 14.5 + + let headerView = PaymentSheetViewController.WalletHeaderView( + options: [.applePay, .link], + appearance: appearance, + delegate: nil + ) + + verify(headerView) + } + + func testAllButtonsSetupIntent() { + let headerView = PaymentSheetViewController.WalletHeaderView( + options: [.applePay, .link], + isPaymentIntent: false, + delegate: nil + ) + verify(headerView) + + headerView.showsCardPaymentMessage = true + verify(headerView, identifier: "Card only") + } + + func verify( + _ view: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + view.autosizeHeight(width: 300) + STPSnapshotVerifyView(view, identifier: identifier, file: file, line: line) + } +} diff --git a/Stripe3DS2/BuildConfigurations/Project-Debug.xcconfig b/Stripe3DS2/BuildConfigurations/Project-Debug.xcconfig new file mode 100644 index 00000000..039738ae --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Project-Debug.xcconfig @@ -0,0 +1,15 @@ +// +// Project-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Project-Shared.xcconfig" + +ENABLE_TESTABILITY = YES +GCC_DYNAMIC_NO_PIC = NO +GCC_OPTIMIZATION_LEVEL = 0 +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) +MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE +ONLY_ACTIVE_ARCH = YES \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Project-Release.xcconfig b/Stripe3DS2/BuildConfigurations/Project-Release.xcconfig new file mode 100644 index 00000000..e2ec59ca --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Project-Release.xcconfig @@ -0,0 +1,12 @@ +// +// Project-Release.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Project-Shared.xcconfig" + +ENABLE_NS_ASSERTIONS = NO +MTL_ENABLE_DEBUG_INFO = NO +VALIDATE_PRODUCT = YES \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Project-Shared.xcconfig b/Stripe3DS2/BuildConfigurations/Project-Shared.xcconfig new file mode 100644 index 00000000..cca99b2c --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Project-Shared.xcconfig @@ -0,0 +1,63 @@ +// +// Project-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +ALWAYS_SEARCH_USER_PATHS = NO +CLANG_ANALYZER_NONNULL = YES +CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE +CLANG_CXX_LANGUAGE_STANDARD = gnu++14 +CLANG_CXX_LIBRARY = libc++ +CLANG_ENABLE_MODULES = YES +CLANG_ENABLE_OBJC_ARC = YES +CLANG_ENABLE_OBJC_WEAK = YES +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_ASSIGN_ENUM = YES +CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES +CLANG_WARN_BOOL_CONVERSION = YES +CLANG_WARN_COMMA = YES +CLANG_WARN_CONSTANT_CONVERSION = YES +CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR +CLANG_WARN_DOCUMENTATION_COMMENTS = YES +CLANG_WARN_EMPTY_BODY = YES +CLANG_WARN_ENUM_CONVERSION = YES +CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES +CLANG_WARN_INFINITE_RECURSION = YES +CLANG_WARN_INT_CONVERSION = YES +CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +CLANG_WARN_OBJC_INTERFACE_IVARS = YES +CLANG_WARN_OBJC_LITERAL_CONVERSION = YES +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR +CLANG_WARN_RANGE_LOOP_ANALYSIS = YES +CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES +CLANG_WARN_SUSPICIOUS_MOVE = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN_UNREACHABLE_CODE = YES +COPY_PHASE_STRIP = NO +CURRENT_PROJECT_VERSION = 1.0 +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +ENABLE_STRICT_OBJC_MSGSEND = YES +GCC_C_LANGUAGE_STANDARD = gnu11 +GCC_NO_COMMON_BLOCKS = YES +GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES +GCC_TREAT_WARNINGS_AS_ERRORS = YES +GCC_WARN_64_TO_32_BIT_CONVERSION = YES +GCC_WARN_ABOUT_MISSING_NEWLINE = YES +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR +GCC_WARN_SIGN_COMPARE = YES +GCC_WARN_UNDECLARED_SELECTOR = YES +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE +GCC_WARN_UNUSED_FUNCTION = YES +GCC_WARN_UNUSED_VARIABLE = YES +IPHONEOS_DEPLOYMENT_TARGET = 13.0 +MTL_FAST_MATH = YES +SDKROOT = iphoneos +SWIFT_VERSION=5.0 +VERSION_INFO_PREFIX = +VERSIONING_SYSTEM = apple-generic diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2-Debug.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2-Debug.xcconfig new file mode 100644 index 00000000..34a46e79 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2-Debug.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2-Release.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2-Release.xcconfig new file mode 100644 index 00000000..1885c226 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2-Release.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2-Release.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2-Shared.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2-Shared.xcconfig new file mode 100644 index 00000000..02d8fe27 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2-Shared.xcconfig @@ -0,0 +1,23 @@ +// +// Stripe3DS2-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +APPLICATION_EXTENSION_API_ONLY = YES +BUILD_LIBRARY_FOR_DISTRIBUTION = YES +CODE_SIGN_STYLE = Automatic +DEFINES_MODULE = YES +DEPLOYMENT_POSTPROCESSING = YES +DYLIB_COMPATIBILITY_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_INSTALL_NAME_BASE = @rpath +GCC_PREFIX_HEADER = $(SRCROOT)/Stripe3DS2/include/Stripe3DS2-Prefix.pch +INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks +IPHONEOS_DEPLOYMENT_TARGET = 13.0 +OTHER_LDFLAGS = +SKIP_INSTALL = YES +STRIP_STYLE = non-global +TARGETED_DEVICE_FAMILY = 1,2 +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Debug.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Debug.xcconfig new file mode 100644 index 00000000..bd71fac1 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Debug.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2DemoUI-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2DemoUI-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Release.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Release.xcconfig new file mode 100644 index 00000000..baf74545 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Release.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2DemoUI-Release.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2DemoUI-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Shared.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Shared.xcconfig new file mode 100644 index 00000000..fb8baac4 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUI-Shared.xcconfig @@ -0,0 +1,13 @@ +// +// Stripe3DS2DemoUI-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon +CODE_SIGN_STYLE = Automatic +IPHONEOS_DEPLOYMENT_TARGET = 13.0 +TARGETED_DEVICE_FAMILY = 1,2 +DYLIB_INSTALL_NAME_BASE = @rpath +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Debug.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Debug.xcconfig new file mode 100644 index 00000000..53c3f9ff --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Debug.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2DemoUITests-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2DemoUITests-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Release.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Release.xcconfig new file mode 100644 index 00000000..7251a5cc --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Release.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2DemoUITests-Release.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2DemoUITests-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Shared.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Shared.xcconfig new file mode 100644 index 00000000..e520a8af --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2DemoUITests-Shared.xcconfig @@ -0,0 +1,13 @@ +// +// Stripe3DS2DemoUITests-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +BUNDLE_LOADER = $(TEST_HOST) +CODE_SIGN_STYLE = Automatic +TARGETED_DEVICE_FAMILY = 1,2 +TEST_HOST = $(BUILT_PRODUCTS_DIR)/Stripe3DS2DemoUI.app/Stripe3DS2DemoUI +DYLIB_INSTALL_NAME_BASE = @rpath +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Debug.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Debug.xcconfig new file mode 100644 index 00000000..1744b025 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Debug.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2Tests-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2Tests-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Release.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Release.xcconfig new file mode 100644 index 00000000..f03b2ac4 --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Release.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2Tests-Release.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +#include "Stripe3DS2Tests-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// \ No newline at end of file diff --git a/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Shared.xcconfig b/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Shared.xcconfig new file mode 100644 index 00000000..2e597a2e --- /dev/null +++ b/Stripe3DS2/BuildConfigurations/Stripe3DS2Tests-Shared.xcconfig @@ -0,0 +1,12 @@ +// +// Stripe3DS2Tests-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 12/5/22 +// https://buildsettingextractor.com +// + +CODE_SIGN_STYLE = Automatic +OTHER_LDFLAGS = -ObjC +TARGETED_DEVICE_FAMILY = 1,2 +DYLIB_INSTALL_NAME_BASE = @rpath +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks diff --git a/Stripe3DS2/Stripe3DS2.xcodeproj/project.pbxproj b/Stripe3DS2/Stripe3DS2.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ab3405e5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2.xcodeproj/project.pbxproj @@ -0,0 +1,1754 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 01706B4660728A5BFAC12840 /* STDSRuntimeException.m in Sources */ = {isa = PBXBuildFile; fileRef = CD33421F13A675DCFE597FFB /* STDSRuntimeException.m */; }; + 05647ABCFBDBE1209D5044AC /* STDSEphemeralKeyPair.m in Sources */ = {isa = PBXBuildFile; fileRef = F9D521B45783D36C8E83F0EA /* STDSEphemeralKeyPair.m */; }; + 0615DD02C0B022AE207DADF0 /* STDSUICustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = 210B22FF4DCB0C6E7C763EAB /* STDSUICustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 081347606D48F64161D95B45 /* mastercard.der in Resources */ = {isa = PBXBuildFile; fileRef = 1104D5855D28E49E104CA004 /* mastercard.der */; }; + 089DAFECA444861762781974 /* NSError+Stripe3DS2.m in Sources */ = {isa = PBXBuildFile; fileRef = 26C20D4B77372845627D6466 /* NSError+Stripe3DS2.m */; }; + 08ECC7E70E7478E76793ED1E /* UIViewController+Stripe3DS2.m in Sources */ = {isa = PBXBuildFile; fileRef = 99451064136843047C7881AD /* UIViewController+Stripe3DS2.m */; }; + 09DFAF7B38EAB76BC66AC9C8 /* STDSChallengeResponseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CE086326AF3DE336BF4F4C /* STDSChallengeResponseObject.m */; }; + 09F0B1945CC12FB1215119FD /* STDSProcessingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 95641ECBE1AE1CC198013405 /* STDSProcessingView.h */; }; + 0A2BC6A9E388B242C46C09D9 /* UIButton+CustomInitialization.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DD55BECC3FB85216FE46218 /* UIButton+CustomInitialization.h */; }; + 0BA0F0CADC4CFF419E1F7EFC /* STDSLabelCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = 6932DD26C7C16938DFA0E198 /* STDSLabelCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0D3D9C95CB14A610EAAAEF6E /* STDSJSONEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 0681C043F732148587737233 /* STDSJSONEncoder.m */; }; + 0D4B42EC5019D213BDBC84E7 /* NSDictionary+DecodingHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C566D6E444FCA4BCEC65F3D2 /* NSDictionary+DecodingHelpers.m */; }; + 0EEFDE04392FD017EA0A017A /* STDSDeviceInformationParameter.m in Sources */ = {isa = PBXBuildFile; fileRef = 25C7D18868DDADEF4CB6220C /* STDSDeviceInformationParameter.m */; }; + 1134F81C7D015BA5EA52BFD1 /* NSDictionary+DecodingHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = C78AA1D7AD2BAB52EE58D078 /* NSDictionary+DecodingHelpers.h */; }; + 1156B25EBE0135B627E174D2 /* STDSProgressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 84967ED0D9B206B3F5AA4F02 /* STDSProgressViewController.m */; }; + 125427F1322501AA12EAEBE6 /* visa.der in Resources */ = {isa = PBXBuildFile; fileRef = A70071543985284E0D9DAC64 /* visa.der */; }; + 128B64380D6724F016EE8D56 /* STDSDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 382C583385FAECA91366769E /* STDSDemoViewController.m */; }; + 136255493F3F0E930FBA8606 /* STDSImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = A09A89BBE7360F93195641C9 /* STDSImageLoader.m */; }; + 1469551DB874336B68F6C39D /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */; }; + 149FE0C2910F08910B250BD8 /* STDSException+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 461F938CDE7ED6D06D9D2700 /* STDSException+Internal.h */; }; + 14BDBD98F8E15576403255C9 /* STDSChallengeInformationView.m in Sources */ = {isa = PBXBuildFile; fileRef = D24777EE9931075405F370DB /* STDSChallengeInformationView.m */; }; + 15D4A2F07EA477B84CD48B15 /* STDSRuntimeErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = DE7D85369E012B15702D8DF9 /* STDSRuntimeErrorEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1767509FDC216602A5E43F0E /* STDSErrorMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 278F2C40987F5218BD031E3D /* STDSErrorMessageTest.m */; }; + 1B20B454D0C309613A1FAF68 /* STDSThreeDSProtocolVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AC648332A706809F1BDFD59 /* STDSThreeDSProtocolVersion.m */; }; + 206B93BDE91CFED74A053B87 /* STDSStackView.h in Headers */ = {isa = PBXBuildFile; fileRef = B7AE3B4732D203134FE096FE /* STDSStackView.h */; }; + 2086DFD1FC02783FB106AD35 /* UIColor+DefaultColors.m in Sources */ = {isa = PBXBuildFile; fileRef = 869733153BCA6BE2EB7A452F /* UIColor+DefaultColors.m */; }; + 2224463CE5FB99D4BEE6A479 /* STDSJSONEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 06C5207432FB07BD71703BCC /* STDSJSONEncoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 24D49F833E1ED28DE557F219 /* STDSException.h in Headers */ = {isa = PBXBuildFile; fileRef = B71A1C110DCC23A9CE929837 /* STDSException.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 24F626CD0F93B7AF59B1D6AA /* STDSThreeDS2Service.m in Sources */ = {isa = PBXBuildFile; fileRef = 77646CEC7EE754F47EDBDA4F /* STDSThreeDS2Service.m */; }; + 27F3EA1BB7E7B56A1A8524BA /* UIColor+ThirteenSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A8B0001705400C661678617 /* UIColor+ThirteenSupport.h */; }; + 284F72D3FD8C06C0E5974086 /* STDSTextFieldCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = 97163B5E9598CA3A298920B9 /* STDSTextFieldCustomization.m */; }; + 28AD7EC6F6DA2AECD7F61BA7 /* NSLayoutConstraint+LayoutSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = E0949399E96663964091C0EF /* NSLayoutConstraint+LayoutSupport.h */; }; + 2938D9CC41899DC78D01EF9F /* STDSDeviceInformationParameter.h in Headers */ = {isa = PBXBuildFile; fileRef = 172AF5C85EDBD92A1030E361 /* STDSDeviceInformationParameter.h */; }; + 2970D4880EBFAEFABA2E8EBD /* STDSChallengeRequestParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 60693BC560C4927ACFD2E6C3 /* STDSChallengeRequestParameters.m */; }; + 2AB06EC20B22306F1DDF9F4B /* STDSChallengeResponseImageObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A9F828840A3B361842E38DE /* STDSChallengeResponseImageObject.m */; }; + 2B09B7FDF8C4AA551F7EE18E /* STDSDeviceInformationParameterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FC52C3C844BDA981EE34BAB9 /* STDSDeviceInformationParameterTests.m */; }; + 2B2787A7103C0D0AB5F00B78 /* STDSAuthenticationResponseObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 138E82072FC6572612A8E248 /* STDSAuthenticationResponseObject.h */; }; + 2BBA82878804711E06CBFCCB /* STDSErrorMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 645D0BB927B7F8A0D37EE04D /* STDSErrorMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2C5621AABECF6192F92A20CF /* STDSStripe3DS2Error.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F4376BB07FD1EE783249AED /* STDSStripe3DS2Error.m */; }; + 2C6DB6516699B4EFADDF3360 /* STDSRuntimeException.h in Headers */ = {isa = PBXBuildFile; fileRef = B8619CA38E2A5B49DBF8546B /* STDSRuntimeException.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2CB6DF5CCCBC1EB9ABD03143 /* STDSNotInitializedException.h in Headers */ = {isa = PBXBuildFile; fileRef = 2FCABBF51CBA7F9FEF757B4C /* STDSNotInitializedException.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D42EA44FC01C834209CFED4 /* Stripe3DS2.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9E1CB633CA5917B2168BB928 /* Stripe3DS2.xcassets */; }; + 2E2022723BC7A4B2DD5ECE76 /* STDSErrorMessage+Internal.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CB591796654445B8434A501 /* STDSErrorMessage+Internal.m */; }; + 30632F7C730BF8D23B607CF7 /* STDSWarningTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 474F39AA3F47D84F8D54E5B7 /* STDSWarningTests.m */; }; + 30B25163BEDEB99CDD101B5F /* NSString+EmptyChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = A8A77DB1C711B8696B2E5FEF /* NSString+EmptyChecking.h */; }; + 310E6B542C52C9EF006A03EC /* 150-issuer.png in Resources */ = {isa = PBXBuildFile; fileRef = 310E6B4E2C52C9EF006A03EC /* 150-issuer.png */; }; + 310E6B552C52C9EF006A03EC /* 300-issuer.png in Resources */ = {isa = PBXBuildFile; fileRef = 310E6B4F2C52C9EF006A03EC /* 300-issuer.png */; }; + 310E6B562C52C9EF006A03EC /* 150-payment.png in Resources */ = {isa = PBXBuildFile; fileRef = 310E6B502C52C9EF006A03EC /* 150-payment.png */; }; + 310E6B572C52C9EF006A03EC /* 300-payment.png in Resources */ = {isa = PBXBuildFile; fileRef = 310E6B512C52C9EF006A03EC /* 300-payment.png */; }; + 310E6B582C52C9EF006A03EC /* 450-issuer.png in Resources */ = {isa = PBXBuildFile; fileRef = 310E6B522C52C9EF006A03EC /* 450-issuer.png */; }; + 310E6B592C52C9EF006A03EC /* 450-payment.png in Resources */ = {isa = PBXBuildFile; fileRef = 310E6B532C52C9EF006A03EC /* 450-payment.png */; }; + 3192FCC841152A7E5E6F19D6 /* STDSAlreadyInitializedException.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F1FD11407319293954C6D81 /* STDSAlreadyInitializedException.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 319DD1C82B0D50FF0083BA32 /* STDSVisionSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 319DD1C72B0D50F80083BA32 /* STDSVisionSupport.h */; }; + 31C636A25BF83316EE7EB57D /* STDSThreeDS2Service.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EAA444FA7C82BDA4AD907BF /* STDSThreeDS2Service.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 31CDFC322BA8E58100B3DD91 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 31CDFC312BA8E58100B3DD91 /* PrivacyInfo.xcprivacy */; }; + 326AD1DD7BB8A2A6DA66EC67 /* STDSThreeDSProtocolVersion+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 47C29E9F3D2A62676B424CD1 /* STDSThreeDSProtocolVersion+Private.h */; }; + 32BE38D04DD76CA4490389C6 /* STDSConfigParametersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F551808E65CDADD9B3A9CB8 /* STDSConfigParametersTests.m */; }; + 3343DA94A5032627843A343C /* STDSEphemeralKeyPair+Testing.h in Headers */ = {isa = PBXBuildFile; fileRef = 52D6E4F76CBA258163E57DF3 /* STDSEphemeralKeyPair+Testing.h */; }; + 335553A547A3CA80B3901CCF /* STDSStripe3DS2Error.h in Headers */ = {isa = PBXBuildFile; fileRef = AE53BAA72835AFA3B35C58D3 /* STDSStripe3DS2Error.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34256504F0E0E519D586B7CE /* STDSErrorMessage+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5463EB1CC8E85BE3BD12DEFF /* STDSErrorMessage+Internal.h */; }; + 3617B07ABD719F1A5F8302FA /* NSError+Stripe3DS2.h in Headers */ = {isa = PBXBuildFile; fileRef = A6778F8CE36F769DF608F932 /* NSError+Stripe3DS2.h */; }; + 3844F0E21742F43BCB32499A /* STDSDeviceInformationParameter+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4869D6E14F01EDB960CB3065 /* STDSDeviceInformationParameter+Private.h */; }; + 390691CA0EAF81418B0C65DB /* UIView+LayoutSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0F0485B7048DDAA7DA8F9B /* UIView+LayoutSupport.m */; }; + 3909D8028AC485BCCF17B48B /* STDSStackView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D6269F8B91341C387A911DB /* STDSStackView.m */; }; + 3AD384AE2C76634D009AB784 /* STDSAnalyticsDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AD384AD2C76634D009AB784 /* STDSAnalyticsDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3B430F172A3AD6AA9AFDFC04 /* STDSNavigationBarCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = E1630D876436E43547FF69CE /* STDSNavigationBarCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3C6D16D8E7B5BC865D956A0B /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; productRef = 0118969F6608A92583CC6C98 /* iOSSnapshotTestCase */; }; + 3C88AE37A760B91E93D423CF /* STDSException.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F030410FDABFF833CD8FE46 /* STDSException.m */; }; + 3C8C3BCDDDDEF6B9E150D4E1 /* STDSChallengeResponseSelectionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 52EEBC7D74623E1A1D7E3219 /* STDSChallengeResponseSelectionInfo.h */; }; + 3D674B4EE5ABACD65EB21A1E /* discover.der in Resources */ = {isa = PBXBuildFile; fileRef = 04AB48F014A8D5BA56FEF463 /* discover.der */; }; + 3F2624C33291FCE00D596768 /* STDSDeviceInformationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9580A82A59EB2B0103EDF47A /* STDSDeviceInformationManager.m */; }; + 3F30A8F418870D176A626F00 /* STDSSpacerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E4A57094A671EB14380882 /* STDSSpacerView.m */; }; + 41246FCC0695BABE1489C53E /* UIViewController+Stripe3DS2.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F72413F99F8E50FD0FA085D /* UIViewController+Stripe3DS2.h */; }; + 4142A3AB3E384F91A1E5550B /* STDSExpandableInformationView.m in Sources */ = {isa = PBXBuildFile; fileRef = AE1BBF3A441B1F782B766F61 /* STDSExpandableInformationView.m */; }; + 425849564519D6454DDA3424 /* NSData+JWEHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B05A609753EE71502409082 /* NSData+JWEHelpers.h */; }; + 435EFC9119D4B42B2C6A6F88 /* STDSAuthenticationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EA4AB3BA2FB41B3D5F38978 /* STDSAuthenticationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4518AD83580FC222B883DAFB /* STDSSimulatorChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 14D60BDF245487FE3362BE07 /* STDSSimulatorChecker.m */; }; + 458BC5A3D0E7B4D05549474F /* STDSBrandingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CFAC8D74AFFC2E61BAD82A5 /* STDSBrandingView.h */; }; + 480CD62EC91F4796D1D8D8DC /* STDSSecTypeUtilitiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 30D22985091381DFCDF077E9 /* STDSSecTypeUtilitiesTests.m */; }; + 48316C1AB1D9151C205A59A4 /* STDSChallengeResponseMessageExtensionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = BAAC56EDE0AAD2E50E02E9AE /* STDSChallengeResponseMessageExtensionObject.h */; }; + 4A9423CC79312BC3A0DEC38B /* STDSChallengeRequestParametersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EAD29E57D206B5D6BAF9099 /* STDSChallengeRequestParametersTest.m */; }; + 4C0E13298F08D391FE8600CF /* STDSJSONWebSignatureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E4C668442518B446D573AD24 /* STDSJSONWebSignatureTests.m */; }; + 4C267E9A30ADC18E71408ED5 /* STDSTextFieldCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = 68ED2A0E11E1B27980E0C60A /* STDSTextFieldCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4C573A44AB0A9CB28D76C621 /* STDSJSONWebEncryption.h in Headers */ = {isa = PBXBuildFile; fileRef = 76EA9DF8572C992E30BCF8A3 /* STDSJSONWebEncryption.h */; }; + 4D73C2FBAC9B96ECB45BDC94 /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */; }; + 4D79F0CFC54E8B923E9238CF /* Stripe3DS2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 4E35267801D94FB84816C519 /* STDSInvalidInputException.h in Headers */ = {isa = PBXBuildFile; fileRef = 853A361AB630A2BD402D1B47 /* STDSInvalidInputException.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4E4E4BC1E2D041FA530336EE /* STDSSwiftTryCatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 9946D28A86E85A6C84EB6C7B /* STDSSwiftTryCatch.m */; }; + 4FE8C076102C195D609977F5 /* STDSChallengeSelectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = A4D460893CB5DDE52AA0AB85 /* STDSChallengeSelectionView.m */; }; + 511513A9180F9835B08CBE56 /* STDSProgressViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = E629F85B783E42244E37DFA9 /* STDSProgressViewController.h */; }; + 574A7976213046F02F7F60C4 /* STDSProcessingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AC1C5F6F1311F50D9C37291 /* STDSProcessingView.m */; }; + 57E58A3B88D9EEB9FAE9B383 /* STDSDeviceInformation.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FBE88F6E2C3153A8315EDC6 /* STDSDeviceInformation.h */; }; + 59756023104E4FB886B48936 /* STDSTextChallengeView.h in Headers */ = {isa = PBXBuildFile; fileRef = 82C41E6DC2CC247ECC17F2B9 /* STDSTextChallengeView.h */; }; + 5B16918BF5F67B0F5FB1AFD5 /* ARes.json in Resources */ = {isa = PBXBuildFile; fileRef = 237977B021FE538E5E4FA35C /* ARes.json */; }; + 5C83A3A4631E51EEECBEAA0F /* STDSEllipticCurvePoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 5049A72A6BEB558D8559A5EB /* STDSEllipticCurvePoint.h */; }; + 5DAC25EBAB19555229654E44 /* STDSAuthenticationResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E32DA72CAFBF2AF8099151 /* STDSAuthenticationResponseTests.m */; }; + 5FE5FB933E2651C8D84FA477 /* STDSAuthenticationResponseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 5043E1CF6955176EC3D43F88 /* STDSAuthenticationResponseObject.m */; }; + 602C526C0B52DF81846DA664 /* STDSOSVersionChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = F363439353A6DD554FC44AC2 /* STDSOSVersionChecker.m */; }; + 613EF855524F7861A6DDD8C1 /* STDSImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = ECC649704CDDA50E73DB3C10 /* STDSImageLoader.h */; }; + 63AFB3C739DD987EE56F801F /* STDSDirectoryServerCertificate.h in Headers */ = {isa = PBXBuildFile; fileRef = 069990DA025BE9F33602E96A /* STDSDirectoryServerCertificate.h */; }; + 64D05353B50FAF3BB76D6527 /* STDSRuntimeErrorEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 1501A7A3010FFE53D00E318F /* STDSRuntimeErrorEvent.m */; }; + 67D431374DECE36B2606D944 /* STDSProtocolErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A6D20D1DAB7D769E5E97528 /* STDSProtocolErrorEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68F314D63323954EFE309E49 /* STDSOSVersionChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE7026135B5049B732CEDEB /* STDSOSVersionChecker.h */; }; + 6B3AA85EE71FC42EF9BD999F /* STDSEllipticCurvePoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EF5AA537586EF706E4056FD /* STDSEllipticCurvePoint.m */; }; + 6BA3F565B6C0B78F6DED8826 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F31A6580385C6390910AFD93 /* Localizable.strings */; }; + 6DB54DBAEFD08967367B6BF7 /* STDSChallengeParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 23C929BF760735CC01E1E8F5 /* STDSChallengeParameters.m */; }; + 6E727D2B738D7A3527952D28 /* STDSTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FDE2481364C454058BF2377 /* STDSTransaction.m */; }; + 72F430506E684D61BD5CFE3B /* STDSChallengeStatusReceiver.h in Headers */ = {isa = PBXBuildFile; fileRef = B7A75140FB62A71261CAF5EC /* STDSChallengeStatusReceiver.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7337FB20DC0EF491B5922913 /* STDSChallengeResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 315AEE1534F9D9974DC1674B /* STDSChallengeResponse.h */; }; + 75150C4678B8207AA6E7D969 /* STDSBrandingView.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C39A42ECBA58571EFA03C5 /* STDSBrandingView.m */; }; + 75CDFDABD7F9813563E3F618 /* Stripe3DS2-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F024DEAD6628A0229856656 /* Stripe3DS2-Bridging-Header.h */; }; + 793D61AD55630DD336A141A7 /* STDSChallengeResponseMessageExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = AA8BE982004398CF8AAA7D1E /* STDSChallengeResponseMessageExtension.h */; }; + 79557307ECD48D17C566997A /* STDSFooterCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = BA96C9FFA48B85F9C42E8C68 /* STDSFooterCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7C20B044ACFD3A50FD4AC9A8 /* acs_challenge.html in Resources */ = {isa = PBXBuildFile; fileRef = 7971EB56716D5E2F59053B6D /* acs_challenge.html */; }; + 7C7073B54628ED836F422F1D /* STDSJailbreakChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 94943BEAB94E949AF9830EFA /* STDSJailbreakChecker.h */; }; + 7EC8E5523F29B0F01FE827DB /* STDSWebView.h in Headers */ = {isa = PBXBuildFile; fileRef = 581EEE576D24047004C828DF /* STDSWebView.h */; }; + 7ED98325E7E54A54BFE54D6C /* STDSAuthenticationRequestParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = AB871F64FC72EBC7A91F96C1 /* STDSAuthenticationRequestParameters.m */; }; + 81B1C12449852CEB3C59793F /* ul-test.der in Resources */ = {isa = PBXBuildFile; fileRef = B6275E7099E55F0C04688890 /* ul-test.der */; }; + 8233B9F131602DD7143FF96F /* STDSBundleLocator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9564F43EF7CF4F60C3C202CD /* STDSBundleLocator.m */; }; + 825ACBE0734BDCE746E66AB0 /* STDSWhitelistView.h in Headers */ = {isa = PBXBuildFile; fileRef = 33CEADC8006E456FD82CE134 /* STDSWhitelistView.h */; }; + 8322973CD09F2E1C88B6046E /* UIColor+DefaultColors.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A567E08748C86FCE2A75FEA /* UIColor+DefaultColors.h */; }; + 83878337274D8C43D492EC45 /* STDSUICustomizationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CB3154597040B803ADE14F9E /* STDSUICustomizationTests.m */; }; + 847926A68A88BEB58C92D9BC /* STDSThreeDSProtocolVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E6A9565E97DF2544254AA94 /* STDSThreeDSProtocolVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88197445522F39DB1E9F6AE7 /* NSString+JWEHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 43CB8F416E4F4CEA34391481 /* NSString+JWEHelpers.m */; }; + 884D59AF7FD70889276AFC02 /* STDSWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA85BC717C46C1B95AF8C1E3 /* STDSWebView.m */; }; + 890E22971E6F7B0FA1C8D649 /* STDSJSONEncodable.h in Headers */ = {isa = PBXBuildFile; fileRef = E6A3C5D4C46E681B7D76B2BA /* STDSJSONEncodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 893713F4464A88FAB92BC2C6 /* Stripe3DS2.h in Headers */ = {isa = PBXBuildFile; fileRef = 28C6B4F6BA431B9342970FD6 /* Stripe3DS2.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8A0872706BF4CDE72EF0C480 /* STDSConfigParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = 89B5ED899439D1C1054CB448 /* STDSConfigParameters.m */; }; + 8A64AFBB2AC15E0E0F57ED25 /* STDSNotInitializedException.m in Sources */ = {isa = PBXBuildFile; fileRef = B7BD1E24EA9427121E148DFC /* STDSNotInitializedException.m */; }; + 8C9BD4B150F384111CBD3BD3 /* STDSErrorMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DDB6222A2EF31FDB7592368 /* STDSErrorMessage.m */; }; + 8E0501C3BB849C2D2D99F34E /* STDSLocalizedString.h in Headers */ = {isa = PBXBuildFile; fileRef = 41E0F638E776A7F3456BDA3E /* STDSLocalizedString.h */; }; + 8E27285FE76F8BECFD70286D /* STDSCompletionEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 10DB3CD8F2541AC06681F2C8 /* STDSCompletionEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8E833B7FCBC3A99072582FEA /* UIFont+DefaultFonts.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E844798B2733C8511AC080E /* UIFont+DefaultFonts.h */; }; + 9146B2E3B89EF3F489D7E233 /* STDSIPAddress.m in Sources */ = {isa = PBXBuildFile; fileRef = CB00E968CF4DD07FB8BF2AAA /* STDSIPAddress.m */; }; + 955CBE0C979291F89567D8A7 /* STDSJSONWebEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 99392D3F702EC6BF7CE15291 /* STDSJSONWebEncryption.m */; }; + 95A2D58051AADDBE16CCA0B6 /* STDSChallengeResponseImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 10C45EE3433B731253E21E24 /* STDSChallengeResponseImage.h */; }; + 9673946B78A7763070E05CD0 /* STDSAuthenticationRequestParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = C91B239583899192C7B26FCF /* STDSAuthenticationRequestParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 97CE8B57A97D037E977E62F3 /* STDSBundleLocator.h in Headers */ = {isa = PBXBuildFile; fileRef = A137A31BEFCCB646FB754EE2 /* STDSBundleLocator.h */; }; + 98075195759ABB91B0D19847 /* STDSJSONWebEncryptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 97505E93BE83F233E69BB5A9 /* STDSJSONWebEncryptionTests.m */; }; + 9843F6AABF7B0A623E7DF979 /* STDSDeviceInformationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A637DA708BC866C2903E0EA /* STDSDeviceInformationManager.h */; }; + 987347CA74818CEB716B9DB3 /* UIFont+DefaultFonts.m in Sources */ = {isa = PBXBuildFile; fileRef = 634095EA97A4E48BF7CAA03F /* UIFont+DefaultFonts.m */; }; + 9B32E9A4CD2BDA61C730F5EB /* STDSJSONDecodable.h in Headers */ = {isa = PBXBuildFile; fileRef = B219360BFB325779485BF702 /* STDSJSONDecodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9B7081D378D1441B98B81207 /* STDSTransactionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A3CCBE9A2E8C1A73D8214E /* STDSTransactionTest.m */; }; + 9C225B9C556C20D1A6F97180 /* STDSChallengeResponseSelectionInfoObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 4CCA15E0819F9A9D89FEF9EA /* STDSChallengeResponseSelectionInfoObject.h */; }; + 9DC5612697835A383DC6606E /* STDSTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = F40CC91AA69F5296B0AA985C /* STDSTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9E46777893FA2D6691F496D7 /* STDSChallengeResponseViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = E45303DCFFDC390971E6E122 /* STDSChallengeResponseViewController.h */; }; + 9ED9C602C10682CD5DD91C12 /* NSLayoutConstraint+LayoutSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = F99B290CF2B4987A4CFE5DCE /* NSLayoutConstraint+LayoutSupport.m */; }; + 9F0F6085FBD892BF5F14CF86 /* STDSWarning.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BEB5006261C5E070FEE62BF /* STDSWarning.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9F52F67A1A3A2199AEA598FC /* STDSEllipticCurvePointTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA1EE83D91993BCAF13EC72 /* STDSEllipticCurvePointTests.m */; }; + 9F9BB9E7FA18FEDA44F7042E /* STDSDirectoryServerCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9754FA4F3CC6AA921787916B /* STDSDirectoryServerCertificate.m */; }; + A026717FFF299A7C1C51565F /* UIView+LayoutSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 99CB2B66DBD356B5D222EDA9 /* UIView+LayoutSupport.h */; }; + A33A549CF12209280E3B1DBA /* STDSAuthenticationRequestParametersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 969DE2FA3845BB7939D3CD2E /* STDSAuthenticationRequestParametersTest.m */; }; + A39CFB56BF33B21A89987F9C /* STDSChallengeResponseObject+TestObjects.m in Sources */ = {isa = PBXBuildFile; fileRef = 99DB24A9B44979EA19347A76 /* STDSChallengeResponseObject+TestObjects.m */; }; + A4CFDBFC3099BD7E1A694E3E /* UIButton+CustomInitialization.m in Sources */ = {isa = PBXBuildFile; fileRef = AA29B74DD3963B1205AA1C0C /* UIButton+CustomInitialization.m */; }; + A5193DADCD0F48911F775D88 /* NSString+JWEHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 018F4AA25D84E9CBD2546BB5 /* NSString+JWEHelpers.h */; }; + A56AE2CD941D7ADAC7FC8BCC /* STDSBase64URLEncodingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E3760B1369DB1A33C9DFE /* STDSBase64URLEncodingTests.m */; }; + A6909D6C1A68963504733939 /* STDSDebuggerChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = C29F78CC37253B0D33FF86F2 /* STDSDebuggerChecker.m */; }; + A78F19DA3D146A258FB9DE3C /* STDSACSNetworkingManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D827473D1858B2BED85BDFA1 /* STDSACSNetworkingManager.h */; }; + A8A86B9BCE9702D74E0D2DB6 /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */; }; + A8CAD8276ED8057A760147CB /* NSData+JWEHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FC15225A5E83948EF546B71 /* NSData+JWEHelpers.m */; }; + A97E1DF84D381952A0FB4C1C /* amex.der in Resources */ = {isa = PBXBuildFile; fileRef = D8B7EE0F935EF44EB2EF7D45 /* amex.der */; }; + AA4D157F31A246890D446981 /* STDSChallengeResponseSelectionInfoObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F046DE266BAAE3DA0AC43785 /* STDSChallengeResponseSelectionInfoObject.m */; }; + AA94519687902E34E5243AEA /* STDSSpacerView.h in Headers */ = {isa = PBXBuildFile; fileRef = 72DBE992B33F45C059EDB597 /* STDSSpacerView.h */; }; + AC474013841CE1DED97F3FA8 /* STDSSimulatorChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = A0ADFB365AD2DE9E84873BB1 /* STDSSimulatorChecker.h */; }; + AE1894FEA510AC394DE73C4B /* STDSTransaction+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A9F5C315222AFCD14C9342F /* STDSTransaction+Private.h */; }; + B012C4A8ACA1D064C40A1B63 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C22D410C297E2C3AFE727A6 /* main.m */; }; + B1C2AA6259CE34B0042A1FB1 /* STDSDeviceInformationManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A4050F381183D6FDCC990D01 /* STDSDeviceInformationManagerTests.m */; }; + B31168F5FEC3464AC212DB85 /* STDSACSNetworkingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D6004F2D9880055C0C0BCF1 /* STDSACSNetworkingManager.m */; }; + B426123CC7FCA337E13304F9 /* NSDictionary+DecodingHelpersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE8BF51F49E161737406B3A /* NSDictionary+DecodingHelpersTest.m */; }; + B51D1C218F460F67B58A1F0D /* STDSProtocolErrorEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 3255706D2304FB551C97305F /* STDSProtocolErrorEvent.m */; }; + B84ED7FBD49865F23B774067 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B59CC3349FD9BBF37624667 /* AppDelegate.m */; }; + B94E5A5BF5F22998E4CA9ED3 /* STDSEphemeralKeyPair.h in Headers */ = {isa = PBXBuildFile; fileRef = 20533E2C69C4B80BB33A4766 /* STDSEphemeralKeyPair.h */; }; + B9A456E52E62E9DAE9F424A7 /* STDSChallengeResponseMessageExtensionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D02FAFC403BC901931A629F /* STDSChallengeResponseMessageExtensionObject.m */; }; + BA00D11D792C99B7D5749F59 /* STDSJailbreakChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BFBB92DBE97E16DE9D18B4A /* STDSJailbreakChecker.m */; }; + BA65731CDB75C3124834586C /* STDSSynchronousLocationManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F02456AB660E709F332C0C7D /* STDSSynchronousLocationManagerTests.m */; }; + BB379E68231A7DA62F87E628 /* STDSNavigationBarCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F1A5A8E5922748A9BEC724 /* STDSNavigationBarCustomization.m */; }; + BF06B99FECD98C26F7B64665 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08D577934984712C20C5E903 /* XCTest.framework */; }; + BF2651A9F3A31CB1AFEC44BB /* STDSCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = A8E62EC973FC5C9905A40CA8 /* STDSCustomization.m */; }; + BF342613EE89845EB1C4B72A /* STDSChallengeRequestParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 762DEC6662DF8240FE19CFFC /* STDSChallengeRequestParameters.h */; }; + BFC5852E14724750E70DA2A6 /* STDSEphemeralKeyPairTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BF80634E06E8D6F598CFC667 /* STDSEphemeralKeyPairTests.m */; }; + C2BA2E6ABDD8E80963FFC671 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08D577934984712C20C5E903 /* XCTest.framework */; }; + C430332104547BA508416B41 /* STDSButtonCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = 5489DCAA65624C6F966816B6 /* STDSButtonCustomization.m */; }; + C4BA75544A7F51355BF4B5F8 /* STDSDirectoryServer.h in Headers */ = {isa = PBXBuildFile; fileRef = 6AA43E7E90E4002DD56B7C3D /* STDSDirectoryServer.h */; }; + CA617B6F5CDFB8FDEEE38636 /* STDSJSONWebSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AA2E6DA60350400C5270E08 /* STDSJSONWebSignature.h */; }; + CCC376FE7424029342A8D15E /* STDSChallengeResponseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B064352BB2876CA7266C410A /* STDSChallengeResponseViewController.m */; }; + CEA1EB91858043A837638FE0 /* STDSSecTypeUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = C8F0E11AA6794BFB1715685E /* STDSSecTypeUtilities.h */; }; + D1427392DA8AD8F5B4118781 /* STDSACSNetworkingManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6118F98576E9A39A7292C6C6 /* STDSACSNetworkingManagerTest.m */; }; + D189220981C7705913D93687 /* STDSJSONEncoderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B9B49A5CF64DCB23CD0AEDE /* STDSJSONEncoderTest.m */; }; + D3432C5BF5211A60D5C15D9E /* STDSIntegrityChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = C02DF839637771684BC57B3A /* STDSIntegrityChecker.h */; }; + D3AA14D1E059E90F5A965CC4 /* STDSSelectionCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = 43540AB7D13C0C9A563C3F4D /* STDSSelectionCustomization.m */; }; + D41F091BE1579B801D1BBC20 /* STDSSelectionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = B458FCA7DFDBF75E62A43BA1 /* STDSSelectionButton.m */; }; + D5706ACF84F0A993CE1885D4 /* STDSTextChallengeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E6B6D3CAE363114723472F /* STDSTextChallengeView.m */; }; + D63E3DDC92DD46062A265D86 /* STDSChallengeParametersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 89ED2133B61092FE1B478204 /* STDSChallengeParametersTests.m */; }; + D819C341ECEE1EFE6FE66085 /* STDSChallengeResponseImageObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 14F83E51E99D453DF9C2DDE1 /* STDSChallengeResponseImageObject.h */; }; + D83D2FF20387FACC383A2A0F /* STDSAlreadyInitializedException.m in Sources */ = {isa = PBXBuildFile; fileRef = 27AC486DA2A80A43C1F517B2 /* STDSAlreadyInitializedException.m */; }; + D927789F89B413C08DE4D388 /* STDSThreeDS2ServiceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FF8ACC073E814B7DEBA7647 /* STDSThreeDS2ServiceTests.m */; }; + D9F8BA88953A91DC082FA6DA /* STDSSelectionButton.h in Headers */ = {isa = PBXBuildFile; fileRef = C4777F15603AC755362BD846 /* STDSSelectionButton.h */; }; + DB96817598FCE73F5131437E /* STDSDirectoryServerCertificate+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = B015B937DD3657D62C61CE58 /* STDSDirectoryServerCertificate+Internal.h */; }; + DBDB8B56FFF5C4234705E698 /* STDSSecTypeUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = CF3DED43BD4E0226BFB0759D /* STDSSecTypeUtilities.m */; }; + DC0D9A57F2E312663C088FB8 /* STDSChallengeParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = D03B98D5861739833B197D8F /* STDSChallengeParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC32281BF0847A98B100C3A1 /* STDSConfigParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = A1BFB8640032796CDA016765 /* STDSConfigParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DDB98914E1135E8D445545C8 /* STDSIPAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = A3DFB001DB5BA7F1AFA7353A /* STDSIPAddress.h */; }; + DE32EAA8F5C2645652FCFB48 /* STDSSwiftTryCatch.h in Headers */ = {isa = PBXBuildFile; fileRef = E5BC34A661D9080A2EE239F8 /* STDSSwiftTryCatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DEEF260365A4D9B0D83E557D /* NSString+EmptyChecking.m in Sources */ = {isa = PBXBuildFile; fileRef = D8D309E4FBE7E73D0BB83BF5 /* NSString+EmptyChecking.m */; }; + DF4D38DC0AF89EAAA8718AAE /* STDSUICustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = A55B2474CD1A39D70869B75B /* STDSUICustomization.m */; }; + E0644CE0260178023E643B23 /* STDSSynchronousLocationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A5DBDDEB5C5DAE2EA8C95CC3 /* STDSSynchronousLocationManager.m */; }; + E08AF96E690D75F6AB52021F /* STDSWarning.m in Sources */ = {isa = PBXBuildFile; fileRef = A502CCC5190F6B642EEC0CE6 /* STDSWarning.m */; }; + E19C8104FC1A9BAEA0BE8A9D /* UIColor+ThirteenSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 37ED28756222539D37554CC7 /* UIColor+ThirteenSupport.m */; }; + E4DA2CE8DC8C60DE40222185 /* STDSDirectoryServerCertificateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AA75957DD13FA92FA866AF3 /* STDSDirectoryServerCertificateTests.m */; }; + E630DCBBEFAAD0435BEF989C /* STDSDeviceInformation.m in Sources */ = {isa = PBXBuildFile; fileRef = 56B1A52548593FE78D801734 /* STDSDeviceInformation.m */; }; + E70CFDC59731BA62DD89906C /* STDSWhitelistView.m in Sources */ = {isa = PBXBuildFile; fileRef = 615F1BD510661B38D6825128 /* STDSWhitelistView.m */; }; + E73028FD4537F6E48F927127 /* NSString+EmptyCheckingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 57B8822D21AADE7DA51931CC /* NSString+EmptyCheckingTests.m */; }; + E7A72BB7CC8DE8DA7FA0DEC5 /* ErrorMessage.json in Resources */ = {isa = PBXBuildFile; fileRef = E12798864D5CFFB7157D4CF5 /* ErrorMessage.json */; }; + E7E424C5264A4DE4A1EC19B6 /* STDSChallengeSelectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = EB910E98EDD6D3E00802793C /* STDSChallengeSelectionView.h */; }; + E90749131EDA0C86BBBFCE5C /* STDSChallengeResponseObject.h in Headers */ = {isa = PBXBuildFile; fileRef = EB1BDF12DBD6264C6199FFA7 /* STDSChallengeResponseObject.h */; }; + EA19A7AEC298F3EC010D1199 /* STDSTestJSONUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FF3235EDD908CC1500399A1 /* STDSTestJSONUtils.m */; }; + EA89021709AFB5F04961EB78 /* cartes-bancaires.der in Resources */ = {isa = PBXBuildFile; fileRef = 83838563F29179FB1E9F2E66 /* cartes-bancaires.der */; }; + EB791827A943AA61976D8C29 /* STDSIntegrityChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = BB065D6900E95C5E90864C16 /* STDSIntegrityChecker.m */; }; + EC51189277D7D68D08C2A65C /* STDSButtonCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = BD94CAE01A17E083E5617E56 /* STDSButtonCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + ECF8755D2602E0E06DCB3210 /* STDSChallengeResponseObjectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E401845EE5759CCD30786AA /* STDSChallengeResponseObjectTest.m */; }; + EE3854BC2A14F87E8D51B0D0 /* STDSInvalidInputException.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FDAC320458399141EB161E2 /* STDSInvalidInputException.m */; }; + EEE77034728D2CFE38CC5A85 /* STDSLabelCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = 01D320CD5ADA49F17B8BC1B0 /* STDSLabelCustomization.m */; }; + EFC72E766F9FD4EE18D309BC /* STDSJSONWebSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = A1DC6B47C06B522B22EB19FF /* STDSJSONWebSignature.m */; }; + F04482C663F6DF8E522B0833 /* STDSCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = AFF187D1D58405C736474042 /* STDSCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F0B9EFD359B1AB28A695CB48 /* STDSSynchronousLocationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C12AE31D386D4A193C00004B /* STDSSynchronousLocationManager.h */; }; + F13A032A4AF15BB61EA18A8D /* STDSChallengeInformationView.h in Headers */ = {isa = PBXBuildFile; fileRef = C893FD867C964775E68AE85F /* STDSChallengeInformationView.h */; }; + F28AD6CA8AB1A5DEE7A8F429 /* STDSDebuggerChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = D122FAE093BC4F649CC6E7AB /* STDSDebuggerChecker.h */; }; + F35963DB86D90320CFA7BCB9 /* STDSExpandableInformationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 939C45AE068CF4F5BEB4C138 /* STDSExpandableInformationView.h */; }; + F4E8353A470750D9BCEA3CD8 /* CRes.json in Resources */ = {isa = PBXBuildFile; fileRef = 8C6D262891811FB3FE0C947E /* CRes.json */; }; + F5513045A25210C524A05DF5 /* STDSSelectionCustomization.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CD753D9BBFC378C1B491B00 /* STDSSelectionCustomization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F569B584D357DC5C55542015 /* STDSFooterCustomization.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF141AC98F213122D037C6 /* STDSFooterCustomization.m */; }; + F939E05536AE838DC489C3AA /* STDSChallengeResponseViewControllerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 466DF80D33CEC20DF4E844A9 /* STDSChallengeResponseViewControllerSnapshotTests.m */; }; + FD6456BD86F07C446B9FB6C8 /* ec_test.der in Resources */ = {isa = PBXBuildFile; fileRef = 3E4C02D9D6978AD5C3D7E3D2 /* ec_test.der */; }; + FD8A81873C3BC7579ED07460 /* STDSCompletionEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B6B71C61CF5ADC7796441F5 /* STDSCompletionEvent.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 35B6793A7F3FB130F40209F1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8D219187B704575AD3CD3EC5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5907C55B1F111921112DF2BF; + remoteInfo = Stripe3DS2DemoUI; + }; + 52E1EB51359DA6E75D851D71 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8D219187B704575AD3CD3EC5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57AEC53510AE0DC0539730F3; + remoteInfo = Stripe3DS2; + }; + 6C355B127D670833C76237D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8D219187B704575AD3CD3EC5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57AEC53510AE0DC0539730F3; + remoteInfo = Stripe3DS2; + }; + 947ED275EAB25AD4CD701936 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8D219187B704575AD3CD3EC5 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57AEC53510AE0DC0539730F3; + remoteInfo = Stripe3DS2; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 00E415680C602ED1D6D8E71F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 4D79F0CFC54E8B923E9238CF /* Stripe3DS2.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 43B749EC915FB6D2FF5ABEE0 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 58FA2FE74AE67CD3CDAFD7E5 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 726DD7176204D90BD7552B84 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 00658E077ECB223C90484AF7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 018F4AA25D84E9CBD2546BB5 /* NSString+JWEHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+JWEHelpers.h"; sourceTree = ""; }; + 01D320CD5ADA49F17B8BC1B0 /* STDSLabelCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSLabelCustomization.m; sourceTree = ""; }; + 02D762271DBCC1AE80E0F9D4 /* Stripe3DS2DemoUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stripe3DS2DemoUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 037C294011A01E4B4DCE5BB7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 04AB48F014A8D5BA56FEF463 /* discover.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = discover.der; sourceTree = ""; }; + 04E6B6D3CAE363114723472F /* STDSTextChallengeView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSTextChallengeView.m; sourceTree = ""; }; + 0681C043F732148587737233 /* STDSJSONEncoder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJSONEncoder.m; sourceTree = ""; }; + 069990DA025BE9F33602E96A /* STDSDirectoryServerCertificate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDirectoryServerCertificate.h; sourceTree = ""; }; + 06C5207432FB07BD71703BCC /* STDSJSONEncoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSJSONEncoder.h; sourceTree = ""; }; + 08D577934984712C20C5E903 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 0BEB5006261C5E070FEE62BF /* STDSWarning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSWarning.h; sourceTree = ""; }; + 0DA1EE83D91993BCAF13EC72 /* STDSEllipticCurvePointTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSEllipticCurvePointTests.m; sourceTree = ""; }; + 0E38A775DB9F529427E900CF /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + 0E3C05BE55CF3086738AA182 /* Stripe3DS2DemoUITests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2DemoUITests-Debug.xcconfig"; sourceTree = ""; }; + 0F024DEAD6628A0229856656 /* Stripe3DS2-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Stripe3DS2-Bridging-Header.h"; sourceTree = ""; }; + 10C45EE3433B731253E21E24 /* STDSChallengeResponseImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseImage.h; sourceTree = ""; }; + 10DB3CD8F2541AC06681F2C8 /* STDSCompletionEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSCompletionEvent.h; sourceTree = ""; }; + 1104D5855D28E49E104CA004 /* mastercard.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = mastercard.der; sourceTree = ""; }; + 138E82072FC6572612A8E248 /* STDSAuthenticationResponseObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSAuthenticationResponseObject.h; sourceTree = ""; }; + 13ED1CC076C6B3FB49C9197A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 14D60BDF245487FE3362BE07 /* STDSSimulatorChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSimulatorChecker.m; sourceTree = ""; }; + 14F83E51E99D453DF9C2DDE1 /* STDSChallengeResponseImageObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseImageObject.h; sourceTree = ""; }; + 1501A7A3010FFE53D00E318F /* STDSRuntimeErrorEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSRuntimeErrorEvent.m; sourceTree = ""; }; + 157970FF5B4A817041E3D668 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 172AF5C85EDBD92A1030E361 /* STDSDeviceInformationParameter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDeviceInformationParameter.h; sourceTree = ""; }; + 18D88DE3E34106F934015BF9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + 1BBADD412A7C2D1FF35A7C86 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 1BFBB92DBE97E16DE9D18B4A /* STDSJailbreakChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJailbreakChecker.m; sourceTree = ""; }; + 1D6269F8B91341C387A911DB /* STDSStackView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSStackView.m; sourceTree = ""; }; + 1E844798B2733C8511AC080E /* UIFont+DefaultFonts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+DefaultFonts.h"; sourceTree = ""; }; + 1F0DADEABA0B043A6CA36DD6 /* STDSDemoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDemoViewController.h; sourceTree = ""; }; + 1F1FD11407319293954C6D81 /* STDSAlreadyInitializedException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSAlreadyInitializedException.h; sourceTree = ""; }; + 1FBE88F6E2C3153A8315EDC6 /* STDSDeviceInformation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDeviceInformation.h; sourceTree = ""; }; + 1FC15225A5E83948EF546B71 /* NSData+JWEHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+JWEHelpers.m"; sourceTree = ""; }; + 20533E2C69C4B80BB33A4766 /* STDSEphemeralKeyPair.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSEphemeralKeyPair.h; sourceTree = ""; }; + 210B22FF4DCB0C6E7C763EAB /* STDSUICustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSUICustomization.h; sourceTree = ""; }; + 2192708AD201F80F6E1A39F7 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + 237977B021FE538E5E4FA35C /* ARes.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = ARes.json; sourceTree = ""; }; + 23A3DDCE911F8C43CF74CDF4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 23C929BF760735CC01E1E8F5 /* STDSChallengeParameters.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeParameters.m; sourceTree = ""; }; + 24CE086326AF3DE336BF4F4C /* STDSChallengeResponseObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseObject.m; sourceTree = ""; }; + 25C7D18868DDADEF4CB6220C /* STDSDeviceInformationParameter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDeviceInformationParameter.m; sourceTree = ""; }; + 26C20D4B77372845627D6466 /* NSError+Stripe3DS2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+Stripe3DS2.m"; sourceTree = ""; }; + 278F2C40987F5218BD031E3D /* STDSErrorMessageTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSErrorMessageTest.m; sourceTree = ""; }; + 27AC486DA2A80A43C1F517B2 /* STDSAlreadyInitializedException.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSAlreadyInitializedException.m; sourceTree = ""; }; + 28C6B4F6BA431B9342970FD6 /* Stripe3DS2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stripe3DS2.h; sourceTree = ""; }; + 28E32DA72CAFBF2AF8099151 /* STDSAuthenticationResponseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSAuthenticationResponseTests.m; sourceTree = ""; }; + 2C2666DF7D23EBC3FFE7CCF1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 2CB591796654445B8434A501 /* STDSErrorMessage+Internal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "STDSErrorMessage+Internal.m"; sourceTree = ""; }; + 2CFAC8D74AFFC2E61BAD82A5 /* STDSBrandingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSBrandingView.h; sourceTree = ""; }; + 2D6004F2D9880055C0C0BCF1 /* STDSACSNetworkingManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSACSNetworkingManager.m; sourceTree = ""; }; + 2EF5AA537586EF706E4056FD /* STDSEllipticCurvePoint.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSEllipticCurvePoint.m; sourceTree = ""; }; + 2FCABBF51CBA7F9FEF757B4C /* STDSNotInitializedException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSNotInitializedException.h; sourceTree = ""; }; + 30D22985091381DFCDF077E9 /* STDSSecTypeUtilitiesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSecTypeUtilitiesTests.m; sourceTree = ""; }; + 310E6B4E2C52C9EF006A03EC /* 150-issuer.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "150-issuer.png"; sourceTree = ""; }; + 310E6B4F2C52C9EF006A03EC /* 300-issuer.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "300-issuer.png"; sourceTree = ""; }; + 310E6B502C52C9EF006A03EC /* 150-payment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "150-payment.png"; sourceTree = ""; }; + 310E6B512C52C9EF006A03EC /* 300-payment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "300-payment.png"; sourceTree = ""; }; + 310E6B522C52C9EF006A03EC /* 450-issuer.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "450-issuer.png"; sourceTree = ""; }; + 310E6B532C52C9EF006A03EC /* 450-payment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "450-payment.png"; sourceTree = ""; }; + 315AEE1534F9D9974DC1674B /* STDSChallengeResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponse.h; sourceTree = ""; }; + 319DD1C72B0D50F80083BA32 /* STDSVisionSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSVisionSupport.h; sourceTree = ""; }; + 31CDFC312BA8E58100B3DD91 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 3255706D2304FB551C97305F /* STDSProtocolErrorEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSProtocolErrorEvent.m; sourceTree = ""; }; + 33CEADC8006E456FD82CE134 /* STDSWhitelistView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSWhitelistView.h; sourceTree = ""; }; + 371F8076630A8839C1F6A508 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + 37ED28756222539D37554CC7 /* UIColor+ThirteenSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+ThirteenSupport.m"; sourceTree = ""; }; + 382C583385FAECA91366769E /* STDSDemoViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDemoViewController.m; sourceTree = ""; }; + 38572D4E32BF3670F1C83409 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + 38F3A7D625E4B0D3506A37F6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 3A6D20D1DAB7D769E5E97528 /* STDSProtocolErrorEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSProtocolErrorEvent.h; sourceTree = ""; }; + 3A8CD68A006F970DDC54469A /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 3A9F828840A3B361842E38DE /* STDSChallengeResponseImageObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseImageObject.m; sourceTree = ""; }; + 3AA2E6DA60350400C5270E08 /* STDSJSONWebSignature.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSJSONWebSignature.h; sourceTree = ""; }; + 3AD384AD2C76634D009AB784 /* STDSAnalyticsDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSAnalyticsDelegate.h; sourceTree = ""; }; + 3B6B71C61CF5ADC7796441F5 /* STDSCompletionEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSCompletionEvent.m; sourceTree = ""; }; + 3B9A821F35B93898A7AC1ADF /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + 3C22D410C297E2C3AFE727A6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 3E4C02D9D6978AD5C3D7E3D2 /* ec_test.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = ec_test.der; sourceTree = ""; }; + 3F017BF6ECE0EBE4E0DFCF9C /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 406D88ADED27D64DED2002A2 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + 40DC4913154C3D3A27E35207 /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 41E0F638E776A7F3456BDA3E /* STDSLocalizedString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSLocalizedString.h; sourceTree = ""; }; + 43540AB7D13C0C9A563C3F4D /* STDSSelectionCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSelectionCustomization.m; sourceTree = ""; }; + 43CB8F416E4F4CEA34391481 /* NSString+JWEHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+JWEHelpers.m"; sourceTree = ""; }; + 461F938CDE7ED6D06D9D2700 /* STDSException+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSException+Internal.h"; sourceTree = ""; }; + 461FDC130846625171164A9E /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + 466DF80D33CEC20DF4E844A9 /* STDSChallengeResponseViewControllerSnapshotTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseViewControllerSnapshotTests.m; sourceTree = ""; }; + 474F39AA3F47D84F8D54E5B7 /* STDSWarningTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSWarningTests.m; sourceTree = ""; }; + 47C29E9F3D2A62676B424CD1 /* STDSThreeDSProtocolVersion+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSThreeDSProtocolVersion+Private.h"; sourceTree = ""; }; + 4869D6E14F01EDB960CB3065 /* STDSDeviceInformationParameter+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSDeviceInformationParameter+Private.h"; sourceTree = ""; }; + 49EF79D69693F937FEC46906 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + 4A567E08748C86FCE2A75FEA /* UIColor+DefaultColors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+DefaultColors.h"; sourceTree = ""; }; + 4B1E3760B1369DB1A33C9DFE /* STDSBase64URLEncodingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSBase64URLEncodingTests.m; sourceTree = ""; }; + 4BC4D9FD1D3869D491CC2CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 4C158B552B0CBE635A2AF7BB /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + 4CCA15E0819F9A9D89FEF9EA /* STDSChallengeResponseSelectionInfoObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseSelectionInfoObject.h; sourceTree = ""; }; + 4DB91F58CF23E72636C69C41 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + 4DD55BECC3FB85216FE46218 /* UIButton+CustomInitialization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIButton+CustomInitialization.h"; sourceTree = ""; }; + 4EA4AB3BA2FB41B3D5F38978 /* STDSAuthenticationResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSAuthenticationResponse.h; sourceTree = ""; }; + 4EAD29E57D206B5D6BAF9099 /* STDSChallengeRequestParametersTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeRequestParametersTest.m; sourceTree = ""; }; + 4EB746D4E46ABD2452A154AE /* Stripe3DS2Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Stripe3DS2Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 5043E1CF6955176EC3D43F88 /* STDSAuthenticationResponseObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSAuthenticationResponseObject.m; sourceTree = ""; }; + 5049A72A6BEB558D8559A5EB /* STDSEllipticCurvePoint.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSEllipticCurvePoint.h; sourceTree = ""; }; + 51E4A57094A671EB14380882 /* STDSSpacerView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSpacerView.m; sourceTree = ""; }; + 52D6E4F76CBA258163E57DF3 /* STDSEphemeralKeyPair+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSEphemeralKeyPair+Testing.h"; sourceTree = ""; }; + 52EEBC7D74623E1A1D7E3219 /* STDSChallengeResponseSelectionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseSelectionInfo.h; sourceTree = ""; }; + 5316D0759F7F2ED0BF1D3B2E /* Stripe3DS2DemoUI-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2DemoUI-Debug.xcconfig"; sourceTree = ""; }; + 5463EB1CC8E85BE3BD12DEFF /* STDSErrorMessage+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSErrorMessage+Internal.h"; sourceTree = ""; }; + 5489DCAA65624C6F966816B6 /* STDSButtonCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSButtonCustomization.m; sourceTree = ""; }; + 56B1A52548593FE78D801734 /* STDSDeviceInformation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDeviceInformation.m; sourceTree = ""; }; + 57B8822D21AADE7DA51931CC /* NSString+EmptyCheckingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+EmptyCheckingTests.m"; sourceTree = ""; }; + 581EEE576D24047004C828DF /* STDSWebView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSWebView.h; sourceTree = ""; }; + 58C38FF9205C9B7D79C51A37 /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + 5B05A609753EE71502409082 /* NSData+JWEHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+JWEHelpers.h"; sourceTree = ""; }; + 5D93713CADD5589B5E6F6A0D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 5E401845EE5759CCD30786AA /* STDSChallengeResponseObjectTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseObjectTest.m; sourceTree = ""; }; + 5E6A9565E97DF2544254AA94 /* STDSThreeDSProtocolVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSThreeDSProtocolVersion.h; sourceTree = ""; }; + 5EAA444FA7C82BDA4AD907BF /* STDSThreeDS2Service.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSThreeDS2Service.h; sourceTree = ""; }; + 5EE8BF51F49E161737406B3A /* NSDictionary+DecodingHelpersTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+DecodingHelpersTest.m"; sourceTree = ""; }; + 5F551808E65CDADD9B3A9CB8 /* STDSConfigParametersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSConfigParametersTests.m; sourceTree = ""; }; + 5FDE2481364C454058BF2377 /* STDSTransaction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSTransaction.m; sourceTree = ""; }; + 5FF3235EDD908CC1500399A1 /* STDSTestJSONUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSTestJSONUtils.m; sourceTree = ""; }; + 60693BC560C4927ACFD2E6C3 /* STDSChallengeRequestParameters.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeRequestParameters.m; sourceTree = ""; }; + 6118F98576E9A39A7292C6C6 /* STDSACSNetworkingManagerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSACSNetworkingManagerTest.m; sourceTree = ""; }; + 615F1BD510661B38D6825128 /* STDSWhitelistView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSWhitelistView.m; sourceTree = ""; }; + 62773D5C85C567F28BCE0DA5 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 634095EA97A4E48BF7CAA03F /* UIFont+DefaultFonts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+DefaultFonts.m"; sourceTree = ""; }; + 645D0BB927B7F8A0D37EE04D /* STDSErrorMessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSErrorMessage.h; sourceTree = ""; }; + 6690476C1BEA59C37576FB36 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 68ED2A0E11E1B27980E0C60A /* STDSTextFieldCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSTextFieldCustomization.h; sourceTree = ""; }; + 6932DD26C7C16938DFA0E198 /* STDSLabelCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSLabelCustomization.h; sourceTree = ""; }; + 6940D75CA36F7DB9FB56196B /* Stripe3DS2DemoUI-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2DemoUI-Release.xcconfig"; sourceTree = ""; }; + 694DF6F47767A651907D55D4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 6AA43E7E90E4002DD56B7C3D /* STDSDirectoryServer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDirectoryServer.h; sourceTree = ""; }; + 6AA75957DD13FA92FA866AF3 /* STDSDirectoryServerCertificateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDirectoryServerCertificateTests.m; sourceTree = ""; }; + 6B487D8DD39DAC17042A3F04 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 6B9B49A5CF64DCB23CD0AEDE /* STDSJSONEncoderTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJSONEncoderTest.m; sourceTree = ""; }; + 6CFF141AC98F213122D037C6 /* STDSFooterCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSFooterCustomization.m; sourceTree = ""; }; + 6DDB6222A2EF31FDB7592368 /* STDSErrorMessage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSErrorMessage.m; sourceTree = ""; }; + 6F030410FDABFF833CD8FE46 /* STDSException.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSException.m; sourceTree = ""; }; + 6FDAC320458399141EB161E2 /* STDSInvalidInputException.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSInvalidInputException.m; sourceTree = ""; }; + 71494F94F36F4B731A27D182 /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + 72DBE992B33F45C059EDB597 /* STDSSpacerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSpacerView.h; sourceTree = ""; }; + 762DEC6662DF8240FE19CFFC /* STDSChallengeRequestParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeRequestParameters.h; sourceTree = ""; }; + 76EA9DF8572C992E30BCF8A3 /* STDSJSONWebEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSJSONWebEncryption.h; sourceTree = ""; }; + 77646CEC7EE754F47EDBDA4F /* STDSThreeDS2Service.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSThreeDS2Service.m; sourceTree = ""; }; + 7859D635964B2854A07B5285 /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + 7971EB56716D5E2F59053B6D /* acs_challenge.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = acs_challenge.html; sourceTree = ""; }; + 7A637DA708BC866C2903E0EA /* STDSDeviceInformationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDeviceInformationManager.h; sourceTree = ""; }; + 7AC1C5F6F1311F50D9C37291 /* STDSProcessingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSProcessingView.m; sourceTree = ""; }; + 7CD753D9BBFC378C1B491B00 /* STDSSelectionCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSelectionCustomization.h; sourceTree = ""; }; + 7D02FAFC403BC901931A629F /* STDSChallengeResponseMessageExtensionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseMessageExtensionObject.m; sourceTree = ""; }; + 7FF8ACC073E814B7DEBA7647 /* STDSThreeDS2ServiceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSThreeDS2ServiceTests.m; sourceTree = ""; }; + 82C41E6DC2CC247ECC17F2B9 /* STDSTextChallengeView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSTextChallengeView.h; sourceTree = ""; }; + 83838563F29179FB1E9F2E66 /* cartes-bancaires.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "cartes-bancaires.der"; sourceTree = ""; }; + 83BDD516620860DC08B36B96 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 84967ED0D9B206B3F5AA4F02 /* STDSProgressViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSProgressViewController.m; sourceTree = ""; }; + 853A361AB630A2BD402D1B47 /* STDSInvalidInputException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSInvalidInputException.h; sourceTree = ""; }; + 869733153BCA6BE2EB7A452F /* UIColor+DefaultColors.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+DefaultColors.m"; sourceTree = ""; }; + 89B5ED899439D1C1054CB448 /* STDSConfigParameters.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSConfigParameters.m; sourceTree = ""; }; + 89ED2133B61092FE1B478204 /* STDSChallengeParametersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeParametersTests.m; sourceTree = ""; }; + 8A6035A8211B3D50D10EB947 /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + 8A9F5C315222AFCD14C9342F /* STDSTransaction+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSTransaction+Private.h"; sourceTree = ""; }; + 8AC648332A706809F1BDFD59 /* STDSThreeDSProtocolVersion.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSThreeDSProtocolVersion.m; sourceTree = ""; }; + 8B0F0485B7048DDAA7DA8F9B /* UIView+LayoutSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+LayoutSupport.m"; sourceTree = ""; }; + 8B59CC3349FD9BBF37624667 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 8B8D429F1C03603DB218700F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 8C6D262891811FB3FE0C947E /* CRes.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CRes.json; sourceTree = ""; }; + 8F72413F99F8E50FD0FA085D /* UIViewController+Stripe3DS2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Stripe3DS2.h"; sourceTree = ""; }; + 939C45AE068CF4F5BEB4C138 /* STDSExpandableInformationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSExpandableInformationView.h; sourceTree = ""; }; + 94943BEAB94E949AF9830EFA /* STDSJailbreakChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSJailbreakChecker.h; sourceTree = ""; }; + 95641ECBE1AE1CC198013405 /* STDSProcessingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSProcessingView.h; sourceTree = ""; }; + 9564F43EF7CF4F60C3C202CD /* STDSBundleLocator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSBundleLocator.m; sourceTree = ""; }; + 9580A82A59EB2B0103EDF47A /* STDSDeviceInformationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDeviceInformationManager.m; sourceTree = ""; }; + 969DE2FA3845BB7939D3CD2E /* STDSAuthenticationRequestParametersTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSAuthenticationRequestParametersTest.m; sourceTree = ""; }; + 97163B5E9598CA3A298920B9 /* STDSTextFieldCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSTextFieldCustomization.m; sourceTree = ""; }; + 97505E93BE83F233E69BB5A9 /* STDSJSONWebEncryptionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJSONWebEncryptionTests.m; sourceTree = ""; }; + 9754FA4F3CC6AA921787916B /* STDSDirectoryServerCertificate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDirectoryServerCertificate.m; sourceTree = ""; }; + 99392D3F702EC6BF7CE15291 /* STDSJSONWebEncryption.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJSONWebEncryption.m; sourceTree = ""; }; + 99451064136843047C7881AD /* UIViewController+Stripe3DS2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Stripe3DS2.m"; sourceTree = ""; }; + 9946D28A86E85A6C84EB6C7B /* STDSSwiftTryCatch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSwiftTryCatch.m; sourceTree = ""; }; + 99CB2B66DBD356B5D222EDA9 /* UIView+LayoutSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+LayoutSupport.h"; sourceTree = ""; }; + 99DB24A9B44979EA19347A76 /* STDSChallengeResponseObject+TestObjects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "STDSChallengeResponseObject+TestObjects.m"; sourceTree = ""; }; + 9A8B0001705400C661678617 /* UIColor+ThirteenSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+ThirteenSupport.h"; sourceTree = ""; }; + 9E1CB633CA5917B2168BB928 /* Stripe3DS2.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stripe3DS2.xcassets; sourceTree = ""; }; + 9F4376BB07FD1EE783249AED /* STDSStripe3DS2Error.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSStripe3DS2Error.m; sourceTree = ""; }; + 9F5C194C81AF1F5578698A27 /* STDSChallengeResponseObject+TestObjects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSChallengeResponseObject+TestObjects.h"; sourceTree = ""; }; + A09A89BBE7360F93195641C9 /* STDSImageLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSImageLoader.m; sourceTree = ""; }; + A0ADFB365AD2DE9E84873BB1 /* STDSSimulatorChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSimulatorChecker.h; sourceTree = ""; }; + A137A31BEFCCB646FB754EE2 /* STDSBundleLocator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSBundleLocator.h; sourceTree = ""; }; + A1BFB8640032796CDA016765 /* STDSConfigParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSConfigParameters.h; sourceTree = ""; }; + A1DC6B47C06B522B22EB19FF /* STDSJSONWebSignature.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJSONWebSignature.m; sourceTree = ""; }; + A3DFB001DB5BA7F1AFA7353A /* STDSIPAddress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSIPAddress.h; sourceTree = ""; }; + A4050F381183D6FDCC990D01 /* STDSDeviceInformationManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDeviceInformationManagerTests.m; sourceTree = ""; }; + A4620559FBB8A42E15044E32 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + A4A3CCBE9A2E8C1A73D8214E /* STDSTransactionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSTransactionTest.m; sourceTree = ""; }; + A4D460893CB5DDE52AA0AB85 /* STDSChallengeSelectionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeSelectionView.m; sourceTree = ""; }; + A502CCC5190F6B642EEC0CE6 /* STDSWarning.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSWarning.m; sourceTree = ""; }; + A55B2474CD1A39D70869B75B /* STDSUICustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSUICustomization.m; sourceTree = ""; }; + A5DBDDEB5C5DAE2EA8C95CC3 /* STDSSynchronousLocationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSynchronousLocationManager.m; sourceTree = ""; }; + A6778F8CE36F769DF608F932 /* NSError+Stripe3DS2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+Stripe3DS2.h"; sourceTree = ""; }; + A70071543985284E0D9DAC64 /* visa.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = visa.der; sourceTree = ""; }; + A8A77DB1C711B8696B2E5FEF /* NSString+EmptyChecking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+EmptyChecking.h"; sourceTree = ""; }; + A8E62EC973FC5C9905A40CA8 /* STDSCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSCustomization.m; sourceTree = ""; }; + A9CD7EC589C5CC6CE5CF9310 /* Stripe3DS2-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2-Debug.xcconfig"; sourceTree = ""; }; + AA29B74DD3963B1205AA1C0C /* UIButton+CustomInitialization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIButton+CustomInitialization.m"; sourceTree = ""; }; + AA85BC717C46C1B95AF8C1E3 /* STDSWebView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSWebView.m; sourceTree = ""; }; + AA8A113E655E23381CB3BDA4 /* Stripe3DS2DemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Stripe3DS2DemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + AA8BE982004398CF8AAA7D1E /* STDSChallengeResponseMessageExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseMessageExtension.h; sourceTree = ""; }; + AB871F64FC72EBC7A91F96C1 /* STDSAuthenticationRequestParameters.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSAuthenticationRequestParameters.m; sourceTree = ""; }; + AE1BBF3A441B1F782B766F61 /* STDSExpandableInformationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSExpandableInformationView.m; sourceTree = ""; }; + AE53BAA72835AFA3B35C58D3 /* STDSStripe3DS2Error.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSStripe3DS2Error.h; sourceTree = ""; }; + AF389752CCEB6FA734AAE31E /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + AFF187D1D58405C736474042 /* STDSCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSCustomization.h; sourceTree = ""; }; + B015B937DD3657D62C61CE58 /* STDSDirectoryServerCertificate+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STDSDirectoryServerCertificate+Internal.h"; sourceTree = ""; }; + B064352BB2876CA7266C410A /* STDSChallengeResponseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseViewController.m; sourceTree = ""; }; + B1A7D41498B79E35BD724681 /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + B219360BFB325779485BF702 /* STDSJSONDecodable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSJSONDecodable.h; sourceTree = ""; }; + B458FCA7DFDBF75E62A43BA1 /* STDSSelectionButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSelectionButton.m; sourceTree = ""; }; + B6275E7099E55F0C04688890 /* ul-test.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ul-test.der"; sourceTree = ""; }; + B71A1C110DCC23A9CE929837 /* STDSException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSException.h; sourceTree = ""; }; + B7A75140FB62A71261CAF5EC /* STDSChallengeStatusReceiver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeStatusReceiver.h; sourceTree = ""; }; + B7AE3B4732D203134FE096FE /* STDSStackView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSStackView.h; sourceTree = ""; }; + B7BD1E24EA9427121E148DFC /* STDSNotInitializedException.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSNotInitializedException.m; sourceTree = ""; }; + B8619CA38E2A5B49DBF8546B /* STDSRuntimeException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSRuntimeException.h; sourceTree = ""; }; + B97D91CB54F48F64CAC9E378 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + BA96C9FFA48B85F9C42E8C68 /* STDSFooterCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSFooterCustomization.h; sourceTree = ""; }; + BAAC56EDE0AAD2E50E02E9AE /* STDSChallengeResponseMessageExtensionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseMessageExtensionObject.h; sourceTree = ""; }; + BB065D6900E95C5E90864C16 /* STDSIntegrityChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSIntegrityChecker.m; sourceTree = ""; }; + BC3EE020DC9358FF56389BBE /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + BD94CAE01A17E083E5617E56 /* STDSButtonCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSButtonCustomization.h; sourceTree = ""; }; + BF80634E06E8D6F598CFC667 /* STDSEphemeralKeyPairTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSEphemeralKeyPairTests.m; sourceTree = ""; }; + C02DF839637771684BC57B3A /* STDSIntegrityChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSIntegrityChecker.h; sourceTree = ""; }; + C12AE31D386D4A193C00004B /* STDSSynchronousLocationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSynchronousLocationManager.h; sourceTree = ""; }; + C29F78CC37253B0D33FF86F2 /* STDSDebuggerChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDebuggerChecker.m; sourceTree = ""; }; + C4777F15603AC755362BD846 /* STDSSelectionButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSelectionButton.h; sourceTree = ""; }; + C566D6E444FCA4BCEC65F3D2 /* NSDictionary+DecodingHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+DecodingHelpers.m"; sourceTree = ""; }; + C78AA1D7AD2BAB52EE58D078 /* NSDictionary+DecodingHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+DecodingHelpers.h"; sourceTree = ""; }; + C893FD867C964775E68AE85F /* STDSChallengeInformationView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeInformationView.h; sourceTree = ""; }; + C8F0E11AA6794BFB1715685E /* STDSSecTypeUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSecTypeUtilities.h; sourceTree = ""; }; + C91B239583899192C7B26FCF /* STDSAuthenticationRequestParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSAuthenticationRequestParameters.h; sourceTree = ""; }; + CB00E968CF4DD07FB8BF2AAA /* STDSIPAddress.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSIPAddress.m; sourceTree = ""; }; + CB3154597040B803ADE14F9E /* STDSUICustomizationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSUICustomizationTests.m; sourceTree = ""; }; + CD33421F13A675DCFE597FFB /* STDSRuntimeException.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSRuntimeException.m; sourceTree = ""; }; + CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stripe3DS2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CECC1E22D0F0336039B435DE /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + CF3DED43BD4E0226BFB0759D /* STDSSecTypeUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSecTypeUtilities.m; sourceTree = ""; }; + D03B98D5861739833B197D8F /* STDSChallengeParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeParameters.h; sourceTree = ""; }; + D122FAE093BC4F649CC6E7AB /* STDSDebuggerChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSDebuggerChecker.h; sourceTree = ""; }; + D24777EE9931075405F370DB /* STDSChallengeInformationView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeInformationView.m; sourceTree = ""; }; + D3F5A6D5A680F008C00A2D69 /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + D7A9D36FEF7E97CF735D82B8 /* Stripe3DS2DemoUITests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2DemoUITests-Release.xcconfig"; sourceTree = ""; }; + D827473D1858B2BED85BDFA1 /* STDSACSNetworkingManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSACSNetworkingManager.h; sourceTree = ""; }; + D8B7EE0F935EF44EB2EF7D45 /* amex.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = amex.der; sourceTree = ""; }; + D8D309E4FBE7E73D0BB83BF5 /* NSString+EmptyChecking.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+EmptyChecking.m"; sourceTree = ""; }; + DE7D85369E012B15702D8DF9 /* STDSRuntimeErrorEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSRuntimeErrorEvent.h; sourceTree = ""; }; + E0949399E96663964091C0EF /* NSLayoutConstraint+LayoutSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSLayoutConstraint+LayoutSupport.h"; sourceTree = ""; }; + E12798864D5CFFB7157D4CF5 /* ErrorMessage.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = ErrorMessage.json; sourceTree = ""; }; + E1630D876436E43547FF69CE /* STDSNavigationBarCustomization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSNavigationBarCustomization.h; sourceTree = ""; }; + E45303DCFFDC390971E6E122 /* STDSChallengeResponseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseViewController.h; sourceTree = ""; }; + E4C668442518B446D573AD24 /* STDSJSONWebSignatureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSJSONWebSignatureTests.m; sourceTree = ""; }; + E5BC34A661D9080A2EE239F8 /* STDSSwiftTryCatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSSwiftTryCatch.h; sourceTree = ""; }; + E629F85B783E42244E37DFA9 /* STDSProgressViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSProgressViewController.h; sourceTree = ""; }; + E64C700DED163CB77DCDEA78 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + E6A3C5D4C46E681B7D76B2BA /* STDSJSONEncodable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSJSONEncodable.h; sourceTree = ""; }; + E9F1A5A8E5922748A9BEC724 /* STDSNavigationBarCustomization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSNavigationBarCustomization.m; sourceTree = ""; }; + EB1BDF12DBD6264C6199FFA7 /* STDSChallengeResponseObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeResponseObject.h; sourceTree = ""; }; + EB910E98EDD6D3E00802793C /* STDSChallengeSelectionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSChallengeSelectionView.h; sourceTree = ""; }; + ECC649704CDDA50E73DB3C10 /* STDSImageLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSImageLoader.h; sourceTree = ""; }; + EF5219762616ACF204F08C19 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + F02456AB660E709F332C0C7D /* STDSSynchronousLocationManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSSynchronousLocationManagerTests.m; sourceTree = ""; }; + F046DE266BAAE3DA0AC43785 /* STDSChallengeResponseSelectionInfoObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSChallengeResponseSelectionInfoObject.m; sourceTree = ""; }; + F112967E9FE8EE02FFFDC313 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + F139E48FDEFD921FF410892F /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + F2BC7014D26158AC9D74CC67 /* Stripe3DS2Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2Tests-Debug.xcconfig"; sourceTree = ""; }; + F363439353A6DD554FC44AC2 /* STDSOSVersionChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSOSVersionChecker.m; sourceTree = ""; }; + F40CC91AA69F5296B0AA985C /* STDSTransaction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSTransaction.h; sourceTree = ""; }; + F4D6DB90E3D01495C1F9BFE9 /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + F5686FB8EFA3AE3B39F85BBC /* Stripe3DS2Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2Tests-Release.xcconfig"; sourceTree = ""; }; + F86F34D1339F7467D0FDAC75 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + F8F6FFED1A0966A49B78F252 /* Stripe3DS2-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Stripe3DS2-Release.xcconfig"; sourceTree = ""; }; + F99B290CF2B4987A4CFE5DCE /* NSLayoutConstraint+LayoutSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSLayoutConstraint+LayoutSupport.m"; sourceTree = ""; }; + F9C39A42ECBA58571EFA03C5 /* STDSBrandingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSBrandingView.m; sourceTree = ""; }; + F9D521B45783D36C8E83F0EA /* STDSEphemeralKeyPair.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSEphemeralKeyPair.m; sourceTree = ""; }; + FAE7026135B5049B732CEDEB /* STDSOSVersionChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STDSOSVersionChecker.h; sourceTree = ""; }; + FC52C3C844BDA981EE34BAB9 /* STDSDeviceInformationParameterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STDSDeviceInformationParameterTests.m; sourceTree = ""; }; + FE1DE9D978E6E682CB094128 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 16B0A41D6C0F157DDF229397 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C2BA2E6ABDD8E80963FFC671 /* XCTest.framework in Frameworks */, + 4D73C2FBAC9B96ECB45BDC94 /* Stripe3DS2.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8ECF69FED0DDA991C821CF6A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1469551DB874336B68F6C39D /* Stripe3DS2.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A56B40D43D552FDE77670CB5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BF06B99FECD98C26F7B64665 /* XCTest.framework in Frameworks */, + A8A86B9BCE9702D74E0D2DB6 /* Stripe3DS2.framework in Frameworks */, + 3C6D16D8E7B5BC865D956A0B /* iOSSnapshotTestCase in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C65BFA70E847549921E39F4E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 148D1BA6A2E8B2B60CA9951F /* Stripe3DS2 */ = { + isa = PBXGroup; + children = ( + E4F3BE80D5F61EAC1694F954 /* include */, + A3DDF4E193616FA951B1B120 /* Resources */, + 5D93713CADD5589B5E6F6A0D /* Info.plist */, + 5B05A609753EE71502409082 /* NSData+JWEHelpers.h */, + 1FC15225A5E83948EF546B71 /* NSData+JWEHelpers.m */, + C78AA1D7AD2BAB52EE58D078 /* NSDictionary+DecodingHelpers.h */, + C566D6E444FCA4BCEC65F3D2 /* NSDictionary+DecodingHelpers.m */, + A6778F8CE36F769DF608F932 /* NSError+Stripe3DS2.h */, + 26C20D4B77372845627D6466 /* NSError+Stripe3DS2.m */, + E0949399E96663964091C0EF /* NSLayoutConstraint+LayoutSupport.h */, + F99B290CF2B4987A4CFE5DCE /* NSLayoutConstraint+LayoutSupport.m */, + A8A77DB1C711B8696B2E5FEF /* NSString+EmptyChecking.h */, + D8D309E4FBE7E73D0BB83BF5 /* NSString+EmptyChecking.m */, + 018F4AA25D84E9CBD2546BB5 /* NSString+JWEHelpers.h */, + 43CB8F416E4F4CEA34391481 /* NSString+JWEHelpers.m */, + D827473D1858B2BED85BDFA1 /* STDSACSNetworkingManager.h */, + 2D6004F2D9880055C0C0BCF1 /* STDSACSNetworkingManager.m */, + 138E82072FC6572612A8E248 /* STDSAuthenticationResponseObject.h */, + 5043E1CF6955176EC3D43F88 /* STDSAuthenticationResponseObject.m */, + 2CFAC8D74AFFC2E61BAD82A5 /* STDSBrandingView.h */, + F9C39A42ECBA58571EFA03C5 /* STDSBrandingView.m */, + A137A31BEFCCB646FB754EE2 /* STDSBundleLocator.h */, + 9564F43EF7CF4F60C3C202CD /* STDSBundleLocator.m */, + C893FD867C964775E68AE85F /* STDSChallengeInformationView.h */, + D24777EE9931075405F370DB /* STDSChallengeInformationView.m */, + 762DEC6662DF8240FE19CFFC /* STDSChallengeRequestParameters.h */, + 60693BC560C4927ACFD2E6C3 /* STDSChallengeRequestParameters.m */, + 315AEE1534F9D9974DC1674B /* STDSChallengeResponse.h */, + 10C45EE3433B731253E21E24 /* STDSChallengeResponseImage.h */, + 14F83E51E99D453DF9C2DDE1 /* STDSChallengeResponseImageObject.h */, + 3A9F828840A3B361842E38DE /* STDSChallengeResponseImageObject.m */, + AA8BE982004398CF8AAA7D1E /* STDSChallengeResponseMessageExtension.h */, + BAAC56EDE0AAD2E50E02E9AE /* STDSChallengeResponseMessageExtensionObject.h */, + 7D02FAFC403BC901931A629F /* STDSChallengeResponseMessageExtensionObject.m */, + EB1BDF12DBD6264C6199FFA7 /* STDSChallengeResponseObject.h */, + 24CE086326AF3DE336BF4F4C /* STDSChallengeResponseObject.m */, + 52EEBC7D74623E1A1D7E3219 /* STDSChallengeResponseSelectionInfo.h */, + 4CCA15E0819F9A9D89FEF9EA /* STDSChallengeResponseSelectionInfoObject.h */, + F046DE266BAAE3DA0AC43785 /* STDSChallengeResponseSelectionInfoObject.m */, + E45303DCFFDC390971E6E122 /* STDSChallengeResponseViewController.h */, + B064352BB2876CA7266C410A /* STDSChallengeResponseViewController.m */, + EB910E98EDD6D3E00802793C /* STDSChallengeSelectionView.h */, + A4D460893CB5DDE52AA0AB85 /* STDSChallengeSelectionView.m */, + D122FAE093BC4F649CC6E7AB /* STDSDebuggerChecker.h */, + C29F78CC37253B0D33FF86F2 /* STDSDebuggerChecker.m */, + 1FBE88F6E2C3153A8315EDC6 /* STDSDeviceInformation.h */, + 56B1A52548593FE78D801734 /* STDSDeviceInformation.m */, + 7A637DA708BC866C2903E0EA /* STDSDeviceInformationManager.h */, + 9580A82A59EB2B0103EDF47A /* STDSDeviceInformationManager.m */, + 172AF5C85EDBD92A1030E361 /* STDSDeviceInformationParameter.h */, + 25C7D18868DDADEF4CB6220C /* STDSDeviceInformationParameter.m */, + 4869D6E14F01EDB960CB3065 /* STDSDeviceInformationParameter+Private.h */, + 6AA43E7E90E4002DD56B7C3D /* STDSDirectoryServer.h */, + 069990DA025BE9F33602E96A /* STDSDirectoryServerCertificate.h */, + 9754FA4F3CC6AA921787916B /* STDSDirectoryServerCertificate.m */, + B015B937DD3657D62C61CE58 /* STDSDirectoryServerCertificate+Internal.h */, + 5049A72A6BEB558D8559A5EB /* STDSEllipticCurvePoint.h */, + 2EF5AA537586EF706E4056FD /* STDSEllipticCurvePoint.m */, + 20533E2C69C4B80BB33A4766 /* STDSEphemeralKeyPair.h */, + F9D521B45783D36C8E83F0EA /* STDSEphemeralKeyPair.m */, + 52D6E4F76CBA258163E57DF3 /* STDSEphemeralKeyPair+Testing.h */, + 5463EB1CC8E85BE3BD12DEFF /* STDSErrorMessage+Internal.h */, + 2CB591796654445B8434A501 /* STDSErrorMessage+Internal.m */, + 461F938CDE7ED6D06D9D2700 /* STDSException+Internal.h */, + 939C45AE068CF4F5BEB4C138 /* STDSExpandableInformationView.h */, + AE1BBF3A441B1F782B766F61 /* STDSExpandableInformationView.m */, + ECC649704CDDA50E73DB3C10 /* STDSImageLoader.h */, + A09A89BBE7360F93195641C9 /* STDSImageLoader.m */, + C02DF839637771684BC57B3A /* STDSIntegrityChecker.h */, + BB065D6900E95C5E90864C16 /* STDSIntegrityChecker.m */, + A3DFB001DB5BA7F1AFA7353A /* STDSIPAddress.h */, + CB00E968CF4DD07FB8BF2AAA /* STDSIPAddress.m */, + 94943BEAB94E949AF9830EFA /* STDSJailbreakChecker.h */, + 1BFBB92DBE97E16DE9D18B4A /* STDSJailbreakChecker.m */, + 76EA9DF8572C992E30BCF8A3 /* STDSJSONWebEncryption.h */, + 99392D3F702EC6BF7CE15291 /* STDSJSONWebEncryption.m */, + 3AA2E6DA60350400C5270E08 /* STDSJSONWebSignature.h */, + A1DC6B47C06B522B22EB19FF /* STDSJSONWebSignature.m */, + 41E0F638E776A7F3456BDA3E /* STDSLocalizedString.h */, + FAE7026135B5049B732CEDEB /* STDSOSVersionChecker.h */, + F363439353A6DD554FC44AC2 /* STDSOSVersionChecker.m */, + 95641ECBE1AE1CC198013405 /* STDSProcessingView.h */, + 7AC1C5F6F1311F50D9C37291 /* STDSProcessingView.m */, + E629F85B783E42244E37DFA9 /* STDSProgressViewController.h */, + 84967ED0D9B206B3F5AA4F02 /* STDSProgressViewController.m */, + C8F0E11AA6794BFB1715685E /* STDSSecTypeUtilities.h */, + CF3DED43BD4E0226BFB0759D /* STDSSecTypeUtilities.m */, + C4777F15603AC755362BD846 /* STDSSelectionButton.h */, + B458FCA7DFDBF75E62A43BA1 /* STDSSelectionButton.m */, + A0ADFB365AD2DE9E84873BB1 /* STDSSimulatorChecker.h */, + 14D60BDF245487FE3362BE07 /* STDSSimulatorChecker.m */, + 72DBE992B33F45C059EDB597 /* STDSSpacerView.h */, + 51E4A57094A671EB14380882 /* STDSSpacerView.m */, + B7AE3B4732D203134FE096FE /* STDSStackView.h */, + 1D6269F8B91341C387A911DB /* STDSStackView.m */, + C12AE31D386D4A193C00004B /* STDSSynchronousLocationManager.h */, + A5DBDDEB5C5DAE2EA8C95CC3 /* STDSSynchronousLocationManager.m */, + 82C41E6DC2CC247ECC17F2B9 /* STDSTextChallengeView.h */, + 04E6B6D3CAE363114723472F /* STDSTextChallengeView.m */, + 8AC648332A706809F1BDFD59 /* STDSThreeDSProtocolVersion.m */, + 47C29E9F3D2A62676B424CD1 /* STDSThreeDSProtocolVersion+Private.h */, + 8A9F5C315222AFCD14C9342F /* STDSTransaction+Private.h */, + 581EEE576D24047004C828DF /* STDSWebView.h */, + AA85BC717C46C1B95AF8C1E3 /* STDSWebView.m */, + 33CEADC8006E456FD82CE134 /* STDSWhitelistView.h */, + 615F1BD510661B38D6825128 /* STDSWhitelistView.m */, + 0F024DEAD6628A0229856656 /* Stripe3DS2-Bridging-Header.h */, + 319DD1C72B0D50F80083BA32 /* STDSVisionSupport.h */, + 4DD55BECC3FB85216FE46218 /* UIButton+CustomInitialization.h */, + AA29B74DD3963B1205AA1C0C /* UIButton+CustomInitialization.m */, + 4A567E08748C86FCE2A75FEA /* UIColor+DefaultColors.h */, + 869733153BCA6BE2EB7A452F /* UIColor+DefaultColors.m */, + 9A8B0001705400C661678617 /* UIColor+ThirteenSupport.h */, + 37ED28756222539D37554CC7 /* UIColor+ThirteenSupport.m */, + 1E844798B2733C8511AC080E /* UIFont+DefaultFonts.h */, + 634095EA97A4E48BF7CAA03F /* UIFont+DefaultFonts.m */, + 99CB2B66DBD356B5D222EDA9 /* UIView+LayoutSupport.h */, + 8B0F0485B7048DDAA7DA8F9B /* UIView+LayoutSupport.m */, + 8F72413F99F8E50FD0FA085D /* UIViewController+Stripe3DS2.h */, + 99451064136843047C7881AD /* UIViewController+Stripe3DS2.m */, + 31CDFC312BA8E58100B3DD91 /* PrivacyInfo.xcprivacy */, + ); + path = Stripe3DS2; + sourceTree = ""; + }; + 18B6E1F5666C797A11E2BE03 = { + isa = PBXGroup; + children = ( + EB6FA022FDF5831C6D162E11 /* Project */, + 280289821F799A8274082E14 /* Frameworks */, + 398BBCD5E4B06CA5006CCC40 /* Products */, + ); + sourceTree = ""; + }; + 20B0FECAB4F18569649603E9 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 3A8CD68A006F970DDC54469A /* Project-Debug.xcconfig */, + 4DB91F58CF23E72636C69C41 /* Project-Release.xcconfig */, + A9CD7EC589C5CC6CE5CF9310 /* Stripe3DS2-Debug.xcconfig */, + F8F6FFED1A0966A49B78F252 /* Stripe3DS2-Release.xcconfig */, + 5316D0759F7F2ED0BF1D3B2E /* Stripe3DS2DemoUI-Debug.xcconfig */, + 6940D75CA36F7DB9FB56196B /* Stripe3DS2DemoUI-Release.xcconfig */, + 0E3C05BE55CF3086738AA182 /* Stripe3DS2DemoUITests-Debug.xcconfig */, + D7A9D36FEF7E97CF735D82B8 /* Stripe3DS2DemoUITests-Release.xcconfig */, + F2BC7014D26158AC9D74CC67 /* Stripe3DS2Tests-Debug.xcconfig */, + F5686FB8EFA3AE3B39F85BBC /* Stripe3DS2Tests-Release.xcconfig */, + ); + path = BuildConfigurations; + sourceTree = ""; + }; + 280289821F799A8274082E14 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 08D577934984712C20C5E903 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 398BBCD5E4B06CA5006CCC40 /* Products */ = { + isa = PBXGroup; + children = ( + CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */, + 02D762271DBCC1AE80E0F9D4 /* Stripe3DS2DemoUI.app */, + AA8A113E655E23381CB3BDA4 /* Stripe3DS2DemoUITests.xctest */, + 4EB746D4E46ABD2452A154AE /* Stripe3DS2Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 47CD3B0E4EAA05F75DA05252 /* CertificateFiles */ = { + isa = PBXGroup; + children = ( + D8B7EE0F935EF44EB2EF7D45 /* amex.der */, + 83838563F29179FB1E9F2E66 /* cartes-bancaires.der */, + 04AB48F014A8D5BA56FEF463 /* discover.der */, + 3E4C02D9D6978AD5C3D7E3D2 /* ec_test.der */, + 1104D5855D28E49E104CA004 /* mastercard.der */, + B6275E7099E55F0C04688890 /* ul-test.der */, + A70071543985284E0D9DAC64 /* visa.der */, + ); + path = CertificateFiles; + sourceTree = ""; + }; + 79E03C9F2FB6092200EE737F /* Stripe3DS2Tests */ = { + isa = PBXGroup; + children = ( + AD446D5D3D8D07D491373E24 /* JSON */, + 23A3DDCE911F8C43CF74CDF4 /* Info.plist */, + 5EE8BF51F49E161737406B3A /* NSDictionary+DecodingHelpersTest.m */, + 57B8822D21AADE7DA51931CC /* NSString+EmptyCheckingTests.m */, + 6118F98576E9A39A7292C6C6 /* STDSACSNetworkingManagerTest.m */, + 969DE2FA3845BB7939D3CD2E /* STDSAuthenticationRequestParametersTest.m */, + 28E32DA72CAFBF2AF8099151 /* STDSAuthenticationResponseTests.m */, + 4B1E3760B1369DB1A33C9DFE /* STDSBase64URLEncodingTests.m */, + 89ED2133B61092FE1B478204 /* STDSChallengeParametersTests.m */, + 4EAD29E57D206B5D6BAF9099 /* STDSChallengeRequestParametersTest.m */, + 5E401845EE5759CCD30786AA /* STDSChallengeResponseObjectTest.m */, + 5F551808E65CDADD9B3A9CB8 /* STDSConfigParametersTests.m */, + A4050F381183D6FDCC990D01 /* STDSDeviceInformationManagerTests.m */, + FC52C3C844BDA981EE34BAB9 /* STDSDeviceInformationParameterTests.m */, + 6AA75957DD13FA92FA866AF3 /* STDSDirectoryServerCertificateTests.m */, + 0DA1EE83D91993BCAF13EC72 /* STDSEllipticCurvePointTests.m */, + BF80634E06E8D6F598CFC667 /* STDSEphemeralKeyPairTests.m */, + 278F2C40987F5218BD031E3D /* STDSErrorMessageTest.m */, + 6B9B49A5CF64DCB23CD0AEDE /* STDSJSONEncoderTest.m */, + 97505E93BE83F233E69BB5A9 /* STDSJSONWebEncryptionTests.m */, + E4C668442518B446D573AD24 /* STDSJSONWebSignatureTests.m */, + 30D22985091381DFCDF077E9 /* STDSSecTypeUtilitiesTests.m */, + F02456AB660E709F332C0C7D /* STDSSynchronousLocationManagerTests.m */, + 5FF3235EDD908CC1500399A1 /* STDSTestJSONUtils.m */, + 7FF8ACC073E814B7DEBA7647 /* STDSThreeDS2ServiceTests.m */, + A4A3CCBE9A2E8C1A73D8214E /* STDSTransactionTest.m */, + CB3154597040B803ADE14F9E /* STDSUICustomizationTests.m */, + 474F39AA3F47D84F8D54E5B7 /* STDSWarningTests.m */, + ); + path = Stripe3DS2Tests; + sourceTree = ""; + }; + 82BC8EB365BF993731432954 /* Sources */ = { + isa = PBXGroup; + children = ( + A4620559FBB8A42E15044E32 /* AppDelegate.h */, + 8B59CC3349FD9BBF37624667 /* AppDelegate.m */, + 3C22D410C297E2C3AFE727A6 /* main.m */, + 9F5C194C81AF1F5578698A27 /* STDSChallengeResponseObject+TestObjects.h */, + 99DB24A9B44979EA19347A76 /* STDSChallengeResponseObject+TestObjects.m */, + 1F0DADEABA0B043A6CA36DD6 /* STDSDemoViewController.h */, + 382C583385FAECA91366769E /* STDSDemoViewController.m */, + ); + path = Sources; + sourceTree = ""; + }; + 8CF6AFFC9FD5638E0D1E988E /* Stripe3DS2DemoUI */ = { + isa = PBXGroup; + children = ( + 98160E2F8248A228E5EC73F8 /* Resources */, + 82BC8EB365BF993731432954 /* Sources */, + 037C294011A01E4B4DCE5BB7 /* Info.plist */, + ); + path = Stripe3DS2DemoUI; + sourceTree = ""; + }; + 98160E2F8248A228E5EC73F8 /* Resources */ = { + isa = PBXGroup; + children = ( + 310E6B4E2C52C9EF006A03EC /* 150-issuer.png */, + 310E6B502C52C9EF006A03EC /* 150-payment.png */, + 310E6B4F2C52C9EF006A03EC /* 300-issuer.png */, + 310E6B512C52C9EF006A03EC /* 300-payment.png */, + 310E6B522C52C9EF006A03EC /* 450-issuer.png */, + 310E6B532C52C9EF006A03EC /* 450-payment.png */, + 7971EB56716D5E2F59053B6D /* acs_challenge.html */, + ); + path = Resources; + sourceTree = ""; + }; + A3DDF4E193616FA951B1B120 /* Resources */ = { + isa = PBXGroup; + children = ( + 47CD3B0E4EAA05F75DA05252 /* CertificateFiles */, + F31A6580385C6390910AFD93 /* Localizable.strings */, + 9E1CB633CA5917B2168BB928 /* Stripe3DS2.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + AD446D5D3D8D07D491373E24 /* JSON */ = { + isa = PBXGroup; + children = ( + 237977B021FE538E5E4FA35C /* ARes.json */, + 8C6D262891811FB3FE0C947E /* CRes.json */, + E12798864D5CFFB7157D4CF5 /* ErrorMessage.json */, + ); + path = JSON; + sourceTree = ""; + }; + C169A9439FD628D620B218AA /* Stripe3DS2DemoUITests */ = { + isa = PBXGroup; + children = ( + 00658E077ECB223C90484AF7 /* Info.plist */, + 466DF80D33CEC20DF4E844A9 /* STDSChallengeResponseViewControllerSnapshotTests.m */, + ); + path = Stripe3DS2DemoUITests; + sourceTree = ""; + }; + E4F3BE80D5F61EAC1694F954 /* include */ = { + isa = PBXGroup; + children = ( + 1F1FD11407319293954C6D81 /* STDSAlreadyInitializedException.h */, + 27AC486DA2A80A43C1F517B2 /* STDSAlreadyInitializedException.m */, + C91B239583899192C7B26FCF /* STDSAuthenticationRequestParameters.h */, + AB871F64FC72EBC7A91F96C1 /* STDSAuthenticationRequestParameters.m */, + 4EA4AB3BA2FB41B3D5F38978 /* STDSAuthenticationResponse.h */, + BD94CAE01A17E083E5617E56 /* STDSButtonCustomization.h */, + 5489DCAA65624C6F966816B6 /* STDSButtonCustomization.m */, + D03B98D5861739833B197D8F /* STDSChallengeParameters.h */, + 23C929BF760735CC01E1E8F5 /* STDSChallengeParameters.m */, + B7A75140FB62A71261CAF5EC /* STDSChallengeStatusReceiver.h */, + 10DB3CD8F2541AC06681F2C8 /* STDSCompletionEvent.h */, + 3B6B71C61CF5ADC7796441F5 /* STDSCompletionEvent.m */, + A1BFB8640032796CDA016765 /* STDSConfigParameters.h */, + 89B5ED899439D1C1054CB448 /* STDSConfigParameters.m */, + AFF187D1D58405C736474042 /* STDSCustomization.h */, + A8E62EC973FC5C9905A40CA8 /* STDSCustomization.m */, + 645D0BB927B7F8A0D37EE04D /* STDSErrorMessage.h */, + 6DDB6222A2EF31FDB7592368 /* STDSErrorMessage.m */, + B71A1C110DCC23A9CE929837 /* STDSException.h */, + 6F030410FDABFF833CD8FE46 /* STDSException.m */, + BA96C9FFA48B85F9C42E8C68 /* STDSFooterCustomization.h */, + 6CFF141AC98F213122D037C6 /* STDSFooterCustomization.m */, + 853A361AB630A2BD402D1B47 /* STDSInvalidInputException.h */, + 6FDAC320458399141EB161E2 /* STDSInvalidInputException.m */, + B219360BFB325779485BF702 /* STDSJSONDecodable.h */, + E6A3C5D4C46E681B7D76B2BA /* STDSJSONEncodable.h */, + 06C5207432FB07BD71703BCC /* STDSJSONEncoder.h */, + 0681C043F732148587737233 /* STDSJSONEncoder.m */, + 6932DD26C7C16938DFA0E198 /* STDSLabelCustomization.h */, + 01D320CD5ADA49F17B8BC1B0 /* STDSLabelCustomization.m */, + E1630D876436E43547FF69CE /* STDSNavigationBarCustomization.h */, + E9F1A5A8E5922748A9BEC724 /* STDSNavigationBarCustomization.m */, + 2FCABBF51CBA7F9FEF757B4C /* STDSNotInitializedException.h */, + B7BD1E24EA9427121E148DFC /* STDSNotInitializedException.m */, + 3A6D20D1DAB7D769E5E97528 /* STDSProtocolErrorEvent.h */, + 3255706D2304FB551C97305F /* STDSProtocolErrorEvent.m */, + DE7D85369E012B15702D8DF9 /* STDSRuntimeErrorEvent.h */, + 1501A7A3010FFE53D00E318F /* STDSRuntimeErrorEvent.m */, + B8619CA38E2A5B49DBF8546B /* STDSRuntimeException.h */, + CD33421F13A675DCFE597FFB /* STDSRuntimeException.m */, + 7CD753D9BBFC378C1B491B00 /* STDSSelectionCustomization.h */, + 43540AB7D13C0C9A563C3F4D /* STDSSelectionCustomization.m */, + AE53BAA72835AFA3B35C58D3 /* STDSStripe3DS2Error.h */, + 9F4376BB07FD1EE783249AED /* STDSStripe3DS2Error.m */, + E5BC34A661D9080A2EE239F8 /* STDSSwiftTryCatch.h */, + 9946D28A86E85A6C84EB6C7B /* STDSSwiftTryCatch.m */, + 68ED2A0E11E1B27980E0C60A /* STDSTextFieldCustomization.h */, + 97163B5E9598CA3A298920B9 /* STDSTextFieldCustomization.m */, + 5EAA444FA7C82BDA4AD907BF /* STDSThreeDS2Service.h */, + 77646CEC7EE754F47EDBDA4F /* STDSThreeDS2Service.m */, + 3AD384AD2C76634D009AB784 /* STDSAnalyticsDelegate.h */, + 5E6A9565E97DF2544254AA94 /* STDSThreeDSProtocolVersion.h */, + F40CC91AA69F5296B0AA985C /* STDSTransaction.h */, + 5FDE2481364C454058BF2377 /* STDSTransaction.m */, + 210B22FF4DCB0C6E7C763EAB /* STDSUICustomization.h */, + A55B2474CD1A39D70869B75B /* STDSUICustomization.m */, + 0BEB5006261C5E070FEE62BF /* STDSWarning.h */, + A502CCC5190F6B642EEC0CE6 /* STDSWarning.m */, + 28C6B4F6BA431B9342970FD6 /* Stripe3DS2.h */, + ); + path = include; + sourceTree = ""; + }; + EB6FA022FDF5831C6D162E11 /* Project */ = { + isa = PBXGroup; + children = ( + 20B0FECAB4F18569649603E9 /* BuildConfigurations */, + 148D1BA6A2E8B2B60CA9951F /* Stripe3DS2 */, + 8CF6AFFC9FD5638E0D1E988E /* Stripe3DS2DemoUI */, + C169A9439FD628D620B218AA /* Stripe3DS2DemoUITests */, + 79E03C9F2FB6092200EE737F /* Stripe3DS2Tests */, + ); + name = Project; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A8117E5A795D5342E5AEF24C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3192FCC841152A7E5E6F19D6 /* STDSAlreadyInitializedException.h in Headers */, + 9673946B78A7763070E05CD0 /* STDSAuthenticationRequestParameters.h in Headers */, + 435EFC9119D4B42B2C6A6F88 /* STDSAuthenticationResponse.h in Headers */, + EC51189277D7D68D08C2A65C /* STDSButtonCustomization.h in Headers */, + DC0D9A57F2E312663C088FB8 /* STDSChallengeParameters.h in Headers */, + 72F430506E684D61BD5CFE3B /* STDSChallengeStatusReceiver.h in Headers */, + 8E27285FE76F8BECFD70286D /* STDSCompletionEvent.h in Headers */, + DC32281BF0847A98B100C3A1 /* STDSConfigParameters.h in Headers */, + F04482C663F6DF8E522B0833 /* STDSCustomization.h in Headers */, + 2BBA82878804711E06CBFCCB /* STDSErrorMessage.h in Headers */, + 24D49F833E1ED28DE557F219 /* STDSException.h in Headers */, + 79557307ECD48D17C566997A /* STDSFooterCustomization.h in Headers */, + 4E35267801D94FB84816C519 /* STDSInvalidInputException.h in Headers */, + 9B32E9A4CD2BDA61C730F5EB /* STDSJSONDecodable.h in Headers */, + 890E22971E6F7B0FA1C8D649 /* STDSJSONEncodable.h in Headers */, + 2224463CE5FB99D4BEE6A479 /* STDSJSONEncoder.h in Headers */, + 0BA0F0CADC4CFF419E1F7EFC /* STDSLabelCustomization.h in Headers */, + 3B430F172A3AD6AA9AFDFC04 /* STDSNavigationBarCustomization.h in Headers */, + 2CB6DF5CCCBC1EB9ABD03143 /* STDSNotInitializedException.h in Headers */, + 67D431374DECE36B2606D944 /* STDSProtocolErrorEvent.h in Headers */, + 15D4A2F07EA477B84CD48B15 /* STDSRuntimeErrorEvent.h in Headers */, + 2C6DB6516699B4EFADDF3360 /* STDSRuntimeException.h in Headers */, + F5513045A25210C524A05DF5 /* STDSSelectionCustomization.h in Headers */, + 335553A547A3CA80B3901CCF /* STDSStripe3DS2Error.h in Headers */, + DE32EAA8F5C2645652FCFB48 /* STDSSwiftTryCatch.h in Headers */, + 4C267E9A30ADC18E71408ED5 /* STDSTextFieldCustomization.h in Headers */, + 31C636A25BF83316EE7EB57D /* STDSThreeDS2Service.h in Headers */, + 847926A68A88BEB58C92D9BC /* STDSThreeDSProtocolVersion.h in Headers */, + 9DC5612697835A383DC6606E /* STDSTransaction.h in Headers */, + 0615DD02C0B022AE207DADF0 /* STDSUICustomization.h in Headers */, + 9F0F6085FBD892BF5F14CF86 /* STDSWarning.h in Headers */, + 893713F4464A88FAB92BC2C6 /* Stripe3DS2.h in Headers */, + 3AD384AE2C76634D009AB784 /* STDSAnalyticsDelegate.h in Headers */, + 425849564519D6454DDA3424 /* NSData+JWEHelpers.h in Headers */, + 1134F81C7D015BA5EA52BFD1 /* NSDictionary+DecodingHelpers.h in Headers */, + 3617B07ABD719F1A5F8302FA /* NSError+Stripe3DS2.h in Headers */, + 28AD7EC6F6DA2AECD7F61BA7 /* NSLayoutConstraint+LayoutSupport.h in Headers */, + 30B25163BEDEB99CDD101B5F /* NSString+EmptyChecking.h in Headers */, + A5193DADCD0F48911F775D88 /* NSString+JWEHelpers.h in Headers */, + A78F19DA3D146A258FB9DE3C /* STDSACSNetworkingManager.h in Headers */, + 2B2787A7103C0D0AB5F00B78 /* STDSAuthenticationResponseObject.h in Headers */, + 458BC5A3D0E7B4D05549474F /* STDSBrandingView.h in Headers */, + 97CE8B57A97D037E977E62F3 /* STDSBundleLocator.h in Headers */, + F13A032A4AF15BB61EA18A8D /* STDSChallengeInformationView.h in Headers */, + BF342613EE89845EB1C4B72A /* STDSChallengeRequestParameters.h in Headers */, + 7337FB20DC0EF491B5922913 /* STDSChallengeResponse.h in Headers */, + 95A2D58051AADDBE16CCA0B6 /* STDSChallengeResponseImage.h in Headers */, + D819C341ECEE1EFE6FE66085 /* STDSChallengeResponseImageObject.h in Headers */, + 793D61AD55630DD336A141A7 /* STDSChallengeResponseMessageExtension.h in Headers */, + 48316C1AB1D9151C205A59A4 /* STDSChallengeResponseMessageExtensionObject.h in Headers */, + E90749131EDA0C86BBBFCE5C /* STDSChallengeResponseObject.h in Headers */, + 3C8C3BCDDDDEF6B9E150D4E1 /* STDSChallengeResponseSelectionInfo.h in Headers */, + 9C225B9C556C20D1A6F97180 /* STDSChallengeResponseSelectionInfoObject.h in Headers */, + 9E46777893FA2D6691F496D7 /* STDSChallengeResponseViewController.h in Headers */, + E7E424C5264A4DE4A1EC19B6 /* STDSChallengeSelectionView.h in Headers */, + F28AD6CA8AB1A5DEE7A8F429 /* STDSDebuggerChecker.h in Headers */, + 57E58A3B88D9EEB9FAE9B383 /* STDSDeviceInformation.h in Headers */, + 9843F6AABF7B0A623E7DF979 /* STDSDeviceInformationManager.h in Headers */, + 3844F0E21742F43BCB32499A /* STDSDeviceInformationParameter+Private.h in Headers */, + 2938D9CC41899DC78D01EF9F /* STDSDeviceInformationParameter.h in Headers */, + C4BA75544A7F51355BF4B5F8 /* STDSDirectoryServer.h in Headers */, + DB96817598FCE73F5131437E /* STDSDirectoryServerCertificate+Internal.h in Headers */, + 63AFB3C739DD987EE56F801F /* STDSDirectoryServerCertificate.h in Headers */, + 5C83A3A4631E51EEECBEAA0F /* STDSEllipticCurvePoint.h in Headers */, + 3343DA94A5032627843A343C /* STDSEphemeralKeyPair+Testing.h in Headers */, + B94E5A5BF5F22998E4CA9ED3 /* STDSEphemeralKeyPair.h in Headers */, + 34256504F0E0E519D586B7CE /* STDSErrorMessage+Internal.h in Headers */, + 149FE0C2910F08910B250BD8 /* STDSException+Internal.h in Headers */, + F35963DB86D90320CFA7BCB9 /* STDSExpandableInformationView.h in Headers */, + DDB98914E1135E8D445545C8 /* STDSIPAddress.h in Headers */, + 613EF855524F7861A6DDD8C1 /* STDSImageLoader.h in Headers */, + D3432C5BF5211A60D5C15D9E /* STDSIntegrityChecker.h in Headers */, + 319DD1C82B0D50FF0083BA32 /* STDSVisionSupport.h in Headers */, + 4C573A44AB0A9CB28D76C621 /* STDSJSONWebEncryption.h in Headers */, + CA617B6F5CDFB8FDEEE38636 /* STDSJSONWebSignature.h in Headers */, + 7C7073B54628ED836F422F1D /* STDSJailbreakChecker.h in Headers */, + 8E0501C3BB849C2D2D99F34E /* STDSLocalizedString.h in Headers */, + 68F314D63323954EFE309E49 /* STDSOSVersionChecker.h in Headers */, + 09F0B1945CC12FB1215119FD /* STDSProcessingView.h in Headers */, + 511513A9180F9835B08CBE56 /* STDSProgressViewController.h in Headers */, + CEA1EB91858043A837638FE0 /* STDSSecTypeUtilities.h in Headers */, + D9F8BA88953A91DC082FA6DA /* STDSSelectionButton.h in Headers */, + AC474013841CE1DED97F3FA8 /* STDSSimulatorChecker.h in Headers */, + AA94519687902E34E5243AEA /* STDSSpacerView.h in Headers */, + 206B93BDE91CFED74A053B87 /* STDSStackView.h in Headers */, + F0B9EFD359B1AB28A695CB48 /* STDSSynchronousLocationManager.h in Headers */, + 59756023104E4FB886B48936 /* STDSTextChallengeView.h in Headers */, + 326AD1DD7BB8A2A6DA66EC67 /* STDSThreeDSProtocolVersion+Private.h in Headers */, + AE1894FEA510AC394DE73C4B /* STDSTransaction+Private.h in Headers */, + 7EC8E5523F29B0F01FE827DB /* STDSWebView.h in Headers */, + 825ACBE0734BDCE746E66AB0 /* STDSWhitelistView.h in Headers */, + 75CDFDABD7F9813563E3F618 /* Stripe3DS2-Bridging-Header.h in Headers */, + 0A2BC6A9E388B242C46C09D9 /* UIButton+CustomInitialization.h in Headers */, + 8322973CD09F2E1C88B6046E /* UIColor+DefaultColors.h in Headers */, + 27F3EA1BB7E7B56A1A8524BA /* UIColor+ThirteenSupport.h in Headers */, + 8E833B7FCBC3A99072582FEA /* UIFont+DefaultFonts.h in Headers */, + A026717FFF299A7C1C51565F /* UIView+LayoutSupport.h in Headers */, + 41246FCC0695BABE1489C53E /* UIViewController+Stripe3DS2.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 57AEC53510AE0DC0539730F3 /* Stripe3DS2 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5C9BBB6FD4175090CFCAA126 /* Build configuration list for PBXNativeTarget "Stripe3DS2" */; + buildPhases = ( + A8117E5A795D5342E5AEF24C /* Headers */, + 559A957A4AA29D178C3E3D8F /* Sources */, + 563147EAF54FC4A425A06D46 /* Resources */, + 726DD7176204D90BD7552B84 /* Embed Frameworks */, + C65BFA70E847549921E39F4E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Stripe3DS2; + productName = Stripe3DS2; + productReference = CE4030C50383B488C97F4B56 /* Stripe3DS2.framework */; + productType = "com.apple.product-type.framework"; + }; + 5907C55B1F111921112DF2BF /* Stripe3DS2DemoUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 37237B2A5629D2257C454AD1 /* Build configuration list for PBXNativeTarget "Stripe3DS2DemoUI" */; + buildPhases = ( + 5D618F152EF19018F5C7E579 /* Sources */, + C4175027D57AF53025CB9719 /* Resources */, + 00E415680C602ED1D6D8E71F /* Embed Frameworks */, + 8ECF69FED0DDA991C821CF6A /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + C7515969CD69027C276886AB /* PBXTargetDependency */, + ); + name = Stripe3DS2DemoUI; + productName = Stripe3DS2DemoUI; + productReference = 02D762271DBCC1AE80E0F9D4 /* Stripe3DS2DemoUI.app */; + productType = "com.apple.product-type.application"; + }; + 7DA168BC86CE957505FA091B /* Stripe3DS2DemoUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5F64A3CED17753DA2643AA63 /* Build configuration list for PBXNativeTarget "Stripe3DS2DemoUITests" */; + buildPhases = ( + 7A43CA17E1E2D826DCBDD88D /* Sources */, + F3AE035E64AB50AB8B90A58E /* Resources */, + 58FA2FE74AE67CD3CDAFD7E5 /* Embed Frameworks */, + A56B40D43D552FDE77670CB5 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 0AE2FB81071258D69C9FCAD4 /* PBXTargetDependency */, + 026B55E92602E24EE7139E1F /* PBXTargetDependency */, + ); + name = Stripe3DS2DemoUITests; + packageProductDependencies = ( + 0118969F6608A92583CC6C98 /* iOSSnapshotTestCase */, + ); + productName = Stripe3DS2DemoUITests; + productReference = AA8A113E655E23381CB3BDA4 /* Stripe3DS2DemoUITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + F2D50FA32F27498F56CD08DD /* Stripe3DS2Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 271DFDD5ABBB719F3BD1AB56 /* Build configuration list for PBXNativeTarget "Stripe3DS2Tests" */; + buildPhases = ( + 0D8CCC419762E2FD4F945EB0 /* Sources */, + 4FDEAB55B960B360B76B3F41 /* Resources */, + 43B749EC915FB6D2FF5ABEE0 /* Embed Frameworks */, + 16B0A41D6C0F157DDF229397 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 798525809F1F67D0C6634927 /* PBXTargetDependency */, + ); + name = Stripe3DS2Tests; + productName = Stripe3DS2Tests; + productReference = 4EB746D4E46ABD2452A154AE /* Stripe3DS2Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8D219187B704575AD3CD3EC5 /* Project object */ = { + isa = PBXProject; + attributes = { + TargetAttributes = { + 7DA168BC86CE957505FA091B = { + TestTargetID = 5907C55B1F111921112DF2BF; + }; + }; + }; + buildConfigurationList = 57B08C966701355644CF5CEB /* Build configuration list for PBXProject "Stripe3DS2" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = 18B6E1F5666C797A11E2BE03; + packageReferences = ( + 176A29DF97FAFE939C7F667B /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + ); + productRefGroup = 398BBCD5E4B06CA5006CCC40 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 57AEC53510AE0DC0539730F3 /* Stripe3DS2 */, + F2D50FA32F27498F56CD08DD /* Stripe3DS2Tests */, + 5907C55B1F111921112DF2BF /* Stripe3DS2DemoUI */, + 7DA168BC86CE957505FA091B /* Stripe3DS2DemoUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4FDEAB55B960B360B76B3F41 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B16918BF5F67B0F5FB1AFD5 /* ARes.json in Resources */, + F4E8353A470750D9BCEA3CD8 /* CRes.json in Resources */, + E7A72BB7CC8DE8DA7FA0DEC5 /* ErrorMessage.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 563147EAF54FC4A425A06D46 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A97E1DF84D381952A0FB4C1C /* amex.der in Resources */, + EA89021709AFB5F04961EB78 /* cartes-bancaires.der in Resources */, + 3D674B4EE5ABACD65EB21A1E /* discover.der in Resources */, + FD6456BD86F07C446B9FB6C8 /* ec_test.der in Resources */, + 081347606D48F64161D95B45 /* mastercard.der in Resources */, + 81B1C12449852CEB3C59793F /* ul-test.der in Resources */, + 125427F1322501AA12EAEBE6 /* visa.der in Resources */, + 2D42EA44FC01C834209CFED4 /* Stripe3DS2.xcassets in Resources */, + 31CDFC322BA8E58100B3DD91 /* PrivacyInfo.xcprivacy in Resources */, + 6BA3F565B6C0B78F6DED8826 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C4175027D57AF53025CB9719 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7C20B044ACFD3A50FD4AC9A8 /* acs_challenge.html in Resources */, + 310E6B542C52C9EF006A03EC /* 150-issuer.png in Resources */, + 310E6B592C52C9EF006A03EC /* 450-payment.png in Resources */, + 310E6B552C52C9EF006A03EC /* 300-issuer.png in Resources */, + 310E6B572C52C9EF006A03EC /* 300-payment.png in Resources */, + 310E6B582C52C9EF006A03EC /* 450-issuer.png in Resources */, + 310E6B562C52C9EF006A03EC /* 150-payment.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F3AE035E64AB50AB8B90A58E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0D8CCC419762E2FD4F945EB0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B426123CC7FCA337E13304F9 /* NSDictionary+DecodingHelpersTest.m in Sources */, + E73028FD4537F6E48F927127 /* NSString+EmptyCheckingTests.m in Sources */, + D1427392DA8AD8F5B4118781 /* STDSACSNetworkingManagerTest.m in Sources */, + A33A549CF12209280E3B1DBA /* STDSAuthenticationRequestParametersTest.m in Sources */, + 5DAC25EBAB19555229654E44 /* STDSAuthenticationResponseTests.m in Sources */, + A56AE2CD941D7ADAC7FC8BCC /* STDSBase64URLEncodingTests.m in Sources */, + D63E3DDC92DD46062A265D86 /* STDSChallengeParametersTests.m in Sources */, + 4A9423CC79312BC3A0DEC38B /* STDSChallengeRequestParametersTest.m in Sources */, + ECF8755D2602E0E06DCB3210 /* STDSChallengeResponseObjectTest.m in Sources */, + 32BE38D04DD76CA4490389C6 /* STDSConfigParametersTests.m in Sources */, + B1C2AA6259CE34B0042A1FB1 /* STDSDeviceInformationManagerTests.m in Sources */, + 2B09B7FDF8C4AA551F7EE18E /* STDSDeviceInformationParameterTests.m in Sources */, + E4DA2CE8DC8C60DE40222185 /* STDSDirectoryServerCertificateTests.m in Sources */, + 9F52F67A1A3A2199AEA598FC /* STDSEllipticCurvePointTests.m in Sources */, + BFC5852E14724750E70DA2A6 /* STDSEphemeralKeyPairTests.m in Sources */, + 1767509FDC216602A5E43F0E /* STDSErrorMessageTest.m in Sources */, + D189220981C7705913D93687 /* STDSJSONEncoderTest.m in Sources */, + 98075195759ABB91B0D19847 /* STDSJSONWebEncryptionTests.m in Sources */, + 4C0E13298F08D391FE8600CF /* STDSJSONWebSignatureTests.m in Sources */, + 480CD62EC91F4796D1D8D8DC /* STDSSecTypeUtilitiesTests.m in Sources */, + BA65731CDB75C3124834586C /* STDSSynchronousLocationManagerTests.m in Sources */, + EA19A7AEC298F3EC010D1199 /* STDSTestJSONUtils.m in Sources */, + D927789F89B413C08DE4D388 /* STDSThreeDS2ServiceTests.m in Sources */, + 9B7081D378D1441B98B81207 /* STDSTransactionTest.m in Sources */, + 83878337274D8C43D492EC45 /* STDSUICustomizationTests.m in Sources */, + 30632F7C730BF8D23B607CF7 /* STDSWarningTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 559A957A4AA29D178C3E3D8F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A8CAD8276ED8057A760147CB /* NSData+JWEHelpers.m in Sources */, + 0D4B42EC5019D213BDBC84E7 /* NSDictionary+DecodingHelpers.m in Sources */, + 089DAFECA444861762781974 /* NSError+Stripe3DS2.m in Sources */, + 9ED9C602C10682CD5DD91C12 /* NSLayoutConstraint+LayoutSupport.m in Sources */, + DEEF260365A4D9B0D83E557D /* NSString+EmptyChecking.m in Sources */, + 88197445522F39DB1E9F6AE7 /* NSString+JWEHelpers.m in Sources */, + B31168F5FEC3464AC212DB85 /* STDSACSNetworkingManager.m in Sources */, + 5FE5FB933E2651C8D84FA477 /* STDSAuthenticationResponseObject.m in Sources */, + 75150C4678B8207AA6E7D969 /* STDSBrandingView.m in Sources */, + 8233B9F131602DD7143FF96F /* STDSBundleLocator.m in Sources */, + 14BDBD98F8E15576403255C9 /* STDSChallengeInformationView.m in Sources */, + 2970D4880EBFAEFABA2E8EBD /* STDSChallengeRequestParameters.m in Sources */, + 2AB06EC20B22306F1DDF9F4B /* STDSChallengeResponseImageObject.m in Sources */, + B9A456E52E62E9DAE9F424A7 /* STDSChallengeResponseMessageExtensionObject.m in Sources */, + 09DFAF7B38EAB76BC66AC9C8 /* STDSChallengeResponseObject.m in Sources */, + AA4D157F31A246890D446981 /* STDSChallengeResponseSelectionInfoObject.m in Sources */, + CCC376FE7424029342A8D15E /* STDSChallengeResponseViewController.m in Sources */, + 4FE8C076102C195D609977F5 /* STDSChallengeSelectionView.m in Sources */, + A6909D6C1A68963504733939 /* STDSDebuggerChecker.m in Sources */, + E630DCBBEFAAD0435BEF989C /* STDSDeviceInformation.m in Sources */, + 3F2624C33291FCE00D596768 /* STDSDeviceInformationManager.m in Sources */, + 0EEFDE04392FD017EA0A017A /* STDSDeviceInformationParameter.m in Sources */, + 9F9BB9E7FA18FEDA44F7042E /* STDSDirectoryServerCertificate.m in Sources */, + 6B3AA85EE71FC42EF9BD999F /* STDSEllipticCurvePoint.m in Sources */, + 05647ABCFBDBE1209D5044AC /* STDSEphemeralKeyPair.m in Sources */, + 2E2022723BC7A4B2DD5ECE76 /* STDSErrorMessage+Internal.m in Sources */, + 4142A3AB3E384F91A1E5550B /* STDSExpandableInformationView.m in Sources */, + 9146B2E3B89EF3F489D7E233 /* STDSIPAddress.m in Sources */, + 136255493F3F0E930FBA8606 /* STDSImageLoader.m in Sources */, + EB791827A943AA61976D8C29 /* STDSIntegrityChecker.m in Sources */, + 955CBE0C979291F89567D8A7 /* STDSJSONWebEncryption.m in Sources */, + EFC72E766F9FD4EE18D309BC /* STDSJSONWebSignature.m in Sources */, + BA00D11D792C99B7D5749F59 /* STDSJailbreakChecker.m in Sources */, + 602C526C0B52DF81846DA664 /* STDSOSVersionChecker.m in Sources */, + 574A7976213046F02F7F60C4 /* STDSProcessingView.m in Sources */, + 1156B25EBE0135B627E174D2 /* STDSProgressViewController.m in Sources */, + DBDB8B56FFF5C4234705E698 /* STDSSecTypeUtilities.m in Sources */, + D41F091BE1579B801D1BBC20 /* STDSSelectionButton.m in Sources */, + 4518AD83580FC222B883DAFB /* STDSSimulatorChecker.m in Sources */, + 3F30A8F418870D176A626F00 /* STDSSpacerView.m in Sources */, + 3909D8028AC485BCCF17B48B /* STDSStackView.m in Sources */, + E0644CE0260178023E643B23 /* STDSSynchronousLocationManager.m in Sources */, + D5706ACF84F0A993CE1885D4 /* STDSTextChallengeView.m in Sources */, + 1B20B454D0C309613A1FAF68 /* STDSThreeDSProtocolVersion.m in Sources */, + 884D59AF7FD70889276AFC02 /* STDSWebView.m in Sources */, + E70CFDC59731BA62DD89906C /* STDSWhitelistView.m in Sources */, + A4CFDBFC3099BD7E1A694E3E /* UIButton+CustomInitialization.m in Sources */, + 2086DFD1FC02783FB106AD35 /* UIColor+DefaultColors.m in Sources */, + E19C8104FC1A9BAEA0BE8A9D /* UIColor+ThirteenSupport.m in Sources */, + 987347CA74818CEB716B9DB3 /* UIFont+DefaultFonts.m in Sources */, + 390691CA0EAF81418B0C65DB /* UIView+LayoutSupport.m in Sources */, + 08ECC7E70E7478E76793ED1E /* UIViewController+Stripe3DS2.m in Sources */, + D83D2FF20387FACC383A2A0F /* STDSAlreadyInitializedException.m in Sources */, + 7ED98325E7E54A54BFE54D6C /* STDSAuthenticationRequestParameters.m in Sources */, + C430332104547BA508416B41 /* STDSButtonCustomization.m in Sources */, + 6DB54DBAEFD08967367B6BF7 /* STDSChallengeParameters.m in Sources */, + FD8A81873C3BC7579ED07460 /* STDSCompletionEvent.m in Sources */, + 8A0872706BF4CDE72EF0C480 /* STDSConfigParameters.m in Sources */, + BF2651A9F3A31CB1AFEC44BB /* STDSCustomization.m in Sources */, + 8C9BD4B150F384111CBD3BD3 /* STDSErrorMessage.m in Sources */, + 3C88AE37A760B91E93D423CF /* STDSException.m in Sources */, + F569B584D357DC5C55542015 /* STDSFooterCustomization.m in Sources */, + EE3854BC2A14F87E8D51B0D0 /* STDSInvalidInputException.m in Sources */, + 0D3D9C95CB14A610EAAAEF6E /* STDSJSONEncoder.m in Sources */, + EEE77034728D2CFE38CC5A85 /* STDSLabelCustomization.m in Sources */, + BB379E68231A7DA62F87E628 /* STDSNavigationBarCustomization.m in Sources */, + 8A64AFBB2AC15E0E0F57ED25 /* STDSNotInitializedException.m in Sources */, + B51D1C218F460F67B58A1F0D /* STDSProtocolErrorEvent.m in Sources */, + 64D05353B50FAF3BB76D6527 /* STDSRuntimeErrorEvent.m in Sources */, + 01706B4660728A5BFAC12840 /* STDSRuntimeException.m in Sources */, + D3AA14D1E059E90F5A965CC4 /* STDSSelectionCustomization.m in Sources */, + 2C5621AABECF6192F92A20CF /* STDSStripe3DS2Error.m in Sources */, + 4E4E4BC1E2D041FA530336EE /* STDSSwiftTryCatch.m in Sources */, + 284F72D3FD8C06C0E5974086 /* STDSTextFieldCustomization.m in Sources */, + 24F626CD0F93B7AF59B1D6AA /* STDSThreeDS2Service.m in Sources */, + 6E727D2B738D7A3527952D28 /* STDSTransaction.m in Sources */, + DF4D38DC0AF89EAAA8718AAE /* STDSUICustomization.m in Sources */, + E08AF96E690D75F6AB52021F /* STDSWarning.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5D618F152EF19018F5C7E579 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B84ED7FBD49865F23B774067 /* AppDelegate.m in Sources */, + A39CFB56BF33B21A89987F9C /* STDSChallengeResponseObject+TestObjects.m in Sources */, + 128B64380D6724F016EE8D56 /* STDSDemoViewController.m in Sources */, + B012C4A8ACA1D064C40A1B63 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7A43CA17E1E2D826DCBDD88D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F939E05536AE838DC489C3AA /* STDSChallengeResponseViewControllerSnapshotTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 026B55E92602E24EE7139E1F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Stripe3DS2DemoUI; + target = 5907C55B1F111921112DF2BF /* Stripe3DS2DemoUI */; + targetProxy = 35B6793A7F3FB130F40209F1 /* PBXContainerItemProxy */; + }; + 0AE2FB81071258D69C9FCAD4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Stripe3DS2; + target = 57AEC53510AE0DC0539730F3 /* Stripe3DS2 */; + targetProxy = 52E1EB51359DA6E75D851D71 /* PBXContainerItemProxy */; + }; + 798525809F1F67D0C6634927 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Stripe3DS2; + target = 57AEC53510AE0DC0539730F3 /* Stripe3DS2 */; + targetProxy = 6C355B127D670833C76237D3 /* PBXContainerItemProxy */; + }; + C7515969CD69027C276886AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Stripe3DS2; + target = 57AEC53510AE0DC0539730F3 /* Stripe3DS2 */; + targetProxy = 947ED275EAB25AD4CD701936 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + F31A6580385C6390910AFD93 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 8A6035A8211B3D50D10EB947 /* bg-BG */, + 40DC4913154C3D3A27E35207 /* ca-ES */, + 7859D635964B2854A07B5285 /* cs-CZ */, + 8B8D429F1C03603DB218700F /* da */, + 2C2666DF7D23EBC3FFE7CCF1 /* de */, + 4C158B552B0CBE635A2AF7BB /* el-GR */, + EF5219762616ACF204F08C19 /* en */, + F112967E9FE8EE02FFFDC313 /* en-GB */, + 1BBADD412A7C2D1FF35A7C86 /* es */, + 406D88ADED27D64DED2002A2 /* es-419 */, + 461FDC130846625171164A9E /* et-EE */, + F139E48FDEFD921FF410892F /* fi */, + 49EF79D69693F937FEC46906 /* fil */, + 4BC4D9FD1D3869D491CC2CF8 /* fr */, + E64C700DED163CB77DCDEA78 /* fr-CA */, + 0E38A775DB9F529427E900CF /* hr */, + 371F8076630A8839C1F6A508 /* hu */, + 62773D5C85C567F28BCE0DA5 /* id */, + 13ED1CC076C6B3FB49C9197A /* it */, + 157970FF5B4A817041E3D668 /* ja */, + F86F34D1339F7467D0FDAC75 /* ko */, + 71494F94F36F4B731A27D182 /* lt-LT */, + 38572D4E32BF3670F1C83409 /* lv-LV */, + B1A7D41498B79E35BD724681 /* ms-MY */, + 18D88DE3E34106F934015BF9 /* mt */, + 6B487D8DD39DAC17042A3F04 /* nb */, + 83BDD516620860DC08B36B96 /* nl */, + D3F5A6D5A680F008C00A2D69 /* nn-NO */, + FE1DE9D978E6E682CB094128 /* pl-PL */, + 6690476C1BEA59C37576FB36 /* pt-BR */, + BC3EE020DC9358FF56389BBE /* pt-PT */, + F4D6DB90E3D01495C1F9BFE9 /* ro-RO */, + 694DF6F47767A651907D55D4 /* ru */, + 58C38FF9205C9B7D79C51A37 /* sk-SK */, + 3B9A821F35B93898A7AC1ADF /* sl-SI */, + 38F3A7D625E4B0D3506A37F6 /* sv */, + 2192708AD201F80F6E1A39F7 /* tr */, + B97D91CB54F48F64CAC9E378 /* vi */, + 3F017BF6ECE0EBE4E0DFCF9C /* zh-Hans */, + AF389752CCEB6FA734AAE31E /* zh-Hant */, + CECC1E22D0F0336039B435DE /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 03D567A9F86C4B204A11D604 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6940D75CA36F7DB9FB56196B /* Stripe3DS2DemoUI-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = Stripe3DS2DemoUI/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.Stripe3DS2DemoUI; + PRODUCT_NAME = Stripe3DS2DemoUI; + SDKROOT = iphoneos; + }; + name = Release; + }; + 380615C693D7BF5A8884139A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5316D0759F7F2ED0BF1D3B2E /* Stripe3DS2DemoUI-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = Stripe3DS2DemoUI/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.Stripe3DS2DemoUI; + PRODUCT_NAME = Stripe3DS2DemoUI; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 437A50F3FA4CE0127490A271 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4DB91F58CF23E72636C69C41 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 7C7BC68E9581B5EDB7602612 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D7A9D36FEF7E97CF735D82B8 /* Stripe3DS2DemoUITests-Release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = Stripe3DS2DemoUITests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.Stripe3DS2DemoUITests; + PRODUCT_NAME = Stripe3DS2DemoUITests; + SDKROOT = iphoneos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Stripe3DS2DemoUI.app/Stripe3DS2DemoUI"; + TEST_TARGET_NAME = Stripe3DS2DemoUI; + }; + name = Release; + }; + 872D92C1EE444AB448EB744C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F5686FB8EFA3AE3B39F85BBC /* Stripe3DS2Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = Stripe3DS2Tests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.Stripe3DS2Tests; + PRODUCT_NAME = Stripe3DS2Tests; + SDKROOT = iphoneos; + }; + name = Release; + }; + 8CD9CB7B7E57B44EE65EF421 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3A8CD68A006F970DDC54469A /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + A084DA5D39CBD41FD4E5294E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F2BC7014D26158AC9D74CC67 /* Stripe3DS2Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = Stripe3DS2Tests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.Stripe3DS2Tests; + PRODUCT_NAME = Stripe3DS2Tests; + SDKROOT = iphoneos; + }; + name = Debug; + }; + C34CBE7C8AA7B8ADB823B098 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0E3C05BE55CF3086738AA182 /* Stripe3DS2DemoUITests-Debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = Stripe3DS2DemoUITests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.Stripe3DS2DemoUITests; + PRODUCT_NAME = Stripe3DS2DemoUITests; + SDKROOT = iphoneos; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Stripe3DS2DemoUI.app/Stripe3DS2DemoUI"; + TEST_TARGET_NAME = Stripe3DS2DemoUI; + }; + name = Debug; + }; + D4CD9EDC1DA055683F427D24 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F8F6FFED1A0966A49B78F252 /* Stripe3DS2-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = Stripe3DS2/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-3ds2"; + PRODUCT_NAME = Stripe3DS2; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; + FCA19B8EBB1E9578613A65BE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A9CD7EC589C5CC6CE5CF9310 /* Stripe3DS2-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = Stripe3DS2/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-3ds2"; + PRODUCT_NAME = Stripe3DS2; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 271DFDD5ABBB719F3BD1AB56 /* Build configuration list for PBXNativeTarget "Stripe3DS2Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A084DA5D39CBD41FD4E5294E /* Debug */, + 872D92C1EE444AB448EB744C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 37237B2A5629D2257C454AD1 /* Build configuration list for PBXNativeTarget "Stripe3DS2DemoUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 380615C693D7BF5A8884139A /* Debug */, + 03D567A9F86C4B204A11D604 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57B08C966701355644CF5CEB /* Build configuration list for PBXProject "Stripe3DS2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8CD9CB7B7E57B44EE65EF421 /* Debug */, + 437A50F3FA4CE0127490A271 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5C9BBB6FD4175090CFCAA126 /* Build configuration list for PBXNativeTarget "Stripe3DS2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FCA19B8EBB1E9578613A65BE /* Debug */, + D4CD9EDC1DA055683F427D24 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5F64A3CED17753DA2643AA63 /* Build configuration list for PBXNativeTarget "Stripe3DS2DemoUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C34CBE7C8AA7B8ADB823B098 /* Debug */, + 7C7BC68E9581B5EDB7602612 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 176A29DF97FAFE939C7F667B /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 0118969F6608A92583CC6C98 /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 8D219187B704575AD3CD3EC5 /* Project object */; +} diff --git a/Stripe3DS2/Stripe3DS2.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Stripe3DS2/Stripe3DS2.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Stripe3DS2/Stripe3DS2.xcodeproj/xcshareddata/xcschemes/Stripe3DS2.xcscheme b/Stripe3DS2/Stripe3DS2.xcodeproj/xcshareddata/xcschemes/Stripe3DS2.xcscheme new file mode 100644 index 00000000..14b59e6f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2.xcodeproj/xcshareddata/xcschemes/Stripe3DS2.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stripe3DS2/Stripe3DS2.xcodeproj/xcshareddata/xcschemes/Stripe3DS2DemoUI.xcscheme b/Stripe3DS2/Stripe3DS2.xcodeproj/xcshareddata/xcschemes/Stripe3DS2DemoUI.xcscheme new file mode 100644 index 00000000..b45aa0a9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2.xcodeproj/xcshareddata/xcschemes/Stripe3DS2DemoUI.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stripe3DS2/Stripe3DS2/Info.plist b/Stripe3DS2/Stripe3DS2/Info.plist new file mode 100644 index 00000000..e1fe4cfb --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/Stripe3DS2/Stripe3DS2/NSData+JWEHelpers.h b/Stripe3DS2/Stripe3DS2/NSData+JWEHelpers.h new file mode 100644 index 00000000..71159625 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSData+JWEHelpers.h @@ -0,0 +1,20 @@ +// +// NSData+JWEHelpers.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (JWEHelpers) + +- (nullable NSString *)_stds_base64URLEncodedString; +- (nullable NSString *)_stds_base64URLDecodedString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSData+JWEHelpers.m b/Stripe3DS2/Stripe3DS2/NSData+JWEHelpers.m new file mode 100644 index 00000000..c1bbab4e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSData+JWEHelpers.m @@ -0,0 +1,35 @@ +// +// NSData+JWEHelpers.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "NSData+JWEHelpers.h" + +#import "NSString+JWEHelpers.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSData (STDSJSONWebEncryption) + +- (nullable NSString *)_stds_base64URLEncodedString { + // ref. https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C + NSString *unpaddedBase64EncodedString = [[[[self base64EncodedStringWithOptions:0] + stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]] // remove extra padding + stringByReplacingOccurrencesOfString:@"+" withString:@"-"] // replace "+" character w/ "-" + stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; // replace "/" character w/ "_" + + return unpaddedBase64EncodedString; +} + +- (nullable NSString *)_stds_base64URLDecodedString { + return [[[self base64EncodedStringWithOptions:0] + stringByReplacingOccurrencesOfString:@"-" withString:@"+"] // replace "-" character w/ "+" + stringByReplacingOccurrencesOfString:@"_" withString:@"/"]; // replace "_" character w/ "/" +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSDictionary+DecodingHelpers.h b/Stripe3DS2/Stripe3DS2/NSDictionary+DecodingHelpers.h new file mode 100644 index 00000000..70371f9f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSDictionary+DecodingHelpers.h @@ -0,0 +1,47 @@ +// +// NSDictionary+DecodingHelpers.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Errors are populated according to the following rules: + - If the field is required and... + - the value is nil or empty -> STDSErrorCodeJSONFieldMissing + - the value is the wrong type -> STDSErrorCodeJSONFieldInvalid + - validator returns NO -> STDSErrorCodeJSONFieldInvalid + + - If the field is not required and... + - the value is nil -> valid, no error + - the value is empty -> STDSErrorCodeJSONFieldInvalid + - the value is the wrong type -> STDSErrorCodeJSONFieldInvalid + - validator returns NO -> STDSErrorCodeJSONFieldInvalid + */ +@interface NSDictionary (DecodingHelpers) + +/// Convenience method to extract an NSArray and populate it with instances of arrayElementType. +/// If isRequired is YES, returns nil without error if the key is not present +- (nullable NSArray *)_stds_arrayForKey:(NSString *)key arrayElementType:(Class)arrayElementType required:(BOOL)isRequired error:(NSError **)error; + +- (nullable NSURL *)_stds_urlForKey:(NSString *)key required:(BOOL)isRequired error:(NSError **)error; + +- (nullable NSDictionary *)_stds_dictionaryForKey:(NSString *)key required:(BOOL)isRequired error:(NSError **)error; + +- (nullable NSNumber *)_stds_boolForKey:(NSString *)key required:(BOOL)isRequired error:(NSError **)error; + +/// Convenience method that calls `_stpStringForKey:validator:required:error:`, passing nil for the validator argument +- (nullable NSString *)_stds_stringForKey:(NSString *)key required:(BOOL)isRequired error:(NSError **)error; + +- (nullable NSString *)_stds_stringForKey:(NSString *)key validator:(nullable BOOL (^)(NSString *))validatorBlock required:(BOOL)isRequired error:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSDictionary+DecodingHelpers.m b/Stripe3DS2/Stripe3DS2/NSDictionary+DecodingHelpers.m new file mode 100644 index 00000000..4d2e2c09 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSDictionary+DecodingHelpers.m @@ -0,0 +1,147 @@ +// +// NSDictionary+DecodingHelpers.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "NSDictionary+DecodingHelpers.h" + +#import "NSError+Stripe3DS2.h" + +@implementation NSDictionary (DecodingHelpers) + +#pragma mark - NSArray + +- (nullable NSArray *)_stds_arrayForKey:(NSString *)key arrayElementType:(Class)arrayElementType required:(BOOL)isRequired error:(NSError * _Nullable __autoreleasing * _Nullable)error { + id value = self[key]; + + // Missing? + if (value == nil || ([value isKindOfClass:[NSArray class]] && ((NSArray *)value).count == 0)) { + if (isRequired && error) { + *error = [NSError _stds_missingJSONFieldError:key]; + } + return nil; + } + + // Invalid type or value? + if (![value isKindOfClass:[NSArray class]]) { + if (error) { + *error = [NSError _stds_invalidJSONFieldError:key]; + } + return nil; + } + + NSMutableArray *returnArray = [NSMutableArray new]; + for (id json in value) { + if (![json isKindOfClass:[NSDictionary class]]) { + if (error) { + *error = [NSError _stds_invalidJSONFieldError:key]; + } + return nil; + } + id element = [arrayElementType decodedObjectFromJSON:json error:error]; + if (element) { + [returnArray addObject:element]; + } + } + + return returnArray; +} + +#pragma mark - NSURL + +- (nullable NSURL *)_stds_urlForKey:(NSString *)key required:(BOOL)isRequired error:(NSError * _Nullable __autoreleasing *)error { + NSString *urlRawString = [self _stds_stringForKey:key validator:^BOOL (NSString *value) { + return [NSURL URLWithString:value] != nil; + } required:isRequired error:error]; + + if (urlRawString) { + return [NSURL URLWithString:urlRawString]; + } else { + return nil; + } +} + +#pragma mark - NSDictionary + +- (nullable NSDictionary *)_stds_dictionaryForKey:(NSString *)key required:(BOOL)isRequired error:(NSError * _Nullable __autoreleasing *)error { + id value = self[key]; + + // Missing? + if (value == nil) { + if (error && isRequired) { + *error = [NSError _stds_missingJSONFieldError:key]; + } + return nil; + } + + // Invalid type? + if (![value isKindOfClass:[NSDictionary class]]) { + if (error) { + *error = [NSError _stds_invalidJSONFieldError:key]; + } + return nil; + } + + return value; +} + +#pragma mark - NSString + +- (nullable NSString *)_stds_stringForKey:(NSString *)key required:(BOOL)isRequired error:(NSError * _Nullable __autoreleasing * _Nullable)error { + return [self _stds_stringForKey:key validator:nil required:isRequired error:error]; +} + +- (nullable NSString *)_stds_stringForKey:(NSString *)key validator:(nullable BOOL (^)(NSString * _Nonnull))validatorBlock required:(BOOL)isRequired error:(NSError * _Nullable __autoreleasing * _Nullable)error { + id value = self[key]; + + // Missing? + if (value == nil || ([value isKindOfClass:[NSString class]] && ((NSString *)value).length == 0)) { + if (error) { + if (isRequired) { + *error = [NSError _stds_missingJSONFieldError:key]; + } else if (value != nil) { + *error = [NSError _stds_invalidJSONFieldError:key]; + } + } + return nil; + } + + // Invalid type or value? + if (![value isKindOfClass:[NSString class]] || (validatorBlock && !validatorBlock(value))) { + if (error) { + *error = [NSError _stds_invalidJSONFieldError:key]; + } + return nil; + } + + return value; +} + +#pragma mark - NSURL + +- (NSNumber *)_stds_boolForKey:(NSString *)key required:(BOOL)isRequired error:(NSError * _Nullable __autoreleasing *)error { + id value = self[key]; + + // Missing? + if (value == nil) { + if (error && isRequired) { + *error = [NSError _stds_missingJSONFieldError:key]; + } + return nil; + } + + // Invalid type? + if (![value isKindOfClass:[NSNumber class]]) { + if (error) { + *error = [NSError _stds_invalidJSONFieldError:key]; + } + return nil; + } + + return value; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/NSError+Stripe3DS2.h b/Stripe3DS2/Stripe3DS2/NSError+Stripe3DS2.h new file mode 100644 index 00000000..6af09054 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSError+Stripe3DS2.h @@ -0,0 +1,32 @@ +// +// NSError+Stripe3DS2.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSError (Stripe3DS2) + + +/// Represents an error where a JSON field value is not valid (e.g. expected 'Y' or 'N' but received something else). ++ (instancetype)_stds_invalidJSONFieldError:(NSString *)fieldName; + +/// Represents an error where a JSON field was either required or conditionally required but missing, empty, or null. ++ (instancetype)_stds_missingJSONFieldError:(NSString *)fieldName; + +/// Represents an error where a network request timed out. ++ (instancetype)_stds_timedOutError; + +// We explicitly do not provide any more info here based on security recommendations +// "the recipient MUST NOT distinguish between format, padding, and length errors of encrypted keys" +// https://tools.ietf.org/html/rfc7516#section-11.5 ++ (instancetype)_stds_jweError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSError+Stripe3DS2.m b/Stripe3DS2/Stripe3DS2/NSError+Stripe3DS2.m new file mode 100644 index 00000000..cd60c4cc --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSError+Stripe3DS2.m @@ -0,0 +1,38 @@ +// +// NSError+Stripe3DS2.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "NSError+Stripe3DS2.h" +#import "STDSLocalizedString.h" + +#import "STDSStripe3DS2Error.h" + +@implementation NSError (Stripe3DS2) + ++ (instancetype)_stds_invalidJSONFieldError:(NSString *)fieldName { + return [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeJSONFieldInvalid + userInfo:@{STDSStripe3DS2ErrorFieldKey: fieldName}]; +} + ++ (instancetype)_stds_missingJSONFieldError:(NSString *)fieldName { + return [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeJSONFieldMissing + userInfo:@{STDSStripe3DS2ErrorFieldKey: fieldName}]; +} + ++ (instancetype)_stds_timedOutError { + return [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeTimeout + userInfo:@{NSLocalizedDescriptionKey : STDSLocalizedString(@"Timeout", @"Error description for when a network request times out. English value is as required by UL certification.")}]; +} + ++ (instancetype)_stds_jweError { + return [[NSError alloc] initWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorCodeDecryptionVerification userInfo:nil]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/NSLayoutConstraint+LayoutSupport.h b/Stripe3DS2/Stripe3DS2/NSLayoutConstraint+LayoutSupport.h new file mode 100644 index 00000000..66596c81 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSLayoutConstraint+LayoutSupport.h @@ -0,0 +1,53 @@ +// +// NSLayoutConstraint+LayoutSupport.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSLayoutConstraint (LayoutSupport) + +/** + Provides an NSLayoutConstraint where the `NSLayoutAttributeTop` is equal for both views, with a multiplier of 1, and a constant of 0. + + @param view1 The view to constrain. + @param view2 The view to constraint to. + @return An NSLayoutConstraint that is constraining the first view to the second at the top. + */ ++ (NSLayoutConstraint *)_stds_topConstraintWithItem:(id)view1 toItem:(id)view2; + +/** + Provides an NSLayoutConstraint where the `NSLayoutAttributeLeft` is equal for both views, with a multiplier of 1, and a constant of 0. + + @param view1 The view to constrain. + @param view2 The view to constraint to. + @return An NSLayoutConstraint that is constraining the first view to the second on the left. + */ ++ (NSLayoutConstraint *)_stds_leftConstraintWithItem:(id)view1 toItem:(id)view2; + +/** + Provides an NSLayoutConstraint where the `NSLayoutAttributeRight` is equal for both views, with a multiplier of 1, and a constant of 0. + + @param view1 The view to constrain. + @param view2 The view to constraint to. + @return An NSLayoutConstraint that is constraining the first view to the second on the right. + */ ++ (NSLayoutConstraint *)_stds_rightConstraintWithItem:(id)view1 toItem:(id)view2; + +/** + Provides an NSLayoutConstraint where the `NSLayoutAttributeBottom` is equal for both views, with a multiplier of 1, and a constant of 0. + + @param view1 The view to constrain. + @param view2 The view to constraint to. + @return An NSLayoutConstraint that is constraining the first view to the second at the bottom. + */ ++ (NSLayoutConstraint *)_stds_bottomConstraintWithItem:(id)view1 toItem:(id)view2; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSLayoutConstraint+LayoutSupport.m b/Stripe3DS2/Stripe3DS2/NSLayoutConstraint+LayoutSupport.m new file mode 100644 index 00000000..8b73b5fe --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSLayoutConstraint+LayoutSupport.m @@ -0,0 +1,30 @@ +// +// NSLayoutConstraint+LayoutSupport.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "NSLayoutConstraint+LayoutSupport.h" + +@implementation NSLayoutConstraint (LayoutSupport) + + ++ (NSLayoutConstraint *)_stds_topConstraintWithItem:(id)view1 toItem:(id)view2 { + return [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeTop multiplier:1 constant:0]; +} + ++ (NSLayoutConstraint *)_stds_leftConstraintWithItem:(id)view1 toItem:(id)view2 { + return [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; +} + ++ (NSLayoutConstraint *)_stds_rightConstraintWithItem:(id)view1 toItem:(id)view2 { + return [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeRight multiplier:1 constant:0]; +} + ++ (NSLayoutConstraint *)_stds_bottomConstraintWithItem:(id)view1 toItem:(id)view2 { + return [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/NSString+EmptyChecking.h b/Stripe3DS2/Stripe3DS2/NSString+EmptyChecking.h new file mode 100644 index 00000000..aa019bf0 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSString+EmptyChecking.h @@ -0,0 +1,19 @@ +// +// NSString+EmptyChecking.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (EmptyChecking) + ++ (BOOL)_stds_isStringEmpty:(NSString *)string; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSString+EmptyChecking.m b/Stripe3DS2/Stripe3DS2/NSString+EmptyChecking.m new file mode 100644 index 00000000..bf749718 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSString+EmptyChecking.m @@ -0,0 +1,25 @@ +// +// NSString+EmptyChecking.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "NSString+EmptyChecking.h" + +@implementation NSString (EmptyChecking) + ++ (BOOL)_stds_isStringEmpty:(NSString *)string { + if (string.length == 0) { + return YES; + } + + if(![string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length) { + return YES; + } + + return NO; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/NSString+JWEHelpers.h b/Stripe3DS2/Stripe3DS2/NSString+JWEHelpers.h new file mode 100644 index 00000000..acb9afde --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSString+JWEHelpers.h @@ -0,0 +1,21 @@ +// +// NSString+JWEHelpers.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (JWEHelpers) + +- (nullable NSString *)_stds_base64URLEncodedString; +- (nullable NSString *)_stds_base64URLDecodedString; +- (nullable NSData *)_stds_base64URLDecodedData; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/NSString+JWEHelpers.m b/Stripe3DS2/Stripe3DS2/NSString+JWEHelpers.m new file mode 100644 index 00000000..2c8d5ad8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/NSString+JWEHelpers.m @@ -0,0 +1,54 @@ +// +// NSString+JWEHelpers.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "NSString+JWEHelpers.h" + +#import "NSData+JWEHelpers.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation NSString (JWEHelpers) + +- (nullable NSString *)_stds_base64URLEncodedString { + return [[self dataUsingEncoding:NSUTF8StringEncoding] _stds_base64URLEncodedString]; +} + +- (nullable NSString *)_stds_base64URLDecodedString { + NSData *data = [self _stds_base64URLDecodedData]; + return data != nil ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil; +} + +// ref. https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#appendix-C +- (nullable NSData *)_stds_base64URLDecodedData { + NSCharacterSet *illegalBase64Chars = [NSCharacterSet characterSetWithCharactersInString:@"+/ \n"]; // TC_SDK_10556_001 & TC_SDK_10557_001 & TC_SDK_10558_001 & TC_SDK_10559_001 + if ([self hasSuffix:@"="] || [self rangeOfCharacterFromSet:illegalBase64Chars].location != NSNotFound) { + return nil; // invalid base64url string TC_SDK_10554_001 & TC_SDK_10555_001 + } + NSMutableString *decodedString = [[[self stringByReplacingOccurrencesOfString:@"-" withString:@"+"] // replace "-" character w/ "+" + stringByReplacingOccurrencesOfString:@"_" withString:@"/"] mutableCopy]; // replace "_" character w/ "/"]; + + switch (decodedString.length % 4) { + case 0: + break; // no padding needed + case 2: + [decodedString appendString:@"=="]; // pad with 2 + break; + case 3: + [decodedString appendString:@"="]; // pad with 1 + break; + default: + return nil; // invalid base64url string + + } + + return [[NSData alloc] initWithBase64EncodedString:decodedString options:0]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/PrivacyInfo.xcprivacy b/Stripe3DS2/Stripe3DS2/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..1f0bb214 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/PrivacyInfo.xcprivacy @@ -0,0 +1,33 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/amex.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/amex.der new file mode 100644 index 00000000..61a1cfd1 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/amex.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/cartes-bancaires.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/cartes-bancaires.der new file mode 100644 index 00000000..23d14ff0 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/cartes-bancaires.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/discover.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/discover.der new file mode 100644 index 00000000..5b17935a Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/discover.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/ec_test.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/ec_test.der new file mode 100644 index 00000000..06587c44 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/ec_test.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/mastercard.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/mastercard.der new file mode 100644 index 00000000..6e136e13 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/mastercard.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/ul-test.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/ul-test.der new file mode 100644 index 00000000..11e5f29b Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/ul-test.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/visa.der b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/visa.der new file mode 100644 index 00000000..a77735df Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/CertificateFiles/visa.der differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@1x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@1x.png new file mode 100644 index 00000000..e6aa5e46 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@1x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@2x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@2x.png new file mode 100644 index 00000000..821c9bea Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@2x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@3x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@3x.png new file mode 100644 index 00000000..42f836e1 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Chevron@3x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Contents.json new file mode 100644 index 00000000..3218fd17 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Chevron.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Chevron@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "Chevron@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Chevron@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/amex-logo.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/amex-logo.imageset/Contents.json new file mode 100644 index 00000000..d7a5fb5e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/amex-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "american-express@1x.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/amex-logo.imageset/american-express@1x.pdf b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/amex-logo.imageset/american-express@1x.pdf new file mode 100644 index 00000000..9582b790 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/amex-logo.imageset/american-express@1x.pdf differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/cartes-bancaires-logo.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/cartes-bancaires-logo.imageset/Contents.json new file mode 100644 index 00000000..ef5cd216 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/cartes-bancaires-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cartes-bancaires-logo.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/cartes-bancaires-logo.imageset/cartes-bancaires-logo.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/cartes-bancaires-logo.imageset/cartes-bancaires-logo.png new file mode 100644 index 00000000..e65785c3 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/cartes-bancaires-logo.imageset/cartes-bancaires-logo.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/discover-logo.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/discover-logo.imageset/Contents.json new file mode 100644 index 00000000..428f8cf0 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/discover-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "discover@1x.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/discover-logo.imageset/discover@1x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/discover-logo.imageset/discover@1x.png new file mode 100644 index 00000000..09ca3812 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/discover-logo.imageset/discover@1x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/Contents.json new file mode 100644 index 00000000..4f4b21e6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "error@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "error@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "error@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@1x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@1x.png new file mode 100644 index 00000000..bf8442f7 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@1x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@2x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@2x.png new file mode 100644 index 00000000..a4d3a314 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@2x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@3x.png b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@3x.png new file mode 100644 index 00000000..5b856c41 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/error.imageset/error@3x.png differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/mastercard-logo.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/mastercard-logo.imageset/Contents.json new file mode 100644 index 00000000..102a36c8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/mastercard-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "mastercard@1x.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/mastercard-logo.imageset/mastercard@1x.pdf b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/mastercard-logo.imageset/mastercard@1x.pdf new file mode 100644 index 00000000..cc999c3f Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/mastercard-logo.imageset/mastercard@1x.pdf differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-logo.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-logo.imageset/Contents.json new file mode 100644 index 00000000..c72228ce --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "visa@1x.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-logo.imageset/visa@1x.pdf b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-logo.imageset/visa@1x.pdf new file mode 100644 index 00000000..7044f715 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-logo.imageset/visa@1x.pdf differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-white-logo.imageset/Contents.json b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-white-logo.imageset/Contents.json new file mode 100644 index 00000000..e91aeeb5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-white-logo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "visa-white@1x.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-white-logo.imageset/visa-white@1x.pdf b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-white-logo.imageset/visa-white@1x.pdf new file mode 100644 index 00000000..437593c4 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2/Resources/Stripe3DS2.xcassets/visa-white-logo.imageset/visa-white@1x.pdf differ diff --git a/Stripe3DS2/Stripe3DS2/Resources/bg-BG.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/bg-BG.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/ca-ES.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/ca-ES.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/cs-CZ.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/da.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/da.lproj/Localizable.strings new file mode 100644 index 00000000..2388efaa --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/da.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Et fejlfindingsprogram er vedhæftet appen."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "En emulator bruges til at køre appen."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Annullér"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Indlæser"; + +/* The no answer to a yes or no question. */ +"No" = "Nej"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Sikker betaling"; + +/* Indicates that a button is selected. */ +"Selected" = "Valgt"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Enheden er jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Integriteten af SDK'en er blevet ændret."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Operativsystemet eller versionen af operativsystemet understøttes ikke."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Udløb"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Fravalgt"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Ja"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/de.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/de.lproj/Localizable.strings new file mode 100644 index 00000000..55a9ea44 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/de.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Ein Debugger ist an die App angeschlossen."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Ein Emulator wird zum Ausführen dieser App verwendet."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Abbrechen"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Wird geladen"; + +/* The no answer to a yes or no question. */ +"No" = "Nein"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Sicherer Bezahlvogang"; + +/* Indicates that a button is selected. */ +"Selected" = "Ausgewählt"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Das Gerät ist beschädigt."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Die Integrität des SDK wurde manipuliert."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Das Betriebssystem oder die Betriebssystemversion wird nicht unterstützt."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Zeitüberschreitung"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Nicht ausgewählt"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Ja"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/el-GR.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/el-GR.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/en-GB.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..ddfe3b2a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/en-GB.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device has been jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered with."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/en.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/en.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/en.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/es-419.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..759db059 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/es-419.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Se ha asociado un depurador a la aplicación."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Se está usando un emulador para ejecutar la aplicación."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancelar"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Cargando"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Proceso de compra seguro"; + +/* Indicates that a button is selected. */ +"Selected" = "Seleccionado"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "El dispositivo ha sido liberado."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Se ha alterado la integridad del SDK."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "El OS o la versión del OS no son compatibles."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Tiempo de espera agotado"; + +/* Indicates that a button is not selected. */ +"Unselected" = "No seleccionado"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Sí"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/es.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/es.lproj/Localizable.strings new file mode 100644 index 00000000..5a6f2732 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/es.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Se ha asociado un depurador a la aplicación."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Se está usando un emulador para ejecutar la aplicación."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancelar"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Cargando"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Proceso de compra seguro"; + +/* Indicates that a button is selected. */ +"Selected" = "Seleccionado"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "El dispositivo ha sido liberado."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Se ha alterado la integridad del SDK."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "El SO o la versión del SO no son compatibles."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Tiempo de espera agotado"; + +/* Indicates that a button is not selected. */ +"Unselected" = "No seleccionado"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Sí"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/et-EE.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/et-EE.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/fi.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/fi.lproj/Localizable.strings new file mode 100644 index 00000000..faeaf2d6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/fi.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Sovellukseen on liitetty virheiden tarkastusohjelma."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Sovelluksen suorittamiseen käytetään emulaattoria."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Peruuta"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Ladataan"; + +/* The no answer to a yes or no question. */ +"No" = "Ei"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Turvallinen maksaminen"; + +/* Indicates that a button is selected. */ +"Selected" = "Valittu"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Laitteen käyttöjärjestelmän rajoitukset on poistettu."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK:n eheyttä on peukaloitu."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Käyttöjärjestelmää tai käyttöjärjestelmäversiota ei tueta."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Aikakatkaisu"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Ei valittu"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Kyllä"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/fil.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/fil.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/fil.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/fr-CA.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..19956a78 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/fr-CA.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Un débogueur est attaché à l'application."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Un émulateur est utilisé pour exécuter l'application."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Annuler"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Chargement"; + +/* The no answer to a yes or no question. */ +"No" = "Non"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Paiement sécurisé"; + +/* Indicates that a button is selected. */ +"Selected" = "Sélectionné"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "L'appareil est débridé."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "L'intégrité de SDK a été modifiée."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Le système d'exploitation ou la version du système d'exploitation n'est pas pris(e) en charge."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Expiration"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Non sélectionné"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Oui"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/fr.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/fr.lproj/Localizable.strings new file mode 100644 index 00000000..9d595679 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/fr.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Un débogueur est attaché à l'application."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Un émulateur est utilisé pour accéder à l'application."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Annuler"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Chargement"; + +/* The no answer to a yes or no question. */ +"No" = "Non"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Paiement sécurisé"; + +/* Indicates that a button is selected. */ +"Selected" = "Sélectionné"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "L'appareil est débridé."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "L'intégrité du SDK a été altérée."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Le système d'exploitation ou la version du système d'exploitation ne sont pas pris en charge."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Non sélectionné"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Oui"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/hr.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/hr.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/hr.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/hu.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/hu.lproj/Localizable.strings new file mode 100644 index 00000000..889cda1b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/hu.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Az alkalmazáshoz hibaelhárítót csatoltak."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Az alkalmazás futtatása emulátorral történik."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Mégse"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Betöltés folyamatban"; + +/* The no answer to a yes or no question. */ +"No" = "Nem"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Biztosnágos kifizetés"; + +/* Indicates that a button is selected. */ +"Selected" = "Kijelölve"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Az eszközt feltörték."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Az SDK integritása sérült."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Az OS vagy OS-verzió nem támogatott."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "időtúllépés"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Nincs kijelölve"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Igen"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/id.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/id.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/id.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/it.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/it.lproj/Localizable.strings new file mode 100644 index 00000000..419bf6b1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/it.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "All'app è connesso un debugger."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Per eseguire l'app viene utilizzato un emulatore."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Annulla"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Caricamento in corso"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Completamento del pagamento sicuro"; + +/* Indicates that a button is selected. */ +"Selected" = "Selezionato"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Il dispositivo è jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "L'integrità dell'SDK è stata manomessa."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Il sistema operativo o la versione del sistema operativo non sono supportati."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Non selezionato"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Sì"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/ja.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/ja.lproj/Localizable.strings new file mode 100644 index 00000000..9cec4421 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/ja.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "アプリにデバッガが添付されています。"; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "アプリの実行にエミュレータが使用されています。"; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "キャンセル"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "読み込み中"; + +/* The no answer to a yes or no question. */ +"No" = "いいえ"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "セキュアなチェックアウト"; + +/* Indicates that a button is selected. */ +"Selected" = "選択済み"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "このデバイスは脱獄状態です。"; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK の完全性が改ざんされています。"; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "OS または OS のバージョンがサポートされていません。"; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "タイムアウト"; + +/* Indicates that a button is not selected. */ +"Unselected" = "選択解除済み"; + +/* The yes answer to a yes or no question. */ +"Yes" = "はい"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/ko.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/ko.lproj/Localizable.strings new file mode 100644 index 00000000..4ae98c37 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/ko.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "디버거가 앱에 연결되었습니다."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "앱을 실행하는 데 에뮬레이터가 사용되고 있습니다."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "취소"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "로드 중"; + +/* The no answer to a yes or no question. */ +"No" = "아니요"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "보안 체크 아웃"; + +/* Indicates that a button is selected. */ +"Selected" = "선택됨"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "장치가 무단 해제되었습니다."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK의 무결성이 변경되었습니다."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = " OS 또는 OS 버전이 지원되지 않습니다."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "시간 초과"; + +/* Indicates that a button is not selected. */ +"Unselected" = "선택 해제됨"; + +/* The yes answer to a yes or no question. */ +"Yes" = "예"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/lt-LT.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/lt-LT.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/lv-LV.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/lv-LV.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/ms-MY.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/ms-MY.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/mt.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/mt.lproj/Localizable.strings new file mode 100644 index 00000000..55b091cf --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/mt.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Debugger huwa mqabbad mal-Applikazzjoni."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Qed jintuża emulatur biex iħaddem l-Applikazzjoni."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Ikkanċella"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Qed jillowdja"; + +/* The no answer to a yes or no question. */ +"No" = "Le"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Checkout sigur"; + +/* Indicates that a button is selected. */ +"Selected" = "Attivata"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "L-apparat huwa mmodifikat (jailbroken)."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "L-integrità tal-SDK ġiet imbagħbsa."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Is-Sistema Operattiva (OS) jew il-Verżjoni tas-Sistema Operattiva (OS) mhijiex appoġġjata."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Waqfa qasira"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Mhijiex attivata"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Iva"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/nb.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/nb.lproj/Localizable.strings new file mode 100644 index 00000000..78613b3d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/nb.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "En debugger er knyttet til App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "En emulator brukes til å kjøre App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Avbryt"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Laster"; + +/* The no answer to a yes or no question. */ +"No" = "Nei"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Sikker utsjekk"; + +/* Indicates that a button is selected. */ +"Selected" = "Valgt"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Enheten er jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Integriteten til SDK har blitt manipulert."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "OS- eller OS-versjonen støttes ikke."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Avbrudd"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Ikke valgt"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Ja"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/nl.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/nl.lproj/Localizable.strings new file mode 100644 index 00000000..335a340a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/nl.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Er is een foutopsporingsprogramma gekoppeld aan de app."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Er wordt een emulator gebruikt om de app uit te voeren."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Annuleren"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Bezig met laden"; + +/* The no answer to a yes or no question. */ +"No" = "Nee"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Beveiligde Checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Geselecteerd"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Het apparaat is opengebroken via een jailbreak."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "De integriteit van de SDK is niet meer intact."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Het besturingssysteem of de versie wordt niet ondersteund."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Time-out"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Niet geselecteerd"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Ja"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/nn-NO.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..65443693 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/nn-NO.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Ein avlusar er knytta til appen."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Ein emulator blir nytta til å køyre appen."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Avbryt"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Lastar inn"; + +/* The no answer to a yes or no question. */ +"No" = "Nei"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Trygg utsjekk"; + +/* Indicates that a button is selected. */ +"Selected" = "Vald"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Eininga er jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Integriteten til SDK er manipulert."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "OS- eller OS-versjonen blir ikkje stydd."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Tidsavbrot"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Uvald"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Ja"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/pl-PL.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/pl-PL.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/pt-BR.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..2e3e7b5b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/pt-BR.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Um depurador está anexado ao aplicativo."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Está sendo usado um emulador para executar o aplicativo."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancelar"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Carregando"; + +/* The no answer to a yes or no question. */ +"No" = "Não"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Finalização de compra segura"; + +/* Indicates that a button is selected. */ +"Selected" = "Selecionado"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "O dispositivo está desbloqueado."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "A integridade do SDK foi adulterada."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "O SO ou a versão do SO não é compatível."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Tempo esgotado"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Não selecionado"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Sim"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/pt-PT.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..3be97a51 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/pt-PT.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Um depurador está anexado à aplicação."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Um emulador está a ser utilizado para executar a aplicação."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancelar"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "A carregar"; + +/* The no answer to a yes or no question. */ +"No" = "Não"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Finalização de compra segura"; + +/* Indicates that a button is selected. */ +"Selected" = "Selecionado"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "O dispositivo está desbloqueado."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "A integridade do SDK foi adulterada."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Sistema operativo ou versão do sistema operativo não suportado(a)."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Tempo limite"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Não selecionado"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Sim"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/ro-RO.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/ro-RO.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/ru.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/ru.lproj/Localizable.strings new file mode 100644 index 00000000..2c7cd69b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/ru.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "К приложению подключен отладчик."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Приложение запускается и работает в симуляторе."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Отмена"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Идет загрузка"; + +/* The no answer to a yes or no question. */ +"No" = "Нет"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Безопасное оформление и оплата заказа"; + +/* Indicates that a button is selected. */ +"Selected" = "Выбранные"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Устройство разблокировано."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "Целостность пакета средств разработки программного обеспечения нарушена."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "Данная операционная система или данная версия операционной системы не поддерживается."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Истечение лимита времени"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Не выбранные"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Да"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/sk-SK.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/sk-SK.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/sl-SI.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/sl-SI.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/sv.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/sv.lproj/Localizable.strings new file mode 100644 index 00000000..e92a466a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/sv.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "En felsökare är ansluten till appen."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "En emulator används för att köra appen."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Avbryt"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Laddar"; + +/* The no answer to a yes or no question. */ +"No" = "Nej"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Säker utcheckning"; + +/* Indicates that a button is selected. */ +"Selected" = "Markerad"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Enheten är jailbreakad."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK:ns integritet har manipulerats."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "OS eller OS-versionen stöds inte."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Avbrott"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Avmarkerad"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Ja"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/tr.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/tr.lproj/Localizable.strings new file mode 100644 index 00000000..bf9064f1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/tr.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "Uygulamaya hata ayıklayıcı eklendi."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "Uygulamayı çalıştırmak için bir emülatör kullanılıyor."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "İptal"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "Yüklüyor"; + +/* The no answer to a yes or no question. */ +"No" = "Hayır"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Güvenli ödeme"; + +/* Indicates that a button is selected. */ +"Selected" = "Seçildi"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "Cihazın üretici yazılım kilidi kaldırılmış."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK bütünlüğü ihlal edilmiş."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "İS veya İS Sürümü desteklenmiyor."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Zaman aşımı"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Seçimi kaldırıldı"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Evet"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/vi.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/vi.lproj/Localizable.strings new file mode 100644 index 00000000..7e168827 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/vi.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "A debugger is attached to the App."; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "An emulator is being used to run the App."; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "Cancel"; + +/* Accessibility label for expandandable text control to indicate text is hidden. */ +"Collapsed" = "Collapsed"; + +/* Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available. */ +"Expanded" = "Expanded"; + +/* Spoken by VoiceOver when the challenge is loading. */ +"Loading" = "Loading"; + +/* The no answer to a yes or no question. */ +"No" = "No"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "Secure checkout"; + +/* Indicates that a button is selected. */ +"Selected" = "Selected"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "The device is jailbroken."; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "The integrity of the SDK has been tampered."; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "The OS or the OS Version is not supported."; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "Timeout"; + +/* Indicates that a button is not selected. */ +"Unselected" = "Unselected"; + +/* The yes answer to a yes or no question. */ +"Yes" = "Yes"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/zh-HK.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..16328109 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/zh-HK.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "調試器已附在應用程式內。"; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "正在用模擬器執行應用程式。"; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "取消"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "正在載入"; + +/* The no answer to a yes or no question. */ +"No" = "否"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "安全結帳"; + +/* Indicates that a button is selected. */ +"Selected" = "已選"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "該設備已越獄。"; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK 的完整性已受損。"; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "不支援此 OS 或 OS 版本。"; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "超時"; + +/* Indicates that a button is not selected. */ +"Unselected" = "已去選"; + +/* The yes answer to a yes or no question. */ +"Yes" = "是"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/zh-Hans.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..b7729c45 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "该应用附带有调试程序。"; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "正在使用模拟器运行此应用。"; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "取消"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "正在加载"; + +/* The no answer to a yes or no question. */ +"No" = "否"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "安全的结账流程"; + +/* Indicates that a button is selected. */ +"Selected" = "已选择"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "该设备已越狱。"; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "该 SDK 的完整性已被破坏。"; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "不支持 OS 或 OS 版本。"; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "超时"; + +/* Indicates that a button is not selected. */ +"Unselected" = "未选择"; + +/* The yes answer to a yes or no question. */ +"Yes" = "是"; + diff --git a/Stripe3DS2/Stripe3DS2/Resources/zh-Hant.lproj/Localizable.strings b/Stripe3DS2/Stripe3DS2/Resources/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..4ea12100 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Resources/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* The text for warning when a debugger is currently attached to the process. */ +"A debugger is attached to the App." = "除錯工具附加在應用程式內。"; + +/* The text for warning when an emulator is being used to run the application. */ +"An emulator is being used to run the App." = "正在用模擬器執行應用程式。"; + +/* The text for the button that cancels the current challenge process. */ +"Cancel" = "取消"; + +/* Spoken by VoiceOver when the screen is loading. */ +"Loading" = "正在載入"; + +/* The no answer to a yes or no question. */ +"No" = "否"; + +/* The title for the challenge response step of an authenticated checkout. */ +"Secure checkout" = "安全結帳"; + +/* Indicates that a button is selected. */ +"Selected" = "已選"; + +/* The text for warning when a device is jailbroken */ +"The device is jailbroken." = "該裝置已越獄。"; + +/* The text for warning when the integrity of the SDK has been tampered with */ +"The integrity of the SDK has been tampered." = "SDK 的完整性已受損。"; + +/* The text for warning when the SDK is running on an unsupported OS or OS version. */ +"The OS or the OS Version is not supported." = "不支援此 OS 或 OS 版本。"; + +/* Error description for when a network request times out. English value is as required by UL certification. */ +"Timeout" = "逾時"; + +/* Indicates that a button is not selected. */ +"Unselected" = "已去選"; + +/* The yes answer to a yes or no question. */ +"Yes" = "是"; + diff --git a/Stripe3DS2/Stripe3DS2/STDSACSNetworkingManager.h b/Stripe3DS2/Stripe3DS2/STDSACSNetworkingManager.h new file mode 100644 index 00000000..cc907e88 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSACSNetworkingManager.h @@ -0,0 +1,30 @@ +// +// STDSACSNetworkingManager.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/3/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSChallengeRequestParameters; +@class STDSErrorMessage; +@protocol STDSChallengeResponse; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSACSNetworkingManager : NSObject + +- (instancetype)initWithURL:(NSURL *)acsURL + sdkContentEncryptionKey:(NSData *)sdkCEK + acsContentEncryptionKey:(NSData *)acsCEK + acsTransactionIdentifier:(NSString *)acsTransactionID; + +- (void)submitChallengeRequest:(STDSChallengeRequestParameters *)request withCompletion:(void (^)(id _Nullable, NSError * _Nullable))completion; + +- (void)sendErrorMessage:(STDSErrorMessage *)errorMessage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSACSNetworkingManager.m b/Stripe3DS2/Stripe3DS2/STDSACSNetworkingManager.m new file mode 100644 index 00000000..f7c8de55 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSACSNetworkingManager.m @@ -0,0 +1,169 @@ +// +// STDSACSNetworkingManager..m +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/3/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSACSNetworkingManager.h" + +#import "STDSChallengeRequestParameters.h" +#import "STDSChallengeResponseObject.h" +#import "STDSJSONEncoder.h" +#import "STDSJSONWebEncryption.h" +#import "STDSStripe3DS2Error.h" +#import "STDSErrorMessage+Internal.h" +#import "NSError+Stripe3DS2.h" + +NS_ASSUME_NONNULL_BEGIN + +/// [Req 239] requires us to abort if the ACS does not respond with the CRes message within 10 seconds. +static const NSTimeInterval kTimeoutInterval = 10; + +@implementation STDSACSNetworkingManager { + NSURL *_acsURL; + NSData *_sdkContentEncryptionKey; + NSData *_acsContentEncryptionKey; + NSString *_acsTransactionIdentifier; + + NSURLSession *_urlSession; + NSURLSessionTask * _Nullable _currentTask; +} + +- (instancetype)initWithURL:(NSURL *)acsURL + sdkContentEncryptionKey:(NSData *)sdkCEK + acsContentEncryptionKey:(NSData *)acsCEK + acsTransactionIdentifier:(nonnull NSString *)acsTransactionID { + self = [super init]; + if (self) { + _acsURL = acsURL; + _sdkContentEncryptionKey = sdkCEK; + _acsContentEncryptionKey = acsCEK; + _acsTransactionIdentifier = [acsTransactionID copy]; + _urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] + delegate:nil + delegateQueue:[NSOperationQueue mainQueue]]; + } + + return self; +} + +- (void)dealloc { + [_urlSession finishTasksAndInvalidate]; +} + +- (void)submitChallengeRequest:(STDSChallengeRequestParameters *)request withCompletion:(void (^)(id _Nullable, NSError * _Nullable))completion { + if (_currentTask != nil) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeAssertionFailed + userInfo:@{@"assertion": [NSString stringWithFormat:@"%@ is not intended to handle multiple concurrent tasks.", NSStringFromClass([self class])]}]); + }); + return; + } + + NSDictionary *requestJSON = [STDSJSONEncoder dictionaryForObject:request]; + NSError *encryptionError = nil; + NSString *encryptedRequest = [STDSJSONWebEncryption directEncryptJSON:requestJSON + withContentEncryptionKey:_sdkContentEncryptionKey + forACSTransactionID:_acsTransactionIdentifier + error:&encryptionError]; + + if (encryptedRequest != nil) { + NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:_acsURL]; + urlRequest.HTTPMethod = @"POST"; + urlRequest.timeoutInterval = kTimeoutInterval; + [urlRequest setValue:@"application/jose;charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; + __weak __typeof(self) weakSelf = self; + NSURLSessionUploadTask *requestTask = [_urlSession uploadTaskWithRequest:[urlRequest copy] fromData:[encryptedRequest dataUsingEncoding:NSUTF8StringEncoding] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + + __typeof(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + strongSelf->_currentTask = nil; + + if (data != nil) { + NSError *decryptionError = nil; + NSDictionary *decrypted = [STDSJSONWebEncryption decryptData:data + withContentEncryptionKey:strongSelf->_acsContentEncryptionKey + error:&decryptionError]; + if (decrypted != nil) { + NSError *challengeResponseError = nil; + id response = [strongSelf decodeJSON:decrypted error:&challengeResponseError]; + completion(response, challengeResponseError); + } else { + completion(nil, decryptionError); + } + } else { + if (error.code == NSURLErrorTimedOut) { + // We convert timeout errors for convenience, since the SDK must treat them differently from generic network errors. + error = [NSError _stds_timedOutError]; + } + completion(nil, error); + } + + }]; + _currentTask = requestTask; + [requestTask resume]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, encryptionError); + }); + } +} + +- (void)sendErrorMessage:(STDSErrorMessage *)errorMessage { + NSDictionary *requestJSON = [STDSJSONEncoder dictionaryForObject:errorMessage]; + NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:_acsURL]; + urlRequest.HTTPMethod = @"POST"; + [urlRequest setValue:@"application/JSON; charset = UTF-8" forHTTPHeaderField:@"Content-Type"]; + NSURLSessionUploadTask *requestTask = [_urlSession uploadTaskWithRequest:[urlRequest copy] + fromData:[NSJSONSerialization dataWithJSONObject:requestJSON options:0 error:nil] + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + // no-op + }]; + [requestTask resume]; +} + +#pragma mark - Helpers + +/** + Returns an STDSChallengeResponseObject instance decoded from the given dict, or populates the error argument. + */ +- (nullable id)decodeJSON:(NSDictionary *)dict error:(NSError * _Nullable *)outError { + NSString *kErrorMessageType = @"Erro"; + NSString *kChallengeResponseType = @"CRes"; + NSString *messageType = dict[@"messageType"]; + NSError *error; + id decodedObject; + + if ([messageType isEqualToString:kErrorMessageType]) { + // Error message type + STDSErrorMessage *errorMessage = [STDSErrorMessage decodedObjectFromJSON:dict error:&error]; + if (errorMessage) { + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeReceivedErrorMessage + userInfo:@{STDSStripe3DS2ErrorMessageErrorKey: errorMessage}]; + } + } else if ([messageType isEqualToString:kChallengeResponseType]) { + // CRes message type + decodedObject = [STDSChallengeResponseObject decodedObjectFromJSON:dict error:&error]; + } else { + // Unknown message type + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeUnknownMessageType + userInfo:nil]; + } + + if (error && outError) { + *outError = error; + } + return decodedObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSAuthenticationResponseObject.h b/Stripe3DS2/Stripe3DS2/STDSAuthenticationResponseObject.h new file mode 100644 index 00000000..78b2c491 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSAuthenticationResponseObject.h @@ -0,0 +1,20 @@ +// +// STDSAuthenticationResponseObject.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 5/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSAuthenticationResponse.h" +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSAuthenticationResponseObject : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSAuthenticationResponseObject.m b/Stripe3DS2/Stripe3DS2/STDSAuthenticationResponseObject.m new file mode 100644 index 00000000..4a9bd175 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSAuthenticationResponseObject.m @@ -0,0 +1,100 @@ +// +// STDSAuthenticationResponseObject.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 5/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSAuthenticationResponseObject.h" + +#import "NSDictionary+DecodingHelpers.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSAuthenticationResponseObject + +@synthesize acsOperatorID = _acsOperatorID; +@synthesize acsReferenceNumber = _acsReferenceNumber; +@synthesize acsSignedContent = _acsSignedContent; +@synthesize acsTransactionID = _acsTransactionID; +@synthesize acsURL = _acsURL; +@synthesize cardholderInfo = _cardholderInfo; +@synthesize status = _status; +@synthesize challengeRequired = _challengeRequired; +@synthesize directoryServerReferenceNumber = _directoryServerReferenceNumber; +@synthesize directoryServerTransactionID = _directoryServerTransactionID; +@synthesize protocolVersion = _protocolVersion; +@synthesize sdkTransactionID = _sdkTransactionID; +@synthesize threeDSServerTransactionID = _threeDSServerTransactionID; +@synthesize willUseDecoupledAuthentication = _willUseDecoupledAuthentication; + ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError **)outError { + if (json == nil) { + return nil; + } + STDSAuthenticationResponseObject *response = [[self alloc] init]; + NSError *error = nil; + + response->_threeDSServerTransactionID = [[json _stds_stringForKey:@"threeDSServerTransID" required:YES error:&error] copy]; + NSString *transStatusString = [json _stds_stringForKey:@"transStatus" required:NO error:&error]; + response->_status = [self statusTypeForString:transStatusString]; + response->_challengeRequired = (response->_status == STDSACSStatusTypeChallengeRequired); + response->_willUseDecoupledAuthentication = [[json _stds_boolForKey:@"acsDecConInd" required:NO error:&error] boolValue]; + response->_acsOperatorID = [[json _stds_stringForKey:@"acsOperatorID" required:NO error:&error] copy]; + response->_acsReferenceNumber = [[json _stds_stringForKey:@"acsReferenceNumber" required:NO error:&error] copy]; + response->_acsSignedContent = [[json _stds_stringForKey:@"acsSignedContent" required:NO error:&error] copy]; + response->_acsTransactionID = [[json _stds_stringForKey:@"acsTransID" required:YES error:&error] copy]; + response->_acsURL = [json _stds_urlForKey:@"acsURL" required:NO error:&error]; + response->_cardholderInfo = [[json _stds_stringForKey:@"cardholderInfo" required:NO error:&error] copy]; + response->_directoryServerReferenceNumber = [[json _stds_stringForKey:@"dsReferenceNumber" required:NO error:&error] copy]; + response->_directoryServerTransactionID = [[json _stds_stringForKey:@"dsTransID" required:NO error:&error] copy]; + response->_protocolVersion = [[json _stds_stringForKey:@"messageVersion" required:YES error:&error] copy]; + response->_sdkTransactionID = [[json _stds_stringForKey:@"sdkTransID" required:YES error:&error] copy]; + + if (error != nil) { + if (outError != nil) { + *outError = error; + } + + return nil; + } + + return response; +} + ++ (STDSACSStatusType)statusTypeForString:(NSString *)statusString { + if ([statusString isEqualToString:@"Y"]) { + return STDSACSStatusTypeAuthenticated; + } + if ([statusString isEqualToString:@"C"]) { + return STDSACSStatusTypeChallengeRequired; + } + if ([statusString isEqualToString:@"D"]) { + return STDSACSStatusTypeDecoupledAuthentication; + } + if ([statusString isEqualToString:@"N"]) { + return STDSACSStatusTypeNotAuthenticated; + } + if ([statusString isEqualToString:@"A"]) { + return STDSACSStatusTypeProofGenerated; + } + if ([statusString isEqualToString:@"U"]) { + return STDSACSStatusTypeError; + } + if ([statusString isEqualToString:@"R"]) { + return STDSACSStatusTypeRejected; + } + if ([statusString isEqualToString:@"I"]) { + return STDSACSStatusTypeInformationalOnly; + } + return STDSACSStatusTypeUnknown; +} + +@end + +id _Nullable STDSAuthenticationResponseFromJSON(NSDictionary *json) { + return [STDSAuthenticationResponseObject decodedObjectFromJSON:json error:NULL]; +} + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSBrandingView.h b/Stripe3DS2/Stripe3DS2/STDSBrandingView.h new file mode 100644 index 00000000..310e18a6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSBrandingView.h @@ -0,0 +1,23 @@ +// +// STDSBrandingView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSBrandingView: UIView + +/// The issuer image to present in the branding view. +@property (nonatomic, strong) UIImage *issuerImage; + +/// The payment system image to present in the branding view. +@property (nonatomic, strong) UIImage *paymentSystemImage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSBrandingView.m b/Stripe3DS2/Stripe3DS2/STDSBrandingView.m new file mode 100644 index 00000000..3f528ec1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSBrandingView.m @@ -0,0 +1,133 @@ +// +// STDSBrandingView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSBrandingView.h" +#import "STDSStackView.h" +#import "UIView+LayoutSupport.h" +#import "STDSVisionSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSBrandingView() + +@property (nonatomic, strong) STDSStackView *stackView; + +@property (nonatomic, strong) UIImageView *issuerImageView; +@property (nonatomic, strong) UIImageView *paymentSystemImageView; + +@property (nonatomic, strong) UIView *issuerView; +@property (nonatomic, strong) UIView *paymentSystemView; + +@end + +@implementation STDSBrandingView + +static const CGFloat kBrandingViewBottomPadding = 24; +static const CGFloat kBrandingViewSpacing = 16; +#if !STP_TARGET_VISION +static const CGFloat kImageViewBorderWidth = 1; +#endif +static const CGFloat kImageViewHorizontalInset = 7; +static const CGFloat kImageViewVerticalInset = 19; +static const CGFloat kImageViewCornerRadius = 6; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self _setupViewHierarchy]; + } + + return self; +} + +- (void)setPaymentSystemImage:(UIImage *)paymentSystemImage { + _paymentSystemImage = paymentSystemImage; + + self.paymentSystemImageView.image = paymentSystemImage; +} + +- (void)setIssuerImage:(UIImage *)issuerImage { + _issuerImage = issuerImage; + + self.issuerImageView.image = issuerImage; +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + +#if !STP_TARGET_VISION + if (self.window.screen.nativeScale > 0) { + self.issuerView.layer.borderWidth = kImageViewBorderWidth / self.window.screen.nativeScale; + self.paymentSystemView.layer.borderWidth = kImageViewBorderWidth / self.window.screen.nativeScale; + } +#endif +} + +- (void)_setupViewHierarchy { + self.layoutMargins = UIEdgeInsetsMake(0, 0, kBrandingViewBottomPadding, 0); + + self.stackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisHorizontal]; + [self addSubview:self.stackView]; + + [self.stackView _stds_pinToSuperviewBounds]; + + self.issuerImageView = [self _newBrandingImageView]; + self.issuerView = [self _newInsetViewWithImageView:self.issuerImageView]; + [self.stackView addArrangedSubview:self.issuerView]; + + [self.stackView addSpacer:kBrandingViewSpacing]; + + self.paymentSystemImageView = [self _newBrandingImageView]; + self.paymentSystemView = [self _newInsetViewWithImageView:self.paymentSystemImageView]; + [self.stackView addArrangedSubview:self.paymentSystemView]; + + NSLayoutConstraint *imageViewWidthConstraint = [NSLayoutConstraint constraintWithItem:self.issuerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0]; + // Setting the priority of the width constraint, so that the priority of the equal widths constraint below takes precedence, allowing both image views to take half of the remaining space equally. + imageViewWidthConstraint.priority = UILayoutPriorityDefaultHigh; + NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.paymentSystemView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.issuerView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; + + [NSLayoutConstraint activateConstraints:@[imageViewWidthConstraint, width]]; +} + +- (UIView *)_newInsetViewWithImageView:(UIImageView *)imageView { + UIView *insetView = [UIView new]; + insetView.layoutMargins = UIEdgeInsetsMake(kImageViewHorizontalInset, kImageViewVerticalInset, kImageViewHorizontalInset, kImageViewVerticalInset); + insetView.layer.cornerRadius = kImageViewCornerRadius; + insetView.backgroundColor = [UIColor whiteColor]; // Issuer images always expect a white background. + insetView.layer.masksToBounds = YES; + insetView.layer.borderColor = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) ? + [UIColor colorWithRed:(CGFloat)0.0 green:(CGFloat)57.0/(CGFloat)255.0 blue:(CGFloat)69.0/(CGFloat)255.0 alpha:(CGFloat)0.25].CGColor : + [UIColor colorWithRed:(CGFloat)195.0/(CGFloat)255.0 green:(CGFloat)214.0/(CGFloat)255.0 blue:(CGFloat)218.0/(CGFloat)255.0 alpha:(CGFloat)0.25].CGColor; + + [insetView addSubview:imageView]; + [imageView _stds_pinToSuperviewBounds]; + + return insetView; +} + +- (UIImageView *)_newBrandingImageView { + UIImageView *imageView = [[UIImageView alloc] init]; + imageView.contentMode = UIViewContentModeScaleAspectFit; + + return imageView; +} + +#if !STP_TARGET_VISION +- (void)traitCollectionDidChange:(UITraitCollection * _Nullable)previousTraitCollection { + CGColorRef borderColor = (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) ? + [UIColor colorWithRed:(CGFloat)0.0 green:(CGFloat)57.0/(CGFloat)255.0 blue:(CGFloat)69.0/(CGFloat)255.0 alpha:(CGFloat)0.25].CGColor : + [UIColor colorWithRed:(CGFloat)195.0/(CGFloat)255.0 green:(CGFloat)214.0/(CGFloat)255.0 blue:(CGFloat)218.0/(CGFloat)255.0 alpha:(CGFloat)0.25].CGColor; + self.issuerView.layer.borderColor = borderColor; + self.paymentSystemView.layer.borderColor = borderColor; +} +#endif + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSBundleLocator.h b/Stripe3DS2/Stripe3DS2/STDSBundleLocator.h new file mode 100644 index 00000000..ed919fa5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSBundleLocator.h @@ -0,0 +1,15 @@ +// +// STDSBundleLocator.h +// Stripe3DS2 +// +// Created by David Estes on 7/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@interface STDSBundleLocator : NSObject + ++ (NSBundle *)stdsResourcesBundle; + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSBundleLocator.m b/Stripe3DS2/Stripe3DS2/STDSBundleLocator.m new file mode 100644 index 00000000..55384068 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSBundleLocator.m @@ -0,0 +1,109 @@ +// +// STDSBundleLocator.m +// Stripe3DS2 +// +// Created by David Estes on 7/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// Based on STPBundleLocator.m in Stripe.framework +// + +#import "STDSBundleLocator.h" + +/** + Using a private class to ensure that it can't be subclassed, which may + change the result of `bundleForClass` + */ +@interface STDSBundleLocatorInternal : NSObject +@end +@implementation STDSBundleLocatorInternal +@end + +@implementation STDSBundleLocator + +// This is copied from SPM's resource_bundle_accessor.m ++ (NSBundle *)stdsSPMBundle { + NSString *bundleName = @"Stripe_Stripe"; + + NSArray *candidates = @[ + NSBundle.mainBundle.resourceURL, + [NSBundle bundleForClass:[self class]].resourceURL, + NSBundle.mainBundle.bundleURL + ]; + + for (NSURL* candiate in candidates) { + NSURL *bundlePath = [candiate URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.bundle", bundleName]]; + + NSBundle *bundle = [NSBundle bundleWithURL:bundlePath]; + if (bundle != nil) { + return bundle; + } + } + + return nil; +} + ++ (NSBundle *)stdsResourcesBundle { + /** + First, find Stripe.framework. + Places to check: + 1. Stripe_Stripe3DS2.bundle (for SwiftPM) + 1. Stripe_Stripe.bundle (for SwiftPM) + 2. Stripe.bundle (for manual static installations, Fabric, and framework-less Cocoapods) + 3. Stripe.framework/Stripe.bundle (for framework-based Cocoapods) + 4. Stripe.framework (for Carthage, manual dynamic installations) + 5. main bundle (for people dragging all our files into their project) + **/ + + static NSBundle *ourBundle; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ +#ifdef SWIFTPM_MODULE_BUNDLE + ourBundle = SWIFTPM_MODULE_BUNDLE; +#endif + + if (ourBundle == nil) { + ourBundle = [STDSBundleLocator stdsSPMBundle]; + } + + if (ourBundle == nil) { + ourBundle = [NSBundle bundleWithPath:@"Stripe.bundle"]; + } + + if (ourBundle == nil) { + // This might be the same as the previous check if not using a dynamic framework + NSString *path = [[NSBundle bundleForClass:[STDSBundleLocatorInternal class]] pathForResource:@"Stripe" ofType:@"bundle"]; + ourBundle = [NSBundle bundleWithPath:path]; + } + + if (ourBundle == nil) { + // This will be the same as mainBundle if not using a dynamic framework + ourBundle = [NSBundle bundleForClass:[STDSBundleLocatorInternal class]]; + } + + if (ourBundle == nil) { + ourBundle = [NSBundle mainBundle]; + } + + // Once we've found Stripe.framework, seek around to find Stripe3DS2.bundle. + // Try to find Stripe3DS2 bundle within our current bundle + NSString *stdsBundlePath = [[ourBundle bundlePath] stringByAppendingPathComponent:@"Stripe3DS2.bundle"]; + NSBundle *stdsBundle = [NSBundle bundleWithPath:stdsBundlePath]; + if (stdsBundle != nil) { + ourBundle = stdsBundle; + } + // If it's not there, it might be a level up from us? + // (CocoaPods arranges us this way, as an example.) + if (stdsBundle == nil) { + NSString *stdsBundlePath = [[[ourBundle bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Stripe3DS2.bundle"]; + stdsBundle = [NSBundle bundleWithPath:stdsBundlePath]; + if (stdsBundle != nil) { + ourBundle = stdsBundle; + } + } + }); + + return ourBundle; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeInformationView.h b/Stripe3DS2/Stripe3DS2/STDSChallengeInformationView.h new file mode 100644 index 00000000..2111ea3f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeInformationView.h @@ -0,0 +1,25 @@ +// +// STDSChallengeInformationView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSLabelCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeInformationView: UIView + +@property (nonatomic, strong, nullable) NSString *headerText; +@property (nonatomic, strong, nullable) UIImage *textIndicatorImage; +@property (nonatomic, strong, nullable) NSString *challengeInformationText; +@property (nonatomic, strong, nullable) NSString *challengeInformationLabel; + +@property (nonatomic, strong, nullable) STDSLabelCustomization *labelCustomization; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeInformationView.m b/Stripe3DS2/Stripe3DS2/STDSChallengeInformationView.m new file mode 100644 index 00000000..6796dfe3 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeInformationView.m @@ -0,0 +1,137 @@ +// +// STDSChallengeInformationView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeInformationView.h" +#import "STDSStackView.h" +#import "STDSSpacerView.h" +#import "UIView+LayoutSupport.h" +#import "NSString+EmptyChecking.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeInformationView () + +@property (nonatomic, strong) STDSStackView *informationStackView; +@property (nonatomic, strong) STDSStackView *indicatorStackView; + +@property (nonatomic, strong) UILabel *headerLabel; +@property (nonatomic, strong) UIImageView *textIndicatorImageView; +@property (nonatomic, strong) UILabel *textLabel; +@property (nonatomic, strong) UILabel *informationLabel; +@property (nonatomic, strong) UIView *indicatorStackViewSpacerView; +@property (nonatomic, strong) UIView *indicatorImageTextSpacerView; + +@end + +@implementation STDSChallengeInformationView + +static const CGFloat kHeaderTextBottomPadding = 8; +static const CGFloat kInformationTextBottomPadding = 20; +static const CGFloat kChallengeInformationViewBottomPadding = 6; +static const CGFloat kTextIndicatorHorizontalPadding = 8; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self _setupViewHierarchy]; + } + + return self; +} + +- (void)setHeaderText:(NSString * _Nullable)headerText { + _headerText = headerText; + + self.headerLabel.text = headerText; + self.headerLabel.hidden = [NSString _stds_isStringEmpty:headerText]; +} + +- (void)setTextIndicatorImage:(UIImage * _Nullable)textIndicatorImage { + _textIndicatorImage = textIndicatorImage; + + self.textIndicatorImageView.image = textIndicatorImage; + self.textIndicatorImageView.hidden = textIndicatorImage == nil; + self.indicatorImageTextSpacerView.hidden = textIndicatorImage == nil; +} + +- (void)setChallengeInformationText:(NSString * _Nullable)challengeInformationText { + _challengeInformationText = challengeInformationText; + + self.textLabel.text = challengeInformationText; + self.textLabel.hidden = [NSString _stds_isStringEmpty:challengeInformationText]; +} + +- (void)setChallengeInformationLabel:(NSString * _Nullable)challengeInformationLabel { + _challengeInformationLabel = challengeInformationLabel; + + self.informationLabel.text = challengeInformationLabel; + self.informationLabel.hidden = [NSString _stds_isStringEmpty:challengeInformationLabel]; + self.indicatorStackViewSpacerView.hidden = self.informationLabel.hidden; +} + +- (void)_setupViewHierarchy { + self.layoutMargins = UIEdgeInsetsMake(0, 0, kChallengeInformationViewBottomPadding, 0); + + self.headerLabel = [self _newInformationLabel]; + + self.textIndicatorImageView = [[UIImageView alloc] init]; + self.textIndicatorImageView.contentMode = UIViewContentModeTop; + self.textIndicatorImageView.hidden = YES; + + self.textLabel = [self _newInformationLabel]; + self.informationLabel = [self _newInformationLabel]; + + self.indicatorStackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisHorizontal]; + + [self.indicatorStackView addArrangedSubview:self.textIndicatorImageView]; + self.indicatorImageTextSpacerView = [[STDSSpacerView alloc] initWithLayoutAxis:STDSStackViewLayoutAxisHorizontal dimension:kTextIndicatorHorizontalPadding]; + self.indicatorImageTextSpacerView.hidden = YES; + [self.indicatorStackView addArrangedSubview:self.indicatorImageTextSpacerView]; + [self.indicatorStackView addArrangedSubview:self.textLabel]; + + self.informationStackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [self.informationStackView addArrangedSubview:self.headerLabel]; + [self.informationStackView addSpacer:kHeaderTextBottomPadding]; + [self.informationStackView addArrangedSubview:self.indicatorStackView]; + self.indicatorStackViewSpacerView = [[STDSSpacerView alloc] initWithLayoutAxis:STDSStackViewLayoutAxisVertical dimension:kInformationTextBottomPadding]; + [self.informationStackView addArrangedSubview:self.indicatorStackViewSpacerView]; + [self.informationStackView addArrangedSubview:self.informationLabel]; + + [self addSubview:self.informationStackView]; + + [self.informationStackView _stds_pinToSuperviewBounds]; + + NSLayoutConstraint *imageViewWidthConstraint = [NSLayoutConstraint constraintWithItem:self.textIndicatorImageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:35]; + [NSLayoutConstraint activateConstraints:@[imageViewWidthConstraint]]; +} + +- (void)setLabelCustomization:(STDSLabelCustomization * _Nullable)labelCustomization { + _labelCustomization = labelCustomization; + + self.headerLabel.font = labelCustomization.headingFont; + self.headerLabel.textColor = labelCustomization.headingTextColor; + + self.textLabel.font = labelCustomization.font; + self.textLabel.textColor = labelCustomization.textColor; + + self.informationLabel.font = labelCustomization.font; + self.informationLabel.textColor = labelCustomization.textColor; +} + +- (UILabel *)_newInformationLabel { + UILabel *label = [[UILabel alloc] init]; + label.numberOfLines = 0; + label.hidden = YES; + + return label; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeRequestParameters.h b/Stripe3DS2/Stripe3DS2/STDSChallengeRequestParameters.h new file mode 100644 index 00000000..d0809963 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeRequestParameters.h @@ -0,0 +1,138 @@ +// +// STDSChallengeRequestParameters.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 4/1/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONEncodable.h" + +@class STDSChallengeParameters; + +typedef NS_ENUM(NSInteger, STDSChallengeCancelType) { + /// The cardholder selected "Cancel" from the UI + STDSChallengeCancelTypeCardholderSelectedCancel, + + /// The transaction timed out + STDSChallengeCancelTypeTransactionTimedOut, +}; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeRequestParameters : NSObject + +/** + Convenience initializer to create parameters for the first Challenge Request for a transaction. + */ +- (instancetype)initWithChallengeParameters:(STDSChallengeParameters *)challengeParams + transactionIdentifier:(NSString *)transactionIdentifier + messageVersion:(NSString *)messageVersion; + +/** + Designated initializer for `STDSChallengeRequestParameters` + */ +- (instancetype)initWithThreeDSServerTransactionIdentifier:(NSString *)threeDSServerTransactionIdentifier + acsTransactionIdentifier:(NSString *)acsTransactionIdentifier + messageVersion:(NSString *)messageVersion + sdkTransactionIdentifier:(NSString *)sdkTransactionIdentifier + requestorAppUrl:(NSString *)requestorAppUrl + sdkCounterStoA:(NSInteger)sdkCounterStoA NS_DESIGNATED_INITIALIZER; + +/** + Returns a new instance of STDSChallengeRequestParameters using the receiver, copying over the properties that are invariant across all CReqs for a given transaction and incrementing sdkCounterStoA. + */ +- (instancetype)nextChallengeRequestParametersByIncrementCounter; + +- (instancetype)init NS_UNAVAILABLE; + +#pragma mark - Required Properties + +/** + Universally unique transaction identifier assigned by the 3DS SDK to identify a single transaction. + */ +@property (nonatomic, readonly) NSString *sdkTransactionIdentifier; + +/** + Transaction identifier assigned by the 3DS Server to uniquely identify + a transaction. + */ +@property (nonatomic, readonly) NSString *threeDSServerTransactionIdentifier; + +/** + Transaction identifier assigned by the Access Control Server (ACS) + to uniquely identify a transaction. + */ +@property (nonatomic, readonly) NSString *acsTransactionIdentifier; + +/** + Identifies the type of message - always "CReq" + */ +@property (nonatomic, readonly) NSString *messageType; + +/** + The protocol version that is supported by the SDK and used for the transaction. + */ +@property (nonatomic, readonly) NSString *messageVersion; + +/** + Counter used as a security measure in the 3DS SDK to ACS secure channel. + */ +@property (nonatomic, readonly) NSString *sdkCounterStoA; + +#pragma mark - Optional/Conditional Properties + +/** + The URL for the application that is requesting 3DS2 verification. + This property can be optionally set and will be included with the + messages sent to the Directory Server during the challenge flow. + */ +@property (nonatomic, copy, nullable) NSString *threeDSRequestorAppURL; + +/** + A STDSChallengeCancelType wrapped in NSNumber, indicating that the authentication has been canceled. + */ +@property (nonatomic, copy, nullable) NSNumber *challengeCancel; + +/** + Contains the data that the Cardholder entered into the Native UI text field. + + @note The setter converts empty strings to nil. + */ +@property (nonatomic, copy, nullable) NSString *challengeDataEntry; + +/** + Data that the Cardholder entered into the HTML UI. + */ +@property (nonatomic, copy, nullable) NSString *challengeHTMLDataEntry; + +/** + Data necessary to support requirements not otherwise defined in the 3- D Secure message. + */ +@property (nonatomic, copy, nullable) NSArray *messageExtension; + +/** + A BOOL indiciating that Cardholder has completed the authentication as requested by selecting the Continue button in an Out- of-Band (OOB) authentication method. + */ +@property (nonatomic, nullable) NSNumber *oobContinue; + +/** + Indicator to resend the challenge information code to the Cardholder. + */ +@property (nonatomic, copy, nullable) NSString *resendChallenge; + +/** + Indicator confirming whether whitelisting was opted by the cardholder. + */ +@property (nonatomic, copy, nullable) NSString *whitelistingDataEntry; + +/** + Indicator informing that the Cardholder submits an empty response (no data entered in the UI). + */ +@property (nonatomic, copy, nullable) NSString *challengeNoEntry; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeRequestParameters.m b/Stripe3DS2/Stripe3DS2/STDSChallengeRequestParameters.m new file mode 100644 index 00000000..18fcb8db --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeRequestParameters.m @@ -0,0 +1,105 @@ +// +// STDSChallengeRequestParameters.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 4/1/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeRequestParameters.h" + +#import "STDSChallengeParameters.h" + +@implementation STDSChallengeRequestParameters + +- (instancetype)initWithChallengeParameters:(STDSChallengeParameters *)challengeParams + transactionIdentifier:(NSString *)transactionIdentifier + messageVersion:(NSString *)messageVersion { + return [self initWithThreeDSServerTransactionIdentifier:challengeParams.threeDSServerTransactionID + acsTransactionIdentifier:challengeParams.acsTransactionID + messageVersion:messageVersion + sdkTransactionIdentifier:transactionIdentifier + requestorAppUrl:challengeParams.threeDSRequestorAppURL + sdkCounterStoA:0]; +} + +- (instancetype)initWithThreeDSServerTransactionIdentifier:(NSString *)threeDSServerTransactionIdentifier + acsTransactionIdentifier:(NSString *)acsTransactionIdentifier + messageVersion:(NSString *)messageVersion + sdkTransactionIdentifier:(NSString *)sdkTransactionIdentifier + requestorAppUrl:(NSString *)requestorAppUrl + sdkCounterStoA:(NSInteger)sdkCounterStoA { + self = [super init]; + if (self) { + _messageType = @"CReq"; + _threeDSServerTransactionIdentifier = [threeDSServerTransactionIdentifier copy]; + _acsTransactionIdentifier = [acsTransactionIdentifier copy]; + _messageVersion = [messageVersion copy]; + _sdkTransactionIdentifier = [sdkTransactionIdentifier copy]; + _threeDSRequestorAppURL = [requestorAppUrl copy]; + _sdkCounterStoA = [NSString stringWithFormat:@"%03ld", (long)sdkCounterStoA]; + } + return self; +} + +- (instancetype)nextChallengeRequestParametersByIncrementCounter { + NSInteger incrementedCounter = [self.sdkCounterStoA intValue] + 1; + return [[STDSChallengeRequestParameters alloc] initWithThreeDSServerTransactionIdentifier:self.threeDSServerTransactionIdentifier + acsTransactionIdentifier:self.acsTransactionIdentifier + messageVersion:self.messageVersion + sdkTransactionIdentifier:self.sdkTransactionIdentifier + requestorAppUrl:self.threeDSRequestorAppURL // TC_SDK_10209_001 + sdkCounterStoA:incrementedCounter]; +} + +- (void)setChallengeDataEntry:(NSString *)challengeDataEntry { + // [Req 40] ...if the cardholder has submitted the response without entering any data in the UI, the Challenge Data Entry field shall not be present in the CReq message. + if (challengeDataEntry.length == 0) { + _challengeDataEntry = nil; + _challengeNoEntry = @"Y"; + } else { + _challengeDataEntry = [challengeDataEntry copy]; + _challengeNoEntry = nil; + } +} + +#pragma mark - Helpers + +- (nullable NSString *)challengeCancelString { + if (self.challengeCancel == nil) { + return nil; + } + + STDSChallengeCancelType challengeCancelType = (STDSChallengeCancelType)[self.challengeCancel integerValue]; + switch (challengeCancelType) { + case STDSChallengeCancelTypeCardholderSelectedCancel: + return @"01"; + case STDSChallengeCancelTypeTransactionTimedOut: + return @"08"; + } + return @"07"; // Unknown +} + +#pragma mark - STDSJSONEncodable + ++ (NSDictionary *)propertyNamesToJSONKeysMapping { + return @{ + NSStringFromSelector(@selector(threeDSServerTransactionIdentifier)): @"threeDSServerTransID", + NSStringFromSelector(@selector(acsTransactionIdentifier)): @"acsTransID", + NSStringFromSelector(@selector(threeDSRequestorAppURL)): @"threeDSRequestorAppURL", + NSStringFromSelector(@selector(challengeCancelString)): @"challengeCancel", + NSStringFromSelector(@selector(challengeDataEntry)): @"challengeDataEntry", + NSStringFromSelector(@selector(challengeHTMLDataEntry)): @"challengeHTMLDataEntry", + NSStringFromSelector(@selector(challengeNoEntry)): @"challengeNoEntry", + NSStringFromSelector(@selector(messageExtension)): @"messageExtension", + NSStringFromSelector(@selector(messageVersion)): @"messageVersion", + NSStringFromSelector(@selector(messageType)): @"messageType", + NSStringFromSelector(@selector(oobContinue)): @"oobContinue", + NSStringFromSelector(@selector(resendChallenge)): @"resendChallenge", + NSStringFromSelector(@selector(sdkTransactionIdentifier)): @"sdkTransID", + NSStringFromSelector(@selector(sdkCounterStoA)): @"sdkCounterStoA", + NSStringFromSelector(@selector(whitelistingDataEntry)): @"whitelistingDataEntry", + }; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponse.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponse.h new file mode 100644 index 00000000..58606d25 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponse.h @@ -0,0 +1,145 @@ +// +// STDSChallengeResponse.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseSelectionInfo.h" +#import "STDSChallengeResponseMessageExtension.h" +#import "STDSChallengeResponseImage.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + The `STDSACSUIType` enum defines the type of UI to be presented. + */ +typedef NS_ENUM(NSInteger, STDSACSUIType) { + + /// No UI associated with the response. + STDSACSUITypeNone = 0, + + /// Text challenge response UI. + STDSACSUITypeText = 1, + + /// Single-select challenge response UI. + STDSACSUITypeSingleSelect = 2, + + /// Multi-select challenge response UI. + STDSACSUITypeMultiSelect = 3, + + /// Out Of Band challenge response UI. + STDSACSUITypeOOB = 4, + + /// HTML challenge response UI. + STDSACSUITypeHTML = 5, +}; + +/// A protocol that represents the information contained within a challenge response. +@protocol STDSChallengeResponse + +/// Universally unique transaction identifier assigned by the 3DS Server to identify a single transaction. +@property (nonatomic, readonly) NSString *threeDSServerTransactionID; + +/// Counter used as a security measure in the ACS to 3DS SDK secure channel. +@property (nonatomic, readonly) NSString *acsCounterACStoSDK; + +/// Universally unique transaction identifier assigned by the ACS to identify a single transaction. +@property (nonatomic, readonly) NSString *acsTransactionID; + +/// HTML provided by the ACS in the Challenge Response message. Utilised when HTML is specified in the ACS UI Type during the Cardholder challenge. +@property (nonatomic, readonly, nullable) NSString *acsHTML; + +/// Optional HTML provided by the ACS in the CRes message to be utilised in the Out of Band flow when the HTML is specified in the ACS UI Type during the Cardholder challenge, displayed when the app is moved to the foreground. +@property (nonatomic, readonly, nullable) NSString *acsHTMLRefresh; + +/// User interface type that the 3DS SDK will render, which includes the specific data mapping and requirements. +@property (nonatomic, readonly) STDSACSUIType acsUIType; + +/** + Indicator of the state of the ACS challenge cycle and whether the challenge has completed or will require additional messages. Shall be populated in all Challenge Response messages to convey the current state of the transaction. + + - Note: + If set to YES, the ACS will populate the Transaction Status in the Challenge Response message. + */ +@property (nonatomic, readonly) BOOL challengeCompletionIndicator; + +/// Header text that for the challenge information screen that is being presented. +@property (nonatomic, readonly, nullable) NSString *challengeInfoHeader; + +/// Label to modify the Challenge Data Entry field provided by the Issuer. +@property (nonatomic, readonly, nullable) NSString *challengeInfoLabel; + +/// Text provided by the ACS/Issuer to Cardholder during the Challenge Message exchange. +@property (nonatomic, readonly, nullable) NSString *challengeInfoText; + +/// Text provided by the ACS/Issuer to Cardholder during OOB authentication to replace Challenge Information Text and Challenge Information Text Indicator +@property (nonatomic, readonly, nullable) NSString *challengeAdditionalInfoText; + +/// Indicates when the Issuer/ACS would like a warning icon or similar visual indicator to draw attention to the “Challenge Information Text” that is being displayed. +@property (nonatomic, readonly) BOOL showChallengeInfoTextIndicator; + +/// Selection information that will be presented to the Cardholder if the option is single or multi-select. The variables will be sent in a JSON Array and parsed by the SDK for display in the user interface. +@property (nonatomic, readonly, nullable) NSArray> *challengeSelectInfo; + +/// Label displayed to the Cardholder for the content in Expandable Information Text. +@property (nonatomic, readonly, nullable) NSString *expandInfoLabel; + +/// Text provided by the Issuer from the ACS to be displayed to the Cardholder for additional information and the format will be an expandable text field. +@property (nonatomic, readonly, nullable) NSString *expandInfoText; + +/// Sent in the initial Challenge Response message from the ACS to the 3DS SDK to provide the URL(s) of the Issuer logo or image to be used in the Native UI. +@property (nonatomic, readonly, nullable) id issuerImage; + +/// Data necessary to support requirements not otherwise defined in the 3-D Secure message are carried in a Message Extension. +@property (nonatomic, readonly, nullable) NSArray> *messageExtensions; + +/// Identifies the type of message that is passed. +@property (nonatomic, readonly) NSString *messageType; + +/// Protocol version identifier. This shall be the Protocol Version Number of the specification utilised by the system creating this message. The Message Version Number is set by the 3DS Server which originates the protocol with the AReq message. The Message Version Number does not change during a 3DS transaction. +@property (nonatomic, readonly) NSString *messageVersion; + +/// Mobile Deep link to an authentication app used in the out-of-band authentication. The App URL will open the appropriate location within the authentication app. +@property (nonatomic, readonly, nullable) NSURL *oobAppURL; + +/// Label to be displayed for the link to the OOB App URL. For example: “oobAppLabel”: “Click here to open Your Bank App” +@property (nonatomic, readonly, nullable) NSString *oobAppLabel; + +/// Label to be used in the UI for the button that the user selects when they have completed the OOB authentication. +@property (nonatomic, readonly, nullable) NSString *oobContinueLabel; + +/// Sent in the initial Challenge Response message from the ACS to the 3DS SDK to provide the URL(s) of the DS or Payment System logo or image to be used in the Native UI. +@property (nonatomic, readonly, nullable) id paymentSystemImage; + +/// Label to be used in the UI for the button that the user selects when they would like to have the authentication information present. +@property (nonatomic, readonly, nullable) NSString *resendInformationLabel; + +/// Universally unique transaction identifier assigned by the 3DS SDK to identify a single transaction. +@property (nonatomic, readonly) NSString *sdkTransactionID; + +/** + Label to be used in the UI for the button that the user selects when they have completed the authentication. + + - Note: + This is not used for OOB authentication. + */ +@property (nonatomic, readonly, nullable) NSString *submitAuthenticationLabel; + +/// Text provided by the ACS/Issuer to Cardholder during a Whitelisting transaction. For example, “Would you like to add this Merchant to your whitelist?” +@property (nonatomic, readonly, nullable) NSString *whitelistingInfoText; + +/// Label to be displayed to the Cardholder for the "why" information section. +@property (nonatomic, readonly, nullable) NSString *whyInfoLabel; + +/// Text provided by the Issuer to be displayed to the Cardholder to explain why the Cardholder is being asked to perform the authentication task. +@property (nonatomic, readonly, nullable) NSString *whyInfoText; + +/// Indicates the state of the associated Transaction. +@property (nonatomic, readonly, nullable) NSString *transactionStatus; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImage.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImage.h new file mode 100644 index 00000000..9128d77d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImage.h @@ -0,0 +1,27 @@ +// +// STDSChallengeResponseImage.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A protocol used to represent information about an individual image resource inside of a challenge response. +@protocol STDSChallengeResponseImage + +/// A medium density image to display as the issuer image. +@property (nonatomic, readonly, nullable) NSURL *mediumDensityURL; + +/// A high density image to display as the issuer image. +@property (nonatomic, readonly, nullable) NSURL *highDensityURL; + +/// An extra-high density image to display as the issuer image. +@property (nonatomic, readonly, nullable) NSURL *extraHighDensityURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImageObject.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImageObject.h new file mode 100644 index 00000000..59e21208 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImageObject.h @@ -0,0 +1,23 @@ +// +// STDSChallengeResponseImageObject.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseImage.h" + +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An object used to represent information about an individual image resource inside of a challenge response. +@interface STDSChallengeResponseImageObject: NSObject + +- (instancetype)initWithMediumDensityURL:(NSURL * _Nullable)mediumDensityURL highDensityURL:(NSURL * _Nullable)highDensityURL extraHighDensityURL:(NSURL * _Nullable)extraHighDensityURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImageObject.m b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImageObject.m new file mode 100644 index 00000000..a3ea6840 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseImageObject.m @@ -0,0 +1,53 @@ +// +// STDSChallengeResponseImageObject.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeResponseImageObject.h" + +#import "NSDictionary+DecodingHelpers.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeResponseImageObject() + +@property (nonatomic, nullable) NSURL *mediumDensityURL; +@property (nonatomic, nullable) NSURL *highDensityURL; +@property (nonatomic, nullable) NSURL *extraHighDensityURL; + +@end + +@implementation STDSChallengeResponseImageObject + +- (instancetype)initWithMediumDensityURL:(NSURL * _Nullable)mediumDensityURL highDensityURL:(NSURL * _Nullable)highDensityURL extraHighDensityURL:(NSURL * _Nullable)extraHighDensityURL { + self = [super init]; + + if (self) { + _mediumDensityURL = mediumDensityURL; + _highDensityURL = highDensityURL; + _extraHighDensityURL = extraHighDensityURL; + } + + return self; +} + ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError * _Nullable __autoreleasing * _Nullable)outError { + if (json == nil) { + return nil; + } + + NSURL *mediumDensityURL = [json _stds_urlForKey:@"medium" required:NO error:nil]; + NSURL *highDensityURL = [json _stds_urlForKey:@"high" required:NO error:nil]; + NSURL *extraHighDensityURL = [json _stds_urlForKey:@"extraHigh" required:NO error:nil]; + + return [[STDSChallengeResponseImageObject alloc] initWithMediumDensityURL:mediumDensityURL + highDensityURL:highDensityURL + extraHighDensityURL:extraHighDensityURL]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtension.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtension.h new file mode 100644 index 00000000..c26425f9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtension.h @@ -0,0 +1,30 @@ +// +// STDSChallengeResponseMessageExtension.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A protocol that encapsulates an individual message extension inside of a challenge response. +@protocol STDSChallengeResponseMessageExtension + +/// The name of the extension data set as defined by the extension owner. +@property (nonatomic, readonly) NSString *name; + +/// A unique identifier for the extension. +@property (nonatomic, readonly) NSString *identifier; + +/// A Boolean value indicating whether the recipient must understand the contents of the extension to interpret the entire message. +@property (nonatomic, readonly, getter = isCriticalityIndicator) BOOL criticalityIndicator; + +/// The data carried in the extension. +@property (nonatomic, readonly) NSDictionary *data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtensionObject.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtensionObject.h new file mode 100644 index 00000000..5ece844b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtensionObject.h @@ -0,0 +1,21 @@ +// +// STDSChallengeResponseMessageExtensionObject.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseMessageExtension.h" + +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An object used to represent an individual message extension inside of a challenge response. +@interface STDSChallengeResponseMessageExtensionObject: NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtensionObject.m b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtensionObject.m new file mode 100644 index 00000000..54e58d42 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseMessageExtensionObject.m @@ -0,0 +1,72 @@ +// +// STDSChallengeResponseMessageExtensionObject.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeResponseMessageExtensionObject.h" + +#import "NSDictionary+DecodingHelpers.h" +#import "NSError+Stripe3DS2.h" + +NS_ASSUME_NONNULL_BEGIN + +static const NSInteger kMaximumStringFieldLength = 64; +static const NSInteger kMaximumDataFieldLength = 8059; + +@implementation STDSChallengeResponseMessageExtensionObject + +@synthesize name = _name; +@synthesize identifier = _identifier; +@synthesize criticalityIndicator = _criticalityIndicator; +@synthesize data = _data; + +- (instancetype)initWithName:(NSString *)name identifier:(NSString *)identifier criticalityIndicator:(BOOL)criticalityIndicator data:(NSDictionary *)data { + self = [super init]; + if (self) { + _name = [name copy]; + _identifier = [identifier copy]; + _criticalityIndicator = criticalityIndicator; + _data = data; + } + return self; +} + ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError * _Nullable __autoreleasing * _Nullable)outError { + if (json == nil) { + return nil; + } + NSError *error; + + NSString *name = [json _stds_stringForKey:@"name" validator:^BOOL (NSString *value) { + return value.length <= kMaximumStringFieldLength; + }required:YES error:&error]; + NSString *identifier = [json _stds_stringForKey:@"id" validator:^BOOL (NSString *value) { + return value.length <= kMaximumStringFieldLength; + } required:YES error:&error]; + BOOL criticalityIndicator= [json _stds_boolForKey:@"criticalityIndicator" required:YES error:&error].boolValue; + NSDictionary *data = [json _stds_dictionaryForKey:@"data" required:YES error:&error]; + // The spec requires data to be "Maximum 8059 characters" + if (data && [NSJSONSerialization dataWithJSONObject:data options:0 error:nil].length > kMaximumDataFieldLength) { + error = [NSError _stds_invalidJSONFieldError:@"data"]; + } + + if (error) { + if (outError) { + *outError = error; + } + return nil; + } + + if (data != nil) { + return [[STDSChallengeResponseMessageExtensionObject alloc] initWithName:name identifier:identifier criticalityIndicator:criticalityIndicator data:data]; + } else { + return nil; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseObject.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseObject.h new file mode 100644 index 00000000..1f245286 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseObject.h @@ -0,0 +1,49 @@ +// +// STDSChallengeResponseObject.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponse.h" +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An object used to represent a challenge response from the ACS. +@interface STDSChallengeResponseObject: NSObject + +- (instancetype)initWithThreeDSServerTransactionID:(NSString *)threeDSServerTransactionID + acsCounterACStoSDK:(NSString *)acsCounterACStoSDK + acsTransactionID:(NSString *)acsTransactionID + acsHTML:(NSString * _Nullable)acsHTML + acsHTMLRefresh:(NSString * _Nullable)acsHTMLRefresh + acsUIType:(STDSACSUIType)acsUIType + challengeCompletionIndicator:(BOOL)challengeCompletionIndicator + challengeInfoHeader:(NSString * _Nullable)challengeInfoHeader + challengeInfoLabel:(NSString * _Nullable)challengeInfoLabel + challengeInfoText:(NSString * _Nullable)challengeInfoText + challengeAdditionalInfoText:(NSString * _Nullable)challengeAdditionalInfoText + showChallengeInfoTextIndicator:(BOOL)showChallengeInfoTextIndicator + challengeSelectInfo:(NSArray> * _Nullable)challengeSelectInfo + expandInfoLabel:(NSString * _Nullable)expandInfoLabel + expandInfoText:(NSString * _Nullable)expandInfoText + issuerImage:(id _Nullable)issuerImage + messageExtensions:(NSArray> * _Nullable)messageExtensions + messageVersion:(NSString *)messageVersion + oobAppURL:(NSURL * _Nullable)oobAppURL + oobAppLabel:(NSString * _Nullable)oobAppLabel + oobContinueLabel:(NSString * _Nullable)oobContinueLabel + paymentSystemImage:(id _Nullable)paymentSystemImage + resendInformationLabel:(NSString * _Nullable)resendInformationLabel + sdkTransactionID:(NSString *)sdkTransactionID + submitAuthenticationLabel:(NSString * _Nullable)submitAuthenticationLabel + whitelistingInfoText:(NSString * _Nullable)whitelistingInfoText + whyInfoLabel:(NSString * _Nullable)whyInfoLabel + whyInfoText:(NSString * _Nullable)whyInfoText + transactionStatus:(NSString * _Nullable)transactionStatus; +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseObject.m b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseObject.m new file mode 100644 index 00000000..7d952bb9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseObject.m @@ -0,0 +1,321 @@ +// +// STDSChallengeResponseObject.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeResponseObject.h" + +#import "NSDictionary+DecodingHelpers.h" +#import "NSError+Stripe3DS2.h" +#import "STDSChallengeResponseSelectionInfoObject.h" +#import "STDSChallengeResponseImageObject.h" +#import "STDSChallengeResponseMessageExtensionObject.h" +#import "NSString+JWEHelpers.h" +#import "STDSStripe3DS2Error.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSChallengeResponseObject + +@synthesize threeDSServerTransactionID = _threeDSServerTransactionID; +@synthesize acsCounterACStoSDK = _acsCounterACStoSDK; +@synthesize acsTransactionID = _acsTransactionID; +@synthesize acsHTML = _acsHTML; +@synthesize acsHTMLRefresh = _acsHTMLRefresh; +@synthesize acsUIType = _acsUIType; +@synthesize challengeCompletionIndicator = _challengeCompletionIndicator; +@synthesize challengeInfoHeader = _challengeInfoHeader; +@synthesize challengeInfoLabel = _challengeInfoLabel; +@synthesize challengeInfoText = _challengeInfoText; +@synthesize challengeAdditionalInfoText = _challengeAdditionalInfoText; +@synthesize showChallengeInfoTextIndicator = _showChallengeInfoTextIndicator; +@synthesize challengeSelectInfo = _challengeSelectInfo; +@synthesize expandInfoLabel = _expandInfoLabel; +@synthesize expandInfoText = _expandInfoText; +@synthesize issuerImage = _issuerImage; +@synthesize messageExtensions = _messageExtensions; +@synthesize messageType = _messageType; +@synthesize messageVersion = _messageVersion; +@synthesize oobAppURL = _oobAppURL; +@synthesize oobAppLabel = _oobAppLabel; +@synthesize oobContinueLabel = _oobContinueLabel; +@synthesize paymentSystemImage = _paymentSystemImage; +@synthesize resendInformationLabel = _resendInformationLabel; +@synthesize sdkTransactionID = _sdkTransactionID; +@synthesize submitAuthenticationLabel = _submitAuthenticationLabel; +@synthesize whitelistingInfoText = _whitelistingInfoText; +@synthesize whyInfoLabel = _whyInfoLabel; +@synthesize whyInfoText = _whyInfoText; +@synthesize transactionStatus = _transactionStatus; + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ -- completion: %@, count: %@", [super description], @(self.challengeCompletionIndicator), self.acsCounterACStoSDK]; +} + +- (instancetype)initWithThreeDSServerTransactionID:(NSString *)threeDSServerTransactionID + acsCounterACStoSDK:(NSString *)acsCounterACStoSDK + acsTransactionID:(NSString *)acsTransactionID + acsHTML:(NSString * _Nullable)acsHTML + acsHTMLRefresh:(NSString * _Nullable)acsHTMLRefresh + acsUIType:(STDSACSUIType)acsUIType + challengeCompletionIndicator:(BOOL)challengeCompletionIndicator + challengeInfoHeader:(NSString * _Nullable)challengeInfoHeader + challengeInfoLabel:(NSString * _Nullable)challengeInfoLabel + challengeInfoText:(NSString * _Nullable)challengeInfoText + challengeAdditionalInfoText:(NSString * _Nullable)challengeAdditionalInfoText + showChallengeInfoTextIndicator:(BOOL)showChallengeInfoTextIndicator + challengeSelectInfo:(NSArray> * _Nullable)challengeSelectInfo + expandInfoLabel:(NSString * _Nullable)expandInfoLabel + expandInfoText:(NSString * _Nullable)expandInfoText + issuerImage:(id _Nullable)issuerImage + messageExtensions:(NSArray> * _Nullable)messageExtensions + messageVersion:(NSString *)messageVersion + oobAppURL:(NSURL * _Nullable)oobAppURL + oobAppLabel:(NSString * _Nullable)oobAppLabel + oobContinueLabel:(NSString * _Nullable)oobContinueLabel + paymentSystemImage:(id _Nullable)paymentSystemImage + resendInformationLabel:(NSString * _Nullable)resendInformationLabel + sdkTransactionID:(NSString *)sdkTransactionID + submitAuthenticationLabel:(NSString * _Nullable)submitAuthenticationLabel + whitelistingInfoText:(NSString * _Nullable)whitelistingInfoText + whyInfoLabel:(NSString * _Nullable)whyInfoLabel + whyInfoText:(NSString * _Nullable)whyInfoText + transactionStatus:(NSString * _Nullable)transactionStatus { + self = [super init]; + + if (self) { + _threeDSServerTransactionID = [threeDSServerTransactionID copy]; + _acsCounterACStoSDK = [acsCounterACStoSDK copy]; + _acsTransactionID = [acsTransactionID copy]; + _acsHTML = [acsHTML copy]; + _acsHTMLRefresh = [acsHTMLRefresh copy]; + _acsUIType = acsUIType; + _challengeCompletionIndicator = challengeCompletionIndicator; + _challengeInfoHeader = [challengeInfoHeader copy]; + _challengeInfoLabel = [challengeInfoLabel copy]; + _challengeInfoText = [challengeInfoText copy]; + _challengeAdditionalInfoText = [challengeAdditionalInfoText copy]; + _showChallengeInfoTextIndicator = showChallengeInfoTextIndicator; + _challengeSelectInfo = [challengeSelectInfo copy]; + _expandInfoLabel = [expandInfoLabel copy]; + _expandInfoText = [expandInfoText copy]; + _issuerImage = issuerImage; + _messageExtensions = [messageExtensions copy]; + _messageType = @"CRes"; + _messageVersion = [messageVersion copy]; + _oobAppURL = oobAppURL; + _oobAppLabel = [oobAppLabel copy]; + _oobContinueLabel = [oobContinueLabel copy]; + _paymentSystemImage = paymentSystemImage; + _resendInformationLabel = [resendInformationLabel copy]; + _sdkTransactionID = [sdkTransactionID copy]; + _submitAuthenticationLabel = [submitAuthenticationLabel copy]; + _whitelistingInfoText = [whitelistingInfoText copy]; + _whyInfoLabel = [whyInfoLabel copy]; + _whyInfoText = [whyInfoText copy]; + _transactionStatus = [transactionStatus copy]; + } + + return self; +} + +#pragma mark Private Helpers + ++ (NSDictionary *)acsUITypeStringMapping { + return @{ + @"01": @(STDSACSUITypeText), + @"02": @(STDSACSUITypeSingleSelect), + @"03": @(STDSACSUITypeMultiSelect), + @"04": @(STDSACSUITypeOOB), + @"05": @(STDSACSUITypeHTML), + }; +} + +/// The message extension identifiers that we support. ++ (NSSet *)supportedMessageExtensions { + return [NSSet new]; +} + +#pragma mark STDSJSONDecodable + ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError **)outError { + if (json == nil) { + return nil; + } + NSError *error; + +#pragma mark Required + NSString *threeDSServerTransactionID = [json _stds_stringForKey:@"threeDSServerTransID" validator:^BOOL (NSString *value) { + return [[NSUUID alloc] initWithUUIDString:value] != nil; + } required:YES error:&error]; + NSString *acsCounterACStoSDK = [json _stds_stringForKey:@"acsCounterAtoS" required:YES error:&error]; + NSString *acsTransactionID = [json _stds_stringForKey:@"acsTransID" required:YES error:&error]; + NSString *challengeCompletionIndicatorRawString = [json _stds_stringForKey:@"challengeCompletionInd" validator:^BOOL (NSString *value) { + return [value isEqualToString:@"N"] || [value isEqualToString:@"Y"]; + } required:YES error:&error]; + // There is only one valid messageType value for this object (@"CRes"), so we don't store it. + [json _stds_stringForKey:@"messageType" validator:^BOOL (NSString *value) { + return [value isEqualToString:@"CRes"]; + } required:YES error:&error]; + NSString *messageVersion = [json _stds_stringForKey:@"messageVersion" required:YES error:&error]; + NSString *sdkTransactionID = [json _stds_stringForKey:@"sdkTransID" required:YES error:&error]; + + BOOL challengeCompletionIndicator = challengeCompletionIndicatorRawString.boolValue; + + STDSACSUIType acsUIType = STDSACSUITypeNone; + if (!challengeCompletionIndicator) { + NSString *acsUITypeRawString = [json _stds_stringForKey:@"acsUiType" validator:^BOOL (NSString *value) { + return [self acsUITypeStringMapping][value] != nil; + } required:YES error:&error]; + + acsUIType = [self acsUITypeStringMapping][acsUITypeRawString].integerValue; + } + + if (error) { + // We failed to populate a required field + if (outError) { + *outError = error; + } + return nil; + } + + // At this point all the above values are valid: e.g. raw string representations of a BOOL or enum will map to a valid value. + +#pragma mark Conditional + NSString *encodedAcsHTML = [json _stds_stringForKey:@"acsHTML" required:(acsUIType == STDSACSUITypeHTML) error: &error]; + NSString *acsHTML = [encodedAcsHTML _stds_base64URLDecodedString]; + if (encodedAcsHTML && !acsHTML) { + // html was not valid base64url + error = [NSError _stds_invalidJSONFieldError:@"acsHTML"]; + } + + NSArray> *challengeSelectInfo = [json _stds_arrayForKey:@"challengeSelectInfo" + arrayElementType:[STDSChallengeResponseSelectionInfoObject class] + required:(acsUIType == STDSACSUITypeSingleSelect || acsUIType == STDSACSUITypeMultiSelect) + error:&error]; + NSString *oobContinueLabel = [json _stds_stringForKey:@"oobContinueLabel" required:(acsUIType == STDSACSUITypeOOB) error:&error]; + NSString *submitAuthenticationLabel = [json _stds_stringForKey:@"submitAuthenticationLabel" required:(acsUIType == STDSACSUITypeText || acsUIType == STDSACSUITypeSingleSelect || acsUIType == STDSACSUITypeMultiSelect || acsUIType == STDSACSUITypeText) error:&error]; + +#pragma mark Optional + NSArray> *messageExtensions = [json _stds_arrayForKey:@"messageExtension" + arrayElementType:[STDSChallengeResponseMessageExtensionObject class] + required:NO + error:&error]; + NSMutableArray *unrecognizedMessageExtensionIdentifiers = [NSMutableArray new]; + for (id messageExtension in messageExtensions) { + if (messageExtension.criticalityIndicator && ![[self supportedMessageExtensions] containsObject:messageExtension.identifier]) { + [unrecognizedMessageExtensionIdentifiers addObject:messageExtension.identifier]; + } + } + if (unrecognizedMessageExtensionIdentifiers.count > 0) { + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorCodeUnrecognizedCriticalMessageExtension userInfo:@{STDSStripe3DS2UnrecognizedCriticalMessageExtensionsKey: unrecognizedMessageExtensionIdentifiers}]; + } + if (messageExtensions.count > 10) { + error = [NSError _stds_invalidJSONFieldError:@"messageExtension"]; + } + + NSString *encodedAcsHTMLRefresh = [json _stds_stringForKey:@"acsHTMLRefresh" required:NO error: &error]; + NSString *acsHTMLRefresh = [encodedAcsHTMLRefresh _stds_base64URLDecodedString]; + if (encodedAcsHTMLRefresh && !acsHTMLRefresh) { + // html was not valid base64url + error = [NSError _stds_invalidJSONFieldError:@"acsHTMLRefresh"]; + } + + BOOL infoLabelRequired = NO; + BOOL headerRequired = NO; + BOOL infoTextRequired = NO; + switch (acsUIType) { + case STDSACSUITypeNone: + break; // no-op + case STDSACSUITypeText: + case STDSACSUITypeSingleSelect: + case STDSACSUITypeMultiSelect: + infoLabelRequired = YES; // TC_SDK_10270_001 & TC_SDK_10276_001 & TC_SDK_10284_001 + headerRequired = YES; // TC_SDK_10268_001 & TC_SDK_10273_001 & TC_SDK_10282_001 + infoTextRequired = YES; // TC_SDK_10272_001 & TC_SDK_10278_001 & TC_SDK_10286_001 + break; + case STDSACSUITypeOOB: + + break; + case STDSACSUITypeHTML: + break; // no-op + } + + + NSString *challengeInfoLabel = [json _stds_stringForKey:@"challengeInfoLabel" validator:nil required:infoLabelRequired error:&error]; + NSString *challengeInfoHeader = [json _stds_stringForKey:@"challengeInfoHeader" required: (oobContinueLabel != nil) || headerRequired error:&error]; // TC_SDK_10292_001 + NSString *challengeInfoText = [json _stds_stringForKey:@"challengeInfoText" required:(oobContinueLabel != nil) || infoTextRequired error:&error]; // TC_SDK_10292_001 + NSString *challengeAdditionalInfoText = [json _stds_stringForKey:@"challengeAddInfo" required:NO error:&error]; + if (!error && submitAuthenticationLabel && (!challengeInfoLabel || !challengeInfoHeader || !challengeInfoText)) { + error = [NSError _stds_missingJSONFieldError:@"challengeInfoLabel or challengeInfoText"]; + } + + NSString *showChallengeInfoTextIndicatorRawString; + if (json[@"challengeInfoTextIndicator"]) { + showChallengeInfoTextIndicatorRawString = [json _stds_stringForKey:@"challengeInfoTextIndicator" validator:^BOOL (NSString *value) { + return [value isEqualToString:@"N"] || [value isEqualToString:@"Y"]; + } required:NO error:&error]; + } + BOOL showChallengeInfoTextIndicator = showChallengeInfoTextIndicatorRawString ? showChallengeInfoTextIndicatorRawString.boolValue : NO; // If the field is missing, we shouldn't show the indicator + NSString *expandInfoLabel = [json _stds_stringForKey:@"expandInfoLabel" required:NO error:&error]; + NSString *expandInfoText = [json _stds_stringForKey:@"expandInfoText" required:NO error:&error]; + NSURL *oobAppURL = [json _stds_urlForKey:@"oobAppURL" required:NO error:&error]; + NSString *oobAppLabel = [json _stds_stringForKey:@"oobAppURL" required:NO error:&error]; + NSDictionary *issuerImageJSON = [json _stds_dictionaryForKey:@"issuerImage" required:NO error:&error]; + STDSChallengeResponseImageObject *issuerImage = [STDSChallengeResponseImageObject decodedObjectFromJSON:issuerImageJSON error:&error]; + NSDictionary *paymentSystemImageJSON = [json _stds_dictionaryForKey:@"psImage" required:NO error:&error]; + STDSChallengeResponseImageObject *paymentSystemImage = [STDSChallengeResponseImageObject decodedObjectFromJSON:paymentSystemImageJSON error:&error]; + NSString *resendInformationLabel = [json _stds_stringForKey:@"resendInformationLabel" required:NO error:&error]; + NSString *whitelistingInfoText = [json _stds_stringForKey:@"whitelistingInfoText" required:NO error:&error]; + if (whitelistingInfoText.length > 64) { + // TC_SDK_10199_001 + error = [NSError _stds_invalidJSONFieldError:@"whitelisting text is greater than 64 characters"]; + } + NSString *whyInfoLabel = [json _stds_stringForKey:@"whyInfoLabel" required:NO error:&error]; + NSString *whyInfoText = [json _stds_stringForKey:@"whyInfoText" required:NO error:&error]; + NSString *transactionStatus = [json _stds_stringForKey:@"transStatus" required:challengeCompletionIndicator error:&error]; + + if (error) { + if (outError) { + *outError = error; + } + return nil; + } + + return [[self alloc] initWithThreeDSServerTransactionID:threeDSServerTransactionID + acsCounterACStoSDK:acsCounterACStoSDK + acsTransactionID:acsTransactionID + acsHTML:acsHTML + acsHTMLRefresh:acsHTMLRefresh + acsUIType:acsUIType + challengeCompletionIndicator:challengeCompletionIndicator + challengeInfoHeader:challengeInfoHeader + challengeInfoLabel:challengeInfoLabel + challengeInfoText:challengeInfoText + challengeAdditionalInfoText:challengeAdditionalInfoText + showChallengeInfoTextIndicator:showChallengeInfoTextIndicator + challengeSelectInfo:challengeSelectInfo + expandInfoLabel:expandInfoLabel + expandInfoText:expandInfoText + issuerImage:issuerImage + messageExtensions:messageExtensions + messageVersion:messageVersion + oobAppURL:oobAppURL + oobAppLabel:oobAppLabel + oobContinueLabel:oobContinueLabel + paymentSystemImage:paymentSystemImage + resendInformationLabel:resendInformationLabel + sdkTransactionID:sdkTransactionID + submitAuthenticationLabel:submitAuthenticationLabel + whitelistingInfoText:whitelistingInfoText + whyInfoLabel:whyInfoLabel + whyInfoText:whyInfoText + transactionStatus:transactionStatus]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfo.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfo.h new file mode 100644 index 00000000..a482c8c1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfo.h @@ -0,0 +1,24 @@ +// +// STDSChallengeResponseSelectionInfo.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// A protocol that encapsulates information about an individual selection inside of a challenge response. +@protocol STDSChallengeResponseSelectionInfo + +/// The name of the selection option. +@property (nonatomic, readonly) NSString *name; + +/// The value of the selection option. +@property (nonatomic, readonly) NSString *value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfoObject.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfoObject.h new file mode 100644 index 00000000..6e34b6c6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfoObject.h @@ -0,0 +1,21 @@ +// +// STDSChallengeResponseSelectionInfoObject.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseSelectionInfo.h" + +NS_ASSUME_NONNULL_BEGIN + +/// An object used to represent information about an individual selection inside of a challenge response. +@interface STDSChallengeResponseSelectionInfoObject: NSObject + +- (instancetype)initWithName:(NSString *)name value:(NSString *)value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfoObject.m b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfoObject.m new file mode 100644 index 00000000..8aa22ee0 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseSelectionInfoObject.m @@ -0,0 +1,46 @@ +// +// STDSChallengeResponseSelectionInfoObject.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeResponseSelectionInfoObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeResponseSelectionInfoObject() + +@property (nonatomic, strong) NSString *name; +@property (nonatomic, strong) NSString *value; + +@end + +@implementation STDSChallengeResponseSelectionInfoObject + +- (instancetype)initWithName:(NSString *)name value:(NSString *)value { + self = [super init]; + + if (self) { + _name = name; + _value = value; + } + + return self; +} + ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError * _Nullable __autoreleasing * _Nullable)outError { + if (json == nil) { + return nil; + } + + NSString *name = [json allKeys].firstObject; + NSString *value = [json objectForKey:name]; + + return [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:name value:value]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseViewController.h b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseViewController.h new file mode 100644 index 00000000..600ed7df --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseViewController.h @@ -0,0 +1,84 @@ +// +// STDSChallengeResponseViewController.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponse.h" +#import "STDSUICustomization.h" +#import "STDSImageLoader.h" +#import "STDSDirectoryServer.h" + +@class STDSChallengeResponseViewController; +@protocol STDSAnalyticsDelegate; + +NS_ASSUME_NONNULL_BEGIN + +@protocol STDSChallengeResponseViewControllerDelegate + +/** + Called when the user taps the Submit button after entering text in the Text flow (STDSACSUITypeText) + */ +- (void)challengeResponseViewController:(STDSChallengeResponseViewController *)viewController didSubmitInput:(NSString *)userInput + whitelistSelection: (id) whitelistSelection; + +/** + Called when the user taps the Submit button after selecting one or more options in the Single-Select (STDSACSUITypeSingleSelect) or Multi-Select (STDSACSUITypeMultiSelect) flow. + */ +- (void)challengeResponseViewController:(STDSChallengeResponseViewController *)viewController didSubmitSelection:(NSArray> *)selection whitelistSelection: (id) whitelistSelection; + +/** + Called when the user submits an HTML form. + */ +- (void)challengeResponseViewController:(STDSChallengeResponseViewController *)viewController didSubmitHTMLForm:(NSString *)form; + +/** + Called when the user taps the Continue button from an Out-of-Band flow (STDSACSUITypeOOB). + */ +- (void)challengeResponseViewControllerDidOOBContinue:(STDSChallengeResponseViewController *)viewController whitelistSelection: (id) whitelistSelection; + +/** + Called when the user taps the Cancel button. + */ +- (void)challengeResponseViewControllerDidCancel:(STDSChallengeResponseViewController *)viewController; + +/** + Called when the user taps the Resend button. + */ +- (void)challengeResponseViewControllerDidRequestResend:(STDSChallengeResponseViewController *)viewController; + +@end + +@protocol STDSChallengeResponseViewControllerPresentationDelegate + +- (void)dismissChallengeResponseViewController:(STDSChallengeResponseViewController *)viewController; + +@end + +@interface STDSChallengeResponseViewController : UIViewController + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, nullable, weak) id presentationDelegate; + +/// Use setChallengeResponser:animated: to update this value +@property (nonatomic, strong, readonly) id response; + +- (instancetype)initWithUICustomization:(STDSUICustomization * _Nullable)uiCustomization + imageLoader:(STDSImageLoader *)imageLoader + directoryServer:(STDSDirectoryServer)directoryServer + analyticsDelegate:(nullable id)analyticsDelegate; + +/// If `setLoading` was called beforehand, this waits until the loading spinner has been shown for at least 1 second before displaying the challenge responseself.processingView.isHidden. +- (void)setChallengeResponse:(id)response animated:(BOOL)animated; + +- (void)setLoading; + +- (void)dismiss; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeResponseViewController.m b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseViewController.m new file mode 100644 index 00000000..e113e5c4 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeResponseViewController.m @@ -0,0 +1,627 @@ +// +// STDSChallengeResponseViewController.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +@import WebKit; + +#import "STDSBundleLocator.h" +#import "STDSLocalizedString.h" +#import "STDSChallengeResponseViewController.h" +#import "STDSImageLoader.h" +#import "STDSStackView.h" +#import "STDSBrandingView.h" +#import "STDSChallengeInformationView.h" +#import "STDSChallengeSelectionView.h" +#import "STDSTextChallengeView.h" +#import "STDSVisionSupport.h" +#import "STDSWhitelistView.h" +#import "STDSExpandableInformationView.h" +#import "STDSWebView.h" +#import "STDSProcessingView.h" +#import "UIView+LayoutSupport.h" +#import "NSString+EmptyChecking.h" +#import "UIColor+DefaultColors.h" +#import "UIButton+CustomInitialization.h" +#import "UIFont+DefaultFonts.h" +#import "UIViewController+Stripe3DS2.h" +#import "include/STDSAnalyticsDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeResponseViewController() + +@property (nonatomic, strong, nullable) id response; +@property (nonatomic) STDSDirectoryServer directoryServer; +@property (weak, nonatomic) idanalyticsDelegate; +/// Used to track how long we've been showing a loading spinner. Nil if we are not showing a spinner. +@property (nonatomic, strong, nullable) NSDate *loadingStartDate; +@property (nonatomic, strong, nullable) STDSUICustomization *uiCustomization; +@property (nonatomic, strong) STDSImageLoader *imageLoader; +@property (nonatomic, strong) NSTimer *processingTimer; +@property (nonatomic, getter=isLoading) BOOL loading; +@property (nonatomic, strong) STDSProcessingView *processingView; +@property (nonatomic, strong, nullable) UIScrollView *scrollView; +@property (nonatomic, strong, nullable) STDSWebView *webView; +@property (nonatomic, strong, nullable) STDSChallengeInformationView *challengeInformationView; +@property (nonatomic, strong) UITapGestureRecognizer *tapOutsideKeyboardGestureRecognizer; + +// User input views +@property (nonatomic, strong) STDSChallengeSelectionView *challengeSelectionView; +@property (nonatomic, strong) STDSTextChallengeView *textChallengeView; +@property (nonatomic, strong) STDSWhitelistView *whitelistView; +@property (nonatomic, strong) UIStackView *buttonStackView; +@end + +@implementation STDSChallengeResponseViewController + +static const NSTimeInterval kInterstepProcessingTime = 1.0; +static const NSTimeInterval kDefaultTransitionAnimationDuration = 0.3; +static const CGFloat kBrandingViewHeight = 107; +static const CGFloat kContentHorizontalInset = 16; +static const CGFloat kExpandableContentHorizontalInset = 27; +static const CGFloat kContentViewTopPadding = 16; +static const CGFloat kContentViewBottomPadding = 26; +static const CGFloat kExpandableContentViewTopPadding = 28; + +static NSString * const kHTMLStringLoadingURL = @"about:blank"; + +- (instancetype)initWithUICustomization:(STDSUICustomization * _Nullable)uiCustomization + imageLoader:(STDSImageLoader *)imageLoader + directoryServer:(STDSDirectoryServer)directoryServer + analyticsDelegate:(nullable id)analyticsDelegate { + self = [super initWithNibName:nil bundle:nil]; + + if (self) { + _uiCustomization = uiCustomization; + _imageLoader = imageLoader; + _tapOutsideKeyboardGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_didTapOutsideKeyboard:)]; + _directoryServer = directoryServer; + _analyticsDelegate = analyticsDelegate; + } + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self _stds_setupNavigationBarElementsWithCustomization:_uiCustomization cancelButtonSelector:@selector(_cancelButtonTapped:)]; + self.view.backgroundColor = self.uiCustomization.backgroundColor; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; + + NSString *imageName = STDSDirectoryServerImageName(self.directoryServer); + UIImage *dsImage = imageName ? [UIImage imageNamed:imageName inBundle:[STDSBundleLocator stdsResourcesBundle] compatibleWithTraitCollection:nil] : nil; + self.processingView = [[STDSProcessingView alloc] initWithCustomization:self.uiCustomization directoryServerLogo:dsImage]; + self.processingView.hidden = !self.isLoading; + + [self.view addSubview:self.processingView]; + [self.processingView _stds_pinToSuperviewBoundsWithoutMargin]; + + [self.view addGestureRecognizer:self.tapOutsideKeyboardGestureRecognizer]; +} + +#if !STP_TARGET_VISION +- (UIStatusBarStyle)preferredStatusBarStyle { + return self.uiCustomization.preferredStatusBarStyle; +} +#endif + +#pragma mark - Public APIs + +- (void)setLoading { + [self _setLoading:YES]; +} + +- (void)setChallengeResponse:(id)response animated:(BOOL)animated { + BOOL isFirstChallengeResponse = _response == nil; + _response = response; + + [self.processingTimer invalidate]; + + if (isFirstChallengeResponse || !self.isLoading || !self.loadingStartDate) { + [self _displayChallengeResponseAnimated:animated]; + } else { + // Show the loading spinner for at least kDefaultProcessingTime seconds before displaying + NSTimeInterval timeSpentLoading = [[NSDate date] timeIntervalSinceDate:self.loadingStartDate]; + if (timeSpentLoading >= kInterstepProcessingTime) { + // loadingStartDate is nil if we called this method in between viewDidLoad and viewDidAppear. + // There is no time requirement for the initial CRes. + [self _displayChallengeResponseAnimated:animated]; + } else { + self.processingTimer = [NSTimer timerWithTimeInterval:(kInterstepProcessingTime - timeSpentLoading) target:self selector:@selector(_timerDidFire:) userInfo:@(animated) repeats:NO]; + [[NSRunLoop currentRunLoop] addTimer:self.processingTimer forMode:NSDefaultRunLoopMode]; + } + } +} + +- (void)dismiss { + if (self.presentationDelegate) { + [self.presentationDelegate dismissChallengeResponseViewController:self]; + } else { + [self dismissViewControllerAnimated:YES completion:nil]; + } +} + +#pragma mark - Private Helpers + +- (void)_setLoading:(BOOL)isLoading { + self.loading = isLoading; + if (!self.viewLoaded || isLoading == !self.processingView.isHidden) { + return; + } + + self.navigationItem.rightBarButtonItem.enabled = !isLoading; + + /* According to the specs [0], this should be set to NO during AReq/Ares and YES during CReq/CRes. + However, according to UL test feedback [1], the AReq/ARes and initial CReq/CRes processing views should be identical. + + [0]: EMV 3-D Secure Protocol and Core Functions Specification v2.1.0 4.2.1.1 + - "The 3DS SDK shall for the CReq/CRes message exchange...[Req 148] Not include the DS logo or any other design element in the Processing screen." + - "The 3DS SDK shall for the AReq/ARes message exchange...[Req 143] If requested, integrate the DS logo into the Processing screen." + + [1]: UL_PreCompTestReport_ID846_201906_1.0 + - "Visual test case TC_SDK_10022_001 - The test case is FAILED because the processing screen for step 1 and step 2 are not identical. Step 1 displays a 'DS logo' while step 2 does not. + + To pass certification, we'll show the DS logo during the initial CReq/CRes (when self.response == nil). + */ + self.processingView.shouldDisplayDSLogo = self.response == nil; + // If there's no response, the blur view has nothing to blur and looks better visually if it's just the background color + // EDIT Jan 2021: The challenge contents is hidden so this never looks good https://jira.corp.stripe.com/browse/MOBILESDK-153 + self.processingView.shouldDisplayBlurView = NO; // self.response != nil; + + if (isLoading) { + [self.view bringSubviewToFront:self.processingView]; + self.processingView.hidden = NO; + + self.loadingStartDate = [NSDate date]; + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, STDSLocalizedString(@"Loading", @"Spoken by VoiceOver when the challenge is loading.")); + } else { + self.processingView.hidden = YES; + self.loadingStartDate = nil; + } +} + +- (void)_timerDidFire:(NSTimer *)timer { + BOOL animated = ((NSNumber *)timer.userInfo).boolValue; + [self.processingTimer invalidate]; + [self _displayChallengeResponseAnimated:animated]; +} + +- (void)_setupViewHierarchy { + self.scrollView = [[UIScrollView alloc] init]; + self.scrollView.backgroundColor = self.uiCustomization.footerCustomization.backgroundColor; + self.scrollView.alwaysBounceVertical = YES; + [self.view addSubview:self.scrollView]; + [self.scrollView _stds_pinToSuperviewBoundsWithoutMargin]; + + STDSStackView *containerStackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [self.scrollView addSubview:containerStackView]; + [containerStackView _stds_pinToSuperviewBoundsWithoutMargin]; + + UIView *contentView = [UIView new]; + contentView.layoutMargins = UIEdgeInsetsMake(kContentViewTopPadding, kContentHorizontalInset, kContentViewBottomPadding, kContentHorizontalInset); + contentView.backgroundColor = self.uiCustomization.backgroundColor; + [containerStackView addArrangedSubview:contentView]; + + STDSStackView *contentStackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [contentView addSubview:contentStackView]; + [contentStackView _stds_pinToSuperviewBounds]; + + STDSBrandingView *brandingView = [self _newConfiguredBrandingView]; + STDSChallengeInformationView *challengeInformationView = [self _newConfiguredChallengeInformationView]; + self.challengeInformationView = challengeInformationView; + UIButton *actionButton = [self _newConfiguredActionButton]; + UIButton *resendButton = [self _newConfiguredResendButton]; + STDSTextChallengeView *textChallengeView = [self _newConfiguredTextChallengeView]; + self.textChallengeView = textChallengeView; + STDSChallengeSelectionView *challengeSelectionView = [self _newConfiguredChallengeSelectionView]; + self.challengeSelectionView = challengeSelectionView; + self.whitelistView = [self _newConfiguredWhitelistView]; + + UIView *expandableContentView = [UIView new]; + expandableContentView.layoutMargins = UIEdgeInsetsMake(kExpandableContentViewTopPadding, kExpandableContentHorizontalInset, 0, kExpandableContentHorizontalInset); + [containerStackView addArrangedSubview:expandableContentView]; + + STDSStackView *expandableContentStackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [expandableContentView addSubview:expandableContentStackView]; + [expandableContentStackView _stds_pinToSuperviewBounds]; + + STDSExpandableInformationView *whyInformationView = [self _newConfiguredWhyInformationView]; + STDSExpandableInformationView *expandableInformationView = [self _newConfiguredExpandableInformationView]; + + [contentStackView addArrangedSubview:brandingView]; + [contentStackView addArrangedSubview:challengeInformationView]; + [contentStackView addArrangedSubview:textChallengeView]; + [contentStackView addArrangedSubview:challengeSelectionView]; + + self.buttonStackView = [self _newSubmitButtonStackView]; + + [self.buttonStackView addArrangedSubview:actionButton]; + + [contentStackView addArrangedSubview:self.buttonStackView]; + + if (_response.acsUIType != STDSACSUITypeOOB && _response.acsUIType != STDSACSUITypeMultiSelect && _response.acsUIType != STDSACSUITypeSingleSelect) { + [self.buttonStackView addArrangedSubview:resendButton]; + } + if (!self.whitelistView.isHidden) { + [contentStackView addSpacer:10]; + } + [contentStackView addArrangedSubview:self.whitelistView]; + [expandableContentStackView addArrangedSubview:whyInformationView]; + [expandableContentStackView addArrangedSubview:expandableInformationView]; + + NSLayoutConstraint *contentViewWidth = [NSLayoutConstraint constraintWithItem:containerStackView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; + NSLayoutConstraint *brandingViewHeightConstraint = [NSLayoutConstraint constraintWithItem:brandingView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:kBrandingViewHeight]; + [NSLayoutConstraint activateConstraints:@[brandingViewHeightConstraint, contentViewWidth]]; + + [self _loadBrandingViewImages:brandingView]; +} + +- (void)_setupWebView { + self.webView = [[STDSWebView alloc] init]; + self.webView.navigationDelegate = self; + [self.view addSubview:self.webView]; + [self.webView _stds_pinToSuperviewBounds]; + [self.webView loadExternalResourceBlockingHTMLString:self.response.acsHTML]; +} + +- (void)_loadBrandingViewImages:(STDSBrandingView *)brandingView { + NSURL *issuerImageURL = [self _highestFideltyURLFromChallengeResponseImage:self.response.issuerImage]; + + if (issuerImageURL != nil) { + [self.imageLoader loadImageFromURL:issuerImageURL completion:^(UIImage * _Nullable image) { + brandingView.issuerImage = image; + }]; + } + + NSURL *paymentSystemImageURL = [self _highestFideltyURLFromChallengeResponseImage:self.response.paymentSystemImage]; + + if (paymentSystemImageURL != nil) { + [self.imageLoader loadImageFromURL:paymentSystemImageURL completion:^(UIImage * _Nullable image) { + brandingView.paymentSystemImage = image; + }]; + } +} + +- (NSURL * _Nullable)_highestFideltyURLFromChallengeResponseImage:(id )image { + return image.extraHighDensityURL ?: image.highDensityURL ?: image.mediumDensityURL; +} + +- (void)_displayChallengeResponseAnimated:(BOOL)animated { + if (self.response != nil) { + [self _setLoading:NO]; + + UIScrollView *existingScrollView = self.scrollView; + STDSWebView *existingWebView = self.webView; + + void (^transitionBlock)(UIView *, BOOL) = ^void(UIView *viewToTransition, BOOL animated) { + NSTimeInterval transitionTime = animated ? kDefaultTransitionAnimationDuration : 0; + viewToTransition.alpha = 0; + [UIView animateWithDuration:transitionTime animations:^{ + viewToTransition.alpha = 1; + } completion:^(BOOL finished) { + [existingScrollView removeFromSuperview]; + [existingWebView removeFromSuperview]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"STDSChallengeResponseViewController.didDisplayChallengeResponse" object:self]; + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.navigationItem.titleView); + }]; + }; + + switch (self.response.acsUIType) { + case STDSACSUITypeNone: + break; + case STDSACSUITypeText: + case STDSACSUITypeSingleSelect: + case STDSACSUITypeMultiSelect: + case STDSACSUITypeOOB: + [self _setupViewHierarchy]; + + transitionBlock(self.scrollView, animated); + break; + case STDSACSUITypeHTML: + [self _setupWebView]; + + transitionBlock(self.webView, animated); + break; + } + } +} + +- (STDSBrandingView *)_newConfiguredBrandingView { + STDSBrandingView *brandingView = [[STDSBrandingView alloc] init]; + brandingView.hidden = self.response.issuerImage == nil && self.response.paymentSystemImage == nil; + + return brandingView; +} + +- (STDSChallengeInformationView *)_newConfiguredChallengeInformationView { + STDSChallengeInformationView *challengeInformationView = [[STDSChallengeInformationView alloc] init]; + challengeInformationView.headerText = self.response.challengeInfoHeader; + challengeInformationView.challengeInformationText = self.response.challengeInfoText; + challengeInformationView.challengeInformationLabel = self.response.challengeInfoLabel; + challengeInformationView.labelCustomization = self.uiCustomization.labelCustomization; + + if (self.response.showChallengeInfoTextIndicator) { + challengeInformationView.textIndicatorImage = [UIImage imageNamed:@"error" inBundle:[STDSBundleLocator stdsResourcesBundle] compatibleWithTraitCollection:nil]; + } + + return challengeInformationView; +} + +- (STDSTextChallengeView *)_newConfiguredTextChallengeView { + STDSTextChallengeView *textChallengeView = [[STDSTextChallengeView alloc] init]; + textChallengeView.hidden = self.response.acsUIType != STDSACSUITypeText; + textChallengeView.textFieldCustomization = self.uiCustomization.textFieldCustomization; + textChallengeView.textField.accessibilityLabel = self.response.challengeInfoLabel; + textChallengeView.backgroundColor = self.uiCustomization.backgroundColor; + + return textChallengeView; +} + +- (STDSChallengeSelectionView *)_newConfiguredChallengeSelectionView { + STDSChallengeSelectionStyle selectionStyle = self.response.acsUIType == STDSACSUITypeMultiSelect ? STDSChallengeSelectionStyleMulti : STDSChallengeSelectionStyleSingle; + STDSChallengeSelectionView *challengeSelectionView = [[STDSChallengeSelectionView alloc] initWithChallengeSelectInfo:self.response.challengeSelectInfo selectionStyle:selectionStyle]; + challengeSelectionView.hidden = self.response.acsUIType != STDSACSUITypeSingleSelect && self.response.acsUIType != STDSACSUITypeMultiSelect; + challengeSelectionView.labelCustomization = self.uiCustomization.labelCustomization; + challengeSelectionView.selectionCustomization = self.uiCustomization.selectionCustomization; + challengeSelectionView.backgroundColor = self.uiCustomization.backgroundColor; + + return challengeSelectionView; +} + +- (UIButton *)_newConfiguredActionButton { + STDSUICustomizationButtonType buttonType = STDSUICustomizationButtonTypeSubmit; + NSString *buttonTitle; + + switch (self.response.acsUIType) { + case STDSACSUITypeNone: + break; + case STDSACSUITypeText: + case STDSACSUITypeSingleSelect: + case STDSACSUITypeMultiSelect: { + buttonTitle = self.response.submitAuthenticationLabel; + + break; + } + case STDSACSUITypeOOB: { + buttonType = STDSUICustomizationButtonTypeContinue; + buttonTitle = self.response.oobContinueLabel; + + break; + } + case STDSACSUITypeHTML: + break; + } + + STDSButtonCustomization *buttonCustomization = [self.uiCustomization buttonCustomizationForButtonType:buttonType]; + UIButton *actionButton = [UIButton _stds_buttonWithTitle:buttonTitle customization:buttonCustomization]; + [actionButton addTarget:self action:@selector(_actionButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; + actionButton.hidden = buttonTitle == nil || [NSString _stds_isStringEmpty:buttonTitle]; + actionButton.accessibilityIdentifier = @"Continue"; + + return actionButton; +} + +- (UIButton *)_newConfiguredResendButton { + STDSButtonCustomization *buttonCustomization = [self.uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeResend]; + + NSString *resendButtonTitle = self.response.resendInformationLabel; + UIButton *resendButton = [UIButton _stds_buttonWithTitle:resendButtonTitle customization:buttonCustomization]; + + resendButton.hidden = resendButtonTitle == nil || [NSString _stds_isStringEmpty:resendButtonTitle]; + [resendButton addTarget:self action:@selector(_resendButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; + + return resendButton; +} + +- (STDSWhitelistView *)_newConfiguredWhitelistView { + STDSWhitelistView *whitelistView = [[STDSWhitelistView alloc] init]; + whitelistView.whitelistText = self.response.whitelistingInfoText; + whitelistView.labelCustomization = self.uiCustomization.labelCustomization; + whitelistView.selectionCustomization = self.uiCustomization.selectionCustomization; + whitelistView.hidden = whitelistView.whitelistText == nil; + whitelistView.accessibilityIdentifier = @"STDSWhitelistView"; + + return whitelistView; +} + +- (STDSExpandableInformationView *)_newConfiguredWhyInformationView { + STDSExpandableInformationView *whyInformationView = [[STDSExpandableInformationView alloc] init]; + whyInformationView.title = self.response.whyInfoLabel; + whyInformationView.text = self.response.whyInfoText; + whyInformationView.customization = self.uiCustomization.footerCustomization; + whyInformationView.hidden = whyInformationView.title == nil; + whyInformationView.backgroundColor = self.uiCustomization.footerCustomization.backgroundColor; + __weak typeof(self) weakSelf = self; + whyInformationView.didTap = ^{ + [weakSelf.textChallengeView endEditing:NO]; + }; + + return whyInformationView; +} + +- (STDSExpandableInformationView *)_newConfiguredExpandableInformationView { + + STDSExpandableInformationView *expandableInformationView = [[STDSExpandableInformationView alloc] init]; + expandableInformationView.title = self.response.expandInfoLabel; + expandableInformationView.text = self.response.expandInfoText; + expandableInformationView.customization = self.uiCustomization.footerCustomization; + expandableInformationView.hidden = expandableInformationView.title == nil; + expandableInformationView.backgroundColor = self.uiCustomization.footerCustomization.backgroundColor; + __weak typeof(self) weakSelf = self; + expandableInformationView.didTap = ^{ + [weakSelf.textChallengeView endEditing:NO]; + }; + + return expandableInformationView; +} + +- (UIStackView *)_newSubmitButtonStackView { + UIStackView *stackView = [[UIStackView alloc] init]; + stackView.axis = UILayoutConstraintAxisVertical; + stackView.distribution = UIStackViewDistributionFillEqually; + stackView.alignment = UIStackViewAlignmentFill; + stackView.spacing = 5; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + +#if !STP_TARGET_VISION + CGSize size = [UIScreen mainScreen].bounds.size; + if (size.width > size.height) { + // hack to detect landscape + stackView.axis = UILayoutConstraintAxisHorizontal; + stackView.alignment = UIStackViewAlignmentCenter; + } +#endif + return stackView; +} + +- (void)_keyboardDidShow:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + + // Get the keyboard’s frame at the end of its animation. + CGRect keyboardFrameEnd = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + + // Convert the keyboard's frame from the screen's coordinate space to your view's coordinate space. + keyboardFrameEnd = [self.view convertRect:keyboardFrameEnd fromView:nil]; + + // Get the intersection between the keyboard's frame and the view's bounds to work with the + // part of the keyboard that overlaps your view. + CGRect viewIntersection = CGRectIntersection(self.view.bounds, keyboardFrameEnd); + CGFloat bottomOffset = 0; + + // Check whether the keyboard intersects your view before adjusting your offset. + if (!CGRectIsEmpty(viewIntersection)) { + // Adjust the offset by the difference between the view's height and the height of the + // intersection rectangle. + bottomOffset = CGRectGetMaxY(self.view.bounds) - CGRectGetMinY(viewIntersection); + } + + UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.scrollView.contentInset.top, 0.0, bottomOffset, 0.0); + self.scrollView.contentInset = contentInsets; + self.scrollView.scrollIndicatorInsets = contentInsets; +} + +- (void)_keyboardWillHide:(NSNotification *)notification { + UIEdgeInsets contentInsets = UIEdgeInsetsMake(self.scrollView.contentInset.top, 0.0, 0.0, 0.0); + self.scrollView.contentInset = contentInsets; + self.scrollView.scrollIndicatorInsets = contentInsets; +} + +- (void)_applicationDidEnterBackground { + if (self.response.acsUIType == STDSACSUITypeOOB) { + [self.analyticsDelegate OOBDidEnterBackground:self.response.threeDSServerTransactionID]; + } +} + +- (void)_applicationWillEnterForeground:(NSNotification *)notification { + if (self.response.acsUIType == STDSACSUITypeOOB) { + + [self.analyticsDelegate OOBWillEnterForeground:self.response.threeDSServerTransactionID]; + + if (self.response.challengeAdditionalInfoText) { + // [Req 316] When Challenge Additional Information Text is present, the SDK would replace the Challenge Information Text and Challenge Information Text Indicator with the Challenge Additional Information Text when the 3DS Requestor App is moved to the foreground. + self.challengeInformationView.challengeInformationText = self.response.challengeAdditionalInfoText; + self.challengeInformationView.textIndicatorImage = nil; + } + + // [REQ 70] + [self submit:self.response.acsUIType]; + } else if (self.response.acsUIType == STDSACSUITypeHTML && self.response.acsHTMLRefresh) { + // [Req 317] When the ACS HTML Refresh element is present, the SDK replaces the ACS HTML with the contents of ACS HTML Refresh when the 3DS Requestor App is moved to the foreground. + [self.webView loadExternalResourceBlockingHTMLString:self.response.acsHTMLRefresh]; + } +} + +- (void)_didTapOutsideKeyboard:(UIGestureRecognizer *)gestureRecognizer { + // Note this doesn't fire if a subview handles the touch (e.g. UIControls, STDSExpandableInformationView) + [self.textChallengeView endEditing:NO]; +} + +#pragma mark - Button callbacks + +- (void)_cancelButtonTapped:(UIButton *)sender { + [self.textChallengeView endEditing:NO]; + [self.delegate challengeResponseViewControllerDidCancel:self]; + [self.analyticsDelegate cancelButtonTappedWithTransactionID:self.response.threeDSServerTransactionID]; +} + +- (void)_resendButtonTapped:(UIButton *)sender { + [self.textChallengeView endEditing:NO]; + [self.delegate challengeResponseViewControllerDidRequestResend:self]; +} + +- (void)submit:(STDSACSUIType)type { + [self.textChallengeView endEditing:NO]; + + switch (type) { + case STDSACSUITypeNone: + break; + case STDSACSUITypeText: { + [self.delegate challengeResponseViewController:self + didSubmitInput:self.textChallengeView.inputText + whitelistSelection:self.whitelistView.selectedResponse]; + + [self.analyticsDelegate OTPSubmitButtonTappedWithTransactionID:self.response.threeDSServerTransactionID]; + break; + } + case STDSACSUITypeSingleSelect: + case STDSACSUITypeMultiSelect: { + [self.delegate challengeResponseViewController:self + didSubmitSelection:self.challengeSelectionView.currentlySelectedChallengeInfo + whitelistSelection:self.whitelistView.selectedResponse]; + break; + } + case STDSACSUITypeOOB: + [self.delegate challengeResponseViewControllerDidOOBContinue:self + whitelistSelection:self.whitelistView.selectedResponse]; + [self.analyticsDelegate OOBContinueButtonTappedWithTransactionID:self.response.threeDSServerTransactionID]; + break; + case STDSACSUITypeHTML: + // No action button in this case, see WKNavigationDelegate. + break; + } +} + +- (void)_actionButtonTapped:(UIButton *)sender { + [self submit:self.response.acsUIType]; +} + +#pragma mark - WKNavigationDelegate + +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + + NSURLRequest *request = navigationAction.request; + + if ([request.URL.absoluteString isEqualToString:kHTMLStringLoadingURL]) { + return decisionHandler(WKNavigationActionPolicyAllow); + } else { + if (navigationAction.navigationType == WKNavigationTypeFormSubmitted || navigationAction.navigationType == WKNavigationTypeLinkActivated || navigationAction.navigationType == WKNavigationTypeOther) { + // When the Cardholder’s response is returned as a parameter string, the form data is passed to the web view instance by triggering a location change to a specified (HTTPS://EMV3DS/challenge) URL with the challenge responses appended to the location URL as query parameters (for example, HTTPS://EMV3DS/challenge?city=Pittsburgh). The web view instance, because it monitors URL changes, receives the Cardholder’s responses as query parameters. + [self.delegate challengeResponseViewController:self didSubmitHTMLForm:request.URL.query]; + } + + return decisionHandler(WKNavigationActionPolicyCancel); + } +} + +- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + if (size.width > size.height) { + // hack to detect landscape + self.buttonStackView.axis = UILayoutConstraintAxisHorizontal; + self.buttonStackView.alignment = UIStackViewAlignmentCenter; + } else { + self.buttonStackView.axis = UILayoutConstraintAxisVertical; + self.buttonStackView.alignment = UIStackViewAlignmentFill; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeSelectionView.h b/Stripe3DS2/Stripe3DS2/STDSChallengeSelectionView.h new file mode 100644 index 00000000..5ac5be0d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeSelectionView.h @@ -0,0 +1,35 @@ +// +// STDSChallengeSelectionView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/6/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseSelectionInfo.h" +#import "STDSLabelCustomization.h" +#import "STDSSelectionCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, STDSChallengeSelectionStyle) { + + /// A display style for selecting a single option. + STDSChallengeSelectionStyleSingle = 0, + + /// A display style for selection multiple options. + STDSChallengeSelectionStyleMulti = 1, +}; + +@interface STDSChallengeSelectionView : UIView + +@property (nonatomic, strong, readonly) NSArray> *currentlySelectedChallengeInfo; +@property (nonatomic, strong) STDSLabelCustomization *labelCustomization; +@property (nonatomic, strong) STDSSelectionCustomization *selectionCustomization; + +- (instancetype)initWithChallengeSelectInfo:(NSArray> *)challengeSelectInfo selectionStyle:(STDSChallengeSelectionStyle)selectionStyle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSChallengeSelectionView.m b/Stripe3DS2/Stripe3DS2/STDSChallengeSelectionView.m new file mode 100644 index 00000000..0b4d15b3 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSChallengeSelectionView.m @@ -0,0 +1,255 @@ +// +// STDSChallengeSelectionView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/6/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSLocalizedString.h" +#import "STDSBundleLocator.h" +#import "STDSChallengeSelectionView.h" +#import "STDSStackView.h" +#import "UIView+LayoutSupport.h" +#import "STDSSelectionButton.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeResponseSelectionRow: STDSStackView + +typedef NS_ENUM(NSInteger, STDSChallengeResponseSelectionRowStyle) { + + /// A display style for showing a radio button. + STDSChallengeResponseSelectionRowStyleRadio = 0, + + /// A display style for shows a checkbox. + STDSChallengeResponseSelectionRowStyleCheckbox = 1, +}; + +typedef void (^STDSChallengeResponseRowSelectedBlock)(STDSChallengeResponseSelectionRow *); + +@property (nonatomic, strong, readonly) id challengeSelectInfo; +@property (nonatomic, getter=isSelected) BOOL selected; +@property (nonatomic, strong) STDSLabelCustomization *labelCustomization; +@property (nonatomic, strong) STDSSelectionCustomization *selectionCustomization; + +- (instancetype)initWithChallengeSelectInfo:(id)challengeSelectInfo rowStyle:(STDSChallengeResponseSelectionRowStyle)rowStyle rowSelectedBlock:(STDSChallengeResponseRowSelectedBlock)rowSelectedBlock; + +@end + +@interface STDSChallengeResponseSelectionRow() + +@property (nonatomic, strong) id challengeSelectInfo; +@property (nonatomic, strong) STDSChallengeResponseRowSelectedBlock rowSelectedBlock; +@property (nonatomic) STDSChallengeResponseSelectionRowStyle rowStyle; +@property (nonatomic, strong) STDSSelectionButton *selectionButton; +@property (nonatomic, strong) UILabel *valueLabel; +@property (nonatomic, strong) UITapGestureRecognizer *valueLabelTapRecognizer; + +@end + +@implementation STDSChallengeResponseSelectionRow + +- (instancetype)initWithChallengeSelectInfo:(id)challengeSelectInfo rowStyle:(STDSChallengeResponseSelectionRowStyle)rowStyle rowSelectedBlock:(STDSChallengeResponseRowSelectedBlock)rowSelectedBlock { + self = [super initWithAlignment:STDSStackViewLayoutAxisHorizontal]; + + if (self) { + _challengeSelectInfo = challengeSelectInfo; + _rowStyle = rowStyle; + _rowSelectedBlock = rowSelectedBlock; + self.isAccessibilityElement = YES; + self.accessibilityIdentifier = @"STDSChallengeResponseSelectionRow"; + + [self _setupViewHierarchy]; + } + + return self; +} + +- (void)_setupViewHierarchy { + self.selectionButton = [[STDSSelectionButton alloc] initWithCustomization:self.selectionCustomization]; + self.selectionButton.customization = self.selectionCustomization; + [self.selectionButton addTarget:self action:@selector(_rowWasSelected) forControlEvents:UIControlEventTouchUpInside]; + + if (self.rowStyle == STDSChallengeResponseSelectionRowStyleCheckbox) { + self.selectionButton.isCheckbox = YES; + } + + self.valueLabel = [[UILabel alloc] init]; + self.valueLabel.text = self.challengeSelectInfo.value; + self.valueLabel.userInteractionEnabled = YES; + self.valueLabel.numberOfLines = 0; + self.valueLabelTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_rowWasSelected)]; + [self.valueLabel addGestureRecognizer:self.valueLabelTapRecognizer]; + + [self addArrangedSubview:self.selectionButton]; + [self addSpacer:15.0]; + [self addArrangedSubview:self.valueLabel]; + [self addArrangedSubview:[UIView new]]; +} + +- (void)_rowWasSelected { + self.rowSelectedBlock(self); +} + +- (BOOL)isSelected { + /// Placeholder until visual and interaction design is complete. + return self.selectionButton.isSelected; +} + +- (void)setSelected:(BOOL)selected { + /// Placeholder until visual and interaction design is complete. + self.selectionButton.selected = selected; +} + +- (void)setLabelCustomization:(STDSLabelCustomization *)labelCustomization { + _labelCustomization = labelCustomization; + + self.valueLabel.font = labelCustomization.font; + self.valueLabel.textColor = labelCustomization.textColor; +} + +- (void)setSelectionCustomization:(STDSSelectionCustomization *)selectionCustomization { + _selectionCustomization = selectionCustomization; + + self.selectionButton.customization = selectionCustomization; +} + +#pragma mark - UIAccessibility + +- (BOOL)accessibilityActivate { + self.rowSelectedBlock(self); + return YES; +} + +- (nullable NSString *)accessibilityLabel { + return self.valueLabel.text; +} + +- (nullable NSString *)accessibilityValue { + return self.selected ? STDSLocalizedString(@"Selected", @"Indicates that a button is selected.") : STDSLocalizedString(@"Unselected", @"Indicates that a button is not selected."); +} + +- (UIAccessibilityTraits)accessibilityTraits { + // remove the selected trait since we manually add that as an accessibilityValue above + return (self.selectionButton.accessibilityTraits & ~UIAccessibilityTraitSelected); +} + +@end + +@interface STDSChallengeSelectionView() + +@property (nonatomic, strong) STDSStackView *containerView; +@property (nonatomic, strong) NSArray *challengeSelectionRows; + +@property (nonatomic) STDSChallengeSelectionStyle selectionStyle; + +@end + +@implementation STDSChallengeSelectionView + +static const CGFloat kChallengeSelectionViewTopPadding = 5; +static const CGFloat kChallengeSelectionViewBottomPadding = 20; +static const CGFloat kChallengeSelectionViewInterRowVerticalPadding = 16; + +- (instancetype)initWithChallengeSelectInfo:(NSArray> *)challengeSelectInfo selectionStyle:(STDSChallengeSelectionStyle)selectionStyle { + self = [super init]; + + if (self) { + _selectionStyle = selectionStyle; + _challengeSelectionRows = [self _rowsForChallengeSelectInfo:challengeSelectInfo]; + + [self _setupViewHierarchy]; + } + + return self; +} + +- (void)_setupViewHierarchy { + self.containerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + + for (STDSChallengeResponseSelectionRow *selectionRow in self.challengeSelectionRows) { + [self.containerView addArrangedSubview:selectionRow]; + + if (selectionRow != self.challengeSelectionRows.lastObject) { + [self.containerView addSpacer:kChallengeSelectionViewInterRowVerticalPadding]; + } + } + + if (self.challengeSelectionRows.count > 0) { + self.layoutMargins = UIEdgeInsetsMake(kChallengeSelectionViewTopPadding, 0, kChallengeSelectionViewBottomPadding, 0); + } else { + self.layoutMargins = UIEdgeInsetsZero; + } + + [self addSubview:self.containerView]; + [self.containerView _stds_pinToSuperviewBounds]; +} + +- (NSArray *)_rowsForChallengeSelectInfo:(NSArray> *)challengeSelectInfo { + NSMutableArray *challengeRows = [NSMutableArray array]; + STDSChallengeResponseSelectionRowStyle rowStyle = self.selectionStyle == STDSChallengeSelectionStyleSingle ? STDSChallengeResponseSelectionRowStyleRadio : STDSChallengeResponseSelectionRowStyleCheckbox; + + for (id selectionInfo in challengeSelectInfo) { + __weak typeof(self) weakSelf = self; + STDSChallengeResponseSelectionRow *challengeRow = [[STDSChallengeResponseSelectionRow alloc] initWithChallengeSelectInfo:selectionInfo rowStyle:rowStyle rowSelectedBlock:^(STDSChallengeResponseSelectionRow * _Nonnull selectedRow) { + __strong typeof(self) strongSelf = weakSelf; + + [strongSelf _rowWasSelected:selectedRow]; + }]; + + if (selectionInfo == challengeSelectInfo.firstObject && self.selectionStyle == STDSChallengeSelectionStyleSingle) { + challengeRow.selected = YES; + } + + [challengeRows addObject:challengeRow]; + } + + return [challengeRows copy]; +} + +- (void)_rowWasSelected:(STDSChallengeResponseSelectionRow *)selectedRow { + switch (self.selectionStyle) { + case STDSChallengeSelectionStyleSingle: + for (STDSChallengeResponseSelectionRow *row in self.challengeSelectionRows) { + row.selected = row == selectedRow; + } + + break; + case STDSChallengeSelectionStyleMulti: + selectedRow.selected = !selectedRow.isSelected; + break; + } +} + +- (NSArray> *)currentlySelectedChallengeInfo { + NSMutableArray *selectedChallengeInfo = [NSMutableArray array]; + + for (STDSChallengeResponseSelectionRow *selectionRow in self.challengeSelectionRows) { + if (selectionRow.isSelected) { + [selectedChallengeInfo addObject:selectionRow.challengeSelectInfo]; + } + } + + return [selectedChallengeInfo copy]; +} + +- (void)setLabelCustomization:(STDSLabelCustomization *)labelCustomization { + _labelCustomization = labelCustomization; + + for (STDSChallengeResponseSelectionRow *row in self.challengeSelectionRows) { + row.labelCustomization = labelCustomization; + } +} + +- (void)setSelectionCustomization:(STDSSelectionCustomization *)selectionCustomization { + _selectionCustomization = selectionCustomization; + + for (STDSChallengeResponseSelectionRow *row in self.challengeSelectionRows) { + row.selectionCustomization = selectionCustomization; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDebuggerChecker.h b/Stripe3DS2/Stripe3DS2/STDSDebuggerChecker.h new file mode 100644 index 00000000..a50689f9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDebuggerChecker.h @@ -0,0 +1,19 @@ +// +// STDSDebuggerChecker.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDebuggerChecker : NSObject + ++ (BOOL)processIsCurrentlyAttachedToDebugger; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDebuggerChecker.m b/Stripe3DS2/Stripe3DS2/STDSDebuggerChecker.m new file mode 100644 index 00000000..cbe3a059 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDebuggerChecker.m @@ -0,0 +1,54 @@ +// +// STDSDebuggerChecker.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDebuggerChecker.h" + +#include +#include +#include +#include +#include + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSDebuggerChecker + +// This checking code has been lifted from the apple documentation on how to determine if you're attached to a debugger: https://developer.apple.com/library/archive/qa/qa1361/_index.html ++ (BOOL)processIsCurrentlyAttachedToDebugger { + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + assert(junk == 0); + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformation.h b/Stripe3DS2/Stripe3DS2/STDSDeviceInformation.h new file mode 100644 index 00000000..d73e1872 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformation.h @@ -0,0 +1,21 @@ +// +// STDSDeviceInformation.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDeviceInformation : NSObject + +- (instancetype)initWithDictionary:(NSDictionary *)deviceInformationDict; + +@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformation.m b/Stripe3DS2/Stripe3DS2/STDSDeviceInformation.m new file mode 100644 index 00000000..92425435 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformation.m @@ -0,0 +1,30 @@ +// +// STDSDeviceInformation.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDeviceInformation.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSDeviceInformation + +- (instancetype)initWithDictionary:(NSDictionary *)deviceInformationDict { + self = [super init]; + if (self) { + _dictionaryValue = [deviceInformationDict copy]; + } + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ : %@", [super description], _dictionaryValue]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformationManager.h b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationManager.h new file mode 100644 index 00000000..b23abfed --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationManager.h @@ -0,0 +1,23 @@ +// +// STDSDeviceInformationManager.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSDeviceInformation; +@class STDSWarning; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDeviceInformationManager : NSObject + ++ (STDSDeviceInformation *)deviceInformationWithWarnings:(NSArray *)warnings + ignoringRestrictions:(BOOL)ignoreRestrictions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformationManager.m b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationManager.m new file mode 100644 index 00000000..16f9f125 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationManager.m @@ -0,0 +1,65 @@ +// +// STDSDeviceInformationManager.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDeviceInformationManager.h" + +#import "STDSDeviceInformation.h" +#import "STDSDeviceInformationParameter.h" +#import "STDSWarning.h" + +NS_ASSUME_NONNULL_BEGIN + +// TC_SDK_10089_001, Req 2 & 5 +static const NSString * const k3DSDataVersion = @"1.6"; + +static const NSString * const kDataVersionKey = @"DV"; +static const NSString * const kDeviceDataKey = @"DD"; +static const NSString * const kDeviceParameterNotAvailableKey = @"DPNA"; +static const NSString * const kDeviceWarningsKey = @"SW"; + +@implementation STDSDeviceInformationManager + ++ (STDSDeviceInformation *)deviceInformationWithWarnings:(NSArray *)warnings + ignoringRestrictions:(BOOL)ignoreRestrictions { + NSMutableDictionary *deviceInformation = [NSMutableDictionary dictionaryWithObject:k3DSDataVersion forKey:kDataVersionKey]; + + for (STDSDeviceInformationParameter *parameter in [STDSDeviceInformationParameter allParameters]) { + + [parameter collectIgnoringRestrictions:ignoreRestrictions withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + if (collected) { + NSMutableDictionary *deviceData = deviceInformation[kDeviceDataKey]; + if (deviceData == nil) { + deviceData = [NSMutableDictionary dictionary]; + deviceInformation[kDeviceDataKey] = deviceData; + } + deviceData[identifier] = value; + } else { + NSMutableDictionary *notAvailableData = deviceInformation[kDeviceParameterNotAvailableKey]; + if (notAvailableData == nil) { + notAvailableData = [NSMutableDictionary dictionary]; + deviceInformation[kDeviceParameterNotAvailableKey] = notAvailableData; + } + notAvailableData[identifier] = value; + } + }]; + } + + NSMutableArray *warningIDs = [NSMutableArray arrayWithCapacity:warnings.count]; + for (STDSWarning *warning in warnings) { + [warningIDs addObject:warning.identifier]; + } + if (warningIDs.count > 0) { + deviceInformation[kDeviceWarningsKey] = [warningIDs copy]; + } + + return [[STDSDeviceInformation alloc] initWithDictionary:deviceInformation]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter+Private.h b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter+Private.h new file mode 100644 index 00000000..22d9f1a1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter+Private.h @@ -0,0 +1,76 @@ +// +// STDSDeviceInformationParameter+Private.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDeviceInformationParameter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDeviceInformationParameter (Private) + +- (instancetype)initWithIdentifier:(NSString *)identifier + permissionCheck:(nullable BOOL (^)(void))permissionCheck + valueCheck:(id _Nullable (^)(void))valueCheck; + +/// Platform: Platform that the device is using ++ (instancetype)platform; +/// Device Model: Mobile device manufacturer and model ++ (instancetype)deviceModel; +/// OS Name: Operating system name ++ (instancetype)OSName; +/// OS Version: Operating system version ++ (instancetype)OSVersion; +/// Locale: Device locale set by the user ++ (instancetype)locale; +/// Time zone: Device time zone ++ (instancetype)timeZone; +/// Advertising ID: Unique ID available for adertising and fraud detection purposes ++ (instancetype)advertisingID; +/// Screen Resolution: Pixel width and pixel height ++ (instancetype)screenResolution; +/// Device Name: User-assigned device name ++ (instancetype)deviceName; +/// IP Address: IP address of device ++ (instancetype)IPAddress; +/// Latitude: Device physical location latitude ++ (instancetype)latitude; +/// Longitude: Device physical location longitude ++ (instancetype)longitude; + +/// Identifier for Vendor: Alphanumeric string that uniquely ideitifies a device to the app's vendor ++ (instancetype)identiferForVendor; +/// UserInterfaceIdiom: Style of interface to use on the current device ++ (instancetype)userInterfaceIdiom; + +/// familyNames: an array of font family names available on the system ++ (instancetype)familyNames; +/// fontNamesForFamilyName: an array of font names available in a particular font family using the system font family ++ (instancetype)fontNamesForFamilyName; +/// systemFont: System font ++ (instancetype)systemFont; +/// labelFontSize: standard font size used for labels ++ (instancetype)labelFontSize; +/// buttonFontSize: standard font size used for buttons ++ (instancetype)buttonFontSize; +/// smallSystemFontSize: size of the standard small system font ++ (instancetype)smallSystemFontSize; +/// systemFontSize: size of the standard system font ++ (instancetype)systemFontSize; + +/// systemLocale: the ID of the generic locale that contains fixed "backstop" settings that provide values for otherwise undefined keys ++ (instancetype)systemLocale; +/// availableLocaleIdentifiers: an array of NSString objecgts, each of which identifies a locale available on the system ++ (instancetype)availableLocaleIdentifiers; +/// preferredLanguages: the user's language preference order as an array of strings ++ (instancetype)preferredLanguages; + +/// defaultTimeZone: the default time zone for the current application ++ (instancetype)defaultTimeZone; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter.h b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter.h new file mode 100644 index 00000000..46a3f66b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter.h @@ -0,0 +1,24 @@ +// +// STDSDeviceInformationParameter.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDeviceInformationParameter : NSObject + ++ (NSArray *)allParameters; + +/// Returns a UUID unique to the app version ++ (NSString *)sdkAppIdentifier; + +- (void)collectIgnoringRestrictions:(BOOL)ignoreRestrictions withHandler:(void (^)(BOOL, NSString *, id))handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter.m b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter.m new file mode 100644 index 00000000..1e35c46a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDeviceInformationParameter.m @@ -0,0 +1,527 @@ +// +// STDSDeviceInformationParameter.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDeviceInformationParameter.h" + +#import +#import + +#import "STDSIPAddress.h" +#import "STDSSynchronousLocationManager.h" +#import "STDSVisionSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +// Code value to use if the parameter is restricted by the region or market +static const NSString * const kParameterRestrictedCode = @"RE01"; +// Code value to use if the platform version does not support the parameter or the parameter has been deprecated +static const NSString * const kParameterUnavailableCode = @"RE02"; +// Code value to use if parameter collection not possible without prompting the user for permission +static const NSString * const kParameterMissingPermissionsCode = @"RE03"; +// Code value to use if parameter value returned is null or blank +static const NSString * const kParameterNilCode = @"RE04"; + +@implementation STDSDeviceInformationParameter +{ + NSString *_identifier; + BOOL (^ _Nullable _permissionCheck)(void); + id (^_valueCheck)(void); +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + permissionCheck:(nullable BOOL (^)(void))permissionCheck + valueCheck:(id (^)(void))valueCheck { + self = [super init]; + if (self) { + _identifier = [identifier copy]; + _permissionCheck = [permissionCheck copy]; + _valueCheck = [valueCheck copy]; + } + + return self; +} + +- (BOOL)_hasPermissions { + if (_permissionCheck == nil) { + return YES; + } + return _permissionCheck(); +} + +- (BOOL)_isRestricted { + static NSSet *sApprovedParameters = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sApprovedParameters = [NSSet setWithObjects: + // platform + @"C001", + // device model + @"C002", + // OS name + @"C003", + // OS version + @"C004", + // locale + @"C005", + // time zone + @"C006", + // advertising id (i.e. hardware id) + @"C007", + // screen solution + @"C008", + nil + ]; + }); + + return ![sApprovedParameters containsObject:_identifier]; +} + +- (void)collectIgnoringRestrictions:(BOOL)ignoreRestrictions withHandler:(void (^)(BOOL, NSString *, id))handler { + if (!ignoreRestrictions && [self _isRestricted]) { + handler(NO, _identifier, kParameterRestrictedCode); + return; + } else if (![self _hasPermissions]) { + handler(NO, _identifier, kParameterMissingPermissionsCode); + return; + } + + NSAssert(_valueCheck != nil, @"STDSDeviceInformationParameter should not have nil _valueCheck."); + id value = _valueCheck != nil ? _valueCheck() : nil; + + handler(value != nil, _identifier, value ?: kParameterUnavailableCode); +} + ++ (NSArray *)allParameters { + static NSArray *allParameters = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + allParameters = @[ + +#pragma mark - Common Parameters + + [STDSDeviceInformationParameter platform], + [STDSDeviceInformationParameter deviceModel], + [STDSDeviceInformationParameter OSName], + [STDSDeviceInformationParameter OSVersion], + [STDSDeviceInformationParameter locale], + [STDSDeviceInformationParameter timeZone], + [STDSDeviceInformationParameter dateTime], + [STDSDeviceInformationParameter screenResolution], + [STDSDeviceInformationParameter deviceName], + [STDSDeviceInformationParameter IPAddress], + [STDSDeviceInformationParameter latitude], + [STDSDeviceInformationParameter longitude], + [STDSDeviceInformationParameter applicationPackageName], + [STDSDeviceInformationParameter sdkAppId], + [STDSDeviceInformationParameter sdkVersion], + + +#pragma mark - iOS-Specific Parameters + + [STDSDeviceInformationParameter identiferForVendor], + [STDSDeviceInformationParameter userInterfaceIdiom], + [STDSDeviceInformationParameter familyNames], + [STDSDeviceInformationParameter fontNamesForFamilyName], + [STDSDeviceInformationParameter systemFont], + [STDSDeviceInformationParameter labelFontSize], + [STDSDeviceInformationParameter buttonFontSize], + [STDSDeviceInformationParameter smallSystemFontSize], + [STDSDeviceInformationParameter systemFontSize], + [STDSDeviceInformationParameter systemLocale], + [STDSDeviceInformationParameter availableLocaleIdentifiers], + [STDSDeviceInformationParameter preferredLanguages], + [STDSDeviceInformationParameter defaultTimeZone], + [STDSDeviceInformationParameter appStoreReciptURL], + [STDSDeviceInformationParameter appStoreReceiptExists], + ]; + + + }); + + return allParameters; +} + ++ (instancetype)platform { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C001" + permissionCheck:nil + valueCheck:^id _Nullable{ + return @"iOS"; + }]; +} + ++ (instancetype)deviceModel { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C002" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [[UIDevice currentDevice] model]; + }]; +} + ++ (instancetype)OSName { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C003" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [[UIDevice currentDevice] systemName]; + }]; +} + ++ (instancetype)OSVersion { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C004" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [[UIDevice currentDevice] systemVersion]; + }]; +} + ++ (instancetype)locale { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C005" + permissionCheck:nil + valueCheck:^id _Nullable{ + NSLocale *locale = [NSLocale currentLocale]; + NSString *language = locale.languageCode; + NSString *country = locale.countryCode; + if (language != nil && country != nil) { + return [@[language, country] componentsJoinedByString:@"-"]; + } else { + return nil; + } + }]; +} + ++ (instancetype)timeZone { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C006" + permissionCheck:nil + valueCheck:^id _Nullable{ + NSTimeZone *localTimeZone = [NSTimeZone localTimeZone]; + NSInteger secondsFromGMT = [localTimeZone secondsFromGMT]; + + // Convert the offset to minutes + NSInteger minutesFromGMT = secondsFromGMT / 60; + + NSString *utcOffsetString = [NSString stringWithFormat:@"%ld", (long)minutesFromGMT]; + + return utcOffsetString; + }]; +} + ++ (instancetype)screenResolution { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C008" + permissionCheck:nil + valueCheck:^id _Nullable{ +#if STP_TARGET_VISION + // Offer something reasonable + CGRect boundsInPixels = CGRectMake(0, 0, 512, 342); +#else + CGRect boundsInPixels = [UIScreen mainScreen].nativeBounds; +#endif + return [NSString stringWithFormat:@"%ldx%ld", (long)boundsInPixels.size.width, (long)boundsInPixels.size.height]; + + }]; +} + ++ (instancetype)deviceName +{ + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C009" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [UIDevice currentDevice].localizedModel; + }]; +} + ++ (instancetype)IPAddress { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C010" + permissionCheck:nil + valueCheck:^id _Nullable{ + return STDSCurrentDeviceIPAddress(); + }]; +} + ++ (instancetype)latitude { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C011" + permissionCheck:^BOOL{ + return [STDSSynchronousLocationManager hasPermissions]; + } + valueCheck:^id _Nullable{ + CLLocation *location = [[STDSSynchronousLocationManager sharedManager] deviceLocation]; + return location != nil ? @(location.coordinate.latitude).stringValue : nil; + }]; +} + ++ (instancetype)longitude { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C012" + permissionCheck:^BOOL{ + return [STDSSynchronousLocationManager hasPermissions]; + } + valueCheck:^id _Nullable{ + CLLocation *location = [[STDSSynchronousLocationManager sharedManager] deviceLocation]; + return location != nil ? @(location.coordinate.longitude).stringValue : nil; + }]; +} + ++ (instancetype)applicationPackageName { + /* + The unique package name/bundle identifier of the application in which the + 3DS SDK is embedded. + • iOS: obtained from the [NSBundle mainBundle] bundleIdentifier + property. + */ + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C013" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [[NSBundle mainBundle] bundleIdentifier]; + }]; +} + + ++ (instancetype)sdkAppId { + /* + Universally unique ID that is created for each installation of the 3DS + Requestor App on a Consumer Device. + Note: This should be the same ID that is passed to the Requestor App in + the AuthenticationRequestParameters object (Refer to Section + 4.12.1 in the EMV 3DS SDK Specification). + */ + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C014" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [STDSDeviceInformationParameter sdkAppIdentifier]; + }]; +} + + ++ (instancetype)sdkVersion { + /* + 3DS SDK version as applied by the implementer and stored securely in the + SDK (refer to Req 58 in the EMV 3DS SDK Specification). + */ + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C015" + permissionCheck:nil + valueCheck:^id _Nullable{ + return @"2.2.0"; + }]; +} + + + ++ (instancetype)dateTime { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"C017" + permissionCheck:nil + valueCheck:^id _Nullable{ + NSDate *currentDate = [NSDate date]; + + // Create a date formatter + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + + // Set the time zone to UTC + [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; + + // Set the desired date format: YYYYMMDDHHMMSS + [dateFormatter setDateFormat:@"yyyyMMddHHmmss"]; + + // Convert the current date to the formatted string + NSString *utcDateString = [dateFormatter stringFromDate:currentDate]; + + return utcDateString; + }]; +} + ++ (instancetype)identiferForVendor { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I001" + permissionCheck:nil + valueCheck:^id _Nullable{ + // N.B. This can return nil if the device is locked + // We've decided to mark this case and similar as parameter unavailable, + // even though we have permission and the device _can_ provide it when + // it's in a different state + return [UIDevice currentDevice].identifierForVendor.UUIDString; + }]; +} + ++ (instancetype)userInterfaceIdiom { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I002" + permissionCheck:nil + valueCheck:^id _Nullable{ + switch ([UIDevice currentDevice].userInterfaceIdiom) { + case UIUserInterfaceIdiomUnspecified: + case UIUserInterfaceIdiomVision: + return @"Unspecified"; + case UIUserInterfaceIdiomPhone: + return @"iPhone"; + case UIUserInterfaceIdiomPad: + return @"iPad"; + case UIUserInterfaceIdiomTV: + return @"TV"; + case UIUserInterfaceIdiomCarPlay: + return @"carPlay"; + case UIUserInterfaceIdiomMac: + return @"Mac"; + } + }]; +} + ++ (instancetype)familyNames { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I003" + permissionCheck:nil + valueCheck:^id _Nullable{ + return UIFont.familyNames; + }]; +} + ++ (instancetype)fontNamesForFamilyName { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I004" + permissionCheck:nil + valueCheck:^id _Nullable{ + NSArray *fontNames = [UIFont fontNamesForFamilyName:[UIFont systemFontOfSize:[UIFont systemFontSize]].familyName]; + if (fontNames.count == 0) { + return @[@""]; // Workaround for TC_SDK_10176_001 + } + return fontNames; + }]; +} + ++ (instancetype)systemFont { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I005" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [UIFont systemFontOfSize:[UIFont systemFontSize]].fontName; + }]; +} + ++ (instancetype)labelFontSize { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I006" + permissionCheck:nil + valueCheck:^id _Nullable{ + return @([UIFont labelFontSize]).stringValue; + }]; +} + ++ (instancetype)buttonFontSize { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I007" + permissionCheck:nil + valueCheck:^id _Nullable{ + return @([UIFont buttonFontSize]).stringValue; + }]; +} + ++ (instancetype)smallSystemFontSize { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I008" + permissionCheck:nil + valueCheck:^id _Nullable{ + return @([UIFont smallSystemFontSize]).stringValue; + }]; +} + ++ (instancetype)systemFontSize { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I009" + permissionCheck:nil + valueCheck:^id _Nullable{ + return @([UIFont systemFontSize]).stringValue; + }]; +} + ++ (instancetype)systemLocale { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I010" + permissionCheck:nil + valueCheck:^id _Nullable{ + NSLocale *locale = [NSLocale systemLocale]; + NSString *language = locale.languageCode; + NSString *country = locale.countryCode; + if (language != nil && country != nil) { + return [@[language, country] componentsJoinedByString:@"-"]; + } else { + return nil; + } + }]; +} + ++ (instancetype)availableLocaleIdentifiers { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I011" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [NSLocale availableLocaleIdentifiers]; + }]; +} + ++ (instancetype)preferredLanguages { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I012" + permissionCheck:nil + valueCheck:^id _Nullable{ + return [NSLocale preferredLanguages]; + }]; +} + ++ (instancetype)defaultTimeZone { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I013" + permissionCheck:nil + valueCheck:^id _Nullable{ + NSTimeZone *defaultTimeZone = [NSTimeZone defaultTimeZone]; + NSInteger secondsFromGMT = [defaultTimeZone secondsFromGMT]; + + // Convert the offset to minutes + NSInteger minutesFromGMT = secondsFromGMT / 60; + + NSString *utcOffsetString = [NSString stringWithFormat:@"%ld", (long)minutesFromGMT]; + + return utcOffsetString; + }]; +} + ++ (instancetype)appStoreReciptURL { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I014" + permissionCheck:nil + valueCheck:^id _Nullable { + NSString *appStoreReceiptURL = [[NSBundle mainBundle] appStoreReceiptURL].absoluteString; + if (appStoreReceiptURL) { + return appStoreReceiptURL; + } + return kParameterNilCode; + }]; +} + ++ (instancetype)appStoreReceiptExists { + return [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"I015" + permissionCheck:nil + valueCheck:^id _Nullable { + // Get the receipt file URL from the app's bundle + NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + + // Check if the file exists and is non-empty + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]; + BOOL isFileNonEmpty = [[[NSFileManager defaultManager] attributesOfItemAtPath:[receiptURL path] error:nil] fileSize] > 0; + + // Return "true" if the receipt file exists and is non-empty, otherwise "false" + if (fileExists && isFileNonEmpty) { + return @"true"; + } else { + return @"false"; + } + }]; +} + ++ (NSString *)sdkAppIdentifier { + static NSString * const appIdentifierKeyPrefix = @"STDSStripe3DS2AppIdentifierKey"; + NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"] ?: @""; + NSString *appIdentifierUserDefaultsKey = [appIdentifierKeyPrefix stringByAppendingString:appVersion]; + NSString *appIdentifier = [[NSUserDefaults standardUserDefaults] stringForKey:appIdentifierUserDefaultsKey]; + if (appIdentifier == nil) { + appIdentifier = [[NSUUID UUID] UUIDString].lowercaseString; + // Clean up any previous app identifiers + NSSet *previousKeys = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] keysOfEntriesPassingTest:^BOOL (NSString *key, id obj, BOOL *stop) { + return [key hasPrefix:appIdentifierKeyPrefix] && ![key isEqualToString:appIdentifierUserDefaultsKey]; + }]; + for (NSString *key in previousKeys) { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; + } + } + [[NSUserDefaults standardUserDefaults] setObject:appIdentifier forKey:appIdentifierUserDefaultsKey]; + return appIdentifier; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDirectoryServer.h b/Stripe3DS2/Stripe3DS2/STDSDirectoryServer.h new file mode 100644 index 00000000..ce903303 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDirectoryServer.h @@ -0,0 +1,132 @@ +// +// Header.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSInteger, STDSDirectoryServer) { + STDSDirectoryServerULTestRSA, + STDSDirectoryServerULTestEC, + STDSDirectoryServerSTPTestRSA, + STDSDirectoryServerSTPTestEC, + STDSDirectoryServerAmex, + STDSDirectoryServerCartesBancaires, + STDSDirectoryServerDiscover, + STDSDirectoryServerMastercard, + STDSDirectoryServerVisa, + STDSDirectoryServerCustom, + STDSDirectoryServerUnknown, +}; + +static NSString * const kULTestRSADirectoryServerID = @"F055545342"; +static NSString * const kULTestECDirectoryServerID = @"F155545342"; + +static NSString * const kSTDSTestRSADirectoryServerID = @"ul_test"; +static NSString * const kSTDSTestECDirectoryServerID = @"ec_test"; + +static NSString * const kSTDSAmexDirectoryServerID = @"A000000025"; +static NSString * const kSTDSCartesBancairesServerID = @"A000000042"; +static NSString * const kSTDSDiscoverDirectoryServerID = @"A000000324"; +static NSString * const kSTDSDiscoverDirectoryServerID_2 = @"A000000152"; +static NSString * const kSTDSMastercardDirectoryServerID = @"A000000004"; +static NSString * const kSTDSVisaDirectoryServerID = @"A000000003"; + + +/// Returns the typed directory server enum or STDSDirectoryServerUnknown if the directoryServerID is not recognized +NS_INLINE STDSDirectoryServer STDSDirectoryServerForID(NSString *directoryServerID) { + if ([directoryServerID isEqualToString:kULTestRSADirectoryServerID]) { + return STDSDirectoryServerULTestRSA; + } else if ([directoryServerID isEqualToString:kULTestECDirectoryServerID]) { + return STDSDirectoryServerULTestEC; + } else if ([directoryServerID isEqualToString:kSTDSTestRSADirectoryServerID]) { + return STDSDirectoryServerSTPTestRSA; + } else if ([directoryServerID isEqualToString:kSTDSTestECDirectoryServerID]) { + return STDSDirectoryServerSTPTestEC; + } else if ([directoryServerID isEqualToString:kSTDSAmexDirectoryServerID]) { + return STDSDirectoryServerAmex; + } else if ([directoryServerID isEqualToString:kSTDSDiscoverDirectoryServerID] || [directoryServerID isEqualToString:kSTDSDiscoverDirectoryServerID_2]) { + return STDSDirectoryServerDiscover; + } else if ([directoryServerID isEqualToString:kSTDSMastercardDirectoryServerID]) { + return STDSDirectoryServerMastercard; + } else if ([directoryServerID isEqualToString:kSTDSVisaDirectoryServerID]) { + return STDSDirectoryServerVisa; + } else if ([directoryServerID isEqualToString:kSTDSCartesBancairesServerID]) { + return STDSDirectoryServerCartesBancaires; + } + + return STDSDirectoryServerUnknown; +} + +/// Returns the directory server ID or nil for STDSDirectoryServerUnknown +NS_INLINE NSString * _Nullable STDSDirectoryServerIdentifier(STDSDirectoryServer directoryServer) { + switch (directoryServer) { + case STDSDirectoryServerULTestRSA: + return kULTestRSADirectoryServerID; + + case STDSDirectoryServerULTestEC: + return kULTestECDirectoryServerID; + + case STDSDirectoryServerSTPTestRSA: + return kSTDSTestRSADirectoryServerID; + + case STDSDirectoryServerSTPTestEC: + return kSTDSTestECDirectoryServerID; + + case STDSDirectoryServerAmex: + return kSTDSAmexDirectoryServerID; + + case STDSDirectoryServerDiscover: + return kSTDSDiscoverDirectoryServerID; + + case STDSDirectoryServerMastercard: + return kSTDSMastercardDirectoryServerID; + + case STDSDirectoryServerVisa: + return kSTDSVisaDirectoryServerID; + + case STDSDirectoryServerCartesBancaires: + return kSTDSCartesBancairesServerID; + + case STDSDirectoryServerCustom: + return nil; + + case STDSDirectoryServerUnknown: + return nil; + } +} + +/// Returns the directory server image name if one exists +NS_INLINE NSString * _Nullable STDSDirectoryServerImageName(STDSDirectoryServer directoryServer) { + switch (directoryServer) { + case STDSDirectoryServerAmex: + return @"amex-logo"; + case STDSDirectoryServerDiscover: + return @"discover-logo"; + case STDSDirectoryServerMastercard: + return @"mastercard-logo"; + case STDSDirectoryServerCartesBancaires: + return @"cartes-bancaires-logo"; + // just default to an arbitrary logo for the test servers + case STDSDirectoryServerULTestEC: + case STDSDirectoryServerULTestRSA: + case STDSDirectoryServerSTPTestRSA: + case STDSDirectoryServerSTPTestEC: + case STDSDirectoryServerVisa: + if ([[UITraitCollection currentTraitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark) { + return @"visa-white-logo"; + } + return @"visa-logo"; + case STDSDirectoryServerCustom: + case STDSDirectoryServerUnknown: + return nil; + + } +} + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate+Internal.h b/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate+Internal.h new file mode 100644 index 00000000..8f45d98a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate+Internal.h @@ -0,0 +1,22 @@ +// +// STDSDirectoryServerCertificate+Internal.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/2/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDirectoryServerCertificate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDirectoryServerCertificate (Internal) + +/// Verifies the certificate chain represented by certificates where each element is a base64 encoded (NOT base64url) certificate ++ (BOOL)_verifyCertificateChain:(NSArray *)certificates withRootCertificates:(NSArray *)rootCertificates; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate.h b/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate.h new file mode 100644 index 00000000..4ac91b09 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate.h @@ -0,0 +1,43 @@ +// +// STDSDirectoryServerCertificate.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSJSONWebSignature; + +#import "STDSDirectoryServer.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, STDSDirectoryServerKeyType) { + STDSDirectoryServerKeyTypeRSA, + STDSDirectoryServerKeyTypeEC, + STDSDirectoryServerKeyTypeUnknown, +}; + +@interface STDSDirectoryServerCertificate : NSObject + ++ (nullable instancetype)certificateForDirectoryServer:(STDSDirectoryServer)directoryServer; + ++ (nullable instancetype)customCertificateWithData:(NSData *)certificateData; + ++ (nullable instancetype)customCertificateWithString:(NSString *)certificateString; + +@property (nonatomic, readonly) STDSDirectoryServerKeyType keyType; + +@property (nonatomic, readonly) SecKeyRef publicKey; + +@property (nonatomic, readonly, copy) NSString *certificateString; + +- (nullable NSData *)encryptDataUsingRSA_OAEP_SHA256:(NSData *)plaintext; + ++ (BOOL)verifyJSONWebSignature:(STDSJSONWebSignature *)jws withRootCertificates:(NSArray *)rootCertificates; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate.m b/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate.m new file mode 100644 index 00000000..b5d180dd --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSDirectoryServerCertificate.m @@ -0,0 +1,326 @@ +// +// STDSDirectoryServerCertificate.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDirectoryServerCertificate.h" +#import "STDSDirectoryServerCertificate+Internal.h" + +#import "NSData+JWEHelpers.h" +#import "NSString+JWEHelpers.h" +#import "STDSEllipticCurvePoint.h" +#import "STDSJSONWebSignature.h" +#import "STDSSecTypeUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDirectoryServerCertificate () +{ + SecCertificateRef _certificate; + STDSDirectoryServer _directoryServer; +} + +- (instancetype)_initForDirectoryServer:(STDSDirectoryServer)directoryServer; + +@end + +@implementation STDSDirectoryServerCertificate + +- (instancetype)_initWithCertificate:(SecCertificateRef _Nullable)certificate forDirectorySever:(STDSDirectoryServer)directoryServer { + self = [super init]; + if (self) { + _certificate = certificate; + switch (directoryServer) { + + case STDSDirectoryServerULTestRSA: { + /** + UL provides the following, which is PKCS#8, but Security framework wants PKCS#1. Luckily all we have to do is remove the first 32 characters which are just a header to convert + @"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr/O0BfXWngO9OJDBsqdR\n5U2h28jrX6Y+LlblTBaYeT2tW7+ca3YzTFXA8duVUwdlWxl3JZCOOeL1feVP6g0TNOHVCkCnirVDLkcozod4aSkNvx+929aDr1ithqhruf0skBc2sMZGBBCNpso6XGzyAf2uZ2+9DvXoKIUYgcr7PQmL2Y0awyQN7KCRcusaotYNz2mOPrL/hAv6hTexkNrQ\nKzFcPwCuc6kN6aNjD+p2CJ51/5p02SNS70nPOmwmg63j6f3n7xVykQ56kNc1l5B5xOpeHJmqk3+hyF1dF/47rQmMFicN41QSvZ5AZJKgWlIn2VQROMkEHkF9ZBRLx1nF\nTwIDAQAB\n-----END PUBLIC KEY-----\n" + */ + static NSString * const kULTestRSAPublicKey = @"MIIBCgKCAQEAr/O0BfXWngO9OJDBsqdR\n5U2h28jrX6Y+LlblTBaYeT2tW7+ca3YzTFXA8duVUwdlWxl3JZCOOeL1feVP6g0TNOHVCkCnirVDLkcozod4aSkNvx+929aDr1ithqhruf0skBc2sMZGBBCNpso6XGzyAf2uZ2+9DvXoKIUYgcr7PQmL2Y0awyQN7KCRcusaotYNz2mOPrL/hAv6hTexkNrQ\nKzFcPwCuc6kN6aNjD+p2CJ51/5p02SNS70nPOmwmg63j6f3n7xVykQ56kNc1l5B5xOpeHJmqk3+hyF1dF/47rQmMFicN41QSvZ5AZJKgWlIn2VQROMkEHkF9ZBRLx1nF\nTwIDAQAB"; + + NSString *cleanedString = [[[kULTestRSAPublicKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@""]; + + NSData *base64Decoded = [[NSData alloc] initWithBase64EncodedString:cleanedString options:0]; + NSDictionary *attributes = @{ + (__bridge NSString *)kSecAttrKeyType: (__bridge NSString *)kSecAttrKeyTypeRSA, + (__bridge NSString *)kSecAttrKeyClass: (__bridge NSString *)kSecAttrKeyClassPublic, + }; + CFErrorRef error = NULL; + SecKeyRef key = SecKeyCreateWithData((__bridge CFDataRef)base64Decoded, (__bridge CFDictionaryRef)attributes, &error); + if (key == NULL) { + return nil; + } + _publicKey = key; + } + break; + + case STDSDirectoryServerULTestEC: { + static NSString * const kULTestECPublicKey = @"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYktbLuAv0v52erE5LPscomKaOmQs\nvevxzOyn9k4sF1hqpBc5kUygzxA9Jl0R/2dTuk8ka7UCujk36xeUsLVpWA=="; + NSString *cleanedString = [[[kULTestECPublicKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@""]; + NSData *base64Decoded = [[NSData alloc] initWithBase64EncodedString:cleanedString options:0]; + // This data is PEM encoded, to get to ec standard we take the last 65 bytes + if (base64Decoded.length >= 65) { + base64Decoded = [base64Decoded subdataWithRange:NSMakeRange(base64Decoded.length - 65, 65)]; + } + NSDictionary *attributes = @{ + (__bridge NSString *)kSecAttrKeyType: (__bridge NSString *)kSecAttrKeyTypeECSECPrimeRandom, + (__bridge NSString *)kSecAttrKeyClass: (__bridge NSString *)kSecAttrKeyClassPublic, + }; + CFErrorRef error = NULL; + SecKeyRef key = SecKeyCreateWithData((__bridge CFDataRef)base64Decoded, (__bridge CFDictionaryRef)attributes, &error); + if (key == NULL) { + return nil; + } + _publicKey = key; + } + break; + + case STDSDirectoryServerSTPTestRSA: + // fall-through + case STDSDirectoryServerSTPTestEC: + // fall-through + case STDSDirectoryServerAmex: + // fall-through + case STDSDirectoryServerCartesBancaires: + // fall-through + case STDSDirectoryServerDiscover: + // fall-through + case STDSDirectoryServerMastercard: + // fall-through + case STDSDirectoryServerVisa: + // fall-through + case STDSDirectoryServerCustom: + // fall-through + case STDSDirectoryServerUnknown: + NSAssert(certificate != NULL, @"Must provide a certificate"); + _publicKey = SecCertificateCopyKey(certificate); + } + _directoryServer = directoryServer; + + if (_publicKey == NULL) { + return nil; + } + } + + return self; +} + +- (instancetype)_initForDirectoryServer:(STDSDirectoryServer)directoryServer { + SecCertificateRef certificate = NULL; + + switch (directoryServer) { + case STDSDirectoryServerULTestRSA: + // fall-through + case STDSDirectoryServerULTestEC: + // The UL test servers don't actually have certificates, just hard-coded key values + break; + + case STDSDirectoryServerSTPTestRSA: + // fall-through + case STDSDirectoryServerSTPTestEC: + // fall-through + case STDSDirectoryServerAmex: + // fall-through + case STDSDirectoryServerCartesBancaires: + // fall-through + case STDSDirectoryServerDiscover: + // fall-through; + case STDSDirectoryServerMastercard: + // fall-through + case STDSDirectoryServerVisa: { + certificate = STDSCertificateForServer(directoryServer); + if (certificate == NULL) { + return nil; + } + } + break; + + case STDSDirectoryServerCustom: + return nil; + + case STDSDirectoryServerUnknown: + return nil; + } + return [self _initWithCertificate:certificate forDirectorySever:directoryServer]; +} + ++ (nullable instancetype)certificateForDirectoryServer:(STDSDirectoryServer)directoryServer { + return [[self alloc] _initForDirectoryServer:directoryServer]; +} + ++ (nullable instancetype)customCertificateWithData:(NSData *)certificateData { + SecCertificateRef certificate = STDSSecCertificateFromData(certificateData); + if (certificate == NULL) { + return nil; + } + return [[self alloc] _initWithCertificate:certificate forDirectorySever:STDSDirectoryServerCustom]; +} + ++ (nullable instancetype)customCertificateWithString:(NSString *)certificateString { + SecCertificateRef certificate = STDSSecCertificateFromString(certificateString); + if (certificate == NULL) { + return nil; + } + + return [[self alloc] _initWithCertificate:certificate forDirectorySever:STDSDirectoryServerCustom]; +} + +- (void)dealloc { + if (_certificate != NULL) { + CFRelease(_certificate); + } + if (_publicKey != NULL) { + CFRelease(_publicKey); + } +} + +- (NSString *)certificateString { + NSData *data = (NSData *)CFBridgingRelease(SecCertificateCopyData(_certificate)); + return [data base64EncodedStringWithOptions:0]; +} + +- (STDSDirectoryServerKeyType)keyType { + switch (_directoryServer) { + case STDSDirectoryServerULTestRSA: + return STDSDirectoryServerKeyTypeRSA; + + case STDSDirectoryServerULTestEC: + return STDSDirectoryServerKeyTypeEC; + + + case STDSDirectoryServerSTPTestRSA: + // fall-through + case STDSDirectoryServerSTPTestEC: + // fall-through + case STDSDirectoryServerAmex: + // fall-through + case STDSDirectoryServerCartesBancaires: + // fall-through + case STDSDirectoryServerDiscover: + // fall-through; + case STDSDirectoryServerMastercard: + // fall-through + case STDSDirectoryServerVisa: + // fall-through + case STDSDirectoryServerCustom: { + NSAssert(_certificate != NULL, @"Must have a valid certificate file"); + if (_certificate == NULL) { + return STDSDirectoryServerKeyTypeUnknown; + } + CFStringRef keyType = STDSSecCertificateCopyPublicKeyType(_certificate); + STDSDirectoryServerKeyType ret = STDSDirectoryServerKeyTypeUnknown; + if (keyType != NULL) { + if (CFStringCompare(keyType, kSecAttrKeyTypeRSA, 0) == kCFCompareEqualTo) { + ret = STDSDirectoryServerKeyTypeRSA; + } else if (CFStringCompare(keyType, kSecAttrKeyTypeECSECPrimeRandom, 0) == kCFCompareEqualTo) { + ret = STDSDirectoryServerKeyTypeEC; + } + + CFRelease(keyType); + } + return ret; + } + + case STDSDirectoryServerUnknown: + NSAssert(0, @"Should not have an STDSDirectoryServerCertificate instance withSTPDirectoryServerUnknown"); + return STDSDirectoryServerKeyTypeUnknown; + } +} + +- (nullable NSData *)encryptDataUsingRSA_OAEP_SHA256:(NSData *)plaintext { + NSAssert(_publicKey != NULL, @"STDSDirectoryServerCertificate should always have _publicKey"); + if (_publicKey == NULL) { + return nil; + } + + CFDataRef encryptedData = SecKeyCreateEncryptedData(_publicKey, + kSecKeyAlgorithmRSAEncryptionOAEPSHA256, + (CFDataRef)plaintext, + NULL); + return (NSData *)CFBridgingRelease(encryptedData); +} + ++ (BOOL)_verifyCertificateChain:(NSArray *)certificatesStrings withRootCertificates:(NSArray *)rootCertificateStrings { + if (certificatesStrings.count == 0 || rootCertificateStrings.count == 0) { + return NO; + } + + NSMutableArray *certificates = [[NSMutableArray alloc] initWithCapacity:certificatesStrings.count]; + for (NSString *certificateString in certificatesStrings) { + SecCertificateRef certificate = STDSSecCertificateFromString(certificateString); + if (certificate == NULL) { + return NO; + } + [certificates addObject:(id)CFBridgingRelease(certificate)]; + } + + NSMutableArray *rootCertificates = [[NSMutableArray alloc] initWithCapacity:rootCertificateStrings.count]; + for (NSString *certificateString in rootCertificateStrings) { + SecCertificateRef certificate = STDSSecCertificateFromString(certificateString); + if (certificate == NULL) { + return NO; + } + [rootCertificates addObject:(id)CFBridgingRelease(certificate)]; + } + + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustRef trust; + OSStatus status = SecTrustCreateWithCertificates((__bridge CFTypeRef)certificates, + policy, + &trust); + if (policy) { + CFRelease(policy); + } + if (status != errSecSuccess) { + return NO; + } + if (rootCertificates.count > 0) { + status = SecTrustSetAnchorCertificates(trust, (__bridge CFTypeRef)rootCertificates); + if (status != errSecSuccess) { + return NO; + } + } + + CFErrorRef error = NULL; + + bool verified = SecTrustEvaluateWithError(trust, &error); + return (BOOL)verified; +} + ++ (BOOL)verifyJSONWebSignature:(STDSJSONWebSignature *)jws withRootCertificates:(NSArray *)rootCertificates { + if (jws.certificateChain.count == 0 || ![self _verifyCertificateChain:jws.certificateChain withRootCertificates:rootCertificates]) { + return NO; + } + + switch (jws.algorithm) { + case STDSJSONWebSignatureAlgorithmES256: + return STDSVerifyEllipticCurveP256Signature(jws.ellipticCurvePoint.x, jws.ellipticCurvePoint.y, jws.digest, jws.signature); + + case STDSJSONWebSignatureAlgorithmPS256: { + if (jws.certificateChain.count == 0) { + return NO; + } + NSString *certificateString = [jws.certificateChain firstObject]; + SecCertificateRef certificate = STDSSecCertificateFromString(certificateString); + if (certificate == NULL) { + return NO; + } + + BOOL verified = STDSVerifyRSASignature(certificate, jws.digest, jws.signature); + CFRelease(certificate); + return verified; + } + + + case STDSJSONWebSignatureAlgorithmUnknown: + return NO; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSEllipticCurvePoint.h b/Stripe3DS2/Stripe3DS2/STDSEllipticCurvePoint.h new file mode 100644 index 00000000..c856951d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSEllipticCurvePoint.h @@ -0,0 +1,26 @@ +// +// STDSEllipticCurvePoint.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSEllipticCurvePoint : NSObject + +- (nullable instancetype)initWithX:(NSData *)x y:(NSData *)y; +- (nullable instancetype)initWithCertificateData:(NSData *)certificateData; +- (nullable instancetype)initWithKey:(SecKeyRef)key; +- (nullable instancetype)initWithJWK:(NSDictionary *)jwk; + +@property (nonatomic, readonly) NSData *x; +@property (nonatomic, readonly) NSData *y; + +@property (nonatomic, readonly) SecKeyRef publicKey; + +@end +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSEllipticCurvePoint.m b/Stripe3DS2/Stripe3DS2/STDSEllipticCurvePoint.m new file mode 100644 index 00000000..49fdf3e5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSEllipticCurvePoint.m @@ -0,0 +1,90 @@ +// +// STDSEllipticCurvePoint.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSEllipticCurvePoint.h" + +#import "NSDictionary+DecodingHelpers.h" +#import "NSString+JWEHelpers.h" +#import "STDSSecTypeUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSEllipticCurvePoint + +- (nullable instancetype)initWithX:(NSData *)x y:(NSData *)y { + self = [super init]; + if (self) { + _x = x; + _y = y; + _publicKey = STDSSecKeyRefFromCoordinates(x, y); + if (_publicKey == NULL) { + return nil; + } + } + + return self; +} + +- (nullable instancetype)initWithKey:(SecKeyRef)key { + self = [super init]; + if (self) { + _publicKey = key; + CFErrorRef error = NULL; + NSData *keyData = (NSData *)CFBridgingRelease(SecKeyCopyExternalRepresentation(key, &error)); + if (keyData == nil) { + return nil; + } + + NSUInteger coordinateLength = (keyData.length - 1) / 2; // -1 because the first byte is formatting 0x04 + NSData *xData = [keyData subdataWithRange:NSMakeRange(1, coordinateLength)]; + NSData *yData = [keyData subdataWithRange:NSMakeRange(1 + coordinateLength, coordinateLength)]; + _x = xData; + _y = yData; + } + + return self; +} + +- (nullable instancetype)initWithCertificateData:(NSData *)certificateData { + SecCertificateRef certificate = STDSSecCertificateFromData(certificateData); + if (certificateData != NULL) { + SecKeyRef key = SecCertificateCopyKey(certificate); + CFRelease(certificate); + if (key != NULL) { + STDSEllipticCurvePoint *point = [self initWithKey:key]; + CFRelease(key); + return point; + } + } + return nil; +} + +- (nullable instancetype)initWithJWK:(NSDictionary *)jwk { + NSString *kty = [jwk _stds_stringForKey:@"kty" validator:^BOOL(NSString * _Nonnull val) { + return [val isEqualToString:@"EC"]; + } required:YES error:NULL]; + NSString *crv = [jwk _stds_stringForKey:@"crv" validator:^BOOL(NSString * _Nonnull val) { + return [val isEqualToString:@"P-256"]; + } required:YES error:NULL]; + + NSData *coordinateX = [[jwk _stds_stringForKey:@"x" required:YES error:NULL] _stds_base64URLDecodedData]; + NSData *coordinateY = [[jwk _stds_stringForKey:@"y" required:YES error:NULL] _stds_base64URLDecodedData]; + + if (kty == nil || + crv == nil || + coordinateX == nil || + coordinateY == nil + ) { + return nil; + } + return [self initWithX:coordinateX y:coordinateY]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair+Testing.h b/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair+Testing.h new file mode 100644 index 00000000..d8efb11d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair+Testing.h @@ -0,0 +1,19 @@ +// +// STDSEphemeralKeyPair+Testing.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSEphemeralKeyPair.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSEphemeralKeyPair (Testing) + ++ (nullable instancetype)testKeyPair; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair.h b/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair.h new file mode 100644 index 00000000..515fbb58 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair.h @@ -0,0 +1,39 @@ +// +// STDSEphemeralKeyPair.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSDirectoryServerCertificate; +@class STDSEllipticCurvePoint; + +#import "STDSDirectoryServer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSEphemeralKeyPair : NSObject + +/// Creates a returns a new elliptic curve key pair using curve P-256 ++ (nullable instancetype)ephemeralKeyPair; + +- (instancetype)init NS_UNAVAILABLE; + +@property (nonatomic, readonly) NSString *publicKeyJWK; +@property (nonatomic, readonly) STDSEllipticCurvePoint *publicKeyCurvePoint; + +/** + Creates and returns a new secret key derived using Elliptic Curve Diffie-Hellman + and the certificate's public key (return nil on failure). + Per OpenSSL documentation: Never use a derived secret directly. Typically it is passed through some + hash function to produce a key (e.g. pass the secret as the first argument to STDSCreateConcatKDFWithSHA256) + */ +- (nullable NSData *)createSharedSecretWithEllipticCurveKey:(STDSEllipticCurvePoint *)ecKey; +- (nullable NSData *)createSharedSecretWithCertificate:(STDSDirectoryServerCertificate *)certificate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair.m b/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair.m new file mode 100644 index 00000000..426ebc5a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSEphemeralKeyPair.m @@ -0,0 +1,108 @@ +// +// STDSEphemeralKeyPair.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSEphemeralKeyPair.h" + +#import "NSData+JWEHelpers.h" +#import "NSDictionary+DecodingHelpers.h" +#import "NSString+JWEHelpers.h" +#import "STDSDirectoryServerCertificate.h" +#import "STDSEllipticCurvePoint.h" +#import "STDSSecTypeUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSEphemeralKeyPair () +{ + SecKeyRef _privateKey; + SecKeyRef _publicKey; +} + +@end + + +@implementation STDSEphemeralKeyPair + +- (instancetype)_initWithPrivateKey:(SecKeyRef)privateKey publicKey:(SecKeyRef)publicKey { + self = [super init]; + if (self) { + _privateKey = privateKey; + _publicKey = publicKey; + } + + return self; +} + ++ (nullable instancetype)ephemeralKeyPair { + NSDictionary *parameters = @{ + (__bridge NSString *)kSecAttrKeyType: (__bridge NSString *)kSecAttrKeyTypeECSECPrimeRandom, + (__bridge NSString *)kSecAttrKeySizeInBits: @(256), + }; + CFErrorRef error = NULL; + SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)parameters, &error); + + if (privateKey != NULL) { + SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); + return [[self alloc] _initWithPrivateKey:privateKey publicKey:publicKey]; + } + + return nil; +} + ++ (nullable instancetype)testKeyPair { + + // values from EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSData *d = [@"iyn--IbkBeNoPu8cN245L6pOQWt2lTH8V0Ds92jQmWA" _stds_base64URLDecodedData]; + NSData *x = [@"C1PL42i6kmNkM61aupEAgLJ4gF1ZRzcV7lqo1TG0mL4" _stds_base64URLDecodedData]; + NSData *y = [@"cNToWLSdcFQKG--PGVEUQrIHP8w6TcRyj0pyFx4-ZMc" _stds_base64URLDecodedData]; + + SecKeyRef privateKey = STDSPrivateSecKeyRefFromCoordinates(x, y, d); + if (privateKey == NULL) { + return nil; + } + SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); + if (publicKey == NULL) { + return nil; + } + + return [[STDSEphemeralKeyPair alloc] _initWithPrivateKey:privateKey publicKey:publicKey]; +} + +- (void)dealloc { + if (_privateKey != NULL) { + CFRelease(_privateKey); + } + if (_publicKey != NULL) { + CFRelease(_publicKey); + } +} + +- (NSString *)publicKeyJWK { + STDSEllipticCurvePoint *publicKeyCurvePoint = [[STDSEllipticCurvePoint alloc] initWithKey:_publicKey]; + return [NSString stringWithFormat:@"{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"%@\",\"y\":\"%@\"}", [publicKeyCurvePoint.x _stds_base64URLEncodedString], [publicKeyCurvePoint.y _stds_base64URLEncodedString]]; +} + +- (nullable NSData *)createSharedSecretWithEllipticCurveKey:(STDSEllipticCurvePoint *)ecKey { + return [self _createSharedSecretWithPrivateKey:_privateKey publicKey:ecKey.publicKey]; +} + +- (nullable NSData *)createSharedSecretWithCertificate:(STDSDirectoryServerCertificate *)certificate { + return [self _createSharedSecretWithPrivateKey:_privateKey publicKey:certificate.publicKey]; +} + +- (nullable NSData *)_createSharedSecretWithPrivateKey:(SecKeyRef)privateKey publicKey:(SecKeyRef)publicKey { + NSDictionary *params = @{(__bridge NSString *)kSecKeyKeyExchangeParameterRequestedSize: @(32)}; + CFErrorRef error = NULL; + CFDataRef secret = SecKeyCopyKeyExchangeResult(privateKey, kSecKeyAlgorithmECDHKeyExchangeStandard, publicKey, (__bridge CFDictionaryRef)params, &error); + + return (NSData *)CFBridgingRelease(secret); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSErrorMessage+Internal.h b/Stripe3DS2/Stripe3DS2/STDSErrorMessage+Internal.h new file mode 100644 index 00000000..3a0730c3 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSErrorMessage+Internal.h @@ -0,0 +1,40 @@ +// +// STDSErrorMessage+Internal.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 4/9/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSErrorMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +// Constructors for the circumstances in which we are required to send an ErrorMessage to the ACS +@interface STDSErrorMessage (Internal) + +/// Received an invalid message type ++ (instancetype)errorForInvalidMessageWithACSTransactionID:(NSString *)acsTransactionID + messageVersion:(NSString *)messageVersion; + +/// Encountered an invalid field parsing a JSON response ++ (nullable instancetype)errorForJSONFieldInvalidWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion error:(NSError *)error; + +/// Encountered a missing field parsing a JSON response ++ (nullable instancetype)errorForJSONFieldMissingWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion error:(NSError *)error; + +/// Encountered an error decrypting a networking response ++ (instancetype)errorForDecryptionErrorWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion; + +/// Timed out ++ (instancetype)errorForTimeoutWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion; + ++ (instancetype)errorForUnrecognizedIDWithACSTransactionID:(NSString *)transactionID messageVersion:(NSString *)messageVersion; + +/// Encountered unrecognized critical message extension(s) ++ (instancetype)errorForUnrecognizedCriticalMessageExtensionsWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion error:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSErrorMessage+Internal.m b/Stripe3DS2/Stripe3DS2/STDSErrorMessage+Internal.m new file mode 100644 index 00000000..02eed1d0 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSErrorMessage+Internal.m @@ -0,0 +1,91 @@ +// +// STDSErrorMessage+Internal.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 4/9/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSErrorMessage+Internal.h" +#import "STDSStripe3DS2Error.h" + +@implementation STDSErrorMessage (Internal) + ++ (NSString *)_stringForErrorCode:(STDSErrorMessageCode)errorCode { + return [NSString stringWithFormat:@"%ld", (long)errorCode]; +} + ++ (instancetype)errorForInvalidMessageWithACSTransactionID:(nonnull NSString *)acsTransactionID messageVersion:(nonnull NSString *)messageVersion { + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageCodeInvalidMessage] + errorComponent:@"C" + errorDescription:@"Message not recognized" + errorDetails:@"Unknown message type" + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; + +} + ++ (nullable instancetype)errorForJSONFieldMissingWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion error:(NSError *)error { + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageCodeRequiredDataElementMissing] + errorComponent:@"C" + errorDescription:@"Missing Field" + errorDetails:error.userInfo[STDSStripe3DS2ErrorFieldKey] + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; +} + ++ (nullable instancetype)errorForJSONFieldInvalidWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion error:(NSError *)error { + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageErrorInvalidDataElement] + errorComponent:@"C" + errorDescription:@"Invalid Field" + errorDetails:error.userInfo[STDSStripe3DS2ErrorFieldKey] + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; +} + ++ (instancetype)errorForDecryptionErrorWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion { + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageErrorDataDecryptionFailure] + errorComponent:@"C" + errorDescription:@"Response could not be decrypted." + errorDetails:@"Response could not be decrypted.s" + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; +} + ++ (instancetype)errorForTimeoutWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion { + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageErrorTimeout] + errorComponent:@"C" + errorDescription:@"Transaction timed out." + errorDetails:@"Transaction timed out." + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; +} + ++ (instancetype)errorForUnrecognizedIDWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion { + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageErrorTransactionIDNotRecognized] + errorComponent:@"C" + errorDescription:@"Unrecognized transaction ID" + errorDetails:@"Unrecognized transaction ID" + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; +} + ++ (instancetype)errorForUnrecognizedCriticalMessageExtensionsWithACSTransactionID:(NSString *)acsTransactionID messageVersion:(NSString *)messageVersion error:(NSError *)error { + NSArray *unrecognizedIDs = error.userInfo[STDSStripe3DS2UnrecognizedCriticalMessageExtensionsKey]; + + return [[[self class] alloc] initWithErrorCode:[self _stringForErrorCode:STDSErrorMessageCodeUnrecognizedCriticalMessageExtension] + errorComponent:@"C" + errorDescription:@"Critical message extension not recognised." + errorDetails:[unrecognizedIDs componentsJoinedByString:@","] + messageVersion:messageVersion + acsTransactionIdentifier:acsTransactionID + errorMessageType:@"CRes"]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSException+Internal.h b/Stripe3DS2/Stripe3DS2/STDSException+Internal.h new file mode 100644 index 00000000..3429aba9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSException+Internal.h @@ -0,0 +1,19 @@ +// +// STDSException+Internal.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSException.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSException (Internal) + ++ (instancetype)exceptionWithMessage:(NSString *)format, ...; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSExpandableInformationView.h b/Stripe3DS2/Stripe3DS2/STDSExpandableInformationView.h new file mode 100644 index 00000000..f6f3ee0e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSExpandableInformationView.h @@ -0,0 +1,23 @@ +// +// STDSExpandableInformationView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSFooterCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSExpandableInformationView : UIView + +@property (nonatomic, strong, nullable) NSString *title; +@property (nonatomic, strong, nullable) NSString *text; +@property (nonatomic, strong, nullable) STDSFooterCustomization *customization; +@property (nonatomic, strong, nullable) void (^didTap)(void); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSExpandableInformationView.m b/Stripe3DS2/Stripe3DS2/STDSExpandableInformationView.m new file mode 100644 index 00000000..9b1bbdfb --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSExpandableInformationView.m @@ -0,0 +1,150 @@ +// +// STDSExpandableInformationView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSLocalizedString.h" +#import "STDSBundleLocator.h" +#import "STDSExpandableInformationView.h" +#import "STDSStackView.h" +#import "UIView+LayoutSupport.h" +#import "NSString+EmptyChecking.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSExpandableInformationView() + +@property (nonatomic, strong) UIView *tappableView; +@property (nonatomic, strong) STDSStackView *textContainerView; +@property (nonatomic, strong) STDSStackView *imageViewStackView; +@property (nonatomic, strong) UIView *imageViewSpacerView; +@property (nonatomic, strong) UILabel *titleLabel; +@property (nonatomic, strong) UILabel *textLabel; +@property (nonatomic, strong) UIImageView *titleImageView; + +@end + +@implementation STDSExpandableInformationView + +static const CGFloat kTitleContainerSpacing = 20; +static const CGFloat kTextContainerSpacing = 13; +static const CGFloat kExpandableInformationViewBottomMargin = 30; +static const CGFloat kTitleImageViewRotationAnimationDuration = (CGFloat)0.2; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self _setupViewHierarchy]; + self.accessibilityIdentifier = @"STDSExpandableInformationView"; + } + + return self; +} + +- (void)_setupViewHierarchy { + self.layoutMargins = UIEdgeInsetsMake(0, 0, kExpandableInformationViewBottomMargin, 0); + + self.titleLabel = [[UILabel alloc] init]; + // Set titleLabel as not an accessibility element because we make the + // container, which is the actual control, have the same accessibility label + // and accurately reflects that interactivity and state of the control + self.titleLabel.isAccessibilityElement = NO; + self.titleLabel.numberOfLines = 0; + + self.textLabel = [[UILabel alloc] init]; + self.textLabel.numberOfLines = 0; + + UIImage *chevronImage = [[UIImage imageNamed:@"Chevron" inBundle:[STDSBundleLocator stdsResourcesBundle] compatibleWithTraitCollection:nil] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.titleImageView = [[UIImageView alloc] initWithImage:chevronImage]; + self.titleImageView.contentMode = UIViewContentModeScaleAspectFit; + + STDSStackView *containerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisHorizontal]; + [self addSubview:containerView]; + [containerView _stds_pinToSuperviewBounds]; + + STDSStackView *titleContainerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [titleContainerView addArrangedSubview:self.titleLabel]; + + self.imageViewStackView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + self.imageViewSpacerView = [UIView new]; + [self.imageViewStackView addArrangedSubview:self.titleImageView]; + + [containerView addArrangedSubview:self.imageViewStackView]; + [containerView addSpacer:kTitleContainerSpacing]; + [containerView addArrangedSubview:titleContainerView]; + [containerView addArrangedSubview:[UIView new]]; + + self.textContainerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + self.textContainerView.hidden = YES; + [self.textContainerView addSpacer:kTextContainerSpacing]; + [self.textContainerView addArrangedSubview:self.textLabel]; + [titleContainerView addArrangedSubview:self.textContainerView]; + + UITapGestureRecognizer *expandTextTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_toggleTextExpansion)]; + [containerView addGestureRecognizer:expandTextTapRecognizer]; + containerView.accessibilityTraits |= UIAccessibilityTraitButton; + containerView.isAccessibilityElement = YES; + self.tappableView = containerView; + [self _updateTappableViewAccessibilityValue]; +} + +- (void)setTitle:(NSString * _Nullable)title { + _title = title; + + self.titleLabel.text = title; + self.tappableView.accessibilityLabel = title; +} + +- (void)setText:(NSString * _Nullable)text { + _text = text; + + self.textLabel.text = text; +} + +- (void)_updateTappableViewAccessibilityValue { + if (self.textContainerView.isHidden) { + self.tappableView.accessibilityValue = STDSLocalizedString(@"Collapsed", @"Accessibility label for expandandable text control to indicate text is hidden."); + } else { + self.tappableView.accessibilityValue = STDSLocalizedString(@"Expanded", @"Accessibility label for expandandable text control to indicate that the UI has been expanded and additional text is available."); + } +} + +- (void)_toggleTextExpansion { + if (self.didTap) { + self.didTap(); + } + self.textContainerView.hidden = !self.textContainerView.hidden; + + CGFloat rotationValue = (CGFloat)M_PI_2; + if (self.textContainerView.isHidden) { + rotationValue = (CGFloat)0; + [self.imageViewStackView removeArrangedSubview:self.imageViewSpacerView]; + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.titleLabel); + } else { + [self.imageViewStackView addArrangedSubview:self.imageViewSpacerView]; + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.textLabel); + } + [self _updateTappableViewAccessibilityValue]; + + [UIView animateWithDuration:kTitleImageViewRotationAnimationDuration animations:^{ + self.titleImageView.transform = CGAffineTransformMakeRotation(rotationValue); + }]; +} + +- (void)setCustomization:(STDSFooterCustomization * _Nullable)customization { + self.titleLabel.font = customization.headingFont; + self.titleLabel.textColor = customization.headingTextColor; + + self.textLabel.font = customization.font; + self.textLabel.textColor = customization.textColor; + + self.titleImageView.tintColor = customization.chevronColor; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSIPAddress.h b/Stripe3DS2/Stripe3DS2/STDSIPAddress.h new file mode 100644 index 00000000..a6d5911f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSIPAddress.h @@ -0,0 +1,15 @@ +// +// STDSIPAddress.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NSString * _Nullable STDSCurrentDeviceIPAddress(void); + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSIPAddress.m b/Stripe3DS2/Stripe3DS2/STDSIPAddress.m new file mode 100644 index 00000000..863eddb8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSIPAddress.m @@ -0,0 +1,43 @@ +// +// STDSIPAddress.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSIPAddress.h" + +#include +#include + +// source: https://zachwaugh.com/posts/programmatically-retrieving-ip-address-of-iphone +NSString * STDSCurrentDeviceIPAddress(void) { + NSString *address = nil; + struct ifaddrs *interfaces = NULL; + struct ifaddrs *temp_addr = NULL; + int success = 0; + + // retrieve the current interfaces - returns 0 on success + success = getifaddrs(&interfaces); + if (success == 0) { + // Loop through linked list of interfaces + temp_addr = interfaces; + while (temp_addr != NULL) { + if( temp_addr->ifa_addr->sa_family == AF_INET) { + // Check if interface is en0 which is the wifi connection on the iPhone + if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { + // Get NSString from C String + address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; + } + } + + temp_addr = temp_addr->ifa_next; + } + } + + // Free memory + freeifaddrs(interfaces); + + return address; +} diff --git a/Stripe3DS2/Stripe3DS2/STDSImageLoader.h b/Stripe3DS2/Stripe3DS2/STDSImageLoader.h new file mode 100644 index 00000000..15f1396a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSImageLoader.h @@ -0,0 +1,36 @@ +// +// STDSImageLoader.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^STDSImageDownloadCompletionBlock)(UIImage * _Nullable); + +@interface STDSImageLoader: NSObject + +/** + Initializes an `STDSImageLoader` with the given parameters. + + @param session The session to initialize the loader with. + @return Returns an initialized `STDSImageLoader` object. + */ +- (instancetype)initWithURLSession:(NSURLSession *)session; + +/** + Attempts to load an image from the specified URL. + + @param URL The URL to load an image for. + @param completion A completion block that is called when the image loading has finished. This will be called on the main queue. + */ +- (void)loadImageFromURL:(NSURL *)URL completion:(STDSImageDownloadCompletionBlock)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSImageLoader.m b/Stripe3DS2/Stripe3DS2/STDSImageLoader.m new file mode 100644 index 00000000..1d87e44f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSImageLoader.m @@ -0,0 +1,50 @@ +// +// STDSImageLoader.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSImageLoader.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSImageLoader() + +@property (nonatomic, strong) NSURLSession *session; + +@end + +@implementation STDSImageLoader + +- (instancetype)initWithURLSession:(NSURLSession *)session { + self = [super init]; + + if (self) { + _session = session; + } + + return self; +} + +- (void)loadImageFromURL:(NSURL *)URL completion:(STDSImageDownloadCompletionBlock)completion { + NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + UIImage *image; + + if (data != nil) { + image = [UIImage imageWithData:data]; + } + + [NSOperationQueue.mainQueue addOperationWithBlock:^{ + completion(image); + }]; + }]; + + [dataTask resume]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSIntegrityChecker.h b/Stripe3DS2/Stripe3DS2/STDSIntegrityChecker.h new file mode 100644 index 00000000..ed584dec --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSIntegrityChecker.h @@ -0,0 +1,19 @@ +// +// STDSIntegrityChecker.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSIntegrityChecker : NSObject + ++ (BOOL)SDKIntegrityIsValid; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSIntegrityChecker.m b/Stripe3DS2/Stripe3DS2/STDSIntegrityChecker.m new file mode 100644 index 00000000..73ed1ea8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSIntegrityChecker.m @@ -0,0 +1,41 @@ +// +// STDSIntegrityChecker.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSIntegrityChecker.h" +#import "Stripe3DS2.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSIntegrityChecker + ++ (BOOL)SDKIntegrityIsValid { + + if (NSClassFromString(@"STDSIntegrityChecker") == [STDSIntegrityChecker class] && + NSClassFromString(@"STDSConfigParameters") == [STDSConfigParameters class] && + NSClassFromString(@"STDSThreeDS2Service") == [STDSThreeDS2Service class] && + NSClassFromString(@"STDSUICustomization") == [STDSUICustomization class] && + NSClassFromString(@"STDSWarning") == [STDSWarning class] && + NSClassFromString(@"STDSAlreadyInitializedException") == [STDSAlreadyInitializedException class] && + NSClassFromString(@"STDSNotInitializedException") == [STDSNotInitializedException class] && + NSClassFromString(@"STDSRuntimeException") == [STDSRuntimeException class] && + NSClassFromString(@"STDSErrorMessage") == [STDSErrorMessage class] && + NSClassFromString(@"STDSRuntimeErrorEvent") == [STDSRuntimeErrorEvent class] && + NSClassFromString(@"STDSProtocolErrorEvent") == [STDSProtocolErrorEvent class] && + NSClassFromString(@"STDSAuthenticationRequestParameters") == [STDSAuthenticationRequestParameters class] && + NSClassFromString(@"STDSChallengeParameters") == [STDSChallengeParameters class] && + NSClassFromString(@"STDSCompletionEvent") == [STDSCompletionEvent class] && + NSClassFromString(@"STDSTransaction") == [STDSTransaction class]) { + return YES; + } + + return NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSJSONWebEncryption.h b/Stripe3DS2/Stripe3DS2/STDSJSONWebEncryption.h new file mode 100644 index 00000000..2b2bc306 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSJSONWebEncryption.h @@ -0,0 +1,45 @@ +// +// STDSJSONWebEncryption.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/24/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDirectoryServer.h" + +@class STDSDirectoryServerCertificate; +@class STDSJSONWebSignature; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSJSONWebEncryption : NSObject + ++ (nullable NSString *)encryptJSON:(NSDictionary *)json + forDirectoryServer:(STDSDirectoryServer)directoryServer + error:(out NSError * _Nullable *)error; + ++ (nullable NSString *)encryptJSON:(NSDictionary *)json + withCertificate:(STDSDirectoryServerCertificate *)certificate + directoryServerID:(NSString *)directoryServerID + serverKeyID:(nullable NSString *)serverKeyID + error:(out NSError * _Nullable *)error; + ++ (nullable NSString *)directEncryptJSON:(NSDictionary *)json + withContentEncryptionKey:(NSData *)contentEncryptionKey + forACSTransactionID:(NSString *)acsTransactionID + error:(out NSError * _Nullable *)error; + ++ (nullable NSDictionary *)decryptData:(NSData *)data + withContentEncryptionKey:(NSData *)contentEncryptionKey + error:(out NSError * _Nullable *)error; + ++ (BOOL)verifyJSONWebSignature:(STDSJSONWebSignature *)jws forDirectoryServer:(STDSDirectoryServer)directoryServer; + ++ (BOOL)verifyJSONWebSignature:(STDSJSONWebSignature *)jws withCertificate:(STDSDirectoryServerCertificate *)certificate rootCertificates:(NSArray *)rootCertificates; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSJSONWebEncryption.m b/Stripe3DS2/Stripe3DS2/STDSJSONWebEncryption.m new file mode 100644 index 00000000..57023a5e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSJSONWebEncryption.m @@ -0,0 +1,407 @@ +// +// STDSJSONWebEncryption.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/24/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSJSONWebEncryption.h" + +#import +#import + +#import "NSData+JWEHelpers.h" +#import "NSError+Stripe3DS2.h" +#import "NSString+JWEHelpers.h" +#import "STDSDirectoryServerCertificate.h" +#import "STDSEphemeralKeyPair.h" +#import "STDSJSONWebSignature.h" +#import "STDSSecTypeUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - _STPJSONWebEncryptionResult +@interface _STPJSONWebEncryptionResult : NSObject + +- (instancetype)initWithCiphertext:(NSData *)ciphertextData + initializationVector:(NSData *)initializationVector + hmacTag:(NSData *)hmacTag; + +@property (nonatomic, copy, readonly) NSData *ciphertextData; +@property (nonatomic, copy, readonly) NSData *initializationVector; +@property (nonatomic, copy, readonly) NSData *hmacTag; + +@end + +@implementation _STPJSONWebEncryptionResult + +- (instancetype)initWithCiphertext:(NSData *)ciphertextData + initializationVector:(NSData *)initializationVector + hmacTag:(NSData *)hmacTag { + self = [super init]; + if (self) { + _ciphertextData = [ciphertextData copy]; + _initializationVector = [initializationVector copy]; + _hmacTag = [hmacTag copy]; + } + + return self; +} + +@end + +#pragma mark - STDSJSONWebEncryption + +static const size_t kContentEncryptionKeyByteCount = 32; +static const size_t kHMACSubKeyByteCount = 16; +static const size_t kAESSubKeyByteCount = 16; + +@implementation STDSJSONWebEncryption + ++ (nullable NSString *)encryptJSON:(NSDictionary *)json + forDirectoryServer:(STDSDirectoryServer)directoryServer + error:(out NSError *__autoreleasing _Nullable * _Nullable)error { + + NSString *ciphertext = nil; + + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate certificateForDirectoryServer:directoryServer]; + NSString *directoryServerID = STDSDirectoryServerIdentifier(directoryServer); + + if (certificate != nil && directoryServerID != nil) { + + ciphertext = [self encryptJSON:json + withCertificate:certificate + directoryServerID:directoryServerID + serverKeyID:nil + error:error]; + } + + if (error != nil && ciphertext == nil) { + *error = *error ?: [NSError _stds_jweError]; + } + + return ciphertext; +} + ++ (nullable NSString *)encryptJSON:(NSDictionary *)json + withCertificate:(STDSDirectoryServerCertificate *)certificate + directoryServerID:(NSString *)directoryServerID + serverKeyID:(nullable NSString *)serverKeyID + error:(out NSError * _Nullable *)error { + + NSString *ciphertext = nil; + NSError *jsonError = nil; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:json options:0 error:&jsonError]; + + if (jsonData != nil) { + + switch (certificate.keyType) { + case STDSDirectoryServerKeyTypeRSA: + ciphertext = [self _RSAEncryptPlaintext:jsonData + withCertificate:certificate + serverKeyID:serverKeyID]; + break; + + case STDSDirectoryServerKeyTypeEC: + ciphertext = [self _directEncryptPlaintext:jsonData + withCertificate:certificate + forDirectoryServer:directoryServerID]; + break; + + case STDSDirectoryServerKeyTypeUnknown: + break; + } + } + + if (error != nil && ciphertext == nil) { + *error = jsonError ?: [NSError _stds_jweError]; + } + + return ciphertext; +} + ++ (nullable NSString *)directEncryptJSON:(NSDictionary *)json + withContentEncryptionKey:(NSData *)contentEncryptionKey + forACSTransactionID:(NSString *)acsTransactionID + error:(out NSError * _Nullable *)error { + NSString *ciphertext = nil; + + NSError *jsonError = nil; + + + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:json options:0 error:&jsonError]; + + if (jsonData != nil) { + NSString *headerString = [NSString stringWithFormat:@"{\"kid\":\"%@\",\"enc\":\"A128CBC-HS256\",\"alg\":\"dir\"}", acsTransactionID]; + ciphertext = [self _directEncryptPlaintext:jsonData + withContentEncryptionKey:contentEncryptionKey + headerString:headerString]; + } + + if (error != nil && ciphertext == nil) { + *error = jsonError ?: [NSError _stds_jweError]; + } + + return ciphertext; +} + ++ (nullable NSData *)_hmacTagWithKey:(NSData *)hmacSubKey + headerString:(NSString *)headerString + initializationVector:(NSData *)initializationVector + cipherText:(NSData *)cipherText { + NSString *encodedHeaderString = [headerString _stds_base64URLEncodedString]; + + // Per JWE spec, the encoded header data is included as additional authenticated data but with ASCII encoding https://tools.ietf.org/html/rfc7516#section-5.1 + NSData *additionalAuthenticatedData = [encodedHeaderString dataUsingEncoding:NSASCIIStringEncoding]; + uint64_t AL = CFSwapInt64HostToBig(additionalAuthenticatedData.length*8); + + NSMutableData *d = [NSMutableData data]; + [d appendBytes:additionalAuthenticatedData.bytes length:additionalAuthenticatedData.length]; + [d appendBytes:initializationVector.bytes length:initializationVector.length]; + [d appendBytes:cipherText.bytes length:cipherText.length]; + [d appendBytes:&AL length:sizeof(AL)]; + + NSMutableData *M = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH]; + + CCHmac(kCCHmacAlgSHA256, + hmacSubKey.bytes, + kHMACSubKeyByteCount, + d.bytes, + d.length, + M.mutableBytes); + + + NSData *hmacTag = [M subdataWithRange:NSMakeRange(0, 16)]; + + return hmacTag; +} + ++ (nullable _STPJSONWebEncryptionResult *)_encryptPlaintext:(NSData *)plaintextData + withKey:(NSData *)contentEncryptionKeyData + headerString:(NSString *)headerString { + // ecrypt JSON according to JWE (RFC 7516) using JWE Compact Serialization + // enc: A128CBC-HS256 + + // Ref: rfc7518 sec 5.2.3 https://www.rfc-editor.org/rfc/rfc7518.txt + + _STPJSONWebEncryptionResult *result = nil; + + static const size_t kInitializationVectorByteCount = 16; + + NSAssert(contentEncryptionKeyData.length == kContentEncryptionKeyByteCount, @"Must use a valid 256 content encryption key"); + if (contentEncryptionKeyData.length == kContentEncryptionKeyByteCount) { + + NSData *hmacSubKeyData = [contentEncryptionKeyData subdataWithRange:NSMakeRange(0, kHMACSubKeyByteCount)]; + NSData *aesSubKeyData = [contentEncryptionKeyData subdataWithRange:NSMakeRange(kHMACSubKeyByteCount, kAESSubKeyByteCount)]; + NSData *initializationVectorData = STDSCryptoRandomData(kInitializationVectorByteCount); + + + // pad with block size for AES + NSMutableData *ciphertextData = [NSMutableData dataWithLength:plaintextData.length + kCCBlockSizeAES128]; + size_t outLength; + CCCryptorStatus aesEncryptionResult = CCCrypt(kCCEncrypt, + kCCAlgorithmAES, + kCCOptionPKCS7Padding, + aesSubKeyData.bytes, + kAESSubKeyByteCount, + initializationVectorData.bytes, + plaintextData.bytes, + (size_t)plaintextData.length, + ciphertextData.mutableBytes, + ciphertextData.length, + &outLength); + if (aesEncryptionResult == kCCSuccess) { + ciphertextData.length = outLength; + + NSData *hmacTag = [self _hmacTagWithKey:hmacSubKeyData + headerString:headerString + initializationVector:initializationVectorData + cipherText:ciphertextData]; + + result = [[_STPJSONWebEncryptionResult alloc] initWithCiphertext:ciphertextData + initializationVector:initializationVectorData + hmacTag:hmacTag]; + } + } + + return result; +} + ++ (nullable NSString *)_RSAEncryptPlaintext:(NSData *)plaintextData + withCertificate:(STDSDirectoryServerCertificate *)certificate + serverKeyID:(nullable NSString *)serverKeyID { + NSData *contentEncryptionKey = STDSCryptoRandomData(kContentEncryptionKeyByteCount); + if (contentEncryptionKey != nil) { + NSString *headerString = nil; + + if (serverKeyID != nil) { + headerString = [NSString stringWithFormat:@"{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\",\"kid\":\"%@\"}", serverKeyID]; + } else { + headerString = @"{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\"}"; + + } + _STPJSONWebEncryptionResult *encryptedData = [self _encryptPlaintext:plaintextData + withKey:contentEncryptionKey + headerString:headerString]; + if (encryptedData != nil) { + NSData *encryptedCEK = [certificate encryptDataUsingRSA_OAEP_SHA256:contentEncryptionKey]; + if (encryptedCEK != nil) { + return [NSString stringWithFormat:@"%@.%@.%@.%@.%@", [headerString _stds_base64URLEncodedString], [encryptedCEK _stds_base64URLEncodedString], [encryptedData.initializationVector _stds_base64URLEncodedString], [encryptedData.ciphertextData _stds_base64URLEncodedString], [encryptedData.hmacTag _stds_base64URLEncodedString]]; + } + } + } + + return nil; +} + ++ (nullable NSString *)_directEncryptPlaintext:(NSData *)plaintextData + withCertificate:(STDSDirectoryServerCertificate *)certificate + forDirectoryServer:(NSString *)directoryServerID { + + STDSEphemeralKeyPair *ephemeralKeyPair = [STDSEphemeralKeyPair ephemeralKeyPair]; + NSData *rawSharedSecret = [ephemeralKeyPair createSharedSecretWithCertificate:certificate]; + NSData *contentEncryptionKey = STDSCreateConcatKDFWithSHA256(rawSharedSecret, kContentEncryptionKeyByteCount, directoryServerID); + + NSString *headerString = [NSString stringWithFormat:@"{\"enc\":\"A128CBC-HS256\",\"alg\":\"ECDH-ES\",\"epk\":%@}", ephemeralKeyPair.publicKeyJWK]; + + return [self _directEncryptPlaintext:plaintextData + withContentEncryptionKey:contentEncryptionKey + headerString:headerString]; +} + ++ (nullable NSString *)_directEncryptPlaintext:(NSData *)plaintextData + withContentEncryptionKey:(NSData *)contentEncryptionKey + headerString:(NSString *)headerString { + + _STPJSONWebEncryptionResult *encryptedData = [self _encryptPlaintext:plaintextData + withKey:contentEncryptionKey + headerString:headerString]; + if (encryptedData != nil) { + return [NSString stringWithFormat:@"%@..%@.%@.%@", [headerString _stds_base64URLEncodedString], [encryptedData.initializationVector _stds_base64URLEncodedString], [encryptedData.ciphertextData _stds_base64URLEncodedString], [encryptedData.hmacTag _stds_base64URLEncodedString]]; + } + + return nil; +} + +#pragma mark - Decryption + ++ (nullable NSDictionary *)decryptData:(NSData *)data + withContentEncryptionKey:(NSData *)contentEncryptionKey + error:(out NSError * _Nullable *)error { + NSString *jweString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSArray *jweComponents = [jweString componentsSeparatedByString:@"."]; + if (jweComponents.count != 5) { + + // Data may be JSON describing error + NSError *jsonError = nil; + NSDictionary *errorJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + if (errorJSON != nil) { + return errorJSON; + } + if (error != NULL) { + *error = jsonError ?: [NSError _stds_jweError]; + } + return nil; + } + + NSString *headerString = [jweComponents[0] _stds_base64URLDecodedString]; + NSData *initializationVector = [jweComponents[2] _stds_base64URLDecodedData]; + NSData *ciphertextData = [jweComponents[3] _stds_base64URLDecodedData]; + NSData *hmacTag = [jweComponents[4] _stds_base64URLDecodedData]; + + if (headerString == nil || + initializationVector == nil || + ciphertextData == nil || + hmacTag == nil + ) { + if (error != NULL) { + *error = [NSError _stds_jweError]; + } + return nil; + } + NSAssert(contentEncryptionKey.length == kContentEncryptionKeyByteCount, @"Must use a valid 256 content encryption key"); + if (contentEncryptionKey.length != kContentEncryptionKeyByteCount) { + if (error != NULL) { + *error = [NSError _stds_jweError]; + } + return nil; + } + + NSData *hmacSubKeyData = [contentEncryptionKey subdataWithRange:NSMakeRange(0, kHMACSubKeyByteCount)]; + NSData *aesSubKeyData = [contentEncryptionKey subdataWithRange:NSMakeRange(kHMACSubKeyByteCount, kAESSubKeyByteCount)]; + + // pad with block size for AES + NSMutableData *plaintextData = [NSMutableData dataWithLength:ciphertextData.length + kCCBlockSizeAES128]; + size_t outLength; + CCCryptorStatus aesDecryptionResult = CCCrypt(kCCDecrypt, + kCCAlgorithmAES, + kCCOptionPKCS7Padding, + aesSubKeyData.bytes, + kAESSubKeyByteCount, + initializationVector.bytes, + ciphertextData.bytes, + (size_t)ciphertextData.length, + plaintextData.mutableBytes, + plaintextData.length, + &outLength); + + if (aesDecryptionResult != kCCSuccess) { + if (error != NULL) { + *error = [NSError _stds_jweError]; + } + return nil; + } + + plaintextData.length = outLength; + NSData *calculatedHMACTag = [self _hmacTagWithKey:hmacSubKeyData + headerString:headerString + initializationVector:initializationVector + cipherText:ciphertextData]; + + if (![calculatedHMACTag isEqualToData:hmacTag]) { + if (error != NULL) { + *error = [NSError _stds_jweError]; + } + return nil; + } + + NSDictionary *decryptedJSON = [NSJSONSerialization JSONObjectWithData:plaintextData + options:0 + error:error]; + if (*error != NULL) { + *error = [NSError _stds_jweError]; + return nil; + } + + return decryptedJSON; +} + +#pragma mark - JSON Web Signature Verification + ++ (BOOL)verifyJSONWebSignature:(STDSJSONWebSignature *)jws forDirectoryServer:(STDSDirectoryServer)directoryServer { + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate certificateForDirectoryServer:directoryServer]; + NSString *certificateString = nil; + + if (directoryServer == STDSDirectoryServerULTestRSA || directoryServer == STDSDirectoryServerULTestEC) { + // for UL tests, the last certificate in the chain is the anchor/root + certificateString = jws.certificateChain.lastObject; + } else { + NSAssert(0, @"We shouldn't be using this path outside of UL testing"); + certificateString = certificate.certificateString; + } + + if (certificateString == nil) { + return NO; + } + + return [self verifyJSONWebSignature:jws withCertificate:certificate rootCertificates:@[certificateString]]; +} + ++ (BOOL)verifyJSONWebSignature:(STDSJSONWebSignature *)jws withCertificate:(__unused STDSDirectoryServerCertificate *)certificate rootCertificates:(NSArray *)rootCertificates { + return [STDSDirectoryServerCertificate verifyJSONWebSignature:jws withRootCertificates:rootCertificates]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSJSONWebSignature.h b/Stripe3DS2/Stripe3DS2/STDSJSONWebSignature.h new file mode 100644 index 00000000..d6f17a19 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSJSONWebSignature.h @@ -0,0 +1,41 @@ +// +// STDSJSONWebSignature.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/2/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSEllipticCurvePoint; + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, STDSJSONWebSignatureAlgorithm) { + STDSJSONWebSignatureAlgorithmES256, + STDSJSONWebSignatureAlgorithmPS256, + STDSJSONWebSignatureAlgorithmUnknown, +}; + +@interface STDSJSONWebSignature : NSObject + +- (nullable instancetype)initWithString:(NSString *)jwsString; +- (nullable instancetype)initWithString:(NSString *)jwsString allowNilKey:(BOOL)allowNilKey; + +@property (nonatomic, readonly) STDSJSONWebSignatureAlgorithm algorithm; + +@property (nonatomic, readonly) NSData *digest; +@property (nonatomic, readonly) NSData *signature; + +@property (nonatomic, readonly) NSData *payload; + +/// non-nil if algorithm == STDSJSONWebSignatureAlgorithmES256 +@property (nonatomic, nullable, readonly) STDSEllipticCurvePoint *ellipticCurvePoint; + +/// non-nil if algorithm == STDSJSONWebSignatureAlgorithmPS256, can be non-nil for algorithm == STDSJSONWebSignatureAlgorithmES256 +@property (nonatomic, nullable, readonly) NSArray *certificateChain; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSJSONWebSignature.m b/Stripe3DS2/Stripe3DS2/STDSJSONWebSignature.m new file mode 100644 index 00000000..2e294937 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSJSONWebSignature.m @@ -0,0 +1,88 @@ +// +// STDSJSONWebSignature.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/2/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSJSONWebSignature.h" + +#import "NSString+JWEHelpers.h" +#import "STDSEllipticCurvePoint.h" +#import "STDSSecTypeUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString * const kJWSAlgorithmKey = @"alg"; +static NSString * const kJWSAlgorithmES256 = @"ES256"; +static NSString * const kJWSAlgorithmPS256 = @"PS256"; + +static NSString * const kJWSCertificateChainKey = @"x5c"; + +@implementation STDSJSONWebSignature + +- (nullable instancetype)initWithString:(NSString *)jwsString { + return [self initWithString:jwsString allowNilKey:NO]; +} + +- (nullable instancetype)initWithString:(NSString *)jwsString allowNilKey:(BOOL)allowNilKey { + self = [super init]; + if (self) { + NSArray *components = [jwsString componentsSeparatedByString:@"."]; + if (components.count != 3) { + return nil; + } + NSData *headerData = [components[0] _stds_base64URLDecodedData]; + if (headerData == nil) { + return nil; + } + NSError *jsonError = nil; + NSDictionary * headerJSON = [NSJSONSerialization JSONObjectWithData:headerData options:0 error:&jsonError]; + if (headerJSON == nil) { + return nil; + } + + if (headerJSON[kJWSCertificateChainKey] != nil && ![headerJSON[kJWSCertificateChainKey] isKindOfClass:[NSArray class]]) { + return nil; + } + + _certificateChain = headerJSON[kJWSCertificateChainKey]; + + NSString *algorithm = headerJSON[kJWSAlgorithmKey]; + if ([algorithm compare:kJWSAlgorithmES256 options: NSCaseInsensitiveSearch] == NSOrderedSame) { + _algorithm = STDSJSONWebSignatureAlgorithmES256; + if (_certificateChain.count > 0) { + SecCertificateRef certificate = STDSSecCertificateFromString(_certificateChain.firstObject); + if (certificate != NULL) { + SecKeyRef key = SecCertificateCopyKey(certificate); + CFRelease(certificate); + if (key != NULL) { + _ellipticCurvePoint = [[STDSEllipticCurvePoint alloc] initWithKey:key]; + CFRelease(key); + } + } + if (_ellipticCurvePoint == nil && !allowNilKey) { + return nil; + } + } else if (!allowNilKey) { + return nil; + } + + } else if ([algorithm compare:kJWSAlgorithmPS256 options: NSCaseInsensitiveSearch] == NSOrderedSame) { + _algorithm = STDSJSONWebSignatureAlgorithmPS256; + } else { + return nil; + } + + _digest = [[@[components[0], components[1]] componentsJoinedByString:@"."] dataUsingEncoding:NSUTF8StringEncoding]; + _signature = [components[2] _stds_base64URLDecodedData]; + _payload = [components[1] _stds_base64URLDecodedData]; + } + + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSJailbreakChecker.h b/Stripe3DS2/Stripe3DS2/STDSJailbreakChecker.h new file mode 100644 index 00000000..303575c5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSJailbreakChecker.h @@ -0,0 +1,19 @@ +// +// STDSJailbreakChecker.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSJailbreakChecker : NSObject + ++ (BOOL)isJailbroken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSJailbreakChecker.m b/Stripe3DS2/Stripe3DS2/STDSJailbreakChecker.m new file mode 100644 index 00000000..6c0dcbf2 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSJailbreakChecker.m @@ -0,0 +1,31 @@ +// +// STDSJailbreakChecker.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSJailbreakChecker.h" + +@import UIKit; + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSJailbreakChecker + +// This was implemented under the following guidance: https://medium.com/@pinmadhon/how-to-check-your-app-is-installed-on-a-jailbroken-device-67fa0170cf56 ++ (BOOL)isJailbroken { + + // Check for existence of files that are common for jailbroken devices + if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"] || + [[NSFileManager defaultManager] fileExistsAtPath:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]) { + return YES; + } + + return NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSLocalizedString.h b/Stripe3DS2/Stripe3DS2/STDSLocalizedString.h new file mode 100644 index 00000000..7a3a76c2 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSLocalizedString.h @@ -0,0 +1,18 @@ +// +// STDSLocalizedString.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 7/9/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSBundleLocator.h" + +#ifndef STDSLocalizedString_h +#define STDSLocalizedString_h + +#define STDSLocalizedString(key, comment) \ +[[STDSBundleLocator stdsResourcesBundle] localizedStringForKey:(key) value:@"" table:nil] + + +#endif /* STDSLocalizedString_h */ diff --git a/Stripe3DS2/Stripe3DS2/STDSOSVersionChecker.h b/Stripe3DS2/Stripe3DS2/STDSOSVersionChecker.h new file mode 100644 index 00000000..72b79cf1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSOSVersionChecker.h @@ -0,0 +1,19 @@ +// +// STDSOSVersionChecker.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSOSVersionChecker : NSObject + ++ (BOOL)isSupportedOSVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSOSVersionChecker.m b/Stripe3DS2/Stripe3DS2/STDSOSVersionChecker.m new file mode 100644 index 00000000..fea650cd --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSOSVersionChecker.m @@ -0,0 +1,21 @@ +// +// STDSOSVersionChecker.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSOSVersionChecker.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSOSVersionChecker + ++ (BOOL)isSupportedOSVersion { + return YES; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSProcessingView.h b/Stripe3DS2/Stripe3DS2/STDSProcessingView.h new file mode 100644 index 00000000..95364fc5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSProcessingView.h @@ -0,0 +1,26 @@ +// +// STDSProcessingView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/19/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class STDSUICustomization; + +@interface STDSProcessingView : UIView + +/// Defaults to NO +@property (nonatomic) BOOL shouldDisplayBlurView; +/// Defaults to YES +@property (nonatomic) BOOL shouldDisplayDSLogo; + +- (instancetype)initWithCustomization:(STDSUICustomization *)customization directoryServerLogo:(nullable UIImage *)directoryServerLogo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSProcessingView.m b/Stripe3DS2/Stripe3DS2/STDSProcessingView.m new file mode 100644 index 00000000..3126f974 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSProcessingView.m @@ -0,0 +1,98 @@ +// +// STDSProcessingView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/19/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSProcessingView.h" +#import "STDSStackView.h" +#import "UIView+LayoutSupport.h" +#import "UIFont+DefaultFonts.h" +#import "STDSUICustomization.h" +#import "STDSBundleLocator.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSProcessingView() + +@property (nonatomic, strong) STDSStackView *imageStackView; +@property (nonatomic, strong) UIView *blurViewPlaceholder; +@property (nonatomic, strong) UIImageView *imageView; + +@end + +@implementation STDSProcessingView + +static const CGFloat kProcessingViewHorizontalMargin = 8; +static const CGFloat kProcessingViewTopPadding = 22; +static const CGFloat kProcessingViewBottomPadding = 36; + +- (instancetype)initWithCustomization:(STDSUICustomization *)customization directoryServerLogo:(nullable UIImage *)directoryServerLogo { + self = [super initWithFrame:CGRectZero]; + + if (self) { + _blurViewPlaceholder = [UIView new]; + _imageView = [[UIImageView alloc] initWithImage:directoryServerLogo]; + _imageView.contentMode = UIViewContentModeScaleAspectFit; + _shouldDisplayDSLogo = YES; + [self _setupViewHierarchyWithCustomization:customization]; + } + + return self; +} + +- (void)setShouldDisplayBlurView:(BOOL)shouldDisplayBlurView { + _shouldDisplayBlurView = shouldDisplayBlurView; + self.blurViewPlaceholder.hidden = shouldDisplayBlurView; +} + +- (void)setShouldDisplayDSLogo:(BOOL)shouldDisplayDSLogo { + _shouldDisplayDSLogo = shouldDisplayDSLogo; + self.imageView.hidden = !shouldDisplayDSLogo; +} + +- (void)_setupViewHierarchyWithCustomization:(STDSUICustomization *)customization { + self.blurViewPlaceholder.backgroundColor = customization.backgroundColor; + UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:customization.blurStyle]]; + blurView.translatesAutoresizingMaskIntoConstraints = NO; + + STDSStackView *containerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + containerView.backgroundColor = customization.backgroundColor; + containerView.layer.cornerRadius = 13; + containerView.translatesAutoresizingMaskIntoConstraints = NO; + + UIActivityIndicatorView *indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:customization.activityIndicatorViewStyle]; + + [self addSubview:blurView]; + [blurView.contentView addSubview:self.blurViewPlaceholder]; + [self addSubview:containerView]; + + [self.blurViewPlaceholder _stds_pinToSuperviewBoundsWithoutMargin]; + [blurView _stds_pinToSuperviewBoundsWithoutMargin]; + + NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]; + NSLayoutConstraint *centerYConstraint = [NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]; + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:-kProcessingViewHorizontalMargin * 2]; + + [NSLayoutConstraint activateConstraints:@[ + centerXConstraint, + centerYConstraint, + widthConstraint, + [containerView.topAnchor constraintGreaterThanOrEqualToAnchor:self.topAnchor], + [self.bottomAnchor constraintGreaterThanOrEqualToAnchor:containerView.bottomAnchor], + ]]; + + [containerView addSpacer:kProcessingViewTopPadding]; + [containerView addArrangedSubview:self.imageView]; + [containerView addSpacer:20]; + [containerView addArrangedSubview:indicatorView]; + [containerView addSpacer:kProcessingViewBottomPadding]; + + [indicatorView startAnimating]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSProgressViewController.h b/Stripe3DS2/Stripe3DS2/STDSProgressViewController.h new file mode 100644 index 00000000..791e286c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSProgressViewController.h @@ -0,0 +1,27 @@ +// +// STDSProgressViewController.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 5/6/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDirectoryServer.h" + +@class STDSImageLoader, STDSUICustomization; +@protocol STDSAnalyticsDelegate; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSProgressViewController : UIViewController + +- (instancetype)initWithDirectoryServer:(STDSDirectoryServer)directoryServer + uiCustomization:(STDSUICustomization * _Nullable)uiCustomization + analyticsDelegate:(nullable id)analyticsDelegate + didCancel:(void (^)(void))didCancel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSProgressViewController.m b/Stripe3DS2/Stripe3DS2/STDSProgressViewController.m new file mode 100644 index 00000000..e7dce700 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSProgressViewController.m @@ -0,0 +1,63 @@ +// +// STDSProgressViewController.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 5/6/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSProgressViewController.h" + +#import "STDSBundleLocator.h" +#import "STDSUICustomization.h" +#import "UIViewController+Stripe3DS2.h" +#import "STDSProcessingView.h" +#import "STDSVisionSupport.h" +#import "include/STDSAnalyticsDelegate.h" + +@interface STDSProgressViewController() +@property (nonatomic, strong, nullable) STDSUICustomization *uiCustomization; +@property (nonatomic, strong) void (^didCancel)(void); +@property (nonatomic) STDSDirectoryServer directoryServer; +@property (nonatomic, weak) id analyticsDelegate; +@end + +@implementation STDSProgressViewController + +- (instancetype)initWithDirectoryServer:(STDSDirectoryServer)directoryServer + uiCustomization:(STDSUICustomization * _Nullable)uiCustomization + analyticsDelegate:(id)analyticsDelegate + didCancel:(void (^)(void))didCancel { + self = [super initWithNibName:nil bundle:nil]; + + if (self) { + _directoryServer = directoryServer; + _uiCustomization = uiCustomization; + _didCancel = didCancel; + } + + return self; +} + +- (void)loadView { + NSString *imageName = STDSDirectoryServerImageName(self.directoryServer); + UIImage *dsImage = imageName ? [UIImage imageNamed:imageName inBundle:[STDSBundleLocator stdsResourcesBundle] compatibleWithTraitCollection:nil] : nil; + self.view = [[STDSProcessingView alloc] initWithCustomization:self.uiCustomization directoryServerLogo:dsImage]; +} + +#if !STP_TARGET_VISION +- (UIStatusBarStyle)preferredStatusBarStyle { + return self.uiCustomization.preferredStatusBarStyle; +} +#endif + +- (void)viewDidLoad { + [super viewDidLoad]; + [self _stds_setupNavigationBarElementsWithCustomization:self.uiCustomization cancelButtonSelector:@selector(_cancelButtonTapped:)]; +} + +- (void)_cancelButtonTapped:(UIButton *)sender { + self.didCancel(); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSSecTypeUtilities.h b/Stripe3DS2/Stripe3DS2/STDSSecTypeUtilities.h new file mode 100644 index 00000000..d0d416e6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSecTypeUtilities.h @@ -0,0 +1,49 @@ +// +// STDSSecTypeUtilities.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/28/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDirectoryServer.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Returns the SecCertificateRef for the specified server or NULL if there's an error +SecCertificateRef _Nullable STDSCertificateForServer(STDSDirectoryServer server); + +/// Returns the public key in the certificate or NULL if there's an error +SecKeyRef _Nullable SecCertificateCopyKey(SecCertificateRef certificate); + +/// Returns one of the values defined for kSecAttrKeyType in or NULL +CFStringRef _Nullable STDSSecCertificateCopyPublicKeyType(SecCertificateRef certificate); + +/// Returns the hashed secret or nil +NSData * _Nullable STDSCreateConcatKDFWithSHA256(NSData *sharedSecret, NSUInteger keyLength, NSString *apv); + +/// Verifies the signature and payload using the Elliptic Curve P-256 with coordinateX and coordinateY +BOOL STDSVerifyEllipticCurveP256Signature(NSData *coordinateX, NSData *coordinateY, NSData *payload, NSData *signature); + +/// Verifies the signature and payload using RSA-PSS +BOOL STDSVerifyRSASignature(SecCertificateRef certificate, NSData *payload, NSData *signature); + +/// Returns data of length numBytes generated using CommonCrypto's CCRandomGenerateBytes or nil on failure +NSData * _Nullable STDSCryptoRandomData(size_t numBytes); + +/// Creates a certificate from base64encoded data +SecCertificateRef _Nullable STDSSecCertificateFromData(NSData *data); + +/// Creates a certificate from a PEM or DER encoded certificate string +SecCertificateRef _Nullable STDSSecCertificateFromString(NSString *certificateString); + +// Creates a public key using Elliptic Curve P-256 with coordinateX and coordinateY +SecKeyRef _Nullable STDSSecKeyRefFromCoordinates(NSData *coordinateX, NSData *coordinateY); + +// Creates a private key using Elliptic Curve P-256 with x, y, and d +SecKeyRef _Nullable STDSPrivateSecKeyRefFromCoordinates(NSData *x, NSData *y, NSData *d); + + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSecTypeUtilities.m b/Stripe3DS2/Stripe3DS2/STDSSecTypeUtilities.m new file mode 100644 index 00000000..2a2331fb --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSecTypeUtilities.m @@ -0,0 +1,366 @@ +// +// STDSSecTypeUtilities.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/28/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSSecTypeUtilities.h" + +#import +#import +#import + +#import "STDSBundleLocator.h" +#import "STDSEllipticCurvePoint.h" + +NS_ASSUME_NONNULL_BEGIN + +SecCertificateRef _Nullable STDSCertificateForServer(STDSDirectoryServer server) { + static NSMutableDictionary *sCertificateData = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sCertificateData = [[NSMutableDictionary alloc] init]; + }); + + NSString *serverKey = nil; + switch (server) { + case STDSDirectoryServerULTestRSA: + serverKey = @"STDSDirectoryServerULTestRSA"; + break; + + case STDSDirectoryServerULTestEC: + serverKey = @"STDSDirectoryServerULTestEC"; + break; + + case STDSDirectoryServerSTPTestRSA: + serverKey = @"STDSDirectoryServerSTPTestRSA"; + break; + + case STDSDirectoryServerSTPTestEC: + serverKey = @"STDSDirectoryServerSTPTestEC"; + break; + + case STDSDirectoryServerAmex: + serverKey = @"STDSDirectoryServerAmex"; + break; + + case STDSDirectoryServerDiscover: + serverKey = @"STDSDirectoryServerDiscover"; + break; + + case STDSDirectoryServerMastercard: + serverKey = @"STDSDirectoryServerMastercard"; + break; + + case STDSDirectoryServerVisa: + serverKey = @"STDSDirectoryServerVisa"; + break; + + case STDSDirectoryServerCartesBancaires: + serverKey = @"STDSDirectoryServerCartesBancaires"; + break; + + case STDSDirectoryServerCustom: + break; + + case STDSDirectoryServerUnknown: + break; + } + + if (serverKey == nil) { + return NULL; + } + + NSData *certificateData = sCertificateData[serverKey]; + if (certificateData == nil) { + NSString *certificatePath = nil; + switch (server) { + case STDSDirectoryServerULTestRSA: + break; + + case STDSDirectoryServerULTestEC: + break; + + case STDSDirectoryServerSTPTestRSA: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"ul-test" ofType:@"der"]; + break; + + case STDSDirectoryServerSTPTestEC: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"ec_test" ofType:@"der"]; + break; + + case STDSDirectoryServerAmex: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"amex" ofType:@"der"]; + break; + + case STDSDirectoryServerDiscover: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"discover" ofType:@"der"]; + break; + + case STDSDirectoryServerMastercard: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"mastercard" ofType:@"der"]; + break; + + case STDSDirectoryServerVisa: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"visa" ofType:@"der"]; + break; + + case STDSDirectoryServerCartesBancaires: + certificatePath = [[STDSBundleLocator stdsResourcesBundle] pathForResource:@"cartes_bancaires" ofType:@"der"]; + break; + + case STDSDirectoryServerCustom: + break; + + case STDSDirectoryServerUnknown: + break; + } + + if (certificatePath != nil) { + certificateData = [NSData dataWithContentsOfFile:certificatePath]; + // cache the file data to limit file IO + sCertificateData[serverKey] = certificateData; + } + } + + // Note to Future: SecCertificateCreateWithData only works with DER formatted data. The other popular + // format for certificate files is PEM. These can be converted before adding to the SDK by invoking + // `openssl x509 -in certificate_PEM.crt -outform der -out certificate_DER.der` + return certificateData != nil ? SecCertificateCreateWithData(NULL, (CFDataRef)certificateData): NULL; +}; + +CFStringRef _Nullable STDSSecCertificateCopyPublicKeyType(SecCertificateRef certificate) { + CFStringRef ret = NULL; + + SecKeyRef key = SecCertificateCopyKey(certificate); + + if (key != NULL) { + CFDictionaryRef attributes = SecKeyCopyAttributes(key); + if (attributes == NULL) { + CFRelease(key); + return NULL; + } + + if (attributes != NULL) { + const void *keyType = CFDictionaryGetValue(attributes, kSecAttrKeyType); + if (keyType != NULL && CFGetTypeID(keyType) == CFStringGetTypeID()) { + ret = CFStringCreateCopy(kCFAllocatorDefault, (CFStringRef)keyType); + } + CFRelease(attributes); + } + CFRelease(key); + } + + return ret; +} + +SecCertificateRef _Nullable STDSSecCertificateFromString(NSString *certificateString) { + static NSString * const kCertificateAnchorPrefix = @"-----BEGIN CERTIFICATE-----"; + static NSString * const kCertificateAnchorSuffix = @"-----END CERTIFICATE-----"; + + // first remove newlines + NSString *certificateStringNoAnchors = [[[certificateString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@""]; + + // remove the begin/end certificate markers + NSUInteger fromIndex = [certificateStringNoAnchors hasPrefix:kCertificateAnchorPrefix] ? kCertificateAnchorPrefix.length : 0; + NSUInteger toIndex = [certificateStringNoAnchors hasSuffix:kCertificateAnchorSuffix] ? certificateStringNoAnchors.length - kCertificateAnchorSuffix.length : certificateStringNoAnchors.length; + certificateStringNoAnchors = [[certificateStringNoAnchors substringWithRange:NSMakeRange(fromIndex, toIndex - fromIndex)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if (certificateStringNoAnchors.length == 0) { + return NULL; + } + NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:certificateStringNoAnchors options:0]; + if (certificateData == nil) { + return NULL; + } + + return STDSSecCertificateFromData(certificateData); +} + +SecCertificateRef _Nullable STDSSecCertificateFromData(NSData *data) { + SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data); + return certificate; +} + +SecKeyRef _Nullable STDSPrivateSecKeyRefFromCoordinates(NSData *x, NSData *y, NSData *d) { + static unsigned char prefixBytes[] = {0x04}; + NSMutableData *bytes = [[NSMutableData alloc] initWithBytes:(void *)prefixBytes length:1]; + [bytes appendData:x]; + [bytes appendData:y]; + [bytes appendData:d]; + NSDictionary *attributes = @{ + (__bridge NSString *)kSecAttrKeyType: (__bridge NSString *)kSecAttrKeyTypeECSECPrimeRandom, + (__bridge NSString *)kSecAttrKeyClass: (__bridge NSString *)kSecAttrKeyClassPrivate, + (__bridge NSString *)kSecAttrKeySizeInBits: @(256), + }; + CFErrorRef error = NULL; + SecKeyRef key = SecKeyCreateWithData((__bridge CFDataRef)bytes, (__bridge CFDictionaryRef)attributes, &error); + return key; +} + +SecKeyRef _Nullable STDSSecKeyRefFromCoordinates(NSData *coordinateX, NSData *coordinateY) { + static unsigned char prefixBytes[] = {0x04}; + NSMutableData *bytes = [[NSMutableData alloc] initWithBytes:(void *)prefixBytes length:1]; + [bytes appendData:coordinateX]; + [bytes appendData:coordinateY]; + NSDictionary *attributes = @{ + (__bridge NSString *)kSecAttrKeyType: (__bridge NSString *)kSecAttrKeyTypeECSECPrimeRandom, + (__bridge NSString *)kSecAttrKeyClass: (__bridge NSString *)kSecAttrKeyClassPublic, + (__bridge NSString *)kSecAttrKeySizeInBits: @(256), + }; + CFErrorRef error = NULL; + SecKeyRef key = SecKeyCreateWithData((__bridge CFDataRef)bytes, (__bridge CFDictionaryRef)attributes, &error); + return key; +} + +// ref. https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1/1797#1797 +NSData * _Nullable STDSDEREncodedSignature(NSData * signature) { + // make sure input signature is of correct R || S format + NSUInteger signatureLength = signature.length; + if (signatureLength == 0 || signatureLength % 2 != 0) { + return nil; + } + + static const uint8_t bytePrefix = 0x00; + + NSMutableData *rBytes = [[NSMutableData alloc] init]; + uint8_t firstRByte; + [signature getBytes:&firstRByte length:1]; + + if (firstRByte >= 0x80) { + // "Signed big-endian encoding of minimal length", we can't have the first bit be 1 because these are postive values + [rBytes appendBytes:&bytePrefix length:1]; + } + [rBytes appendBytes:signature.bytes length:signatureLength / 2]; + + NSMutableData *sBytes = [[NSMutableData alloc] init]; + uint8_t firstSByte; + [signature getBytes:&firstSByte range:NSMakeRange(signatureLength / 2, 1)]; + + if (firstSByte >= 0x80) { + // "Signed big-endian encoding of minimal length", we can't have the first bit be 1 because these are postive values + [sBytes appendBytes:&bytePrefix length:1]; + } + [sBytes appendBytes:(signature.bytes + (signatureLength / 2)) length:signatureLength / 2]; + + uint8_t rLength = (uint8_t)rBytes.length; + uint8_t sLength = (uint8_t)sBytes.length; + + static const uint8_t derBytePrefix = 0x30; + NSMutableData *derEncoded = [[NSMutableData alloc] initWithBytes:&derBytePrefix length:1]; + + static const uint8_t derSeparatorByte = 0x02; + // numBytes does not include the 0x30 byte + uint8_t numBytes = rLength + sLength + 2 + 2; // + 2 for separators, + 2 for r and s size bytes + [derEncoded appendBytes:&numBytes length:1]; + [derEncoded appendBytes:&derSeparatorByte length:1]; + + [derEncoded appendBytes:&rLength length:1]; + [derEncoded appendBytes:rBytes.bytes length:rBytes.length]; + + [derEncoded appendBytes:&derSeparatorByte length:1]; + + [derEncoded appendBytes:&sLength length:1]; + [derEncoded appendBytes:sBytes.bytes length:sBytes.length]; + + return [derEncoded copy]; +} + +BOOL STDSVerifyEllipticCurveP256Signature(NSData *coordinateX, NSData *coordinateY, NSData *payload, NSData *signature) { + BOOL ret = NO; + + // make P-256 curve key from coordinates + SecKeyRef key = STDSSecKeyRefFromCoordinates(coordinateX, coordinateY); + + if (key != NULL) { + size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; + unsigned char hashBytes[hashBytesSize]; + CC_SHA256(payload.bytes, (CC_LONG)payload.length, hashBytes); + CFErrorRef error = NULL; + NSData *derEncodedSignature = STDSDEREncodedSignature(signature); + if (derEncodedSignature == nil) { + CFRelease(key); + return NO; + } + ret = (BOOL)SecKeyVerifySignature(key, kSecKeyAlgorithmECDSASignatureDigestX962SHA256, (__bridge CFDataRef)[NSData dataWithBytes:hashBytes length:hashBytesSize], (__bridge CFDataRef)derEncodedSignature, &error); + CFRelease(key); + } + return ret; +} + +BOOL STDSVerifyRSASignature(SecCertificateRef certificate, NSData *payload, NSData *signature) { + BOOL ret = NO; + + SecKeyRef key = SecCertificateCopyKey(certificate); + if (key != NULL && signature) { + CFErrorRef error = NULL; + size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH; + unsigned char hashBytes[hashBytesSize]; + CC_SHA256(payload.bytes, (CC_LONG)payload.length, hashBytes); + + ret = (BOOL)SecKeyVerifySignature(key, kSecKeyAlgorithmRSASignatureDigestPSSSHA256, (__bridge CFDataRef)[NSData dataWithBytes:hashBytes length:hashBytesSize], (__bridge CFDataRef)signature, &error); + CFRelease(key); + } + return ret; +} + +NSData * _Nullable STDSCryptoRandomData(size_t numBytes) { + void *randomBytes[numBytes]; + memset(randomBytes, 0, numBytes); + if (CCRandomGenerateBytes(randomBytes, numBytes) == kCCSuccess) { + NSData *data = [NSData dataWithBytes:randomBytes length:numBytes]; + return data; + } + return NULL; +} + +NSData * _Nullable _STPCreateKDFFormattedData(NSData *data) { + uint32_t bigEndianLength = CFSwapInt32HostToBig((uint32_t)data.length); + NSMutableData *encodedLength = [NSMutableData dataWithBytes:&bigEndianLength length:4]; + [encodedLength appendData:data]; + return [encodedLength copy]; +} + +NSData * _Nullable STDSCreateConcatKDFWithSHA256(NSData *sharedSecret, NSUInteger keyLength, NSString *apv) { + NSData *concatKDFData = nil; + + uint32_t bigEndianKeyLength = CFSwapInt32HostToBig((uint32_t)keyLength*8); + + // algorithmID and partyUInfo are intentionally empty strings based on the Core Spec + // section 6.2.3.3 which states that they should be null. The KDF standard, NIST.800-56A, + // requires that null values still have the length bytes set to 0. + NSData *algorithmID = _STPCreateKDFFormattedData([@"" dataUsingEncoding:NSASCIIStringEncoding]); + NSData *partyUInfo = _STPCreateKDFFormattedData([@"" dataUsingEncoding:NSUTF8StringEncoding]); + NSData *partyVInfo = _STPCreateKDFFormattedData([apv dataUsingEncoding:NSUTF8StringEncoding]); + NSData *suppPubInfo = [NSData dataWithBytes:&bigEndianKeyLength length:4]; + + if (algorithmID == nil || + partyUInfo == nil || + partyVInfo == nil || + suppPubInfo == nil + ) { + return nil; + } + + NSMutableData *otherInfo = [algorithmID mutableCopy]; + [otherInfo appendData:partyUInfo]; + [otherInfo appendData:partyVInfo]; + [otherInfo appendData:suppPubInfo]; + + const unsigned char roundOneBytes[4] = {0, 0, 0, 1}; + NSMutableData *roundOneHashInput = [[NSMutableData alloc] initWithBytes:roundOneBytes length:4]; + [roundOneHashInput appendData:sharedSecret]; + [roundOneHashInput appendData:otherInfo]; + + NSMutableData *roundOneHashOutput = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; + + if (CC_SHA256(roundOneHashInput.bytes, (CC_LONG)roundOneHashInput.length, roundOneHashOutput.mutableBytes) != nil) { + concatKDFData = [NSData dataWithBytes:roundOneHashOutput.bytes length:MAX(keyLength, roundOneHashOutput.length)]; + } + + return concatKDFData; +} + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSelectionButton.h b/Stripe3DS2/Stripe3DS2/STDSSelectionButton.h new file mode 100644 index 00000000..5cb78b7c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSelectionButton.h @@ -0,0 +1,26 @@ +// +// STDSSelectionButton.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 6/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSSelectionCustomization; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSSelectionButton : UIButton + +@property (nonatomic) STDSSelectionCustomization *customization; + +/// This control can either be styled as a radio button or a checkbox +@property (nonatomic) BOOL isCheckbox; + +- (instancetype)initWithCustomization:(STDSSelectionCustomization *)customization; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSelectionButton.m b/Stripe3DS2/Stripe3DS2/STDSSelectionButton.m new file mode 100644 index 00000000..97bf19e4 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSelectionButton.m @@ -0,0 +1,167 @@ +// +// STDSSelectionButton.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 6/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSSelectionButton.h" + +#import "STDSSelectionCustomization.h" + +@interface _STDSSelectionButtonView: UIView +@property (nonatomic) BOOL isCheckbox; +@property (nonatomic) STDSSelectionCustomization *customization; +@property (nonatomic, getter = isSelected) BOOL selected; +@end + +@implementation _STDSSelectionButtonView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + self.opaque = NO; + } + + return self; +} + +- (void)setSelected:(BOOL)selected { + _selected = selected; + [self setNeedsDisplay]; +} + + +- (void)setCustomization:(STDSSelectionCustomization *)customization { + _customization = customization; + [self setNeedsDisplay]; +} + + +- (void)drawRect:(CGRect)rect { + if (self.isCheckbox) { + [self _drawCheckboxWithRect:rect]; + } else { + [self _drawRadioButtonWithRect:rect]; + } +} + +- (void)_drawRadioButtonWithRect:(CGRect)rect { + // Draw background + UIBezierPath *background = [UIBezierPath bezierPathWithOvalInRect:rect]; + if (self.isSelected) { + [self.customization.primarySelectedColor setFill]; + } else { + [self.customization.unselectedBackgroundColor setFill]; + } + [background fill]; + + // Draw unselected border + if (!self.isSelected) { + UIBezierPath *border = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, 0.5, 0.5)]; + [self.customization.unselectedBorderColor setStroke]; + [border stroke]; + } + + // Draw inner circle if selected + if (self.isSelected) { + CGRect selectedRect = CGRectInset(rect, 9, 9); + UIBezierPath *selected = [UIBezierPath bezierPathWithOvalInRect:selectedRect]; + [self.customization.secondarySelectedColor setFill]; + [selected fill]; + } +} + +- (void)_drawCheckboxWithRect:(CGRect)rect { + // Draw background + UIBezierPath *background = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8]; + if (self.isSelected) { + [self.customization.primarySelectedColor setFill]; + } else { + [self.customization.unselectedBackgroundColor setFill]; + } + [background fill]; + + // Draw unselected border + if (!self.isSelected) { + UIBezierPath *border = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, 0.5, 0.5) cornerRadius:8]; + border.lineWidth = 0.5; + [self.customization.unselectedBorderColor setStroke]; + [border stroke]; + } + + // Draw check mark if selected + if (self.isSelected) { + UIBezierPath *checkmark = [UIBezierPath bezierPath]; + [checkmark moveToPoint: CGPointMake(10, 15)]; + [checkmark addLineToPoint:CGPointMake(13.5, 18.5)]; + [checkmark addLineToPoint:CGPointMake(22, 10)]; + [self.customization.secondarySelectedColor setStroke]; + checkmark.lineWidth = 2; + [checkmark stroke]; + } +} + +@end + +static const CGFloat kMinimumTouchAreaDimension = 42.f; +static const CGFloat kContentSizeDimension = 30.f; + +@implementation STDSSelectionButton { + _STDSSelectionButtonView *_contentView; +} + +- (instancetype)initWithCustomization:(STDSSelectionCustomization *)customization { + self = [super init]; + if (self) { + _contentView = [[_STDSSelectionButtonView alloc] initWithFrame:CGRectMake(0, 0, kContentSizeDimension, kContentSizeDimension)]; + _contentView.userInteractionEnabled = NO; + [self addSubview:_contentView]; + self.customization = customization; + } + return self; +} + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { + BOOL pointInside = [super pointInside:point withEvent:event]; + if (!pointInside && + self.enabled && + !self.isHidden && + (CGRectGetWidth(self.bounds) < kMinimumTouchAreaDimension || CGRectGetHeight(self.bounds) < kMinimumTouchAreaDimension) + ) { + // Make sure that we intercept touch events even outside our bounds if they are within the + // minimum touch area. Otherwise this button is too hard to tap + CGRect expandedBounds = CGRectInset(self.bounds, MIN(CGRectGetWidth(self.bounds) - kMinimumTouchAreaDimension, 0), MIN(CGRectGetHeight(self.bounds) < kMinimumTouchAreaDimension, 0)); + pointInside = CGRectContainsPoint(expandedBounds, point); + } + + return pointInside; +} + +- (CGSize)intrinsicContentSize { + return CGSizeMake(kContentSizeDimension, kContentSizeDimension); +} + +- (void)setSelected:(BOOL)selected { + [super setSelected:selected]; + _contentView.selected = selected; +} + +- (void)setCustomization:(STDSSelectionCustomization *)customization { + _contentView.customization = customization; +} + +- (STDSSelectionCustomization *)customization { + return _contentView.customization; +} + +- (void)setIsCheckbox:(BOOL)isCheckbox { + _contentView.isCheckbox = isCheckbox; +} + +- (BOOL)isCheckbox { + return _contentView.isCheckbox; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSSimulatorChecker.h b/Stripe3DS2/Stripe3DS2/STDSSimulatorChecker.h new file mode 100644 index 00000000..7b6e77d7 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSimulatorChecker.h @@ -0,0 +1,19 @@ +// +// STDSSimulatorChecker.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSSimulatorChecker : NSObject + ++ (BOOL)isRunningOnSimulator; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSimulatorChecker.m b/Stripe3DS2/Stripe3DS2/STDSSimulatorChecker.m new file mode 100644 index 00000000..47ea89e1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSimulatorChecker.m @@ -0,0 +1,25 @@ +// +// STDSSimulatorChecker.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/8/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSSimulatorChecker.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSSimulatorChecker + ++ (BOOL)isRunningOnSimulator { +#if TARGET_IPHONE_SIMULATOR + return YES; +#else + return NO; +#endif +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSpacerView.h b/Stripe3DS2/Stripe3DS2/STDSSpacerView.h new file mode 100644 index 00000000..072ad1ed --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSpacerView.h @@ -0,0 +1,20 @@ +// +// STDSSpacerView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSStackView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSSpacerView : UIView + +- (instancetype)initWithLayoutAxis:(STDSStackViewLayoutAxis)layoutAxis dimension:(CGFloat)dimension; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSpacerView.m b/Stripe3DS2/Stripe3DS2/STDSSpacerView.m new file mode 100644 index 00000000..a674e68a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSpacerView.m @@ -0,0 +1,34 @@ +// +// STDSSpacerView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSSpacerView.h" + +@implementation STDSSpacerView + +- (instancetype)initWithLayoutAxis:(STDSStackViewLayoutAxis)layoutAxis dimension:(CGFloat)dimension { + self = [super initWithFrame:CGRectZero]; + + if (self) { + NSLayoutConstraint *constraint; + + switch (layoutAxis) { + case STDSStackViewLayoutAxisHorizontal: + constraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:dimension]; + break; + case STDSStackViewLayoutAxisVertical: + constraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:dimension]; + break; + } + + [NSLayoutConstraint activateConstraints:@[constraint]]; + } + + return self; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSStackView.h b/Stripe3DS2/Stripe3DS2/STDSStackView.h new file mode 100644 index 00000000..cfedb01a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSStackView.h @@ -0,0 +1,56 @@ +// +// STDSStackView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, STDSStackViewLayoutAxis) { + + /// A horizontal layout for the stack view to use. + STDSStackViewLayoutAxisHorizontal = 0, + + /// A vertical layout for the stack view to use. + STDSStackViewLayoutAxisVertical = 1, +}; + +@interface STDSStackView: UIView + +/** + Initializes an `STDSStackView`. + + @param alignment The alignment for the stack view to use. + @return An initialized `STDSStackView`. + */ +- (instancetype)initWithAlignment:(STDSStackViewLayoutAxis)alignment; + +/** + Adds a subview to the list of arranged subviews. Views will be displayed in the order they are added. + + @param view The view to add to the stack view. + */ +- (void)addArrangedSubview:(UIView *)view; + +/** + Removes a subview from the list of arranged subviews. + + @param view The view to remove. + */ +- (void)removeArrangedSubview:(UIView *)view; + +/** + Adds a spacer that fits the layout axis of the `STDSStackView`. + + @param dimension How wide or tall the spacer should be, depending on the axis of the `STDSStackView`. + @note Spacers added through this function will not be removed or hidden automatically when they no longer fall between two views. For more precise interactions, add an `STDSSpacerView` manually through `addArrangedSubview:`. + */ +- (void)addSpacer:(CGFloat)dimension; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSStackView.m b/Stripe3DS2/Stripe3DS2/STDSStackView.m new file mode 100644 index 00000000..7ec01d1e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSStackView.m @@ -0,0 +1,164 @@ +// +// STDSStackView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSStackView.h" +#import "STDSSpacerView.h" +#import "NSLayoutConstraint+LayoutSupport.h" + +@interface STDSStackView() + +@property (nonatomic) STDSStackViewLayoutAxis layoutAxis; +@property (nonatomic, strong) NSMutableArray *arrangedSubviews; +@property (nonatomic, strong, readonly) NSArray *visibleArrangedSubviews; + +@end + +@implementation STDSStackView + +static NSString *UIViewHiddenKeyPath = @"hidden"; + +- (instancetype)initWithAlignment:(STDSStackViewLayoutAxis)layoutAxis { + self = [super initWithFrame:CGRectZero]; + + if (self) { + _layoutAxis = layoutAxis; + _arrangedSubviews = [NSMutableArray array]; + } + + return self; +} + +- (NSArray *)visibleArrangedSubviews { + return [self.arrangedSubviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *object, NSDictionary *bindings) { + return !object.isHidden; + }]]; +} + +- (void)addArrangedSubview:(UIView *)view { + view.translatesAutoresizingMaskIntoConstraints = false; + + [self _deactivateExistingConstraints]; + + [self.arrangedSubviews addObject:view]; + [self addSubview:view]; + + [self _applyConstraints]; + + [view addObserver:self forKeyPath:UIViewHiddenKeyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL]; +} + +- (void)removeArrangedSubview:(UIView *)view { + if (![self.arrangedSubviews containsObject:view]) { + return; + } + + [self _deactivateExistingConstraints]; + + [view removeObserver:self forKeyPath:UIViewHiddenKeyPath]; + + [self.arrangedSubviews removeObject:view]; + [view removeFromSuperview]; + + [self _applyConstraints]; +} + +- (void)addSpacer:(CGFloat)dimension { + STDSSpacerView *spacerView = [[STDSSpacerView alloc] initWithLayoutAxis:self.layoutAxis dimension:dimension]; + + [self addArrangedSubview:spacerView]; +} + +- (void)dealloc { + for (UIView *view in self.arrangedSubviews) { + [view removeObserver:self forKeyPath:UIViewHiddenKeyPath]; + } +} + +- (void)_applyConstraints { + if (self.layoutAxis == STDSStackViewLayoutAxisHorizontal) { + [self _applyHorizontalConstraints]; + } else { + [self _applyVerticalConstraints]; + } +} + +- (void)_deactivateExistingConstraints { + [NSLayoutConstraint deactivateConstraints:self.constraints]; +} + +- (void)_applyVerticalConstraints { + UIView *previousView; + + for (UIView *view in self.visibleArrangedSubviews) { + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint _stds_leftConstraintWithItem:view toItem:self]; + NSLayoutConstraint *rightConstraint = [NSLayoutConstraint _stds_rightConstraintWithItem:view toItem:self]; + NSLayoutConstraint *topConstraint; + + if (previousView == nil) { + topConstraint = [NSLayoutConstraint _stds_topConstraintWithItem:view toItem:self]; + } else { + topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; + } + + [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, rightConstraint]]; + + if (view == self.visibleArrangedSubviews.lastObject) { + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint _stds_bottomConstraintWithItem:view toItem:self]; + + [NSLayoutConstraint activateConstraints:@[bottomConstraint]]; + } + + previousView = view; + } +} + +- (void)_applyHorizontalConstraints { + UIView *previousView; + NSLayoutConstraint *previousRightConstraint; + + for (UIView *view in self.visibleArrangedSubviews) { + NSLayoutConstraint *topConstraint = [NSLayoutConstraint _stds_topConstraintWithItem:view toItem:self]; + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint _stds_bottomConstraintWithItem:view toItem:self]; + NSLayoutConstraint *rightConstraint = [NSLayoutConstraint _stds_rightConstraintWithItem:view toItem:self]; + + if (previousView == nil) { + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint _stds_leftConstraintWithItem:view toItem:self]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, rightConstraint, bottomConstraint]]; + } else { + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousView attribute:NSLayoutAttributeRight multiplier:1 constant:0]; + + if (previousRightConstraint != nil) { + [NSLayoutConstraint deactivateConstraints:@[previousRightConstraint]]; + } + + NSLayoutConstraint *previousConstraint = [NSLayoutConstraint constraintWithItem:previousView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, rightConstraint, previousConstraint, bottomConstraint]]; + } + + previousView = view; + previousRightConstraint = rightConstraint; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([object isKindOfClass:[UIView class]] && [keyPath isEqualToString:UIViewHiddenKeyPath]) { + BOOL hiddenStatusChanged = [change[NSKeyValueChangeNewKey] boolValue] != [change[NSKeyValueChangeOldKey] boolValue]; + + if (hiddenStatusChanged) { + [self _deactivateExistingConstraints]; + + [self _applyConstraints]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/STDSSynchronousLocationManager.h b/Stripe3DS2/Stripe3DS2/STDSSynchronousLocationManager.h new file mode 100644 index 00000000..883c6a35 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSynchronousLocationManager.h @@ -0,0 +1,26 @@ +// +// STDSSynchronousLocationManager.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class CLLocation; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSSynchronousLocationManager : NSObject + ++ (instancetype)sharedManager; + ++ (BOOL)hasPermissions; + +// May be long running. Will return nil on failure or if app lacks permissions +- (nullable CLLocation *)deviceLocation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSSynchronousLocationManager.m b/Stripe3DS2/Stripe3DS2/STDSSynchronousLocationManager.m new file mode 100644 index 00000000..8d73003f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSSynchronousLocationManager.m @@ -0,0 +1,123 @@ +// +// STDSSynchronousLocationManager.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/23/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSSynchronousLocationManager.h" +#import "STDSVisionSupport.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +static const int64_t kLocationFetchTimeoutSeconds = 15; + +typedef void (^LocationUpdateCompletionBlock)(CLLocation * _Nullable); + +@interface STDSSynchronousLocationManager () + +@end + +@implementation STDSSynchronousLocationManager +{ + CLLocationManager * _Nullable _locationManager; + dispatch_queue_t _Nullable _locationFetchQueue; + NSMutableArray *_pendingLocationUpdateCompletions; +} + ++ (instancetype)sharedManager { + static STDSSynchronousLocationManager *sharedManager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedManager = [[STDSSynchronousLocationManager alloc] init]; + }); + return sharedManager; +} + ++ (BOOL)hasPermissions { +// TODO: Revisit this after we drop iOS 13, iOS 14 has a new API for authorizationStatus +#ifdef STP_TARGET_VISION + if (@available(iOS 14.0, *)) { + CLAuthorizationStatus authorizationStatus = [[[CLLocationManager alloc] init] authorizationStatus]; + return [CLLocationManager locationServicesEnabled] && + authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse; + } else { + // This should never happen + return NO; + } +#else + CLAuthorizationStatus authorizationStatus = [CLLocationManager authorizationStatus]; + return [CLLocationManager locationServicesEnabled] && + (authorizationStatus == kCLAuthorizationStatusAuthorizedAlways || authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse); +#endif +} + +- (instancetype)init { + self = [super init]; + if (self) { + if ([STDSSynchronousLocationManager hasPermissions]) { + _locationManager = [[CLLocationManager alloc] init]; + _locationManager.delegate = self; + _locationFetchQueue = dispatch_queue_create("com.stripe.3ds2locationqueue", DISPATCH_QUEUE_SERIAL); + } + _pendingLocationUpdateCompletions = [NSMutableArray array]; + } + + return self; +} + +- (nullable CLLocation *)deviceLocation { + + __block CLLocation *location = nil; + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter(group); + [self _fetchDeviceLocation:^(CLLocation * _Nullable latestLocation) { + location = latestLocation; + dispatch_group_leave(group); + }]; + + dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * kLocationFetchTimeoutSeconds)); + return location; +} + +- (void)_fetchDeviceLocation:(void (^)(CLLocation * _Nullable))completion { + + if (![STDSSynchronousLocationManager hasPermissions] || _locationFetchQueue == nil) { + return completion(nil); + } + + dispatch_async(_locationFetchQueue, ^{ + [self->_pendingLocationUpdateCompletions addObject:completion]; + + if (self->_pendingLocationUpdateCompletions.count == 1) { + [self->_locationManager requestLocation]; + } + }); +} + +- (void)_stopUpdatingLocationAndReportResult:(nullable CLLocation *)location { + [_locationManager stopUpdatingLocation]; + + dispatch_async(_locationFetchQueue, ^{ + for (LocationUpdateCompletionBlock completion in self->_pendingLocationUpdateCompletions) { + completion(location); + } + [self->_pendingLocationUpdateCompletions removeAllObjects]; + }); +} + +#pragma mark - CLLocationManagerDelegate +- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + [self _stopUpdatingLocationAndReportResult:locations.firstObject]; +} + +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { + [self _stopUpdatingLocationAndReportResult:nil]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSTextChallengeView.h b/Stripe3DS2/Stripe3DS2/STDSTextChallengeView.h new file mode 100644 index 00000000..d44ea54a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSTextChallengeView.h @@ -0,0 +1,26 @@ +// +// STDSTextChallengeView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSTextFieldCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSTextField: UITextField + +@end + +@interface STDSTextChallengeView : UIView + +@property (nonatomic, strong, nullable) STDSTextFieldCustomization *textFieldCustomization; +@property (nonatomic, copy, readonly, nullable) NSString *inputText; +@property (nonatomic, strong) STDSTextField *textField; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSTextChallengeView.m b/Stripe3DS2/Stripe3DS2/STDSTextChallengeView.m new file mode 100644 index 00000000..eb390e5e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSTextChallengeView.m @@ -0,0 +1,128 @@ +// +// STDSTextChallengeView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSTextChallengeView.h" +#import "STDSStackView.h" +#import "STDSVisionSupport.h" +#import "UIView+LayoutSupport.h" +#import "NSString+EmptyChecking.h" +#import "UIColor+ThirteenSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSTextField + +static const CGFloat kTextFieldMargin = (CGFloat)8.0; + +- (CGRect)textRectForBounds:(CGRect)bounds { + return CGRectInset(bounds, kTextFieldMargin, 0); +} + +- (CGRect)editingRectForBounds:(CGRect)bounds { + return CGRectInset(bounds, kTextFieldMargin, 0); +} + +- (nullable NSString *)accessibilityIdentifier { + return @"STDSTextField"; +} + +@end + +@interface STDSTextChallengeView() + +@property (nonatomic, strong) STDSStackView *containerView; +@property (nonatomic, strong) NSLayoutConstraint *borderViewHeightConstraint; + +@end + +@implementation STDSTextChallengeView + +static const CGFloat kBorderViewHeight = 1; +static const CGFloat kTextFieldKernSpacing = 3; +static const CGFloat kTextFieldPlaceholderKernSpacing = 14; +static const CGFloat kTextChallengeViewBottomPadding = 11; + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self _setupViewHierarchy]; + } + + return self; +} + +- (void)_setupViewHierarchy { + self.layoutMargins = UIEdgeInsetsMake(0, 0, kTextChallengeViewBottomPadding, 0); + + self.containerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + + self.textField = [[STDSTextField alloc] init]; + self.textField.autocorrectionType = UITextAutocorrectionTypeNo; + self.textField.autocapitalizationType = UITextAutocapitalizationTypeNone; + self.textField.delegate = self; + self.textField.clearButtonMode = UITextFieldViewModeWhileEditing; + self.textField.textContentType = UITextContentTypeOneTimeCode; + [self.textField.defaultTextAttributes setValue:@(kTextFieldKernSpacing) forKey:NSKernAttributeName]; + + UIView *borderView = [UIView new]; + borderView.backgroundColor = [UIColor _stds_colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { + return [[UIColor _stds_systemGray2Color] colorWithAlphaComponent:(CGFloat)0.6]; + }]; + + [self.containerView addArrangedSubview:self.textField]; + [self.containerView addArrangedSubview:borderView]; + [self addSubview:self.containerView]; + [self.containerView _stds_pinToSuperviewBounds]; + + self.borderViewHeightConstraint = [NSLayoutConstraint constraintWithItem:borderView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:kBorderViewHeight]; + [NSLayoutConstraint activateConstraints:@[self.borderViewHeightConstraint]]; +} + +- (void)setTextFieldCustomization:(STDSTextFieldCustomization * _Nullable)textFieldCustomization { + _textFieldCustomization = textFieldCustomization; + + self.textField.font = textFieldCustomization.font; + self.textField.textColor = textFieldCustomization.textColor; + self.textField.layer.borderColor = textFieldCustomization.borderColor.CGColor; + self.textField.layer.borderWidth = textFieldCustomization.borderWidth; + self.textField.layer.cornerRadius = textFieldCustomization.cornerRadius; + self.textField.keyboardAppearance = textFieldCustomization.keyboardAppearance; + NSDictionary *placeholderTextAttributes = @{ + NSKernAttributeName: @(kTextFieldPlaceholderKernSpacing), + NSBaselineOffsetAttributeName: @(3.0f), + NSForegroundColorAttributeName: textFieldCustomization.placeholderTextColor, + }; + self.textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"••••••" attributes:placeholderTextAttributes]; +} + +- (NSString * _Nullable)inputText { + return self.textField.text; +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + +#if !STP_TARGET_VISION + if (self.window.screen.nativeScale > 0) { + self.borderViewHeightConstraint.constant = kBorderViewHeight / self.window.screen.nativeScale; + } +#endif +} + +#pragma mark - UITextFieldDelegate + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + + return NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSThreeDSProtocolVersion+Private.h b/Stripe3DS2/Stripe3DS2/STDSThreeDSProtocolVersion+Private.h new file mode 100644 index 00000000..43257e78 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSThreeDSProtocolVersion+Private.h @@ -0,0 +1,53 @@ +// +// STDSThreeDSProtocolVersion+Private.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSThreeDSProtocolVersion.h" + +NS_ASSUME_NONNULL_BEGIN + + +typedef NS_ENUM(NSInteger, STDSThreeDSProtocolVersion) { + STDSThreeDSProtocolVersion2_1_0, + STDSThreeDSProtocolVersion2_2_0, + STDSThreeDSProtocolVersionUnknown, + STDSThreeDSProtocolVersionFallbackTest, +}; + +static NSString * const kThreeDS2ProtocolVersion2_1_0 = @"2.1.0"; +static NSString * const kThreeDS2ProtocolVersion2_2_0 = @"2.2.0"; +static NSString * const kThreeDSProtocolVersionFallbackTest = @"2.0.0"; + +NS_INLINE STDSThreeDSProtocolVersion STDSThreeDSProtocolVersionForString(NSString *stringValue) { + if ([stringValue isEqualToString:kThreeDS2ProtocolVersion2_1_0]) { + return STDSThreeDSProtocolVersion2_1_0; + } else if ([stringValue isEqualToString:kThreeDS2ProtocolVersion2_2_0]) { + return STDSThreeDSProtocolVersion2_2_0; + } else if ([stringValue isEqualToString:kThreeDSProtocolVersionFallbackTest]) { + return STDSThreeDSProtocolVersionFallbackTest; + } else { + return STDSThreeDSProtocolVersionUnknown; + } +} + +NS_INLINE NSString * _Nullable STDSThreeDSProtocolVersionStringValue(STDSThreeDSProtocolVersion protocolVersion) { + switch (protocolVersion) { + case STDSThreeDSProtocolVersion2_1_0: + return kThreeDS2ProtocolVersion2_1_0; + + case STDSThreeDSProtocolVersion2_2_0: + return kThreeDS2ProtocolVersion2_2_0; + + case STDSThreeDSProtocolVersionFallbackTest: + return kThreeDSProtocolVersionFallbackTest; + + case STDSThreeDSProtocolVersionUnknown: + return nil; + } +} + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSThreeDSProtocolVersion.m b/Stripe3DS2/Stripe3DS2/STDSThreeDSProtocolVersion.m new file mode 100644 index 00000000..f157455e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSThreeDSProtocolVersion.m @@ -0,0 +1,15 @@ +// +// STDSThreeDSProtocolVersion.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 6/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSThreeDSProtocolVersion+Private.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString * const Stripe3DS2ProtocolVersion = @"2.2.0"; + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSTransaction+Private.h b/Stripe3DS2/Stripe3DS2/STDSTransaction+Private.h new file mode 100644 index 00000000..e744c38d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSTransaction+Private.h @@ -0,0 +1,44 @@ +// +// STDSTransaction+Private.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSTransaction.h" + +@class STDSDeviceInformation; +@class STDSDirectoryServerCertificate; +@protocol STDSAnalyticsDelegate; + +#import "STDSDirectoryServer.h" +#import "STDSThreeDSProtocolVersion+Private.h" +#import "STDSUICustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSTransaction () + +- (instancetype)initWithDeviceInformation:(STDSDeviceInformation *)deviceInformation + directoryServer:(STDSDirectoryServer)directoryServer + protocolVersion:(STDSThreeDSProtocolVersion)protocolVersion + uiCustomization:(STDSUICustomization *)uiCustomization + analyticsDelegate:(nullable id)analyticsDelegate; + +- (instancetype)initWithDeviceInformation:(STDSDeviceInformation *)deviceInformation + directoryServerID:(NSString *)directoryServerID + serverKeyID:(nullable NSString *)serverKeyID + directoryServerCertificate:(STDSDirectoryServerCertificate *)directoryServerCertificate + rootCertificateStrings:(NSArray *)rootCertificateStrings + protocolVersion:(STDSThreeDSProtocolVersion)protocolVersion + uiCustomization:(STDSUICustomization *)uiCustomization + analyticsDelegate:(nullable id)analyticsDelegate; + +@property (nonatomic, strong) NSTimer *timeoutTimer; +@property (nonatomic) BOOL bypassTestModeVerification; // Should be used during internal testing ONLY +@property (nonatomic) BOOL useULTestLOA; // Should only be used when running tests with the UL reference app + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSVisionSupport.h b/Stripe3DS2/Stripe3DS2/STDSVisionSupport.h new file mode 100644 index 00000000..4d33e15f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSVisionSupport.h @@ -0,0 +1,21 @@ +// +// STDSVisionSupport.h +// Stripe3DS2 +// +// Created by David Estes on 11/21/23. +// + +#ifndef STDSVisionSupport_h +#define STDSVisionSupport_h + +#ifdef TARGET_OS_VISION +#if TARGET_OS_VISION +#define STP_TARGET_VISION 1 +#else +#endif +#else +#define STP_TARGET_VISION 0 +#endif + + +#endif /* STDSVisionSupport_h */ diff --git a/Stripe3DS2/Stripe3DS2/STDSWebView.h b/Stripe3DS2/Stripe3DS2/STDSWebView.h new file mode 100644 index 00000000..7e6673fc --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSWebView.h @@ -0,0 +1,22 @@ +// +// STDSWebView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSWebView : WKWebView + +/** + Convenience method that prepends the given HTML string with a CSP meta tag that disables external resource loading, and passes it to `loadHTMLString:baseURL:`. + */ +- (WKNavigation *)loadExternalResourceBlockingHTMLString:(NSString *)html; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSWebView.m b/Stripe3DS2/Stripe3DS2/STDSWebView.m new file mode 100644 index 00000000..d30da6bc --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSWebView.m @@ -0,0 +1,37 @@ +// +// STDSWebView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSWebView.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSWebView + +- (instancetype)init { + WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; + configuration.preferences.javaScriptEnabled = NO; + return [super initWithFrame:CGRectZero configuration:configuration]; +} + +/// Overriden to do nothing per 3DS2 security guidelines. +- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler { + +} + +- (WKNavigation *)loadExternalResourceBlockingHTMLString:(NSString *)html { + NSString *cspMetaTag = @""; + return [self loadHTMLString:[cspMetaTag stringByAppendingString:html] baseURL:nil]; +} + +- (nullable NSString *)accessibilityIdentifier { + return @"STDSWebView"; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSWhitelistView.h b/Stripe3DS2/Stripe3DS2/STDSWhitelistView.h new file mode 100644 index 00000000..a744f2b8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSWhitelistView.h @@ -0,0 +1,25 @@ +// +// STDSWhitelistView.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseSelectionInfo.h" +#import "STDSLabelCustomization.h" +#import "STDSSelectionCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSWhitelistView : UIView + +@property (nonatomic, strong, nullable) NSString *whitelistText; +@property (nonatomic, readonly, nullable) id selectedResponse; +@property (nonatomic, strong, nullable) STDSLabelCustomization *labelCustomization; +@property (nonatomic, strong, nullable) STDSSelectionCustomization *selectionCustomization; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/STDSWhitelistView.m b/Stripe3DS2/Stripe3DS2/STDSWhitelistView.m new file mode 100644 index 00000000..a5181fa0 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/STDSWhitelistView.m @@ -0,0 +1,103 @@ +// +// STDSWhitelistView.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSLocalizedString.h" +#import "STDSWhitelistView.h" +#import "STDSStackView.h" +#import "STDSChallengeResponseSelectionInfoObject.h" +#import "NSString+EmptyChecking.h" +#import "UIView+LayoutSupport.h" +#import "STDSSelectionButton.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSWhitelistView() + +@property (nonatomic, strong) UILabel *whitelistLabel; +@property (nonatomic, strong) STDSSelectionButton *selectionButton; + +@end + +@implementation STDSWhitelistView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + [self _setupViewHierarchy]; + } + + return self; +} + +- (void)_setupViewHierarchy { + self.layoutMargins = UIEdgeInsetsZero; + + STDSStackView *containerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [self addSubview:containerView]; + [containerView _stds_pinToSuperviewBounds]; + + self.whitelistLabel = [[UILabel alloc] init]; + self.whitelistLabel.numberOfLines = 0; + + self.selectionButton = [[STDSSelectionButton alloc] initWithCustomization:self.selectionCustomization]; + self.selectionButton.isCheckbox = YES; + [self.selectionButton addTarget:self action:@selector(_selectionButtonWasTapped) forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *stackView = [self _buildStackView]; + [stackView addArrangedSubview:self.selectionButton]; + [stackView addArrangedSubview:self.whitelistLabel]; + + [containerView addArrangedSubview:stackView]; +} + +- (void)setWhitelistText:(NSString * _Nullable)whitelistText { + _whitelistText = whitelistText; + + self.whitelistLabel.text = whitelistText; + self.whitelistLabel.hidden = [NSString _stds_isStringEmpty:whitelistText]; + self.selectionButton.hidden = self.whitelistLabel.hidden; +} + +- (id _Nullable)selectedResponse { + if (self.selectionButton.selected) { + return [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Y" value:STDSLocalizedString(@"Yes", @"The yes answer to a yes or no question.")];; + } + + return [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"N" value:STDSLocalizedString(@"No", @"The no answer to a yes or no question.")]; +} + +- (void)setLabelCustomization:(STDSLabelCustomization * _Nullable)labelCustomization { + _labelCustomization = labelCustomization; + + self.whitelistLabel.font = labelCustomization.font; + self.whitelistLabel.textColor = labelCustomization.textColor; +} + +- (void)setSelectionCustomization:(STDSSelectionCustomization * _Nullable)selectionCustomization { + _selectionCustomization = selectionCustomization; + self.selectionButton.customization = selectionCustomization; +} + +- (UIStackView *)_buildStackView { + UIStackView *stackView = [[UIStackView alloc] init]; + stackView.axis = UILayoutConstraintAxisHorizontal; + stackView.distribution = UIStackViewDistributionFillProportionally; + stackView.alignment = UIStackViewAlignmentCenter; + stackView.spacing = 20; + stackView.translatesAutoresizingMaskIntoConstraints = NO; + return stackView; +} + +- (void)_selectionButtonWasTapped { + self.selectionButton.selected = !self.selectionButton.selected; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/Stripe3DS2-Bridging-Header.h b/Stripe3DS2/Stripe3DS2/Stripe3DS2-Bridging-Header.h new file mode 100644 index 00000000..cfc1861e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/Stripe3DS2-Bridging-Header.h @@ -0,0 +1,12 @@ +// +// Stripe3DS2-Bridging-Header.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 4/10/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#ifndef Stripe3DS2_Bridging_Header_h +#define Stripe3DS2_Bridging_Header_h + +#endif /* Stripe3DS2_Bridging_Header_h */ diff --git a/Stripe3DS2/Stripe3DS2/UIButton+CustomInitialization.h b/Stripe3DS2/Stripe3DS2/UIButton+CustomInitialization.h new file mode 100644 index 00000000..badb4d6d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIButton+CustomInitialization.h @@ -0,0 +1,21 @@ +// +// UIButton+CustomInitialization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSUICustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UIButton (CustomInitialization) + ++ (UIButton *)_stds_buttonWithTitle:(NSString * _Nullable)title customization:(STDSButtonCustomization * _Nullable)customization; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIButton+CustomInitialization.m b/Stripe3DS2/Stripe3DS2/UIButton+CustomInitialization.m new file mode 100644 index 00000000..9dc7e574 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIButton+CustomInitialization.m @@ -0,0 +1,69 @@ +// +// UIButton+CustomInitialization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "UIButton+CustomInitialization.h" +#import "STDSVisionSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation UIButton (CustomInitialization) + +#if !STP_TARGET_VISION +static const CGFloat kDefaultButtonContentInset = (CGFloat)12.0; +#endif + ++ (UIButton *)_stds_buttonWithTitle:(NSString * _Nullable)title customization:(STDSButtonCustomization * _Nullable)customization { + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + button.clipsToBounds = YES; +#if !STP_TARGET_VISION // UIButton edge insets not supported on visionOS + button.contentEdgeInsets = UIEdgeInsetsMake(kDefaultButtonContentInset, 0, kDefaultButtonContentInset, 0); +#endif + [[self class] _stds_configureButton:button withTitle:title customization:customization]; + + return button; +} + ++ (void)_stds_configureButton:(UIButton *)button withTitle:(NSString * _Nullable)buttonTitle customization:(STDSButtonCustomization * _Nullable)buttonCustomization { + button.backgroundColor = buttonCustomization.backgroundColor; + button.layer.cornerRadius = buttonCustomization.cornerRadius; + + UIFont *font = buttonCustomization.font; + UIColor *textColor = buttonCustomization.textColor; + + if (buttonTitle != nil) { + NSMutableDictionary *attributesDictionary = [NSMutableDictionary dictionary]; + + if (font != nil) { + attributesDictionary[NSFontAttributeName] = font; + } + + if (textColor != nil) { + attributesDictionary[NSForegroundColorAttributeName] = textColor; + } + switch (buttonCustomization.titleStyle) { + case STDSButtonTitleStyleDefault: + break; + case STDSButtonTitleStyleSentenceCapitalized: + buttonTitle = [buttonTitle localizedCapitalizedString]; + break; + case STDSButtonTitleStyleLowercase: + buttonTitle = [buttonTitle localizedLowercaseString]; + break; + case STDSButtonTitleStyleUppercase: + buttonTitle = [buttonTitle localizedUppercaseString]; + break; + } + + NSAttributedString *title = [[NSAttributedString alloc] initWithString:buttonTitle attributes:attributesDictionary]; + [button setAttributedTitle:title forState:UIControlStateNormal]; + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIColor+DefaultColors.h b/Stripe3DS2/Stripe3DS2/UIColor+DefaultColors.h new file mode 100644 index 00000000..3c45eb9c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIColor+DefaultColors.h @@ -0,0 +1,21 @@ +// +// UIColor+DefaultColors.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIColor (DefaultColors) + +/// The challenge view footer background color ++ (UIColor *)_stds_defaultFooterBackgroundColor; ++ (UIColor *)_stds_blueColor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIColor+DefaultColors.m b/Stripe3DS2/Stripe3DS2/UIColor+DefaultColors.m new file mode 100644 index 00000000..8c33d519 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIColor+DefaultColors.m @@ -0,0 +1,24 @@ +// +// UIColor+DefaultColors.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "UIColor+DefaultColors.h" +#import "UIColor+ThirteenSupport.h" + +@implementation UIColor (DefaultColors) + ++ (UIColor *)_stds_defaultFooterBackgroundColor { + return [UIColor _stds_systemGray5Color]; +} + ++ (UIColor *)_stds_blueColor { + return [UIColor _stds_colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { + return (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) ? [UIColor colorWithRed:(CGFloat)(29.0 / 255.0) green:(CGFloat)(115.0 / 255.0) blue:(CGFloat)(250.0 / 255.0) alpha:1.0] : [UIColor colorWithRed:(CGFloat)(39.0 / 255.0) green:(CGFloat)(125.0 / 255.0) blue:(CGFloat)(255.0 / 255.0) alpha:1.0]; + }]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/UIColor+ThirteenSupport.h b/Stripe3DS2/Stripe3DS2/UIColor+ThirteenSupport.h new file mode 100644 index 00000000..fdccd761 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIColor+ThirteenSupport.h @@ -0,0 +1,24 @@ +// +// UIColor+ThirteenSupport.h +// Stripe3DS2 +// +// Created by David Estes on 8/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIColor (STDSThirteenSupport) + ++ (UIColor *)_stds_colorWithDynamicProvider:(UIColor * _Nonnull (^)(UITraitCollection *traitCollection))dynamicProvider; ++ (UIColor *)_stds_systemGray5Color; ++ (UIColor *)_stds_systemGray2Color; ++ (UIColor *)_stds_systemBackgroundColor; ++ (UIColor *)_stds_labelColor; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIColor+ThirteenSupport.m b/Stripe3DS2/Stripe3DS2/UIColor+ThirteenSupport.m new file mode 100644 index 00000000..ffff47be --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIColor+ThirteenSupport.m @@ -0,0 +1,33 @@ +// +// UIColor+ThirteenSupport.m +// Stripe3DS2 +// +// Created by David Estes on 8/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "UIColor+ThirteenSupport.h" + +@implementation UIColor (STDSThirteenSupport) + ++ (UIColor *)_stds_colorWithDynamicProvider:(UIColor * _Nonnull (^)(UITraitCollection *traitCollection))dynamicProvider { + return [UIColor colorWithDynamicProvider:dynamicProvider]; +} + ++ (UIColor *)_stds_systemGray5Color { + return [UIColor systemGray5Color]; +} + ++ (UIColor *)_stds_systemGray2Color { + return [UIColor systemGray2Color]; +} + ++ (UIColor *)_stds_systemBackgroundColor { + return [UIColor systemBackgroundColor]; +} + ++ (UIColor *)_stds_labelColor { + return [UIColor labelColor]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/UIFont+DefaultFonts.h b/Stripe3DS2/Stripe3DS2/UIFont+DefaultFonts.h new file mode 100644 index 00000000..ceb5bfa9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIFont+DefaultFonts.h @@ -0,0 +1,23 @@ +// +// UIFont+DefaultFonts.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIFont (DefaultFonts) + ++ (UIFont *)_stds_defaultHeadingTextFont; + ++ (UIFont *)_stds_defaultLabelTextFontWithScale:(CGFloat)scale; ++ (UIFont *)_stds_defaultButtonTextFontWithScale:(CGFloat)scale; ++ (UIFont *)_stds_defaultBoldLabelTextFontWithScale:(CGFloat)scale; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIFont+DefaultFonts.m b/Stripe3DS2/Stripe3DS2/UIFont+DefaultFonts.m new file mode 100644 index 00000000..26e10ef7 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIFont+DefaultFonts.m @@ -0,0 +1,41 @@ +// +// UIFont+DefaultFonts.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "UIFont+DefaultFonts.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation UIFont (DefaultFonts) + ++ (UIFont *)_stds_defaultHeadingTextFont { + UIFontDescriptor *fontDescriptor = [[UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline] fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; + + return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize * (CGFloat)1.1]; +} + ++ (UIFont *)_stds_defaultLabelTextFontWithScale:(CGFloat)scale { + UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; + + return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize * scale]; +} + ++ (UIFont *)_stds_defaultBoldLabelTextFontWithScale:(CGFloat)scale { + UIFontDescriptor *fontDescriptor = [[UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody] fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; + + return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize * scale]; +} + ++ (UIFont *)_stds_defaultButtonTextFontWithScale:(CGFloat)scale { + UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline]; + + return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize * scale]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIView+LayoutSupport.h b/Stripe3DS2/Stripe3DS2/UIView+LayoutSupport.h new file mode 100644 index 00000000..2c849dee --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIView+LayoutSupport.h @@ -0,0 +1,24 @@ +// +// UIView+LayoutSupport.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (LayoutSupport) + +/** + Pins the view to its superview's bounds. + */ +- (void)_stds_pinToSuperviewBounds; + +- (void)_stds_pinToSuperviewBoundsWithoutMargin; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIView+LayoutSupport.m b/Stripe3DS2/Stripe3DS2/UIView+LayoutSupport.m new file mode 100644 index 00000000..374088b0 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIView+LayoutSupport.m @@ -0,0 +1,35 @@ +// +// UIView+LayoutSupport.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "UIView+LayoutSupport.h" + +@implementation UIView (LayoutSupport) + +- (void)_stds_pinToSuperviewBounds { + self.translatesAutoresizingMaskIntoConstraints = false; + + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeTopMargin multiplier:1 constant:0]; + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeBottomMargin multiplier:1 constant:0]; + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeLeftMargin multiplier:1 constant:0]; + NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeRightMargin multiplier:1 constant:0]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leftConstraint, rightConstraint]]; +} + +- (void)_stds_pinToSuperviewBoundsWithoutMargin { + self.translatesAutoresizingMaskIntoConstraints = false; + + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeTop multiplier:1 constant:0]; + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; + NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.superview attribute:NSLayoutAttributeRight multiplier:1 constant:0]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leftConstraint, rightConstraint]]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/UIViewController+Stripe3DS2.h b/Stripe3DS2/Stripe3DS2/UIViewController+Stripe3DS2.h new file mode 100644 index 00000000..130a490f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIViewController+Stripe3DS2.h @@ -0,0 +1,21 @@ +// +// UIViewController+Stripe3DS2.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 5/6/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class STDSUICustomization; + +@interface UIViewController (Stripe3DS2) + +- (void)_stds_setupNavigationBarElementsWithCustomization:(STDSUICustomization *)customization cancelButtonSelector:(SEL)cancelButtonSelector; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/UIViewController+Stripe3DS2.m b/Stripe3DS2/Stripe3DS2/UIViewController+Stripe3DS2.m new file mode 100644 index 00000000..2fe1a6a3 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/UIViewController+Stripe3DS2.m @@ -0,0 +1,53 @@ +// +// UIViewController+Stripe3DS2.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 5/6/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "UIViewController+Stripe3DS2.h" + +#import "UIButton+CustomInitialization.h" +#import "STDSUICustomization.h" + +@implementation UIViewController (Stripe3DS2) + +- (void)_stds_setupNavigationBarElementsWithCustomization:(STDSUICustomization *)customization cancelButtonSelector:(SEL)cancelButtonSelector { + STDSNavigationBarCustomization *navigationBarCustomization = customization.navigationBarCustomization; + + self.navigationController.navigationBar.barStyle = customization.navigationBarCustomization.barStyle; + + // Cancel button + STDSButtonCustomization *cancelButtonCustomization = [customization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeCancel]; + UIButton *cancelButton = [UIButton _stds_buttonWithTitle:navigationBarCustomization.buttonText customization:cancelButtonCustomization]; + // The cancel button's frame has a size of 0 in iOS 8 + cancelButton.frame = CGRectMake(0, 0, cancelButton.intrinsicContentSize.width, cancelButton.intrinsicContentSize.height); + cancelButton.accessibilityIdentifier = @"Cancel"; + [cancelButton addTarget:self action:cancelButtonSelector forControlEvents:UIControlEventTouchUpInside]; + UIBarButtonItem *cancelBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:cancelButton]; + self.navigationItem.rightBarButtonItem = cancelBarButtonItem; + + // Title + self.title = navigationBarCustomization.headerText; + NSMutableDictionary *titleTextAttributes = [NSMutableDictionary dictionary]; + UIFont *headerFont = navigationBarCustomization.font; + if (headerFont) { + titleTextAttributes[NSFontAttributeName] = headerFont; + } + UIColor *headerColor = navigationBarCustomization.textColor; + if (headerColor) { + titleTextAttributes[NSForegroundColorAttributeName] = headerColor; + } + self.navigationController.navigationBar.titleTextAttributes = titleTextAttributes; + + // Color + self.navigationController.navigationBar.barTintColor = navigationBarCustomization.barTintColor; + self.navigationController.navigationBar.translucent = navigationBarCustomization.translucent; + + if (navigationBarCustomization.scrollEdgeAppearance) { + self.navigationController.navigationBar.scrollEdgeAppearance = navigationBarCustomization.scrollEdgeAppearance; + } +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/include/STDSAlreadyInitializedException.h b/Stripe3DS2/Stripe3DS2/include/STDSAlreadyInitializedException.h new file mode 100644 index 00000000..c39a85d8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSAlreadyInitializedException.h @@ -0,0 +1,22 @@ +// +// STDSAlreadyInitializedException.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSException.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSAlreadyInitializedException` represents an exception that will be thrown in the `STDSThreeDS2Service` instance has already been initialized. + + @see STDSThreeDS2Service + */ +@interface STDSAlreadyInitializedException : STDSException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSAlreadyInitializedException.m b/Stripe3DS2/Stripe3DS2/include/STDSAlreadyInitializedException.m new file mode 100644 index 00000000..af161983 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSAlreadyInitializedException.m @@ -0,0 +1,17 @@ +// +// STDSAlreadyInitializedException.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSAlreadyInitializedException.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSAlreadyInitializedException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSAnalyticsDelegate.h b/Stripe3DS2/Stripe3DS2/include/STDSAnalyticsDelegate.h new file mode 100644 index 00000000..206a2623 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSAnalyticsDelegate.h @@ -0,0 +1,25 @@ +// +// STDSAnalyticsDelegate.h +// Stripe3DS2 +// +// Created by Kenneth Ackerson on 8/21/24. +// + +NS_ASSUME_NONNULL_BEGIN + +@protocol STDSAnalyticsDelegate + +- (void)didReceiveChallengeResponseWithTransactionID:(NSString *)transactionID flow:(NSString *)type; + +- (void)cancelButtonTappedWithTransactionID:(NSString *)transactionID; + +- (void)OTPSubmitButtonTappedWithTransactionID:(NSString *)transactionID; + +- (void)OOBContinueButtonTappedWithTransactionID:(NSString *)transactionID; + +- (void)OOBDidEnterBackground:(NSString *)transactionID; +- (void)OOBWillEnterForeground:(NSString *)transactionID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationRequestParameters.h b/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationRequestParameters.h new file mode 100644 index 00000000..766e4857 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationRequestParameters.h @@ -0,0 +1,68 @@ +// +// STDSAuthenticationRequestParameters.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONEncodable.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSAuthenticationRequestParameters : NSObject + +/** + Designated initializer for `STDSAuthenticationRequestParameters`. + + @param sdkTransactionIdentifier The SDK Transaction Identifier, as created by `[STDSTransaction createTransaction]` + @param deviceData Optional device data collected by the SDK. + @param sdkEphemeralPublicKey The SDK ephemeral public key. + @param sdkAppIdentifier The SDK app identifier. + @param sdkReferenceNumber The SDK reference number. + @param messageVersion The protocol version that is supported by the SDK and used for the transaction. + + @exception InvalidInputException Thrown if an input parameter is invalid. @see InvalidInputException + */ +- (instancetype)initWithSDKTransactionIdentifier:(NSString *)sdkTransactionIdentifier + deviceData:(nullable NSString *)deviceData + sdkEphemeralPublicKey:(NSString *)sdkEphemeralPublicKey + sdkAppIdentifier:(NSString *)sdkAppIdentifier + sdkReferenceNumber:(NSString *)sdkReferenceNumber + messageVersion:(NSString *)messageVersion; + +/** + The encrypted device data as a JWE string. + */ +@property (nonatomic, readonly, nullable) NSString *deviceData; + +/** + The SDK Transaction Identifier. + */ +@property (nonatomic, readonly) NSString *sdkTransactionIdentifier; + +/** + The SDK App Identifier. + */ +@property (nonatomic, readonly) NSString *sdkAppIdentifier; + +/** + The SDK reference number. + */ +@property (nonatomic, readonly) NSString *sdkReferenceNumber; + +/** + The SDK ephemeral public key. + */ +@property (nonatomic, readonly) NSString *sdkEphemeralPublicKey; + +/** + The protocol version that is used for the transaction. + */ +@property (nonatomic, readonly) NSString *messageVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationRequestParameters.m b/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationRequestParameters.m new file mode 100644 index 00000000..dc6286b7 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationRequestParameters.m @@ -0,0 +1,63 @@ +// +// STDSAuthenticationRequestParameters.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSAuthenticationRequestParameters.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSAuthenticationRequestParameters () + +@property (nonatomic, nullable, readonly) NSDictionary *sdkEphemeralPublicKeyJSON; + +@end + +@implementation STDSAuthenticationRequestParameters + +- (instancetype)initWithSDKTransactionIdentifier:(NSString *)sdkTransactionIdentifier + deviceData:(nullable NSString *)deviceData + sdkEphemeralPublicKey:(NSString *)sdkEphemeralPublicKey + sdkAppIdentifier:(NSString *)sdkAppIdentifier + sdkReferenceNumber:(NSString *)sdkReferenceNumber + messageVersion:(NSString *)messageVersion { + self = [super init]; + if (self) { + _sdkTransactionIdentifier = [sdkTransactionIdentifier copy]; + _deviceData = [deviceData copy]; + _sdkEphemeralPublicKey = [sdkEphemeralPublicKey copy]; + _sdkAppIdentifier = [sdkAppIdentifier copy]; + _sdkReferenceNumber = [sdkReferenceNumber copy]; + _messageVersion = [messageVersion copy]; + } + return self; +} + +- (nullable NSDictionary *)sdkEphemeralPublicKeyJSON { + NSData *data = [self.sdkEphemeralPublicKey dataUsingEncoding:NSUTF8StringEncoding]; + if (data == nil) { + return nil; + } + + return [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; +} + +#pragma mark - STDSJSONEncodable + ++ (NSDictionary *)propertyNamesToJSONKeysMapping { + return @{ + NSStringFromSelector(@selector(sdkTransactionIdentifier)): @"sdkTransID", + NSStringFromSelector(@selector(deviceData)): @"sdkEncData", + NSStringFromSelector(@selector(sdkEphemeralPublicKeyJSON)): @"sdkEphemPubKey", + NSStringFromSelector(@selector(sdkAppIdentifier)): @"sdkAppID", + NSStringFromSelector(@selector(sdkReferenceNumber)): @"sdkReferenceNumber", + NSStringFromSelector(@selector(messageVersion)): @"messageVersion", + }; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationResponse.h b/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationResponse.h new file mode 100644 index 00000000..d527f38d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSAuthenticationResponse.h @@ -0,0 +1,115 @@ +// +// STDSAuthenticationResponse.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + The `STDSACSStatusType` enum defines the status of a transaction, as detailed in + 3DS2 Spec Seq 3.33: + */ +typedef NS_ENUM(NSInteger, STDSACSStatusType) { + /// The status is unknown or invalid + STDSACSStatusTypeUnknown = 0, + + /// Authenticated + STDSACSStatusTypeAuthenticated = 1, + + /// Requires a Cardholder challenge to complete authentication + STDSACSStatusTypeChallengeRequired = 2, + + /// Requires a Cardholder challenge using Decoupled Authentication + STDSACSStatusTypeDecoupledAuthentication = 3, + + /// Not authenticated + STDSACSStatusTypeNotAuthenticated = 4, + + /// Not authenticated, but a proof of authentication attempt (Authentication Value) + /// was generated + STDSACSStatusTypeProofGenerated = 5, + + /// Not authenticated, as authentication could not be performed due to technical or + /// other issue + STDSACSStatusTypeError = 6, + + /// Not authenticated because the Issuer is rejecting authentication and requesting + /// that authorisation not be attempted + STDSACSStatusTypeRejected = 7, + + /// Authentication not requested by the 3DS Server for data sent for informational + /// purposes only + STDSACSStatusTypeInformationalOnly = 8, +}; + +/** + A native protocol representing the response sent by the 3DS Server. + Only parameters relevant to performing 3DS2 authentication in the mobile SDK are exposed. + */ +@protocol STDSAuthenticationResponse + +/// Universally unique transaction identifier assigned by the 3DS Server to identify a single transaction. +@property (nonatomic, readonly) NSString *threeDSServerTransactionID; + +/// Transaction status +@property (nonatomic, readonly) STDSACSStatusType status; + +/// Indication of whether a challenge is required. +@property (nonatomic, readonly, getter=isChallengeRequired) BOOL challengeRequired; + +/// Indicates whether the ACS confirms utilisation of Decoupled Authentication and agrees to utilise Decoupled Authentication to authenticate the Cardholder. +@property (nonatomic, readonly) BOOL willUseDecoupledAuthentication; + +/** + DS assigned ACS identifier. + Each DS can provide a unique ID to each ACS on an individual basis. + */ +@property (nonatomic, readonly, nullable) NSString *acsOperatorID; + +/// Unique identifier assigned by the EMVCo Secretariat upon Testing and Approval. +@property (nonatomic, readonly, nullable) NSString *acsReferenceNumber; + +/// Contains the JWS object (represented as a string) created by the ACS for the ARes message. +@property (nonatomic, readonly, nullable) NSString *acsSignedContent; + +/// Universally Unique transaction identifier assigned by the ACS to identify a single transaction. +@property (nonatomic, readonly) NSString *acsTransactionID; + +/// Fully qualified URL of the ACS to be used for the challenge. +@property (nonatomic, readonly, nullable) NSURL *acsURL; + +/** + Text provided by the ACS/Issuer to Cardholder during a Frictionless or Decoupled transaction. The Issuer can provide information to Cardholder. + For example, “Additional authentication is needed for this transaction, please contact (Issuer Name) at xxx-xxx-xxxx.” + */ +@property (nonatomic, readonly, nullable) NSString *cardholderInfo; + +/// EMVCo-assigned unique identifier to track approved DS. +@property (nonatomic, readonly, nullable) NSString *directoryServerReferenceNumber; + +/// Universally unique transaction identifier assigned by the DS to identify a single transaction. +@property (nonatomic, readonly, nullable) NSString *directoryServerTransactionID; + +/** + Protocol version identifier This shall be the Protocol Version Number of the specification utilised by the system creating this message. + The Message Version Number is set by the 3DS Server which originates the protocol with the AReq message. + The Message Version Number does not change during a 3DS transaction. + */ +@property (nonatomic, readonly) NSString *protocolVersion; + +/// Universally unique transaction identifier assigned by the 3DS SDK to identify a single transaction. +@property (nonatomic, readonly) NSString *sdkTransactionID; + +@end + +/// A utility to parse an STDSAuthenticationResponse from JSON +id _Nullable STDSAuthenticationResponseFromJSON(NSDictionary *json); + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSButtonCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSButtonCustomization.h new file mode 100644 index 00000000..dd63f982 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSButtonCustomization.h @@ -0,0 +1,83 @@ +// +// STDSButtonCustomization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// +#import +#import + +#import "STDSCustomization.h" + +/// An enum that defines the different types of buttons that are able to be customized. +typedef NS_ENUM(NSInteger, STDSUICustomizationButtonType) { + + /// The submit button type. + STDSUICustomizationButtonTypeSubmit = 0, + + /// The continue button type. + STDSUICustomizationButtonTypeContinue = 1, + + /// The next button type. + STDSUICustomizationButtonTypeNext = 2, + + /// The cancel button type. + STDSUICustomizationButtonTypeCancel = 3, + + /// The resend button type. + STDSUICustomizationButtonTypeResend = 4, +}; + +/// An enumeration of the case transformations that can be applied to the button's title +typedef NS_ENUM(NSInteger, STDSButtonTitleStyle) { + /// Default style, doesn't modify the title + STDSButtonTitleStyleDefault, + + /// Applies localizedUppercaseString to the title + STDSButtonTitleStyleUppercase, + + /// Applies localizedLowercaseString to the title + STDSButtonTitleStyleLowercase, + + /// Applies localizedCapitalizedString to the title + STDSButtonTitleStyleSentenceCapitalized, +}; + +NS_ASSUME_NONNULL_BEGIN + +/// A customization object to use to configure the UI of a button. +@interface STDSButtonCustomization: STDSCustomization + +/// The default settings for the provided button type. ++ (instancetype)defaultSettingsForButtonType:(STDSUICustomizationButtonType)type; + +/** + Initializes an instance of STDSButtonCustomization with the given backgroundColor and colorRadius. + */ +- (instancetype)initWithBackgroundColor:(UIColor *)backgroundColor cornerRadius:(CGFloat)cornerRadius; + +/** + This is unavailable because there are no sensible default property values without a button type. + Use `defaultSettingsForButtonType:` or `initWithBackgroundColor:cornerRadius:` instead. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + The background color of the button. + The default for .resend and .cancel is clear. + The default for .submit, .continue, and .next is blue. + */ +@property (nonatomic) UIColor *backgroundColor; + +/// The corner radius of the button. Defaults to 8. +@property (nonatomic) CGFloat cornerRadius; + +/** + The capitalization style of the button title + */ +@property (nonatomic) STDSButtonTitleStyle titleStyle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSButtonCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSButtonCustomization.m new file mode 100644 index 00000000..a82a6cbe --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSButtonCustomization.m @@ -0,0 +1,69 @@ +// +// STDSButtonCustomization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSButtonCustomization.h" + +#import "STDSUICustomization.h" +#import "UIColor+DefaultColors.h" +#import "UIFont+DefaultFonts.h" + +static const CGFloat kDefaultButtonCornerRadius = 8.0; +static const CGFloat kDefaultButtonFontScale = (CGFloat)0.9; + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSButtonCustomization + ++ (instancetype)defaultSettingsForButtonType:(STDSUICustomizationButtonType)type { + UIColor *backgroundColor = [UIColor _stds_blueColor]; + CGFloat cornerRadius = kDefaultButtonCornerRadius; + UIFont *font = [UIFont _stds_defaultBoldLabelTextFontWithScale:kDefaultButtonFontScale]; + UIColor *textColor = UIColor.whiteColor; + switch (type) { + case STDSUICustomizationButtonTypeContinue: + case STDSUICustomizationButtonTypeSubmit: + case STDSUICustomizationButtonTypeNext: + break; + case STDSUICustomizationButtonTypeResend: + backgroundColor = UIColor.clearColor; + textColor = [UIColor _stds_blueColor]; + font = nil; + break; + case STDSUICustomizationButtonTypeCancel: + backgroundColor = UIColor.clearColor; + textColor = nil; + font = nil; + break; + } + STDSButtonCustomization *buttonCustomization = [[self alloc] initWithBackgroundColor:backgroundColor cornerRadius:cornerRadius]; + buttonCustomization.font = font; + buttonCustomization.textColor = textColor; + return buttonCustomization; +} + +- (instancetype)initWithBackgroundColor:(UIColor *)backgroundColor cornerRadius:(CGFloat)cornerRadius { + self = [super init]; + if (self) { + _backgroundColor = backgroundColor; + _cornerRadius = cornerRadius; + } + return self; +} + +- (id)copyWithZone:(nullable NSZone *)zone { + STDSButtonCustomization *copy = [super copyWithZone:zone]; + copy.backgroundColor = self.backgroundColor; + copy.cornerRadius = self.cornerRadius; + copy.titleStyle = self.titleStyle; + + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSChallengeParameters.h b/Stripe3DS2/Stripe3DS2/include/STDSChallengeParameters.h new file mode 100644 index 00000000..e1aa151c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSChallengeParameters.h @@ -0,0 +1,61 @@ +// +// STDSChallengeParameters.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@protocol STDSAuthenticationResponse; + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSChallengeParameters` contains information from the 3DS Server's + authentication response that are used by the 3DS2 SDK to initiate + the challenge flow. + */ +@interface STDSChallengeParameters : NSObject + +/** + Convenience intiializer to create an instace of `STDSChallengeParameters` from an + `STDSAuthenticationResponse` + */ +- (instancetype)initWithAuthenticationResponse:(id)authResponse; + +/** + Transaction identifier assigned by the 3DS Server to uniquely identify + a transaction. + */ +@property (nonatomic, copy) NSString *threeDSServerTransactionID; + +/** + Transaction identifier assigned by the Access Control Server (ACS) + to uniquely identify a transaction. + */ +@property (nonatomic, copy) NSString *acsTransactionID; + +/** + The reference number of the relevant Access Control Server. + */ +@property (nonatomic, copy) NSString *acsReferenceNumber; + +/** + The encrypted message sent by the Access Control Server + containing the ACS URL, epthemeral public key, and the + 3DS2 SDK ephemeral public key. + */ +@property (nonatomic, copy) NSString *acsSignedContent; + +/** + The URL for the application that is requesting 3DS2 verification. + This property can be optionally set and will be included with the + messages sent to the Directory Server during the challenge flow. + */ +@property (nonatomic, copy, nullable) NSString *threeDSRequestorAppURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSChallengeParameters.m b/Stripe3DS2/Stripe3DS2/include/STDSChallengeParameters.m new file mode 100644 index 00000000..ac166cd7 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSChallengeParameters.m @@ -0,0 +1,31 @@ +// +// STDSChallengeParameters.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeParameters.h" + +#import "STDSAuthenticationResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSChallengeParameters + +- (instancetype)initWithAuthenticationResponse:(id)authResponse { + self = [self init]; + if (self) { + _threeDSServerTransactionID = [authResponse.threeDSServerTransactionID copy]; + _acsTransactionID = [authResponse.acsTransactionID copy]; + _acsReferenceNumber = [authResponse.acsReferenceNumber copy]; + _acsSignedContent = [authResponse.acsSignedContent copy]; + } + + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSChallengeStatusReceiver.h b/Stripe3DS2/Stripe3DS2/include/STDSChallengeStatusReceiver.h new file mode 100644 index 00000000..a352e0c4 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSChallengeStatusReceiver.h @@ -0,0 +1,67 @@ +// +// STDSChallengeStatusReceiver.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import + +@class STDSTransaction, STDSCompletionEvent, STDSRuntimeErrorEvent, STDSProtocolErrorEvent; + +NS_ASSUME_NONNULL_BEGIN + +/** + Implement the `STDSChallengeStatusReceiver` protocol to receive challenge status notifications at the end of the challenge process. + @see `STDSTransaction.doChallenge` + */ +@protocol STDSChallengeStatusReceiver + +/** + Called when the challenge process is completed. + + @param completionEvent Information about the completion of the challenge process. @see `STDSCompletionEvent` + */ +- (void)transaction:(STDSTransaction *)transaction didCompleteChallengeWithCompletionEvent:(STDSCompletionEvent *)completionEvent; + +/** + Called when the user selects the option to cancel the transaction on the challenge screen. + */ +- (void)transactionDidCancel:(STDSTransaction *)transaction; + +/** + Called when the challenge process reaches or exceeds the timeout interval that was passed to `STDSTransaction.doChallenge` + */ +- (void)transactionDidTimeOut:(STDSTransaction *)transaction; + +/** + Called when the 3DS SDK receives an EMV 3-D Secure protocol-defined error message from the ACS. + + @param protocolErrorEvent The error code and details. @see `STDSProtocolErrorEvent` + */ +- (void)transaction:(STDSTransaction *)transaction didErrorWithProtocolErrorEvent:(STDSProtocolErrorEvent *)protocolErrorEvent; + +/** + Called when the 3DS SDK encounters errors during the challenge process. These errors include all errors except those covered by `didErrorWithProtocolErrorEvent`. + + @param runtimeErrorEvent The error code and details. @see `STDSRuntimeErrorEvent` + */ +- (void)transaction:(STDSTransaction *)transaction didErrorWithRuntimeErrorEvent:(STDSRuntimeErrorEvent *)runtimeErrorEvent; + +@optional + +/** + Optional method that will be called when the transaction displays a new challenge screen. + */ +- (void)transactionDidPresentChallengeScreen:(STDSTransaction *)transaction; + +/** + Optional method for custom dismissal of the challenge view controller. Meant only for internal use by Stripe SDK. + */ +- (void)dismissChallengeViewController:(UIViewController *)challengeViewController forTransaction:(STDSTransaction *)transaction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSCompletionEvent.h b/Stripe3DS2/Stripe3DS2/include/STDSCompletionEvent.h new file mode 100644 index 00000000..851c71db --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSCompletionEvent.h @@ -0,0 +1,40 @@ +// +// STDSCompletionEvent.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSCompletionEvent` contains information about completion of the challenge process. + */ +@interface STDSCompletionEvent : NSObject + +/** + Designated initializer for `STDSCompletionEvent`. + */ +- (instancetype)initWithSDKTransactionIdentifier:(NSString *)identifier transactionStatus:(NSString *)transactionStatus NS_DESIGNATED_INITIALIZER; + +/** + `STDSCompletionEvent` should not be directly initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + The SDK Transaction ID. + */ +@property (nonatomic, readonly) NSString *sdkTransactionIdentifier; + +/** + The transaction status that was received in the final challenge response. + */ +@property (nonatomic, readonly) NSString *transactionStatus; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSCompletionEvent.m b/Stripe3DS2/Stripe3DS2/include/STDSCompletionEvent.m new file mode 100644 index 00000000..6ee3c43d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSCompletionEvent.m @@ -0,0 +1,26 @@ +// +// STDSCompletionEvent.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSCompletionEvent.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSCompletionEvent + +- (instancetype)initWithSDKTransactionIdentifier:(NSString *)identifier transactionStatus:(NSString *)transactionStatus { + self = [super init]; + if (self) { + _sdkTransactionIdentifier = [identifier copy]; + _transactionStatus = [transactionStatus copy]; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSConfigParameters.h b/Stripe3DS2/Stripe3DS2/include/STDSConfigParameters.h new file mode 100644 index 00000000..4d77ba5d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSConfigParameters.h @@ -0,0 +1,96 @@ +// +// STDSConfigParameters.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The default group name that will be used to group additional + configuration parameters. + */ +extern NSString * const kSTDSConfigDefaultGroupName; + +/** + `STDSConfigParameters` represents additional configuration parameters + that can be passed to the Stripe3DS2 SDK during initialization. + + There are currently no supported additional parameters and apps can + just pass `[STDSConfigParameters alloc] initWithStandardParameters` + to the `STDSThreeDS2Service` instance. + */ +@interface STDSConfigParameters : NSObject + +/** + Convenience initializer to get an `STDSConfigParameters` instance + with the default expected configuration parameters. + */ +- (instancetype)initWithStandardParameters; + +/** + Adds the parameter to this instance. + + @param paramName The name of the parameter to add + @param paramValue The value of the parameter to add + @param paramGroup The group to which this parameter will be added. If `nil` the parameter will be added to `kSTDSConfigDefaultGroupName` + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `paramName` or `paramValue` are `nil`. @see STDSInvalidInputException + */ +- (void)addParameterNamed:(NSString *)paramName withValue:(NSString *)paramValue toGroup:(nullable NSString *)paramGroup; + +/** + Adds the parameter to the default group in this instance. + + @param paramName The name of the parameter to add + @param paramValue The value of the parameter to add + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `paramName` or `paramValue` are `nil`. @see STDSInvalidInputException + */ +- (void)addParameterNamed:(NSString *)paramName withValue:(NSString *)paramValue; + +/** + Returns the value for `paramName` in `paramGroup` or `nil` if the parameter value is not set. + + @param paramName The name of the parameter to return + @param paramGroup The group from which to fetch the parameter value. If `nil` will default to `kSTDSConfigDefaultGroupName` + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `paramName` is `nil`. @see STDSInvalidInputException + */ +- (nullable NSString *)parameterValue:(NSString *)paramName inGroup:(nullable NSString *)paramGroup; + +/** + Returns the value for `paramName` in the default group or `nil` if the parameter value is not set. + + @param paramName The name of the parameter to return + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `paramName` is `nil`. @see STDSInvalidInputException + */ +- (nullable NSString *)parameterValue:(NSString *)paramName; + +/** + Removes the specified parameter from the group and returns the value or `nil` if the parameter was not found. + + @param paramName The name of the parameter to remove + @param paramGroup The group from which to remove this parameter. If `nil` will default to `kSTDSConfigDefaultGroupName` + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `paramName` is `nil`. @see STDSInvalidInputException + */ +- (nullable NSString *)removeParameterNamed:(NSString *)paramName fromGroup:(nullable NSString *)paramGroup; + +/** + Removes the specified parameter from the default group and returns the value or `nil` if the parameter was not found. + + @param paramName The name of the parameter to remove + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `paramName` is `nil`. @see STDSInvalidInputException + */ +- (nullable NSString *)removeParameterNamed:(NSString *)paramName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSConfigParameters.m b/Stripe3DS2/Stripe3DS2/include/STDSConfigParameters.m new file mode 100644 index 00000000..e334a5bb --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSConfigParameters.m @@ -0,0 +1,113 @@ +// +// STDSConfigParameters.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSConfigParameters.h" + +#import "STDSException+Internal.h" +#import "STDSInvalidInputException.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString * const kSTDSConfigDefaultGroupName = @"STDSConfigParameters.group.default"; + +@implementation STDSConfigParameters +{ + NSMutableDictionary *_parameters; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _parameters = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (instancetype)initWithStandardParameters { + self = [self init]; + if (self) { + // Nothing for now because we don't have any standard parameters + } + + return self; +} + +- (void)addParameterNamed:(NSString *)paramName withValue:(NSString *)paramValue { + [self _addParameterNamed:paramName withValue:paramValue toGroup:kSTDSConfigDefaultGroupName]; +} + +- (void)addParameterNamed:(NSString *)paramName withValue:(NSString *)paramValue toGroup:(nullable NSString *)paramGroup { + [self _addParameterNamed:paramName withValue:paramValue toGroup:paramGroup ?: kSTDSConfigDefaultGroupName]; +} + +- (void)_addParameterNamed:(NSString *)paramName withValue:(NSString *)paramValue toGroup:(NSString *)paramGroup { + if (paramName == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramName passed to instance %@", self]; + } else if (paramValue == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramValue passed to instance %@", self]; + } else if (paramGroup == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramGroup passed to instance %@", self]; + } + + NSMutableDictionary *groupParameters = _parameters[paramGroup]; + if (groupParameters == nil) { + groupParameters = [[NSMutableDictionary alloc] init]; + _parameters[paramGroup] = groupParameters; + } + + if (groupParameters[paramName] != nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"Cannot override value of %@ for parameter %@ in group %@ with value %@", groupParameters[paramName], paramName, paramGroup, paramValue]; + } + + groupParameters[paramName] = paramValue; +} + +- (nullable NSString *)parameterValue:(NSString *)paramName { + return [self _parameterValue:paramName inGroup:kSTDSConfigDefaultGroupName]; +} + +- (nullable NSString *)parameterValue:(NSString *)paramName inGroup:(nullable NSString *)paramGroup { + return [self _parameterValue:paramName inGroup:paramGroup ?: kSTDSConfigDefaultGroupName]; +} + +- (nullable NSString *)_parameterValue:(NSString *)paramName inGroup:(NSString *)paramGroup { + if (paramName == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramName passed to instance %@", self]; + } else if (paramGroup == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramGroup passed to instance %@", self]; + } + + NSMutableDictionary *groupParameters = _parameters[paramGroup]; + return groupParameters[paramName]; +} + +- (nullable NSString *)removeParameterNamed:(NSString *)paramName { + return [self _removeParameterNamed:paramName fromGroup:kSTDSConfigDefaultGroupName]; +} + +- (nullable NSString *)removeParameterNamed:(NSString *)paramName fromGroup:(nullable NSString *)paramGroup { + return [self _removeParameterNamed:paramName fromGroup:paramGroup ?: kSTDSConfigDefaultGroupName]; +} + +- (nullable NSString *)_removeParameterNamed:(NSString *)paramName fromGroup:(NSString *)paramGroup { + if (paramName == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramName passed to instance %@", self]; + } else if (paramGroup == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"nil paramGroup passed to instance %@", self]; + } + + NSMutableDictionary *groupParameters = _parameters[paramGroup]; + NSString *paramValue = groupParameters[paramName]; + [groupParameters removeObjectForKey:paramName]; + return paramValue; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSCustomization.h new file mode 100644 index 00000000..516eff9e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSCustomization.h @@ -0,0 +1,25 @@ +// +// STDSCustomization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// This class provides a common set of customization parameters, used to customize elements of the UI. +@interface STDSCustomization : NSObject + +/// The font to use for text. +@property (nonatomic, nullable) UIFont *font; + +/// The color to use for the text. +@property (nonatomic, nullable) UIColor *textColor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSCustomization.m new file mode 100644 index 00000000..4544024c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSCustomization.m @@ -0,0 +1,25 @@ +// +// STDSCustomization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSCustomization + +- (id)copyWithZone:(nullable NSZone *)zone { + STDSCustomization *copy = [[[self class] allocWithZone:zone] init]; + copy.font = self.font; + copy.textColor = self.textColor; + + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSErrorMessage.h b/Stripe3DS2/Stripe3DS2/include/STDSErrorMessage.h new file mode 100644 index 00000000..29c6c5a6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSErrorMessage.h @@ -0,0 +1,103 @@ +// +// STDSErrorMessage.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONEncodable.h" +#import "STDSJSONDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Error codes as defined by the 3DS2 spec. +typedef NS_ENUM(NSInteger, STDSErrorMessageCode) { + /// The SDK received a message that is not an ARes, CRes, or ErrorMessage. + STDSErrorMessageCodeInvalidMessage = 101, + + /// A required data element is missing from the network response. + STDSErrorMessageCodeRequiredDataElementMissing = 201, + + // Critical message extension not recognised + STDSErrorMessageCodeUnrecognizedCriticalMessageExtension = 202, + + /// A data element is not in the required format or the value is invalid. + STDSErrorMessageErrorInvalidDataElement = 203, + + // Transaction ID not recognized + STDSErrorMessageErrorTransactionIDNotRecognized = 301, + + /// A network response could not be decrypted or verified. + STDSErrorMessageErrorDataDecryptionFailure = 302, + + /// The SDK timed out + STDSErrorMessageErrorTimeout = 402, +}; + +/** + `STDSErrorMessage` represents an error message that is returned by the ACS or to be sent to the ACS. + */ +@interface STDSErrorMessage : NSObject + +/** + Designated initializer for `STDSErrorMessage`. + + @param errorCode The error code. + @param errorComponent The component that identified the error. + @param errorDescription Text describing the error. + @param errorDetails Additional error details. Optional. + */ +- (instancetype)initWithErrorCode:(NSString *)errorCode + errorComponent:(NSString *)errorComponent + errorDescription:(NSString *)errorDescription + errorDetails:(nullable NSString *)errorDetails + messageVersion:(NSString *)messageVersion + acsTransactionIdentifier:(nullable NSString *)acsTransactionIdentifier + errorMessageType:(NSString *)errorMessageType; + +/** + The error code. + */ +@property (nonatomic, readonly) NSString *errorCode; + +/** + The 3-D Secure component that identified the error. + */ +@property (nonatomic, readonly) NSString *errorComponent; + +/** + Text describing the error. + */ +@property (nonatomic, readonly) NSString *errorDescription; + +/** + Additional error details. + */ +@property (nonatomic, nullable, readonly) NSString *errorDetails; + +/** + The protocol version identifier. + */ +@property (nonatomic, readonly) NSString *messageVersion; + +/** + The ACS transaction identifier. + */ +@property (nonatomic, readonly, nullable) NSString *acsTransactionIdentifier; + +/** + The message type that was identified as erroneous. + */ +@property (nonatomic, readonly, nullable) NSString *errorMessageType; + +/** + A representation of the `STDSErrorMessage` as an `NSError` + */ +- (NSError *)NSErrorValue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSErrorMessage.m b/Stripe3DS2/Stripe3DS2/include/STDSErrorMessage.m new file mode 100644 index 00000000..1e80b645 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSErrorMessage.m @@ -0,0 +1,94 @@ +// +// STDSErrorMessage.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSErrorMessage.h" + +#import "NSDictionary+DecodingHelpers.h" +#import "STDSJSONEncoder.h" +#import "STDSStripe3DS2Error.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSErrorMessage + +- (instancetype)initWithErrorCode:(NSString *)errorCode + errorComponent:(NSString *)errorComponent + errorDescription:(NSString *)errorDescription + errorDetails:(nullable NSString *)errorDetails + messageVersion:(NSString *)messageVersion + acsTransactionIdentifier:(nullable NSString *)acsTransactionIdentifier + errorMessageType:(NSString *)errorMessageType { + self = [super init]; + if (self) { + _errorCode = [errorCode copy]; + _errorComponent = [errorComponent copy]; + _errorDescription = [errorDescription copy]; + _errorDetails = [errorDetails copy]; + _messageVersion = [messageVersion copy]; + _acsTransactionIdentifier = [acsTransactionIdentifier copy]; + _errorMessageType = [errorMessageType copy]; + } + return self; +} + +- (NSString *)messageType { + return @"Erro"; +} + +- (NSError *)NSErrorValue { + return [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:[self.errorCode integerValue] + userInfo: [STDSJSONEncoder dictionaryForObject:self]]; +} + +#pragma mark - STDSJSONEncodable + ++ (NSDictionary *)propertyNamesToJSONKeysMapping { + return @{ + NSStringFromSelector(@selector(errorCode)): @"errorCode", + NSStringFromSelector(@selector(errorComponent)): @"errorComponent", + NSStringFromSelector(@selector(errorDescription)): @"errorDescription", + NSStringFromSelector(@selector(errorDetails)): @"errorDetail", + NSStringFromSelector(@selector(messageType)): @"messageType", + NSStringFromSelector(@selector(messageVersion)): @"messageVersion", + NSStringFromSelector(@selector(acsTransactionIdentifier)): @"acsTransID", + NSStringFromSelector(@selector(errorMessageType)): @"errorMessageType", + }; +} + +#pragma mark - STDSJSONDecodable + ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError * _Nullable __autoreleasing * _Nullable)outError { + if (json == nil) { + return nil; + } + NSError *error; + + // Required + NSString *errorCode = [json _stds_stringForKey:@"errorCode" required:YES error:&error]; + NSString *errorComponent = [json _stds_stringForKey:@"errorComponent" required:YES error:&error]; + NSString *errorDescription = [json _stds_stringForKey:@"errorDescription" required:YES error:&error]; + NSString *errorDetail = [json _stds_stringForKey:@"errorDetail" required:YES error:&error]; + NSString *messageVersion = [json _stds_stringForKey:@"messageVersion" required:YES error:&error]; + + // Optional + NSString *errorMessageType = [json _stds_stringForKey:@"errorMessageType" required:NO error:&error]; + NSString *acsTransactionIdentifier = [json _stds_stringForKey:@"acsTransID" required:NO error:nil]; + + if (error) { + if (outError) { + *outError = error; + } + return nil; + } + return [[self alloc] initWithErrorCode:errorCode errorComponent:errorComponent errorDescription:errorDescription errorDetails:errorDetail messageVersion:messageVersion acsTransactionIdentifier:acsTransactionIdentifier errorMessageType:errorMessageType]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSException.h b/Stripe3DS2/Stripe3DS2/include/STDSException.h new file mode 100644 index 00000000..1f2c5ecd --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSException.h @@ -0,0 +1,25 @@ +// +// STDSException.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + An abstract class to represent 3DS2 SDK custom exceptions + */ +@interface STDSException : NSException + +/** + A description of the exception. + */ +@property (nonatomic, readonly) NSString *message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSException.m b/Stripe3DS2/Stripe3DS2/include/STDSException.m new file mode 100644 index 00000000..673d5bbd --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSException.m @@ -0,0 +1,27 @@ +// +// STDSException.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSException.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSException + ++ (instancetype)exceptionWithMessage:(NSString *)format, ... { + va_list args; + va_start(args, format); + NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + STDSException *exception = [[[self class] alloc] initWithName:NSStringFromClass([self class]) reason:message userInfo:nil]; + exception->_message = [message copy]; + return exception; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSFooterCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSFooterCustomization.h new file mode 100644 index 00000000..990ffd9b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSFooterCustomization.h @@ -0,0 +1,41 @@ +// +// STDSFooterCustomization.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 6/10/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + The Challenge view displays a footer with additional details that + expand when tapped. This object configures the appearance of that view. +*/ +@interface STDSFooterCustomization : STDSCustomization + +/// The default settings. ++ (instancetype)defaultSettings; + +/** + The background color of the footer. + Defaults to gray. + */ +@property (nonatomic) UIColor *backgroundColor; + +/// The color of the chevron. Defaults to a dark gray. +@property (nonatomic) UIColor *chevronColor; + +/// The color of the heading text. Defaults to black. +@property (nonatomic) UIColor *headingTextColor; + +/// The font to use for the heading text. +@property (nonatomic) UIFont *headingFont; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSFooterCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSFooterCustomization.m new file mode 100644 index 00000000..6c770fda --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSFooterCustomization.m @@ -0,0 +1,45 @@ +// +// STDSFooterCustomization.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 6/10/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSFooterCustomization.h" + +#import "UIFont+DefaultFonts.h" +#import "UIColor+DefaultColors.h" +#import "UIColor+ThirteenSupport.h" + +@implementation STDSFooterCustomization + ++ (instancetype)defaultSettings { + return [self new]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + self.textColor = UIColor._stds_labelColor; + _headingTextColor = UIColor._stds_labelColor; + _backgroundColor = [UIColor _stds_defaultFooterBackgroundColor]; + _chevronColor = [UIColor _stds_systemGray2Color]; + self.font = [UIFont _stds_defaultLabelTextFontWithScale:(CGFloat)0.9]; + _headingFont = [UIFont _stds_defaultLabelTextFontWithScale:(CGFloat)0.9]; + } + return self; +} + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + STDSFooterCustomization *copy = [super copyWithZone:zone]; + copy.headingTextColor = self.headingTextColor; + copy.headingFont = self.headingFont; + copy.backgroundColor = self.backgroundColor; + copy.chevronColor = self.chevronColor; + + return copy; +} + + +@end diff --git a/Stripe3DS2/Stripe3DS2/include/STDSInvalidInputException.h b/Stripe3DS2/Stripe3DS2/include/STDSInvalidInputException.h new file mode 100644 index 00000000..b30d4e10 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSInvalidInputException.h @@ -0,0 +1,21 @@ +// +// STDSInvalidInputException.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSException.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSInvalidInputException` represents an exception that will be thrown by + Stripe3DS2 SDK methods that are called with invalid input arguments. + */ +@interface STDSInvalidInputException : STDSException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSInvalidInputException.m b/Stripe3DS2/Stripe3DS2/include/STDSInvalidInputException.m new file mode 100644 index 00000000..7ff4cd3e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSInvalidInputException.m @@ -0,0 +1,17 @@ +// +// STDSInvalidInputException.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSInvalidInputException.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSInvalidInputException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSJSONDecodable.h b/Stripe3DS2/Stripe3DS2/include/STDSJSONDecodable.h new file mode 100644 index 00000000..f93428ac --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSJSONDecodable.h @@ -0,0 +1,33 @@ +// +// STDSJSONDecodable.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol STDSJSONDecodable + +/** + Initializes an instance of the class from its JSON representation. + + This method recognizes two categories of errors: + - a required field is missing. + - a required field value is in valid (e.g. expected 'Y' or 'N' but received 'X'). + + Errors populating optional fields are ignored. + + @param json The JSON dictionary that represents an object of this type + @param outError If there was a missing required field or invalid field value, contains an instance of NSError. + + @return The object represented by the JSON dictionary. If the object could not be decoded, returns nil and populates the outError argument. + */ ++ (nullable instancetype)decodedObjectFromJSON:(nullable NSDictionary *)json error:(NSError **)outError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSJSONEncodable.h b/Stripe3DS2/Stripe3DS2/include/STDSJSONEncodable.h new file mode 100644 index 00000000..9861a23c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSJSONEncodable.h @@ -0,0 +1,22 @@ +// +// STDSJSONEncodable.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol STDSJSONEncodable + +/** + Returns a map of property names to their JSON representation's key value. For example, `STDSChallengeParameters` has a property called `acsTransactionID`, but the 3DS2 JSON spec expects a field called `acsTransID`. This dictionary represents a mapping from the former to the latter (in other words, [STDSChallengeParameters propertyNamesToJSONKeysMapping][@"acsTransactionID"] == @"acsTransID".) + */ ++ (NSDictionary *)propertyNamesToJSONKeysMapping; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSJSONEncoder.h b/Stripe3DS2/Stripe3DS2/include/STDSJSONEncoder.h new file mode 100644 index 00000000..7ccc07e6 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSJSONEncoder.h @@ -0,0 +1,27 @@ +// +// STDSJSONEncoder.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONEncodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSJSONEncoder` is a utility class to help with converting API objects into JSON + */ +@interface STDSJSONEncoder : NSObject + +/** + Method to convert an STDSJSONEncodable object into a JSON dictionary. + */ ++ (NSDictionary *)dictionaryForObject:(NSObject *)object; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSJSONEncoder.m b/Stripe3DS2/Stripe3DS2/include/STDSJSONEncoder.m new file mode 100644 index 00000000..4c55b02c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSJSONEncoder.m @@ -0,0 +1,51 @@ +// +// STDSJSONEncoder.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSJSONEncoder.h" + +@implementation STDSJSONEncoder + ++ (NSDictionary *)dictionaryForObject:(nonnull NSObject *)object { + NSMutableDictionary *keyPairs = [NSMutableDictionary dictionary]; + [[object.class propertyNamesToJSONKeysMapping] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull propertyName, NSString * _Nonnull keyName, __unused BOOL * _Nonnull stop) { + id value = [self jsonEncodableValueForObject:[object valueForKey:propertyName]]; + if (value != [NSNull null]) { + keyPairs[keyName] = value; + } + }]; + return [keyPairs copy]; +} + ++ (id)jsonEncodableValueForObject:(NSObject *)object { + if ([object conformsToProtocol:@protocol(STDSJSONEncodable)]) { + return [self dictionaryForObject:(NSObject*)object]; + } else if ([object isKindOfClass:[NSDictionary class]]) { + NSDictionary *dict = (NSDictionary *)object; + NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:dict.count]; + + [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, __unused BOOL * _Nonnull stop) { + result[key] = [self jsonEncodableValueForObject:value]; + }]; + + return result; + } else if ([object isKindOfClass:[NSArray class]]) { + NSArray *array = (NSArray *)object; + NSMutableArray *result = [NSMutableArray arrayWithCapacity:array.count]; + + for (NSObject *element in array) { + [result addObject:[self jsonEncodableValueForObject:element]]; + } + return result; + } else if ([object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNumber class]]) { + return object; + } else { + return [NSNull null]; + } +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/include/STDSLabelCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSLabelCustomization.h new file mode 100644 index 00000000..af75cf39 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSLabelCustomization.h @@ -0,0 +1,31 @@ +// +// STDSLabelCustomization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + A customization object to use to configure the UI of a text label. + + The font and textColor inherited from `STDSCustomization` configure non-heading labels. + */ +@interface STDSLabelCustomization : STDSCustomization + +/// The default settings. ++ (instancetype)defaultSettings; + +/// The color of the heading text. Defaults to black. +@property (nonatomic) UIColor *headingTextColor; + +/// The font to use for the heading text. +@property (nonatomic) UIFont *headingFont; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSLabelCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSLabelCustomization.m new file mode 100644 index 00000000..bb57c5f8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSLabelCustomization.m @@ -0,0 +1,43 @@ +// +// STDSLabelCustomization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSLabelCustomization.h" + +#import "UIFont+DefaultFonts.h" +#import "UIColor+ThirteenSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSLabelCustomization + ++ (instancetype)defaultSettings { + return [self new]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + self.textColor = UIColor._stds_labelColor; + _headingTextColor = UIColor._stds_labelColor; + self.font = [UIFont _stds_defaultLabelTextFontWithScale:(CGFloat)0.9]; + _headingFont = [UIFont _stds_defaultHeadingTextFont]; + } + return self; +} + +- (id)copyWithZone:(nullable NSZone *)zone { + STDSLabelCustomization *copy = [super copyWithZone:zone]; + copy.headingTextColor = self.headingTextColor; + copy.headingFont = self.headingFont; + + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSNavigationBarCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSNavigationBarCustomization.h new file mode 100644 index 00000000..5d3c1a0b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSNavigationBarCustomization.h @@ -0,0 +1,66 @@ +// +// STDSNavigationBarCustomization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import + +#import "STDSCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + A customization object to use to configure a UINavigationBar. + + The font and textColor inherited from `STDSCustomization` configure the + title of the navigation bar, and default to nil. + */ +@interface STDSNavigationBarCustomization : STDSCustomization + +/// The default settings. ++ (instancetype)defaultSettings; + +/** + The scroll edge appearance to set on the navigation bar. + + Defaults to `nil` + */ +@property (nonatomic, nullable) UINavigationBarAppearance *scrollEdgeAppearance; + +/** + The tint color of the navigation bar background. + Defaults to nil. + */ +@property (nonatomic, nullable) UIColor *barTintColor; + +/** + The navigation bar style. + Defaults to UIBarStyleDefault. + */ +@property (nonatomic) UIBarStyle barStyle; + +/** + A Boolean value indicating whether the navigation bar is translucent or not. + Defaults to YES. + */ +@property (nonatomic) BOOL translucent; + +/** + The text to display in the title of the navigation bar. + Defaults to "Secure checkout". + */ +@property (nonatomic, copy) NSString *headerText; + +/** + The text to display for the button in the navigation bar. + Defaults to "Cancel". + */ +@property (nonatomic, copy) NSString *buttonText; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSNavigationBarCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSNavigationBarCustomization.m new file mode 100644 index 00000000..5e062356 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSNavigationBarCustomization.m @@ -0,0 +1,44 @@ +// +// STDSNavigationBarCustomization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSLocalizedString.h" +#import "STDSNavigationBarCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSNavigationBarCustomization + ++ (instancetype)defaultSettings { + return [self new]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _barTintColor = nil; + _headerText = STDSLocalizedString(@"Secure checkout", @"The title for the challenge response step of an authenticated checkout."); + _buttonText = STDSLocalizedString(@"Cancel", "The text for the button that cancels the current challenge process."); + _translucent = YES; + } + return self; +} + +- (id)copyWithZone:(nullable NSZone *)zone { + STDSNavigationBarCustomization *copy = [super copyWithZone:zone]; + copy.barTintColor = self.barTintColor; + copy.headerText = self.headerText; + copy.buttonText = self.buttonText; + copy.barStyle = self.barStyle; + copy.translucent = self.translucent; + + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSNotInitializedException.h b/Stripe3DS2/Stripe3DS2/include/STDSNotInitializedException.h new file mode 100644 index 00000000..cb836d5e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSNotInitializedException.h @@ -0,0 +1,23 @@ +// +// STDSNotInitializedException.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSException.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSNotInitializedException` represents an exception that will be thrown by + the the Stripe3DS2 SDK if methods are called without initializing `STDSThreeDS2Service`. + + @see STDSThreeDS2Service + */ +@interface STDSNotInitializedException : STDSException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSNotInitializedException.m b/Stripe3DS2/Stripe3DS2/include/STDSNotInitializedException.m new file mode 100644 index 00000000..58544cc2 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSNotInitializedException.m @@ -0,0 +1,17 @@ +// +// STDSNotInitializedException.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSNotInitializedException.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSNotInitializedException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSProtocolErrorEvent.h b/Stripe3DS2/Stripe3DS2/include/STDSProtocolErrorEvent.h new file mode 100644 index 00000000..1e3142a7 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSProtocolErrorEvent.h @@ -0,0 +1,42 @@ +// +// STDSProtocolErrorEvent.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSErrorMessage; + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSProtocolErrorEvent` contains details about erorrs received from or sent to the ACS. + */ +@interface STDSProtocolErrorEvent : NSObject + +/** + Designated initializer for `STDSProtocolErrorEvent`. + */ +- (instancetype)initWithSDKTransactionIdentifier:(NSString *)identifier errorMessage:(STDSErrorMessage *)errorMessage; + +/** + `STDSProtocolErrorEvent` should not be directly initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + Details about the error. + */ +@property (nonatomic, readonly) STDSErrorMessage *errorMessage; + +/** + The SDK Transaction Identifier. + */ +@property (nonatomic, readonly) NSString *sdkTransactionIdentifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSProtocolErrorEvent.m b/Stripe3DS2/Stripe3DS2/include/STDSProtocolErrorEvent.m new file mode 100644 index 00000000..0dbd75b5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSProtocolErrorEvent.m @@ -0,0 +1,28 @@ +// +// STDSProtocolErrorEvent.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSProtocolErrorEvent.h" + +#import "STDSErrorMessage.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSProtocolErrorEvent + +- (instancetype)initWithSDKTransactionIdentifier:(NSString *)identifier errorMessage:(STDSErrorMessage *)errorMessage { + self = [super init]; + if (self) { + _sdkTransactionIdentifier = identifier; + _errorMessage = errorMessage; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSRuntimeErrorEvent.h b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeErrorEvent.h new file mode 100644 index 00000000..658cdb90 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeErrorEvent.h @@ -0,0 +1,53 @@ +// +// STDSRuntimeErrorEvent.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXTERN NSString * const kSTDSRuntimeErrorCodeParsingError; +FOUNDATION_EXTERN NSString * const kSTDSRuntimeErrorCodeEncryptionError; + +/** + `STDSRuntimeErrorEvent` contains details about run-time errors encountered during authentication. + + The following are examples of run-time errors: + - ACS is unreachable + - Unparseable message + - Network issues + */ +@interface STDSRuntimeErrorEvent : NSObject + +/** + A code corresponding to the type of error this represents. + */ +@property (nonatomic, readonly) NSString *errorCode; + +/** + Details about the error. + */ +@property (nonatomic, readonly) NSString *errorMessage; + +/** + Designated initializer for `STDSRuntimeErrorEvent`. + */ +- (instancetype)initWithErrorCode:(NSString *)errorCode errorMessage:(NSString *)errorMessage NS_DESIGNATED_INITIALIZER; + +/** + A representation of the `STDSRuntimeErrorEvent` as an `NSError` + */ +- (NSError *)NSErrorValue; + +/** + `STDSRuntimeErrorEvent` should not be directly initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSRuntimeErrorEvent.m b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeErrorEvent.m new file mode 100644 index 00000000..8155e06e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeErrorEvent.m @@ -0,0 +1,37 @@ +// +// STDSRuntimeErrorEvent.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSRuntimeErrorEvent.h" + +#import "STDSStripe3DS2Error.h" + +NS_ASSUME_NONNULL_BEGIN + +NSString * const kSTDSRuntimeErrorCodeParsingError = @"STDSRuntimeErrorCodeParsingError"; +NSString * const kSTDSRuntimeErrorCodeEncryptionError = @"STDSRuntimeErrorCodeEncryptionError"; + +@implementation STDSRuntimeErrorEvent + +- (instancetype)initWithErrorCode:(NSString *)errorCode errorMessage:(NSString *)errorMessage { + self = [super init]; + if (self) { + _errorCode = [errorCode copy]; + _errorMessage = [errorMessage copy]; + } + return self; +} + +- (NSError *)NSErrorValue { + return [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:[self.errorCode isEqualToString:kSTDSRuntimeErrorCodeParsingError] ? STDSErrorCodeRuntimeParsing : STDSErrorCodeRuntimeEncryption + userInfo:@{@"errorMessage": self.errorMessage}]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSRuntimeException.h b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeException.h new file mode 100644 index 00000000..5b63a2d3 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeException.h @@ -0,0 +1,21 @@ +// +// STDSRuntimeException.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSException.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSRuntimeException` represents an exception that will be thrown by the + Stripe3DS2 SDK if it encounters an internal error. + */ +@interface STDSRuntimeException : STDSException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSRuntimeException.m b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeException.m new file mode 100644 index 00000000..3a70d376 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSRuntimeException.m @@ -0,0 +1,17 @@ +// +// STDSRuntimeException.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSRuntimeException.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSRuntimeException + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSSelectionCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSSelectionCustomization.h new file mode 100644 index 00000000..a9818bb9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSSelectionCustomization.h @@ -0,0 +1,48 @@ +// +// STDSSelectionCustomization.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 6/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + A customization object that configures the appearance of + radio buttons and checkboxes. + */ +@interface STDSSelectionCustomization: NSObject + +/// The default settings. ++ (instancetype)defaultSettings; + +/** + The primary color of the selected state. + Defaults to blue. + */ +@property (nonatomic) UIColor *primarySelectedColor; + +/** + The secondary color of the selected state (e.g. the checkmark color). + Defaults to white. + */ +@property (nonatomic) UIColor *secondarySelectedColor; + +/** + The background color displayed in the unselected state. + Defaults to light blue. + */ +@property (nonatomic) UIColor *unselectedBackgroundColor; + +/** + The color of the border drawn around the view in the unselected state. + Defaults to blue. + */ +@property (nonatomic) UIColor *unselectedBorderColor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSSelectionCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSSelectionCustomization.m new file mode 100644 index 00000000..d5fdcf35 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSSelectionCustomization.m @@ -0,0 +1,64 @@ +// +// STDSSelectionCustomization.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 6/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSSelectionCustomization.h" + +#import "UIColor+DefaultColors.h" +#import "UIColor+ThirteenSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSSelectionCustomization + ++ (instancetype)defaultSettings { + return [self new]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _primarySelectedColor = [UIColor _stds_blueColor]; + _secondarySelectedColor = UIColor.whiteColor; + _unselectedBackgroundColor = [UIColor _stds_colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { + return (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) ? + [UIColor colorWithRed:(CGFloat)231.0 / (CGFloat)255.0 + green:(CGFloat)241.0 / (CGFloat)255.0 + blue:(CGFloat)254.0 / (CGFloat)255.0 + alpha:1.0] : + [UIColor colorWithRed:(CGFloat)30.0 / (CGFloat)255.0 + green:(CGFloat)63.0 / (CGFloat)255.0 + blue:(CGFloat)84.0 / (CGFloat)255.0 + alpha:1.0]; + }]; + _unselectedBorderColor = [UIColor _stds_colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) { + return (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight) ? + [UIColor colorWithRed:(CGFloat)131.0 / (CGFloat)255.0 + green:(CGFloat)191.0 / (CGFloat)255.0 + blue:(CGFloat)250.0 / (CGFloat)255.0 + alpha:1] : + [UIColor colorWithRed:(CGFloat)65.0 / (CGFloat)255.0 + green:(CGFloat)94.0 / (CGFloat)255.0 + blue:(CGFloat)123.0 / (CGFloat)255.0 + alpha:1]; + }]; + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + STDSSelectionCustomization *copy = [STDSSelectionCustomization new]; + copy.primarySelectedColor = self.primarySelectedColor; + copy.secondarySelectedColor = self.secondarySelectedColor; + copy.unselectedBackgroundColor = self.unselectedBackgroundColor; + copy.unselectedBorderColor = self.unselectedBorderColor; + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSStripe3DS2Error.h b/Stripe3DS2/Stripe3DS2/include/STDSStripe3DS2Error.h new file mode 100644 index 00000000..2e506fe2 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSStripe3DS2Error.h @@ -0,0 +1,68 @@ +// +// STDSStripe3DS2Error.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSString * const STDSStripe3DS2ErrorDomain; + +/** + NSError.userInfo contains this key if we received an ErrorMessage instead of the expected response object. + The value of this key is the ErrorMessage. + */ +FOUNDATION_EXPORT NSString * const STDSStripe3DS2ErrorMessageErrorKey; + +/** + NSError.userInfo contains this key if we errored parsing JSON. + The value of this key is the invalid or missing field. + */ +FOUNDATION_EXPORT NSString * const STDSStripe3DS2ErrorFieldKey; + +/** + NSError.userInfo contains this key if we couldn't recognize critical message extension(s) + The value of this key is an array of identifiers. + */ +FOUNDATION_EXPORT NSString * const STDSStripe3DS2UnrecognizedCriticalMessageExtensionsKey; + + +typedef NS_ENUM(NSInteger, STDSErrorCode) { + + /// Code triggered an assertion + STDSErrorCodeAssertionFailed = 204, + + // JSON Parsing + /// Received invalid or malformed data + STDSErrorCodeJSONFieldInvalid = 203, + /// Expected field missing + STDSErrorCodeJSONFieldMissing = 201, + + /// Critical message extension not recognised + STDSErrorCodeUnrecognizedCriticalMessageExtension = 202, + + /// Decryption or verification error + STDSErrorCodeDecryptionVerification = 302, + + /// Error code corresponding to a `STDSRuntimeErrorEvent` for an unparseable network response + STDSErrorCodeRuntimeParsing = 400, + /// Error code corresponding to a `STDSRuntimeErrorEvent` for an error with decrypting or verifying a network response + STDSErrorCodeRuntimeEncryption = 401, + + // Networking + /// We received an ErrorMessage instead of the expected response object. `userInfo[STDSStripe3DS2ErrorMessageErrorKey]` will contain the ErrorMessage object. + STDSErrorCodeReceivedErrorMessage = 1000, + /// We received an unknown message type. + STDSErrorCodeUnknownMessageType = 1001, + /// Request timed out + STDSErrorCodeTimeout = 1002, + + /// Unknown + STDSErrorCodeUnknownError = 2000, +}; + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSStripe3DS2Error.m b/Stripe3DS2/Stripe3DS2/include/STDSStripe3DS2Error.m new file mode 100644 index 00000000..6d2330ed --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSStripe3DS2Error.m @@ -0,0 +1,15 @@ +// +// STDSStripe3DS2Error.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSStripe3DS2Error.h" + +NSString * const STDSStripe3DS2ErrorDomain = @"com.stripe3ds2"; +NSString * const STDSStripe3DS2ErrorMessageErrorKey = @"STDSStripe3DS2ErrorMessageErrorKey"; +NSString * const STDSStripe3DS2ErrorFieldKey = @"STDSStripe3DS2ErrorFieldKey"; +NSString * const STDSStripe3DS2UnrecognizedCriticalMessageExtensionsKey = @"STDSStripe3DS2UnrecognizedCriticalMessageExtensionsKey"; + diff --git a/Stripe3DS2/Stripe3DS2/include/STDSSwiftTryCatch.h b/Stripe3DS2/Stripe3DS2/include/STDSSwiftTryCatch.h new file mode 100644 index 00000000..47443368 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSSwiftTryCatch.h @@ -0,0 +1,50 @@ +// +// STDSSwiftTryCatch.h +// +// Created by William Falcon on 10/10/14. +// Copyright (c) 2014 William Falcon. All rights reserved. +// +/* + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Provides try catch functionality for swift by wrapping around Objective-C + */ +@interface STDSSwiftTryCatch : NSObject + +/** + Provides try catch functionality for swift by wrapping around Objective-C + */ ++ (void)tryBlock:(void(^)(void))tryBlock catchBlock:(void(^)(NSException*exception))catchBlock finallyBlock:(void(^)(void))finallyBlock; +/** + Throws Objective-C exception with name and reason set to `s` + */ ++ (void)throwString:(NSString*)s; + +/** + Throws exception `e` + */ ++ (void)throwException:(NSException*)e; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSSwiftTryCatch.m b/Stripe3DS2/Stripe3DS2/include/STDSSwiftTryCatch.m new file mode 100644 index 00000000..a9ac8601 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSSwiftTryCatch.m @@ -0,0 +1,59 @@ +// +// STDSSwiftTryCatch.h +// +// Created by William Falcon on 10/10/14. +// Copyright (c) 2014 William Falcon. All rights reserved. +// +/* + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +#import "STDSSwiftTryCatch.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSSwiftTryCatch + ++ (void)tryBlock:(void(^)(void))tryBlock catchBlock:(void(^)(NSException*exception))catchBlock finallyBlock:(void(^)(void))finallyBlock { + @try { + tryBlock ? tryBlock() : nil; + } + + @catch (NSException *exception) { + catchBlock ? catchBlock(exception) : nil; + } + @finally { + finallyBlock ? finallyBlock() : nil; + } +} + ++ (void)throwString:(NSString*)s +{ + @throw [NSException exceptionWithName:s reason:s userInfo:nil]; +} + ++ (void)throwException:(NSException*)e +{ + @throw e; +} + + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSTextFieldCustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSTextFieldCustomization.h new file mode 100644 index 00000000..6f2bfede --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSTextFieldCustomization.h @@ -0,0 +1,47 @@ +// +// STDSTextFieldCustomization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + A customization object to use to configure the UI of a text field. + + The font and textColor inherited from `STDSCustomization` configure + the user input text. + */ +@interface STDSTextFieldCustomization : STDSCustomization + +/** + The default settings. + + The default textColor is black. + */ ++ (instancetype)defaultSettings; + +/// The border width of the text field. Defaults to 2. +@property (nonatomic) CGFloat borderWidth; + +/// The color of the border of the text field. Defaults to clear. +@property (nonatomic) UIColor *borderColor; + +/// The corner radius of the edges of the text field. Defaults to 8. +@property (nonatomic) CGFloat cornerRadius; + +/// The appearance of the keyboard. Defaults to UIKeyboardAppearanceDefault. +@property (nonatomic) UIKeyboardAppearance keyboardAppearance; + +/// The color of the placeholder text. Defaults to light gray. +@property (nonatomic) UIColor *placeholderTextColor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSTextFieldCustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSTextFieldCustomization.m new file mode 100644 index 00000000..18a85c8d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSTextFieldCustomization.m @@ -0,0 +1,50 @@ +// +// STDSTextFieldCustomization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSTextFieldCustomization.h" + +#import "UIFont+DefaultFonts.h" +#import "UIColor+ThirteenSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSTextFieldCustomization + ++ (instancetype)defaultSettings { + return [self new]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + self.font = [UIFont _stds_defaultLabelTextFontWithScale:(CGFloat)1.9]; + _borderWidth = 2; + _cornerRadius = 8; + _keyboardAppearance = UIKeyboardAppearanceDefault; + + self.textColor = UIColor._stds_labelColor; + _borderColor = UIColor.clearColor; + _placeholderTextColor = UIColor._stds_systemGray2Color; + } + return self; +} + +- (id)copyWithZone:(nullable NSZone *)zone { + STDSTextFieldCustomization *copy = [super copyWithZone:zone]; + copy.borderWidth = self.borderWidth; + copy.borderColor = self.borderColor; + copy.cornerRadius = self.cornerRadius; + copy.keyboardAppearance = self.keyboardAppearance; + copy.placeholderTextColor = self.placeholderTextColor; + + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSThreeDS2Service.h b/Stripe3DS2/Stripe3DS2/include/STDSThreeDS2Service.h new file mode 100644 index 00000000..a3d98444 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSThreeDS2Service.h @@ -0,0 +1,90 @@ +// +// STDSThreeDS2Service.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@class STDSConfigParameters; +@class STDSTransaction; +@class STDSUICustomization; +@class STDSWarning; +@protocol STDSAnalyticsDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSThreeDS2Service` is the main 3DS SDK interface and provides methods to process transactions. + */ +@interface STDSThreeDS2Service : NSObject + +/** + A list of warnings that may be populated once the SDK has been initialized. + */ +@property (nonatomic, readonly, nullable) NSArray *warnings; + +/** + Initializes the 3DS SDK instance. + + This method should be called at the start of the payment stage of a transaction. + **Note: Until the `STDSThreeDS2Service instance is initialized, it will be unusable.** + + - Performs security checks + - Collects device information + + @param config Configuration information that will be used during initialization. @see STDSConfigParameters + @param locale Optional override for the locale to use in UI. If `nil`, will default to the current system locale. + @param uiSettings Optional custom UI settings. If `nil`, will default to `[STDSUICustomization defaultSettings]`. + This argument is copied; any further changes to the customization object have no effect. @see STDSUICustomization + + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `config` is `nil` or any of `config`, `locale`, or `uiSettings` are invalid. @see STDSInvalidInputException + @exception STDSAlreadyInitializedException Will throw an `STDSAlreadyInitializedException` if the 3DS SDK instance has already been initialized. @see STDSSDKAlreadyInitializedException + @exception STDSRuntimeException Will throw an `STDSRuntimeException` if there is an internal error in the SDK. @see STDSRuntimeException + */ +- (void)initializeWithConfig:(STDSConfigParameters *)config + locale:(nullable NSLocale *)locale + uiSettings:(nullable STDSUICustomization *)uiSettings; + +- (void)initializeWithConfig:(STDSConfigParameters *)config + locale:(nullable NSLocale *)locale + uiSettings:(nullable STDSUICustomization *)uiSettings + analyticsDelegate:(nonnull id)analyticsDelegate; + +/** + Creates and returns an instance of `STDSTransaction`. + + @param directoryServerID The Directory Server identifier returned in the authentication response + @param protocolVersion 3DS protocol version according to which the transaction will be created. Uses the default value of 2.2.0 if nil + + @exception STDSNotInitializedException Will throw an `STDSNotInitializedException` if the the `STDSThreeDS2Service` instance hasn't been initialized with a call to `initializeWithConfig:locale:uiSettings:`. @see STDSNotInitializedException + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if `directoryServerID` is not recognized or if the `protocolVersion` is not supported by this version of the SDK. @see STDSInvalidInputException + @exception STDSRuntimeException Will throw an `STDSRuntimeException` if there is an internal error in the SDK. @see STDSRuntimeException + */ +- (STDSTransaction *)createTransactionForDirectoryServer:(NSString *)directoryServerID + withProtocolVersion:(nullable NSString *)protocolVersion; + +/** + Creates and returns an instance of `STDSTransaction` using a custom directory server certificate. + Will return nil if unable to create a certificate from the provided params. + + @param directoryServerID The Directory Server identifier returned in the authentication response + @param serverKeyID An additional authentication key used by some Directory Servers + @param certificateString A Base64-encoded PEM or DER formatted certificate string containing the directory server's public key + @param rootCertificateStrings An arry of base64-encoded PEM or DER formatted certificate strings containing the DS root certificate used for signature checks + @param protocolVersion 3DS protocol version according to which the transaction will be created. Uses the default value of 2.2.0 if nil + + @exception STDSNotInitializedException Will throw an `STDSNotInitializedException` if the the `STDSThreeDS2Service` instance hasn't been initialized with a call to `initializeWithConfig:locale:uiSettings:`. @see STDSNotInitializedException + @exception STDSInvalidInputException Will throw an `STDSInvalidInputException` if the `protocolVersion` is not supported by this version of the SDK. @see STDSInvalidInputException + */ +- (nullable STDSTransaction *)createTransactionForDirectoryServer:(NSString *)directoryServerID + serverKeyID:(nullable NSString *)serverKeyID + certificateString:(NSString *)certificateString + rootCertificateStrings:(NSArray *)rootCertificateStrings + withProtocolVersion:(nullable NSString *)protocolVersion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSThreeDS2Service.m b/Stripe3DS2/Stripe3DS2/include/STDSThreeDS2Service.m new file mode 100644 index 00000000..934b6043 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSThreeDS2Service.m @@ -0,0 +1,202 @@ +// +// STDSThreeDS2Service.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSThreeDS2Service.h" + +#include + +#import "STDSAlreadyInitializedException.h" +#import "STDSConfigParameters.h" +#import "STDSDebuggerChecker.h" +#import "STDSDeviceInformationManager.h" +#import "STDSDirectoryServerCertificate.h" +#import "STDSException+Internal.h" +#import "STDSInvalidInputException.h" +#import "STDSLocalizedString.h" +#import "STDSJailbreakChecker.h" +#import "STDSIntegrityChecker.h" +#import "STDSNotInitializedException.h" +#import "STDSOSVersionChecker.h" +#import "STDSSecTypeUtilities.h" +#import "STDSSimulatorChecker.h" +#import "STDSThreeDSProtocolVersion.h" +#import "STDSTransaction+Private.h" +#import "STDSWarning.h" + +static const int kServiceNotInitialized = 0; +static const int kServiceInitialized = 1; + +static NSString * const kInternalStripeTestingConfigParam = @"kInternalStripeTestingConfigParam"; +static NSString * const kIgnoreDeviceInformationRestrictionsParam = @"kIgnoreDeviceInformationRestrictionsParam"; +static NSString * const kUseULTestLOAParam = @"kUseULTestLOAParam"; + +@implementation STDSThreeDS2Service +{ + atomic_int _initialized; + + STDSDeviceInformation *_deviceInformation; + STDSUICustomization *_uiSettings; + STDSConfigParameters *_configuration; + __weak id _analyticsDelegate; +} + +@synthesize warnings = _warnings; + +- (void)initializeWithConfig:(STDSConfigParameters *)config + locale:(nullable NSLocale *)locale + uiSettings:(nullable STDSUICustomization *)uiSettings + analyticsDelegate:(nonnull id)analyticsDelegate { + _analyticsDelegate = analyticsDelegate; + [self initializeWithConfig:config locale:locale uiSettings:uiSettings]; +} + +- (void)initializeWithConfig:(STDSConfigParameters *)config + locale:(nullable NSLocale *)locale + uiSettings:(nullable STDSUICustomization *)uiSettings { + if (config == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:[NSString stringWithFormat:@"%@ config parameter must be non-nil.", NSStringFromSelector(_cmd)]]; + } + + int notInitialized = kServiceNotInitialized; // Can't pass a const to atomic_compare_exchange_strong_explicit so copy here + if (!atomic_compare_exchange_strong_explicit(&_initialized, ¬Initialized, kServiceInitialized, memory_order_release, memory_order_relaxed)) { + @throw [STDSAlreadyInitializedException exceptionWithMessage:[NSString stringWithFormat:@"STDSThreeDS2Service instance %p has already been initialized.", self]]; + } + + _configuration = config; + _uiSettings = uiSettings ? [uiSettings copy] : [STDSUICustomization defaultSettings]; + + NSMutableArray *warnings = [NSMutableArray array]; + if ([STDSJailbreakChecker isJailbroken]) { + STDSWarning *jailbrokenWarning = [[STDSWarning alloc] initWithIdentifier:@"SW01" message:STDSLocalizedString(@"The device is jailbroken.", @"The text for warning when a device is jailbroken") severity:STDSWarningSeverityHigh]; + [warnings addObject:jailbrokenWarning]; + } + + if (![STDSIntegrityChecker SDKIntegrityIsValid]) { + STDSWarning *integrityWarning = [[STDSWarning alloc] initWithIdentifier:@"SW02" message:STDSLocalizedString(@"The integrity of the SDK has been tampered.", @"The text for warning when the integrity of the SDK has been tampered with") severity:STDSWarningSeverityHigh]; + [warnings addObject:integrityWarning]; + } + + if ([STDSSimulatorChecker isRunningOnSimulator]) { + STDSWarning *simulatorWarning = [[STDSWarning alloc] initWithIdentifier:@"SW03" message:STDSLocalizedString(@"An emulator is being used to run the App.", @"The text for warning when an emulator is being used to run the application.") severity:STDSWarningSeverityHigh]; + [warnings addObject:simulatorWarning]; + } + + if ([STDSDebuggerChecker processIsCurrentlyAttachedToDebugger]) { + STDSWarning *debuggerWarning = [[STDSWarning alloc] initWithIdentifier:@"SW04" message:STDSLocalizedString(@"A debugger is attached to the App.", @"The text for warning when a debugger is currently attached to the process.") severity:STDSWarningSeverityMedium]; + [warnings addObject:debuggerWarning]; + } + + if (![STDSOSVersionChecker isSupportedOSVersion]) { + STDSWarning *versionWarning = [[STDSWarning alloc] initWithIdentifier:@"SW05" message:STDSLocalizedString(@"The OS or the OS Version is not supported.", "The text for warning when the SDK is running on an unsupported OS or OS version.") severity:STDSWarningSeverityHigh]; + [warnings addObject:versionWarning]; + } + + _warnings = [warnings copy]; + + _deviceInformation = [STDSDeviceInformationManager deviceInformationWithWarnings:_warnings + ignoringRestrictions:[[_configuration parameterValue:kIgnoreDeviceInformationRestrictionsParam] isEqualToString:@"Y"]]; + +} + +- (STDSTransaction *)createTransactionForDirectoryServer:(NSString *)directoryServerID + withProtocolVersion:(nullable NSString *)protocolVersion { + if (_initialized != kServiceInitialized) { + @throw [STDSNotInitializedException exceptionWithMessage:@"STDSThreeDS2Service instance %p has not been initialized before call to %@", self, NSStringFromSelector(_cmd)]; + } + + if (directoryServerID == nil) { + @throw [STDSInvalidInputException exceptionWithMessage:@"%@ directoryServerID parameter must be non-nil.", NSStringFromSelector(_cmd)]; + } + + STDSDirectoryServer directoryServer = STDSDirectoryServerForID(directoryServerID); + if (directoryServer == STDSDirectoryServerUnknown) { + if ([[_configuration parameterValue:kInternalStripeTestingConfigParam] isEqualToString:@"Y"]) { + directoryServer = STDSDirectoryServerULTestRSA; + } else { + @throw [STDSInvalidInputException exceptionWithMessage:@"%@ is an invalid directoryServerID value", directoryServerID]; + } + } + + if (protocolVersion != nil && ![self _supportsProtocolVersion:protocolVersion]) { + @throw [STDSInvalidInputException exceptionWithMessage:@"3DS2 Protocol Version %@ is not supported by this SDK", protocolVersion]; + } + + + + STDSTransaction *transaction = [[STDSTransaction alloc] initWithDeviceInformation:_deviceInformation + directoryServer:directoryServer + protocolVersion:(protocolVersion != nil) ? STDSThreeDSProtocolVersionForString(protocolVersion) : STDSThreeDSProtocolVersion2_2_0 + uiCustomization:_uiSettings + analyticsDelegate:_analyticsDelegate]; + transaction.bypassTestModeVerification = [[_configuration parameterValue:kInternalStripeTestingConfigParam] isEqualToString:@"Y"]; + transaction.useULTestLOA = [[_configuration parameterValue:kUseULTestLOAParam] isEqualToString:@"Y"]; + return transaction; + +} + +- (nullable STDSTransaction *)createTransactionForDirectoryServer:(NSString *)directoryServerID + serverKeyID:(nullable NSString *)serverKeyID + certificateString:(NSString *)certificateString + rootCertificateStrings:(NSArray *)rootCertificateStrings + withProtocolVersion:(nullable NSString *)protocolVersion { + if (_initialized != kServiceInitialized) { + @throw [STDSNotInitializedException exceptionWithMessage:@"STDSThreeDS2Service instance %p has not been initialized before call to %@", self, NSStringFromSelector(_cmd)]; + } + + if (protocolVersion != nil && ![self _supportsProtocolVersion:protocolVersion]) { + @throw [STDSInvalidInputException exceptionWithMessage:@"3DS2 Protocol Version %@ is not supported by this SDK", protocolVersion]; + } + + STDSTransaction *transaction = nil; + + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate customCertificateWithString:certificateString]; + + if (certificate != nil) { + transaction = [[STDSTransaction alloc] initWithDeviceInformation:_deviceInformation + directoryServerID:directoryServerID + serverKeyID:serverKeyID + directoryServerCertificate:certificate + rootCertificateStrings:rootCertificateStrings + protocolVersion:(protocolVersion != nil) ? STDSThreeDSProtocolVersionForString(protocolVersion) : STDSThreeDSProtocolVersion2_2_0 + uiCustomization:_uiSettings + analyticsDelegate:_analyticsDelegate]; + transaction.bypassTestModeVerification = [_configuration parameterValue:kInternalStripeTestingConfigParam] != nil; + } + + return transaction; +} + +- (nullable NSArray *)warnings { + if (_initialized != kServiceInitialized) { + @throw [STDSNotInitializedException exceptionWithMessage:@"STDSThreeDS2Service instance %p has not been initialized before call to %@", self, NSStringFromSelector(_cmd)]; + } + + return _warnings; +} + +#pragma mark - Internal + +- (BOOL)_supportsProtocolVersion:(NSString *)protocolVersion { + STDSThreeDSProtocolVersion version = STDSThreeDSProtocolVersionForString(protocolVersion); + switch (version) { + case STDSThreeDSProtocolVersion2_1_0: + return YES; + + case STDSThreeDSProtocolVersion2_2_0: + return YES; + + case STDSThreeDSProtocolVersionFallbackTest: + // only support fallback test if we have the internal testing config param + return [[_configuration parameterValue:kInternalStripeTestingConfigParam] isEqualToString:@"Y"]; + + case STDSThreeDSProtocolVersionUnknown: + return NO; + } +} + +@end diff --git a/Stripe3DS2/Stripe3DS2/include/STDSThreeDSProtocolVersion.h b/Stripe3DS2/Stripe3DS2/include/STDSThreeDSProtocolVersion.h new file mode 100644 index 00000000..b4161a6e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSThreeDSProtocolVersion.h @@ -0,0 +1,15 @@ +// +// STDSThreeDSProtocolVersion.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 6/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXPORT NSString * const Stripe3DS2ProtocolVersion; + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSTransaction.h b/Stripe3DS2/Stripe3DS2/include/STDSTransaction.h new file mode 100644 index 00000000..02a1f1af --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSTransaction.h @@ -0,0 +1,94 @@ +// +// STDSTransaction.h +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import + +typedef void (^STDSTransactionVoidBlock)(void); + +@class STDSAuthenticationRequestParameters, STDSChallengeParameters; +@protocol STDSChallengeStatusReceiver; + +NS_ASSUME_NONNULL_BEGIN + +/** + `STDSTransaction` holds parameters that the 3DS Server requires to create AReq messages and to perform the Challenge Flow. + */ +@interface STDSTransaction : NSObject + +/** + The UI type of the presented challenge for this transaction if applicable. Will be one of + "none" + "text" + "single_select" + "multi_select" + "oob" + "html" + */ +@property (nonatomic, readonly, copy) NSString *presentedChallengeUIType; + +/** + Encrypts device information collected during initialization and returns it along with SDK details. + + @return Encrypted device information and details about this SDK. @see STDSAuthenticationRequestParameters + + @exception SDKRuntimeException Thrown if an internal error is encountered. + */ +- (STDSAuthenticationRequestParameters *)createAuthenticationRequestParameters; + +/** + Returns a UIViewController instance displaying the Directory Server logo and a spinner. Present this during the Authentication Request/Response. + */ +- (UIViewController *)createProgressViewControllerWithDidCancel:(STDSTransactionVoidBlock)didCancel; + +/** + Initiates the challenge process, displaying challenge UI as needed. + + @param presentingViewController The UIViewController used to present the challenge response UIViewController + @param challengeParameters Details required to conduct the challenge process. @see STDSChallengeParameters + @param challengeStatusReceiver A callback object to receive the status of the challenge. See @STDSChallengeStatusReceiver + @param timeout An interval in seconds within which the challenge process will finish. Must be at least 5 minutes. + + @exception STDSInvalidInputException Thrown if an argument is invalid (e.g. timeout less than 5 minutes). @see STDSInvalidInputException + @exception STDSSDKRuntimeException Thrown if an internal error is encountered, and if you call this method after calling `close`. @see SDKRuntimeException + + @note challengeStatusReceiver must conform to . This is a workaround: When the static Stripe3DS2 is compiled into Stripe.framework, the resulting swiftinterface and generated .h files reference this protocol. To allow users to build without including Stripe3DS2 directly, we'll take an `id` here instead. + */ +- (void)doChallengeWithViewController:(UIViewController *)presentingViewController + challengeParameters:(STDSChallengeParameters *)challengeParameters + challengeStatusReceiver:(id)challengeStatusReceiver + timeout:(NSTimeInterval)timeout; + +/** + Returns the version of the Stripe3DS2 SDK, e.g. @"1.0" + */ +- (NSString *)sdkVersion; + +/** +Cleans up resources held by `STDSTransaction`. Call this when the transaction is completed, if `doChallengeWithChallengeParameters:challengeStatusReceiver:timeout` is not called. + + @note Don't use this object after calling this method. Calling `doChallengeWithViewController:challengeParameters:challengeStatusReceiver:timeout` after calling this method will throw an `STDSSDKRuntimeException` + */ +- (void)close; + +/** + Alternate challenge initiation method meant only for internal use by Stripe SDK. + */ +- (void)doChallengeWithChallengeParameters:(STDSChallengeParameters *)challengeParameters + challengeStatusReceiver:(id)challengeStatusReceiver + timeout:(NSTimeInterval)timeout + presentationBlock:(void (^)(UIViewController *, void(^)(void)))presentationBlock; + +/** + Function to manually cancel the challenge flow. + */ +- (void)cancelChallengeFlow; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSTransaction.m b/Stripe3DS2/Stripe3DS2/include/STDSTransaction.m new file mode 100644 index 00000000..422b2dbb --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSTransaction.m @@ -0,0 +1,555 @@ +// +// STDSTransaction.m +// Stripe3DS2 +// +// Created by Yuki Tokuhiro on 3/21/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSTransaction.h" +#import "STDSTransaction+Private.h" + +#import "STDSBundleLocator.h" +#import "NSDictionary+DecodingHelpers.h" +#import "NSError+Stripe3DS2.h" +#import "NSString+JWEHelpers.h" +#import "STDSACSNetworkingManager.h" +#import "STDSAuthenticationRequestParameters.h" +#import "STDSChallengeRequestParameters.h" +#import "STDSCompletionEvent.h" +#import "STDSChallengeParameters.h" +#import "STDSChallengeResponseObject.h" +#import "STDSChallengeResponseViewController.h" +#import "STDSChallengeStatusReceiver.h" +#import "STDSDeviceInformation.h" +#import "STDSEllipticCurvePoint.h" +#import "STDSEphemeralKeyPair.h" +#import "STDSErrorMessage+Internal.h" +#import "STDSException+Internal.h" +#import "STDSInvalidInputException.h" +#import "STDSJSONWebEncryption.h" +#import "STDSJSONWebSignature.h" +#import "STDSProgressViewController.h" +#import "STDSProtocolErrorEvent.h" +#import "STDSRuntimeErrorEvent.h" +#import "STDSRuntimeException.h" +#import "STDSSecTypeUtilities.h" +#import "STDSStripe3DS2Error.h" +#import "STDSDeviceInformationParameter.h" +#import "STDSAnalyticsDelegate.h" + +static const NSTimeInterval kMinimumTimeout = 5 * 60; +static NSString * const kStripeLOA = @"3DS_LOA_SDK_STIN_020200_00961"; +static NSString * const kULTestLOA = @"3DS_LOA_SDK_PPFU_020100_00007"; + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSTransaction() + +@property (nonatomic, weak) id challengeStatusReceiver; +@property (nonatomic, strong, nullable) STDSChallengeResponseViewController *challengeResponseViewController; +/// Stores the most recent parameters used to make a CReq +@property (nonatomic, nullable) STDSChallengeRequestParameters *challengeRequestParameters; +/// YES if `close` was called or the challenge flow finished. +@property (nonatomic, getter=isCompleted) BOOL completed; +@end + +@implementation STDSTransaction +{ + STDSDeviceInformation *_deviceInformation; + STDSDirectoryServer _directoryServer; + STDSEphemeralKeyPair *_ephemeralKeyPair; + STDSThreeDSProtocolVersion _protocolVersion; + NSString *_identifier; + + STDSDirectoryServerCertificate *_customDirectoryServerCertificate; + NSArray *_rootCertificateStrings; + NSString *_customDirectoryServerID; + NSString *_serverKeyID; + + STDSACSNetworkingManager *_networkingManager; + + STDSUICustomization *_uiCustomization; + + __weak id _analyticsDelegate; +} + +- (instancetype)initWithDeviceInformation:(STDSDeviceInformation *)deviceInformation + directoryServer:(STDSDirectoryServer)directoryServer + protocolVersion:(STDSThreeDSProtocolVersion)protocolVersion + uiCustomization:(nonnull STDSUICustomization *)uiCustomization + analyticsDelegate:(nullable id)analyticsDelegate { + self = [super init]; + if (self) { + _deviceInformation = deviceInformation; + _directoryServer = directoryServer; + _protocolVersion = protocolVersion; + _completed = NO; + _identifier = [NSUUID UUID].UUIDString.lowercaseString; + _ephemeralKeyPair = [STDSEphemeralKeyPair ephemeralKeyPair]; + _uiCustomization = uiCustomization; + if (_ephemeralKeyPair == nil) { + return nil; + } + } + + return self; +} + +- (instancetype)initWithDeviceInformation:(STDSDeviceInformation *)deviceInformation + directoryServerID:(NSString *)directoryServerID + serverKeyID:(nullable NSString *)serverKeyID + directoryServerCertificate:(STDSDirectoryServerCertificate *)directoryServerCertificate + rootCertificateStrings:(NSArray *)rootCertificateStrings + protocolVersion:(STDSThreeDSProtocolVersion)protocolVersion + uiCustomization:(STDSUICustomization *)uiCustomization + analyticsDelegate:(nullable id)analyticsDelegate { + self = [super init]; + if (self) { + _deviceInformation = deviceInformation; + _directoryServer = STDSDirectoryServerCustom; + _customDirectoryServerCertificate = directoryServerCertificate; + _rootCertificateStrings = rootCertificateStrings; + _customDirectoryServerID = [directoryServerID copy]; + _serverKeyID = [serverKeyID copy]; + _protocolVersion = protocolVersion; + _completed = NO; + _identifier = [NSUUID UUID].UUIDString.lowercaseString; + _ephemeralKeyPair = [STDSEphemeralKeyPair ephemeralKeyPair]; + _uiCustomization = uiCustomization; + if (_ephemeralKeyPair == nil) { + return nil; + } + } + + return self; +} + +- (NSString *)sdkVersion { + return [[STDSBundleLocator stdsResourcesBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; +} + +- (NSString *)presentedChallengeUIType { + return [self UIType:self.challengeResponseViewController.response.acsUIType]; +} + +- (NSString *)UIType:(STDSACSUIType)uiType { + switch (uiType) { + + case STDSACSUITypeNone: + return @"none"; + + case STDSACSUITypeText: + return @"text"; + + case STDSACSUITypeSingleSelect: + return @"single_select"; + + case STDSACSUITypeMultiSelect: + return @"multi_select"; + + case STDSACSUITypeOOB: + return @"oob"; + + case STDSACSUITypeHTML: + return @"html"; + } +} + +- (STDSAuthenticationRequestParameters *)createAuthenticationRequestParameters { + NSError *error = nil; + NSString *encryptedDeviceData = nil; + + NSMutableDictionary *dictionary = [_deviceInformation.dictionaryValue mutableCopy]; + dictionary[@"DD"][@"C018"] = _identifier; + + NSString *SDKReferenceNumber = self.useULTestLOA ? kULTestLOA : kStripeLOA; + dictionary[@"DD"][@"C016"] = SDKReferenceNumber; + + if (_directoryServer == STDSDirectoryServerCustom) { + encryptedDeviceData = [STDSJSONWebEncryption encryptJSON:dictionary + withCertificate:_customDirectoryServerCertificate + directoryServerID:_customDirectoryServerID + serverKeyID:_serverKeyID + error:&error]; + } else { + encryptedDeviceData = [STDSJSONWebEncryption encryptJSON:dictionary + forDirectoryServer:_directoryServer + error:&error]; + } + if (encryptedDeviceData == nil) { + @throw [STDSRuntimeException exceptionWithMessage:@"Error encrypting device information %@", error]; + } + + return [[STDSAuthenticationRequestParameters alloc] initWithSDKTransactionIdentifier:_identifier + deviceData:encryptedDeviceData + sdkEphemeralPublicKey:_ephemeralKeyPair.publicKeyJWK + sdkAppIdentifier:[STDSDeviceInformationParameter sdkAppIdentifier] + sdkReferenceNumber:SDKReferenceNumber + messageVersion:[self _messageVersion]]; +} + +- (UIViewController *)createProgressViewControllerWithDidCancel:(void (^)(void))didCancel { + return [[STDSProgressViewController alloc] initWithDirectoryServer:[self _directoryServerForUI] + uiCustomization:_uiCustomization + analyticsDelegate:_analyticsDelegate + didCancel:didCancel]; +} + +- (void)doChallengeWithViewController:(UIViewController *)presentingViewController + challengeParameters:(STDSChallengeParameters *)challengeParameters + challengeStatusReceiver:(id)challengeStatusReceiver + timeout:(NSTimeInterval)timeout { + + [self doChallengeWithChallengeParameters:challengeParameters + challengeStatusReceiver:challengeStatusReceiver + timeout:timeout + presentationBlock:^(UIViewController * _Nonnull challengeVC, void (^completion)(void)) { + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:challengeVC]; + + // Disable "swipe to dismiss" behavior in iOS 13 + if ([navigationController respondsToSelector:NSSelectorFromString(@"isModalInPresentation")]) { + [navigationController setValue:@YES forKey:@"modalInPresentation"]; + } + + [presentingViewController presentViewController:navigationController animated:YES completion:^{ + completion(); + }]; + }]; +} + +- (void)doChallengeWithChallengeParameters:(STDSChallengeParameters *)challengeParameters + challengeStatusReceiver:(id)challengeStatusReceiver + timeout:(NSTimeInterval)timeout + presentationBlock:(void (^)(UIViewController *, void(^)(void)))presentationBlock { + if (self.isCompleted) { + @throw [STDSRuntimeException exceptionWithMessage:@"The transaction has already completed."]; + } else if (timeout < kMinimumTimeout) { + @throw [STDSInvalidInputException exceptionWithMessage:@"Timeout value of %lf seconds is less than 5 minutes", timeout]; + } + self.challengeStatusReceiver = challengeStatusReceiver; + self.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:(timeout) target:self selector:@selector(_didTimeout) userInfo:nil repeats:NO]; + + self.challengeRequestParameters = [[STDSChallengeRequestParameters alloc] initWithChallengeParameters:challengeParameters + transactionIdentifier:_identifier + messageVersion:[self _messageVersion]]; + + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:challengeParameters.acsSignedContent allowNilKey:self.bypassTestModeVerification]; + BOOL validJWS = jws != nil; + if (validJWS && !self.bypassTestModeVerification) { + if (_customDirectoryServerCertificate != nil) { + if (_rootCertificateStrings.count == 0) { + validJWS = NO; + } else { + validJWS = [STDSJSONWebEncryption verifyJSONWebSignature:jws withCertificate:_customDirectoryServerCertificate rootCertificates:_rootCertificateStrings]; + } + } else { + validJWS = [STDSJSONWebEncryption verifyJSONWebSignature:jws forDirectoryServer:_directoryServer]; + } + } + if (!validJWS) { + dispatch_async(dispatch_get_main_queue(), ^{ + [challengeStatusReceiver transaction:self + didErrorWithRuntimeErrorEvent:[[STDSRuntimeErrorEvent alloc] initWithErrorCode:kSTDSRuntimeErrorCodeEncryptionError errorMessage:@"Error verifying JWS response."]]; + [self _cleanUp]; + }); + return; + } + + NSError *jsonError = nil; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jws.payload options:0 error:&jsonError]; + NSDictionary *acsEphmeralKeyJWK = [json _stds_dictionaryForKey:@"acsEphemPubKey" required:NO error:NULL]; + STDSEllipticCurvePoint *acsEphemeralKey = [[STDSEllipticCurvePoint alloc] initWithJWK:acsEphmeralKeyJWK]; + NSString *acsURLString = [json _stds_stringForKey:@"acsURL" required:NO error:NULL]; + NSURL *acsURL = [NSURL URLWithString:acsURLString ?: @""]; + if (acsEphemeralKey == nil || acsURL == nil) { + dispatch_async(dispatch_get_main_queue(), ^{ + [challengeStatusReceiver transaction:self + didErrorWithRuntimeErrorEvent:[[STDSRuntimeErrorEvent alloc] initWithErrorCode:kSTDSRuntimeErrorCodeParsingError errorMessage:[NSString stringWithFormat:@"Unable to create key or url from ACS json: %@\n\n jsonError: %@", json, jsonError]]]; + [self _cleanUp]; + }); + return; + } + + NSData *ecdhSecret = [_ephemeralKeyPair createSharedSecretWithEllipticCurveKey:acsEphemeralKey]; + if (ecdhSecret == nil) { + dispatch_async(dispatch_get_main_queue(), ^{ + [challengeStatusReceiver transaction:self + didErrorWithRuntimeErrorEvent:[[STDSRuntimeErrorEvent alloc] initWithErrorCode:kSTDSRuntimeErrorCodeEncryptionError errorMessage:@"Error during Diffie-Helman key exchange"]]; + [self _cleanUp]; + }); + return; + } + + NSData *contentEncryptionKeySDKtoACS = STDSCreateConcatKDFWithSHA256(ecdhSecret, 32, self.useULTestLOA ? kULTestLOA : kStripeLOA); + // These two keys are intentionally identical + // ref. Protocol and Core Functions Specification Version 2.2.0 Section 6.2.3.2 & 6.2.3.3 + // "In this version of the specification [contentEncryptionKeyACStoSDK] and [contentEncryptionKeySDKtoACS] + // are extracted with the same value." + NSData *contentEncryptionKeyACStoSDK = [contentEncryptionKeySDKtoACS copy]; + + _networkingManager = [[STDSACSNetworkingManager alloc] initWithURL:acsURL + sdkContentEncryptionKey:contentEncryptionKeySDKtoACS + acsContentEncryptionKey:contentEncryptionKeyACStoSDK + acsTransactionIdentifier:self.challengeRequestParameters.acsTransactionIdentifier]; + // Start the Challenge flow + STDSImageLoader *imageLoader = [[STDSImageLoader alloc] initWithURLSession:NSURLSession.sharedSession]; + self.challengeResponseViewController = [[STDSChallengeResponseViewController alloc] initWithUICustomization:_uiCustomization + imageLoader:imageLoader + directoryServer:[self _directoryServerForUI] + analyticsDelegate:_analyticsDelegate]; + self.challengeResponseViewController.delegate = self; + + presentationBlock(self.challengeResponseViewController, ^{ [self _makeChallengeRequest:self.challengeRequestParameters didCancel:NO]; }); +} + +- (void)close { + [self _cleanUp]; +} + +- (void)cancelChallengeFlow { + [self challengeResponseViewControllerDidCancel:self.challengeResponseViewController]; +} + +- (void)dealloc { + [self _cleanUp]; +} + +#pragma mark - Private + +// When we get a directory certificate and ID from the server, we mark it as Custom for encryption, +// but may have a correct mapping to a DS logo for the UI +- (STDSDirectoryServer)_directoryServerForUI { + return (_customDirectoryServerID != nil) ? STDSDirectoryServerForID(_customDirectoryServerID) : _directoryServer; +} + +- (void)_makeChallengeRequest:(STDSChallengeRequestParameters *)challengeRequestParameters didCancel:(BOOL)didCancel { + [self.challengeResponseViewController setLoading]; + __weak STDSTransaction *weakSelf = self; + [_networkingManager submitChallengeRequest:self.challengeRequestParameters + withCompletion:^(id _Nullable response, NSError * _Nullable error) { + STDSTransaction *strongSelf = weakSelf; + if (strongSelf == nil || strongSelf.isCompleted) { + return; + } + // Parsing or network errors + if (response == nil || error) { + if (!error) { + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorCodeUnknownError userInfo:nil]; + } + [strongSelf _handleError:error]; + return; + } + // Consistency errors (e.g. acsTransID changes) + NSError *validationError; + if (![strongSelf _validateChallengeResponse:response error:&validationError]) { + [strongSelf _handleError:validationError]; + return; + } + [strongSelf _handleChallengeResponse:response didCancel:didCancel]; + }]; +} + +- (BOOL)_validateChallengeResponse:(id)challengeResponse error:(NSError **)outError { + NSError *error; + if (![challengeResponse.acsTransactionID isEqualToString:self.challengeRequestParameters.acsTransactionIdentifier] || + ![challengeResponse.threeDSServerTransactionID isEqualToString:self.challengeRequestParameters.threeDSServerTransactionIdentifier] || + ![challengeResponse.sdkTransactionID isEqualToString:self.challengeRequestParameters.sdkTransactionIdentifier]) { + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorMessageErrorTransactionIDNotRecognized userInfo:nil]; + } else if (![challengeResponse.messageVersion isEqualToString:self.challengeRequestParameters.messageVersion]) { + error = [NSError _stds_invalidJSONFieldError:@"messageVersion"]; + } else if (!self.bypassTestModeVerification && ![challengeResponse.acsCounterACStoSDK isEqualToString:self.challengeRequestParameters.sdkCounterStoA]) { + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorCodeDecryptionVerification userInfo:nil]; + } else if (challengeResponse.acsUIType == STDSACSUITypeHTML && !challengeResponse.acsHTML) { + error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorCodeDecryptionVerification userInfo:nil]; + } + + if (error && outError) { + *outError = error; + } + return error == nil; +} + +- (void) _handleError:(NSError *)error { + // All the codes corresponding to errors that we treat as protocol errors (ie send to the ACS and report as an STDSProtocolErrorEvent) + NSSet *protocolErrorCodes = [NSSet setWithArray:@[@(STDSErrorCodeUnknownMessageType), + @(STDSErrorCodeJSONFieldInvalid), + @(STDSErrorCodeJSONFieldMissing), + @(STDSErrorCodeReceivedErrorMessage), + @(STDSErrorMessageErrorTransactionIDNotRecognized), + @(STDSErrorCodeUnrecognizedCriticalMessageExtension), + @(STDSErrorCodeDecryptionVerification)]]; + if (error.domain == STDSStripe3DS2ErrorDomain) { + NSString *sdkTransactionIdentifier = _identifier; + NSString *acsTransactionIdentifier = self.challengeRequestParameters.acsTransactionIdentifier; + NSString *messageVersion = [self _messageVersion]; + STDSErrorMessage *errorMessage; + switch (error.code) { + case STDSErrorCodeReceivedErrorMessage: + errorMessage = error.userInfo[STDSStripe3DS2ErrorMessageErrorKey]; + break; + case STDSErrorCodeUnknownMessageType: + errorMessage = [STDSErrorMessage errorForInvalidMessageWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion]; + break; + case STDSErrorCodeJSONFieldInvalid: + errorMessage = [STDSErrorMessage errorForJSONFieldInvalidWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion error:error]; + break; + case STDSErrorCodeJSONFieldMissing: + errorMessage = [STDSErrorMessage errorForJSONFieldMissingWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion error:error]; + break; + case STDSErrorCodeTimeout: + errorMessage = [STDSErrorMessage errorForTimeoutWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion]; + break; + case STDSErrorMessageErrorTransactionIDNotRecognized: + errorMessage = [STDSErrorMessage errorForUnrecognizedIDWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion]; + break; + case STDSErrorCodeUnrecognizedCriticalMessageExtension: + errorMessage = [STDSErrorMessage errorForUnrecognizedCriticalMessageExtensionsWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion error:error]; + break; + case STDSErrorCodeDecryptionVerification: + errorMessage = [STDSErrorMessage errorForDecryptionErrorWithACSTransactionID:acsTransactionIdentifier messageVersion:messageVersion]; + break; + default: + break; + } + + // Send the ErrorMessage (unless we received one) + if (error.code != STDSErrorCodeReceivedErrorMessage && errorMessage != nil) { + [_networkingManager sendErrorMessage:errorMessage]; + } + + // If it's a protocol error, call back to the challengeStatusReceiver + if ([protocolErrorCodes containsObject:@(error.code)] && errorMessage != nil) { + STDSProtocolErrorEvent *protocolErrorEvent = [[STDSProtocolErrorEvent alloc] initWithSDKTransactionIdentifier:sdkTransactionIdentifier + errorMessage:errorMessage]; + [self.challengeStatusReceiver transaction:self didErrorWithProtocolErrorEvent:protocolErrorEvent]; + } + + } + + if (error.domain != STDSStripe3DS2ErrorDomain || ![protocolErrorCodes containsObject:@(error.code)]) { + // This error is not a protocol error, and therefore a runtime error. + NSString *errorCode = [NSString stringWithFormat:@"%ld", (long)error.code]; + STDSRuntimeErrorEvent *runtimeErrorEvent = [[STDSRuntimeErrorEvent alloc] initWithErrorCode:errorCode errorMessage:error.localizedDescription]; + [self.challengeStatusReceiver transaction:self didErrorWithRuntimeErrorEvent:runtimeErrorEvent]; + } + + [self _dismissChallengeResponseViewController]; + [self _cleanUp]; +} + +- (void)_handleChallengeResponse:(id)challengeResponse didCancel:(BOOL)didCancel { + + if (challengeResponse.challengeCompletionIndicator) { + // Final CRes + // We need to pass didCancel to here because we can't distinguish between cancellation and auth failure from the CRes + // (they both result in a transactionStatus of "N") + if (didCancel) { + // We already dismissed the view controller + [self.challengeStatusReceiver transactionDidCancel:self]; + [self _cleanUp]; + } else { + [self _dismissChallengeResponseViewController]; + STDSCompletionEvent *completionEvent = [[STDSCompletionEvent alloc] initWithSDKTransactionIdentifier:_identifier + transactionStatus:challengeResponse.transactionStatus]; + [self.challengeStatusReceiver transaction:self didCompleteChallengeWithCompletionEvent:completionEvent]; + [self _cleanUp]; + } + } else { + [self.challengeResponseViewController setChallengeResponse:challengeResponse animated:YES]; + + if ([self.challengeStatusReceiver respondsToSelector:@selector(transactionDidPresentChallengeScreen:)]) { + [self.challengeStatusReceiver transactionDidPresentChallengeScreen:self]; + } + } + + [_analyticsDelegate didReceiveChallengeResponseWithTransactionID:challengeResponse.threeDSServerTransactionID flow:[self UIType:challengeResponse.acsUIType]]; +} + +- (void)_cleanUp { + [self.timeoutTimer invalidate]; + self.completed = YES; + self.challengeResponseViewController = nil; + self.challengeStatusReceiver = nil; + _networkingManager = nil; +} + +- (void)_didTimeout { + [self _dismissChallengeResponseViewController]; + [_networkingManager sendErrorMessage:[STDSErrorMessage errorForTimeoutWithACSTransactionID:self.challengeRequestParameters.acsTransactionIdentifier messageVersion:[self _messageVersion]]]; + [self.challengeStatusReceiver transactionDidTimeOut:self]; + [self _cleanUp]; +} + +- (void)_dismissChallengeResponseViewController { + if ([self.challengeStatusReceiver respondsToSelector:@selector(dismissChallengeViewController:forTransaction:)]) { + [self.challengeStatusReceiver dismissChallengeViewController:self.challengeResponseViewController forTransaction:self]; + } else { + [self.challengeResponseViewController dismissViewControllerAnimated:YES completion:nil]; + } +} + +#pragma mark Helpers + +- (nullable NSString *)_messageVersion { + NSString *messageVersion = STDSThreeDSProtocolVersionStringValue(_protocolVersion); + if (messageVersion == nil) { + @throw [STDSRuntimeException exceptionWithMessage:@"Error determining message version."]; + } + return messageVersion; +} + +/// Convenience method to construct a CSV from the names of each STDSChallengeResponseSelectionInfo in the given array +- (NSString *)_csvForChallengeResponseSelectionInfo:(NSArray> *)selectionInfoArray { + NSMutableArray *selectionInfoNames = [NSMutableArray new]; + for (id selectionInfo in selectionInfoArray) { + [selectionInfoNames addObject:selectionInfo.name]; + } + return [selectionInfoNames componentsJoinedByString:@","]; +} + +#pragma mark - STDSChallengeResponseViewController + +- (void)challengeResponseViewController:(nonnull STDSChallengeResponseViewController *)viewController didSubmitInput:(nonnull NSString *)userInput whitelistSelection:(nonnull id)whitelistSelection { + self.challengeRequestParameters = [self.challengeRequestParameters nextChallengeRequestParametersByIncrementCounter]; + self.challengeRequestParameters.challengeDataEntry = userInput; + self.challengeRequestParameters.whitelistingDataEntry = whitelistSelection.name; + [self _makeChallengeRequest:self.challengeRequestParameters didCancel:NO]; +} + +- (void)challengeResponseViewController:(nonnull STDSChallengeResponseViewController *)viewController didSubmitSelection:(nonnull NSArray> *)selection whitelistSelection:(nonnull id)whitelistSelection { + self.challengeRequestParameters = [self.challengeRequestParameters nextChallengeRequestParametersByIncrementCounter]; + self.challengeRequestParameters.challengeDataEntry = [self _csvForChallengeResponseSelectionInfo:selection]; + self.challengeRequestParameters.whitelistingDataEntry = whitelistSelection.name; + [self _makeChallengeRequest:self.challengeRequestParameters didCancel:NO]; +} + +- (void)challengeResponseViewControllerDidOOBContinue:(nonnull STDSChallengeResponseViewController *)viewController whitelistSelection:(nonnull id)whitelistSelection { + self.challengeRequestParameters = [self.challengeRequestParameters nextChallengeRequestParametersByIncrementCounter]; + self.challengeRequestParameters.oobContinue = @(YES); + self.challengeRequestParameters.whitelistingDataEntry = whitelistSelection.name; + [self _makeChallengeRequest:self.challengeRequestParameters didCancel:NO]; +} + +- (void)challengeResponseViewControllerDidCancel:(STDSChallengeResponseViewController *)viewController { + self.challengeRequestParameters = [self.challengeRequestParameters nextChallengeRequestParametersByIncrementCounter]; + self.challengeRequestParameters.challengeCancel = @(STDSChallengeCancelTypeCardholderSelectedCancel); + [self _dismissChallengeResponseViewController]; + [self _makeChallengeRequest:self.challengeRequestParameters didCancel:YES]; +} + +- (void)challengeResponseViewControllerDidRequestResend:(STDSChallengeResponseViewController *)viewController { + self.challengeRequestParameters = [self.challengeRequestParameters nextChallengeRequestParametersByIncrementCounter]; + self.challengeRequestParameters.resendChallenge = @"Y"; + [self _makeChallengeRequest:self.challengeRequestParameters didCancel:NO]; +} + +- (void)challengeResponseViewController:(nonnull STDSChallengeResponseViewController *)viewController didSubmitHTMLForm:(nonnull NSString *)form { + self.challengeRequestParameters = [self.challengeRequestParameters nextChallengeRequestParametersByIncrementCounter]; + self.challengeRequestParameters.challengeHTMLDataEntry = form; + [self _makeChallengeRequest:self.challengeRequestParameters didCancel:NO]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSUICustomization.h b/Stripe3DS2/Stripe3DS2/include/STDSUICustomization.h new file mode 100644 index 00000000..651b22f1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSUICustomization.h @@ -0,0 +1,109 @@ +// +// STDSUICustomization.h +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSCustomization.h" +#import "STDSButtonCustomization.h" +#import "STDSNavigationBarCustomization.h" +#import "STDSLabelCustomization.h" +#import "STDSTextFieldCustomization.h" +#import "STDSFooterCustomization.h" +#import "STDSSelectionCustomization.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + The `STDSUICustomization` provides configuration for UI elements. + + It's important to configure this object appropriately before using it to initialize a + `STDSThreeDS2Service` object. `STDSThreeDS2Service` makes a copy of the customization + settings you provide; it ignores any subsequent changes you make to your `STDSUICustomization` instance. +*/ +@interface STDSUICustomization: NSObject + +/// The default settings. See individual properties for their default values. ++ (instancetype)defaultSettings; + +/** + Provides custom settings for the UINavigationBar of all UIViewControllers the SDK display. + The default is `[STDSNavigationBarCustomization defaultSettings]`. + */ +@property (nonatomic) STDSNavigationBarCustomization *navigationBarCustomization; + +/** + Provides custom settings for labels. + The default is `[STDSLabelCustomization defaultSettings]`. + */ +@property (nonatomic) STDSLabelCustomization *labelCustomization; + +/** + Provides custom settings for text fields. + The default is `[STDSTextFieldCustomization defaultSettings]`. + */ +@property (nonatomic) STDSTextFieldCustomization *textFieldCustomization; + +/** + The primary background color of all UIViewControllers the SDK display. + Defaults to white. + */ +@property (nonatomic) UIColor *backgroundColor; + +/** + The Challenge view displays a footer with additional details. This controls the background color of that view. + Defaults to gray. + */ +@property (nonatomic) STDSFooterCustomization *footerCustomization; + +/** + Sets a given button customization for the specified type. + + @param buttonCustomization The buttom customization to use. + @param buttonType The type of button to use the customization for. + */ +- (void)setButtonCustomization:(STDSButtonCustomization *)buttonCustomization forType:(STDSUICustomizationButtonType)buttonType; + +/** + Retrieves a button customization object for the given button type. + + @param buttonType The button type to retrieve a customization object for. + @return A button customization object, or the default if none was set. + @see STDSButtonCustomization + */ +- (STDSButtonCustomization *)buttonCustomizationForButtonType:(STDSUICustomizationButtonType)buttonType; + +/** + Provides custom settings for radio buttons and checkboxes. + The default is `[STDSSelectionCustomization defaultSettings]`. + */ +@property (nonatomic) STDSSelectionCustomization *selectionCustomization; + + +/** + The preferred status bar style for all UIViewControllers the SDK display. + Defaults to UIStatusBarStyleDefault. + */ +@property (nonatomic) UIStatusBarStyle preferredStatusBarStyle; + +#pragma mark - Progress View + +/** + The style of UIActivityIndicatorViews displayed. + This should contrast with `backgroundColor`. Defaults to regular on iOS 13+, + gray on iOS 10-12. + */ +@property (nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle; + +/** + The style of the UIBlurEffect displayed underneath the UIActivityIndicatorView. + Defaults to UIBlurEffectStyleDefault. + */ +@property (nonatomic) UIBlurEffectStyle blurStyle; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSUICustomization.m b/Stripe3DS2/Stripe3DS2/include/STDSUICustomization.m new file mode 100644 index 00000000..8d1d6812 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSUICustomization.m @@ -0,0 +1,83 @@ +// +// STDSUICustomization.m +// Stripe3DS2 +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSUICustomization.h" +#import "UIColor+ThirteenSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSUICustomization() + +@property (nonatomic, strong) NSMutableDictionary *buttonCustomizationDictionary; + +@end + +@implementation STDSUICustomization + ++ (instancetype)defaultSettings { + return [[STDSUICustomization alloc] init]; +} + +- (instancetype)init { + self = [super init]; + + if (self) { + _buttonCustomizationDictionary = [@{ + @(STDSUICustomizationButtonTypeNext): [STDSButtonCustomization defaultSettingsForButtonType:STDSUICustomizationButtonTypeNext + ], + @(STDSUICustomizationButtonTypeCancel): [STDSButtonCustomization defaultSettingsForButtonType:STDSUICustomizationButtonTypeCancel], + @(STDSUICustomizationButtonTypeResend): [STDSButtonCustomization defaultSettingsForButtonType:STDSUICustomizationButtonTypeResend], + @(STDSUICustomizationButtonTypeSubmit): [STDSButtonCustomization defaultSettingsForButtonType:STDSUICustomizationButtonTypeSubmit], + @(STDSUICustomizationButtonTypeContinue): [STDSButtonCustomization defaultSettingsForButtonType:STDSUICustomizationButtonTypeContinue], + } mutableCopy]; + _navigationBarCustomization = [STDSNavigationBarCustomization defaultSettings]; + _labelCustomization = [STDSLabelCustomization defaultSettings]; + _textFieldCustomization = [STDSTextFieldCustomization defaultSettings]; + _footerCustomization = [STDSFooterCustomization defaultSettings]; + _selectionCustomization = [STDSSelectionCustomization defaultSettings]; + _backgroundColor = UIColor._stds_systemBackgroundColor; + _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; + _blurStyle = UIBlurEffectStyleRegular; + _preferredStatusBarStyle = UIStatusBarStyleDefault; + } + + return self; +} + +- (void)setButtonCustomization:(STDSButtonCustomization *)buttonCustomization forType:(STDSUICustomizationButtonType)buttonType { + self.buttonCustomizationDictionary[@(buttonType)] = buttonCustomization; +} + +- (STDSButtonCustomization *)buttonCustomizationForButtonType:(STDSUICustomizationButtonType)buttonType { + return self.buttonCustomizationDictionary[@(buttonType)]; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + STDSUICustomization *copy = [[[self class] allocWithZone:zone] init]; + copy.navigationBarCustomization = [self.navigationBarCustomization copy]; + copy.labelCustomization = [self.labelCustomization copy]; + copy.textFieldCustomization = [self.textFieldCustomization copy]; + NSMutableDictionary *buttonCustomizationDictionary = [NSMutableDictionary new]; + for (NSNumber *buttonCustomization in self.buttonCustomizationDictionary) { + buttonCustomizationDictionary[buttonCustomization] = [self.buttonCustomizationDictionary[buttonCustomization] copy]; + } + copy.buttonCustomizationDictionary = buttonCustomizationDictionary; + copy.footerCustomization = [self.footerCustomization copy]; + copy.selectionCustomization = [self.selectionCustomization copy]; + copy.backgroundColor = self.backgroundColor; + copy.activityIndicatorViewStyle = self.activityIndicatorViewStyle; + copy.blurStyle = self.blurStyle; + copy.preferredStatusBarStyle = self.preferredStatusBarStyle; + return copy; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSWarning.h b/Stripe3DS2/Stripe3DS2/include/STDSWarning.h new file mode 100644 index 00000000..5f09fe70 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSWarning.h @@ -0,0 +1,69 @@ +// +// STDSWarning.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/12/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The `STDSWarningSeverity` enum defines the severity levels of warnings generated + during SDK initialization. @see STDSThreeDS2Service + */ +typedef NS_ENUM(NSInteger, STDSWarningSeverity) { + /** + Low severity + */ + STDSWarningSeverityLow = 0, + + /** + Medium severity + */ + STDSWarningSeverityMedium, + + /** + High severity + */ + STDSWarningSeverityHigh, +}; + +/** + The `STDSWarning` class represents warnings generated by `STDSThreeDS2Service` during + security checks run during initialization. @see STDSThreeDS2Service + */ +@interface STDSWarning : NSObject + +/** + Designated initializer for `STDSWarning`. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + message:(NSString *)message + severity:(STDSWarningSeverity)severity NS_DESIGNATED_INITIALIZER; + +/** + `STDSWarning` should not be directly initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + The identifier for this warning instance. + */ +@property (nonatomic, readonly) NSString *identifier; + +/** + The descriptive message for this warning. + */ +@property (nonatomic, readonly) NSString *message; + +/** + The severity of this warning. + */ +@property (nonatomic, readonly) STDSWarningSeverity severity; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/STDSWarning.m b/Stripe3DS2/Stripe3DS2/include/STDSWarning.m new file mode 100644 index 00000000..ad0d6301 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/STDSWarning.m @@ -0,0 +1,30 @@ +// +// STDSWarning.m +// Stripe3DS2 +// +// Created by Cameron Sabol on 2/12/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSWarning.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STDSWarning + +- (instancetype)initWithIdentifier:(NSString *)identifier + message:(NSString *)message + severity:(STDSWarningSeverity)severity { + self = [super init]; + if (self) { + _identifier = [identifier copy]; + _message = [message copy]; + _severity = severity; + } + + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2/include/Stripe3DS2-Prefix.pch b/Stripe3DS2/Stripe3DS2/include/Stripe3DS2-Prefix.pch new file mode 100644 index 00000000..888096ff --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/Stripe3DS2-Prefix.pch @@ -0,0 +1,14 @@ +// +// Stripe3DS2-Prefix.pch +// Stripe3DS2 +// +// Created by Cameron Sabol on 4/16/20. +// Copyright © 2020 Stripe. All rights reserved. +// + +#ifndef Stripe3DS2_pch +#define Stripe3DS2_pch + +#import "STDSLocalizedString.h" + +#endif /* Stripe3DS2_pch */ diff --git a/Stripe3DS2/Stripe3DS2/include/Stripe3DS2.h b/Stripe3DS2/Stripe3DS2/include/Stripe3DS2.h new file mode 100644 index 00000000..bf2a2ae9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2/include/Stripe3DS2.h @@ -0,0 +1,53 @@ +// +// Stripe3DS2.h +// Stripe3DS2 +// +// Created by Cameron Sabol on 1/16/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +//! Project version number for Stripe3DS2. +FOUNDATION_EXPORT double Stripe3DS2VersionNumber; + +//! Project version string for Stripe3DS2. +FOUNDATION_EXPORT const unsigned char Stripe3DS2VersionString[]; + +#import "STDSConfigParameters.h" +#import "STDSThreeDS2Service.h" +#import "STDSUICustomization.h" +#import "STDSWarning.h" +#import "STDSAnalyticsDelegate.h" + +#import "STDSAlreadyInitializedException.h" +#import "STDSInvalidInputException.h" +#import "STDSNotInitializedException.h" +#import "STDSRuntimeException.h" + +#import "STDSErrorMessage.h" +#import "STDSProtocolErrorEvent.h" +#import "STDSRuntimeErrorEvent.h" +#import "STDSStripe3DS2Error.h" +#import "STDSThreeDSProtocolVersion.h" + +#import "STDSAuthenticationRequestParameters.h" +#import "STDSAuthenticationResponse.h" +#import "STDSChallengeParameters.h" +#import "STDSChallengeStatusReceiver.h" +#import "STDSCompletionEvent.h" +#import "STDSJSONDecodable.h" +#import "STDSJSONEncoder.h" +#import "STDSTransaction.h" + +#import "STDSButtonCustomization.h" +#import "STDSCustomization.h" +#import "STDSException.h" +#import "STDSFooterCustomization.h" +#import "STDSJSONEncodable.h" +#import "STDSLabelCustomization.h" +#import "STDSNavigationBarCustomization.h" +#import "STDSSelectionCustomization.h" +#import "STDSTextFieldCustomization.h" + +#import "STDSSwiftTryCatch.h" diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Info.plist b/Stripe3DS2/Stripe3DS2DemoUI/Info.plist new file mode 100644 index 00000000..80cd6052 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/150-issuer.png b/Stripe3DS2/Stripe3DS2DemoUI/Resources/150-issuer.png new file mode 100644 index 00000000..df12ebda Binary files /dev/null and b/Stripe3DS2/Stripe3DS2DemoUI/Resources/150-issuer.png differ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/150-payment.png b/Stripe3DS2/Stripe3DS2DemoUI/Resources/150-payment.png new file mode 100644 index 00000000..3f2c5dd1 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2DemoUI/Resources/150-payment.png differ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/300-issuer.png b/Stripe3DS2/Stripe3DS2DemoUI/Resources/300-issuer.png new file mode 100644 index 00000000..e9f63b2d Binary files /dev/null and b/Stripe3DS2/Stripe3DS2DemoUI/Resources/300-issuer.png differ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/300-payment.png b/Stripe3DS2/Stripe3DS2DemoUI/Resources/300-payment.png new file mode 100644 index 00000000..a30a47d0 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2DemoUI/Resources/300-payment.png differ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/450-issuer.png b/Stripe3DS2/Stripe3DS2DemoUI/Resources/450-issuer.png new file mode 100644 index 00000000..136ec3eb Binary files /dev/null and b/Stripe3DS2/Stripe3DS2DemoUI/Resources/450-issuer.png differ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/450-payment.png b/Stripe3DS2/Stripe3DS2DemoUI/Resources/450-payment.png new file mode 100644 index 00000000..cec16725 Binary files /dev/null and b/Stripe3DS2/Stripe3DS2DemoUI/Resources/450-payment.png differ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Resources/acs_challenge.html b/Stripe3DS2/Stripe3DS2DemoUI/Resources/acs_challenge.html new file mode 100644 index 00000000..4b576e2b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Resources/acs_challenge.html @@ -0,0 +1,178 @@ + + + + + 3DS - One-Time Passcode - PA + + + + + + +
+ + + + +
+
+

Purchase Authentication

+

We have send you a text message with a code to your registered mobile number ending in ***.

+

You are paying Merchant ABC the amount of $xxx.xx on mm/dd/yy.

+
+ +
+

Enter your code below:

+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + Need some help? +

Help content will be displayed here.

+ +
+
+
+
+ + Learn more about authentication +

Authentication information will be displayed here.

+ +
+
+
+ + +
+ +
+
+ diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/AppDelegate.h b/Stripe3DS2/Stripe3DS2DemoUI/Sources/AppDelegate.h new file mode 100644 index 00000000..0483838d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/AppDelegate.h @@ -0,0 +1,15 @@ +// +// AppDelegate.h +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/AppDelegate.m b/Stripe3DS2/Stripe3DS2DemoUI/Sources/AppDelegate.m new file mode 100644 index 00000000..0cf5fc19 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/AppDelegate.m @@ -0,0 +1,36 @@ +// +// AppDelegate.m +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +@import Stripe3DS2; + +#import "AppDelegate.h" +#import "STDSDemoViewController.h" +#import "STDSImageLoader.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + + STDSImageLoader *imageLoader = [[STDSImageLoader alloc] initWithURLSession:NSURLSession.sharedSession]; + STDSDemoViewController *demoViewController = [[STDSDemoViewController alloc] initWithImageLoader:imageLoader]; + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:demoViewController]; + + self.window.rootViewController = navigationController; + + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSChallengeResponseObject+TestObjects.h b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSChallengeResponseObject+TestObjects.h new file mode 100644 index 00000000..3a1ab053 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSChallengeResponseObject+TestObjects.h @@ -0,0 +1,23 @@ +// +// STDSChallengeResponseObject+TestObjects.h +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 3/7/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeResponseObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSChallengeResponseObject (TestObjects) + ++ (id)textChallengeResponseWithWhitelist:(BOOL)whitelist resendCode:(BOOL)resendCode; ++ (id)singleSelectChallengeResponse; ++ (id)multiSelectChallengeResponse; ++ (id)OOBChallengeResponse; ++ (id)HTMLChallengeResponse; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSChallengeResponseObject+TestObjects.m b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSChallengeResponseObject+TestObjects.m new file mode 100644 index 00000000..4e923545 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSChallengeResponseObject+TestObjects.m @@ -0,0 +1,198 @@ +// +// STDSChallengeResponseObject+TestObjects.m +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 3/7/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSChallengeResponseObject+TestObjects.h" +#import "STDSChallengeResponseSelectionInfoObject.h" +#import "STDSChallengeResponseImageObject.h" + +@implementation STDSChallengeResponseObject (TestObjects) + ++ (id)textChallengeResponseWithWhitelist:(BOOL)whitelist resendCode:(BOOL)resendCode { + return [[STDSChallengeResponseObject alloc] initWithThreeDSServerTransactionID:@"" + acsCounterACStoSDK:@"" + acsTransactionID:@"" + acsHTML:nil + acsHTMLRefresh:nil + acsUIType:STDSACSUITypeText + challengeCompletionIndicator:NO + challengeInfoHeader:@"Verify by phone" + challengeInfoLabel:@"Enter your 6 digit code:" + challengeInfoText:@"Great! We have sent you a text message with secure code to your registered mobile phone number.\n\nSent to a number ending in •••• •••• 4729." + challengeAdditionalInfoText:nil + showChallengeInfoTextIndicator:NO + challengeSelectInfo:nil + expandInfoLabel:@"Expand Info Label" + expandInfoText:@"This field displays expandable information text provided by the ACS." + issuerImage:[self issuerImage] + messageExtensions:nil + messageVersion:@"" + oobAppURL:nil + oobAppLabel:nil + oobContinueLabel:nil + paymentSystemImage:[self paymentImage] + resendInformationLabel:resendCode ? @"Resend code" : nil + sdkTransactionID:@"" + submitAuthenticationLabel:@"Submit" + whitelistingInfoText:whitelist ? @"Would you like to add this Merchant to your whitelist?" : nil + whyInfoLabel:@"Learn more about authentication" + whyInfoText:@"This is additional information about authentication. You are being provided extra information you wouldn't normally see, because you've tapped on the above label." + transactionStatus:nil]; +} + ++ (id)singleSelectChallengeResponse { + STDSChallengeResponseSelectionInfoObject *infoObject1 = [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Mobile" value:@"***-***-*321"]; + STDSChallengeResponseSelectionInfoObject *infoObject2 = [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Email" value:@"a******3@g****.com"]; + + return [[STDSChallengeResponseObject alloc] initWithThreeDSServerTransactionID:@"" + acsCounterACStoSDK:@"" + acsTransactionID:@"" + acsHTML:nil + acsHTMLRefresh:nil + acsUIType:STDSACSUITypeSingleSelect + challengeCompletionIndicator:NO + challengeInfoHeader:@"Payment Security" + challengeInfoLabel:nil + challengeInfoText:@"Hi Steve, your online payment is being secured using Card Network. Please select the location you would like to receive the code from YourBank." + challengeAdditionalInfoText:nil + showChallengeInfoTextIndicator:NO + challengeSelectInfo:@[infoObject1, infoObject2] + expandInfoLabel:@"Need some help?" + expandInfoText:@"You've indicated that you need help! We'd be happy to assist with that, by providing helpful text here that makes sense in context." + issuerImage:nil + messageExtensions:nil + messageVersion:@"" + oobAppURL:nil + oobAppLabel:nil + oobContinueLabel:nil + paymentSystemImage:nil + resendInformationLabel:nil + sdkTransactionID:@"" + submitAuthenticationLabel:@"Next" + whitelistingInfoText:nil + whyInfoLabel:@"Learn more about authentication" + whyInfoText:@"This is additional information about authentication. You are being provided extra information you wouldn't normally see, because you've tapped on the above label." + transactionStatus:nil]; +} + ++ (id)multiSelectChallengeResponse { + STDSChallengeResponseSelectionInfoObject *infoObject1 = [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Option1" value:@"Chicago, Illinois"]; + STDSChallengeResponseSelectionInfoObject *infoObject2 = [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Option2" value:@"Portland, Oregon"]; + STDSChallengeResponseSelectionInfoObject *infoObject3 = [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Option3" value:@"Dallas, Texas"]; + STDSChallengeResponseSelectionInfoObject *infoObject4 = [[STDSChallengeResponseSelectionInfoObject alloc] initWithName:@"Option4" value:@"St Louis, Missouri"]; + + return [[STDSChallengeResponseObject alloc] initWithThreeDSServerTransactionID:@"" + acsCounterACStoSDK:@"" + acsTransactionID:@"" + acsHTML:nil + acsHTMLRefresh:nil + acsUIType:STDSACSUITypeMultiSelect + challengeCompletionIndicator:NO + challengeInfoHeader:@"Payment Security" + challengeInfoLabel:@"Question 2: What cities have you lived in?" + challengeInfoText:@"Please answer 3 security questions from YourBank to complete your payment.\n\nSelect all that apply." + challengeAdditionalInfoText:nil + showChallengeInfoTextIndicator:NO + challengeSelectInfo:@[infoObject1, infoObject2, infoObject3, infoObject4] + expandInfoLabel:nil + expandInfoText:nil + issuerImage:nil + messageExtensions:nil + messageVersion:@"" + oobAppURL:nil + oobAppLabel:nil + oobContinueLabel:nil + paymentSystemImage:nil + resendInformationLabel:nil + sdkTransactionID:@"" + submitAuthenticationLabel:@"Next" + whitelistingInfoText:nil + whyInfoLabel:@"Learn more about authentication" + whyInfoText:@"This is additional information about authentication. You are being provided extra information you wouldn't normally see, because you've tapped on the above label." + transactionStatus:nil]; +} + ++ (id)OOBChallengeResponse { + return [[STDSChallengeResponseObject alloc] initWithThreeDSServerTransactionID:@"" + acsCounterACStoSDK:@"" + acsTransactionID:@"" + acsHTML:nil + acsHTMLRefresh:nil + acsUIType:STDSACSUITypeOOB + challengeCompletionIndicator:NO + challengeInfoHeader:@"Payment Security" + challengeInfoLabel:nil + challengeInfoText:@"For added security, you will be authenticated with YourBank application.\n\nStep 1 - Open your YourBank application directly from your phone and verify this payment.\n\nStep 2 - Tap continue after you have completed authentication with your YourBank application." + challengeAdditionalInfoText:nil + showChallengeInfoTextIndicator:YES + challengeSelectInfo:nil + expandInfoLabel:@"Need some help?" + expandInfoText:@"You've indicated that you need help! We'd be happy to assist with that, by providing helpful text here that makes sense in context." + issuerImage:[self issuerImage] + messageExtensions:nil + messageVersion:@"" + oobAppURL:nil + oobAppLabel:nil + oobContinueLabel:@"Continue" + paymentSystemImage:[self paymentImage] + resendInformationLabel:nil + sdkTransactionID:@"" + submitAuthenticationLabel:nil + whitelistingInfoText:nil + whyInfoLabel:@"Learn more about authentication" + whyInfoText:@"This is additional information about authentication. You are being provided extra information you wouldn't normally see, because you've tapped on the above label." + transactionStatus:nil]; +} + ++ (id)HTMLChallengeResponse { + NSString *htmlFilePath = [[NSBundle mainBundle] pathForResource:@"acs_challenge" ofType:@"html"]; + NSString *html = [NSString stringWithContentsOfFile:htmlFilePath encoding:NSUTF8StringEncoding error:nil]; + return [[STDSChallengeResponseObject alloc] initWithThreeDSServerTransactionID:@"" + acsCounterACStoSDK:@"" + acsTransactionID:@"" + acsHTML:html + acsHTMLRefresh:nil + acsUIType:STDSACSUITypeHTML + challengeCompletionIndicator:NO + challengeInfoHeader:nil + challengeInfoLabel:nil + challengeInfoText:nil + challengeAdditionalInfoText:nil + showChallengeInfoTextIndicator:NO + challengeSelectInfo:nil + expandInfoLabel:nil + expandInfoText:nil + issuerImage:nil + messageExtensions:nil + messageVersion:@"" + oobAppURL:nil + oobAppLabel:nil + oobContinueLabel:nil + paymentSystemImage:nil + resendInformationLabel:nil + sdkTransactionID:@"" + submitAuthenticationLabel:nil + whitelistingInfoText:nil + whyInfoLabel:nil + whyInfoText:nil + transactionStatus:nil]; +} + ++ (id)issuerImage { + return [[STDSChallengeResponseImageObject alloc] initWithMediumDensityURL: + [[NSBundle mainBundle] URLForResource:@"150-issuer" withExtension:@"png"] + highDensityURL:[[NSBundle mainBundle] URLForResource:@"300-issuer" withExtension:@"png"] + extraHighDensityURL:[[NSBundle mainBundle] URLForResource:@"450-issuer" withExtension:@"png"]]; +} + ++ (id)paymentImage { + return [[STDSChallengeResponseImageObject alloc] initWithMediumDensityURL:[[NSBundle mainBundle] URLForResource:@"150-payment" withExtension:@"png"] + highDensityURL:[[NSBundle mainBundle] URLForResource:@"300-payment" withExtension:@"png"] + extraHighDensityURL:[[NSBundle mainBundle] URLForResource:@"450-payment" withExtension:@"png"]]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSDemoViewController.h b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSDemoViewController.h new file mode 100644 index 00000000..db53f304 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSDemoViewController.h @@ -0,0 +1,20 @@ +// +// STDSDemoViewController.h +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 3/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSImageLoader.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDemoViewController: UIViewController + +- (instancetype)initWithImageLoader:(STDSImageLoader *)imageLoader; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSDemoViewController.m b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSDemoViewController.m new file mode 100644 index 00000000..8a19f781 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/STDSDemoViewController.m @@ -0,0 +1,235 @@ +// +// STDSDemoViewController.m +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 3/11/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSDemoViewController.h" +#import "STDSChallengeResponseViewController.h" +#import "STDSChallengeResponseObject+TestObjects.h" +#import "STDSProgressViewController.h" +#import "STDSStackView.h" +#import "UIView+LayoutSupport.h" +#import "UIColor+ThirteenSupport.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSDemoViewController () + +@property (nonatomic, strong) STDSImageLoader *imageLoader; +@property (nonatomic) BOOL shouldLoadSlowly; +@property (nonatomic) STDSUICustomization *customization; +@property (nonatomic) BOOL isDarkMode; + +@end + +@implementation STDSDemoViewController + +- (instancetype)initWithImageLoader:(STDSImageLoader *)imageLoader { + self = [super initWithNibName:nil bundle:nil]; + + if (self) { + _imageLoader = imageLoader; + _customization = [STDSUICustomization defaultSettings]; + + UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init]; + [appearance configureWithOpaqueBackground]; + _customization.navigationBarCustomization.scrollEdgeAppearance = appearance; + } + + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor _stds_systemBackgroundColor]; + + STDSStackView *containerView = [[STDSStackView alloc] initWithAlignment:STDSStackViewLayoutAxisVertical]; + [self.view addSubview:containerView]; + [containerView _stds_pinToSuperviewBounds]; + + NSDictionary *buttonTitleToSelectorMapping = @{ + @"Toggle Dark Mode": NSStringFromSelector(@selector(toggleDarkMode)), + @"Present Text Challenge": NSStringFromSelector(@selector(presentTextChallenge)), + @"Present Text Challenge With Whitelist": NSStringFromSelector(@selector(presentTextChallengeWithWhitelist)), + @"Present Text Challenge With Resend": NSStringFromSelector(@selector(presentTextChallengeWithResendCode)), + @"Present Text Challenge With Whitelist and Resend": NSStringFromSelector(@selector(presentTextChallengeWithResendCodeAndWhitelist)), + @"Present Text Challenge (loads slowly w/ initial progressView)": NSStringFromSelector(@selector(presentTextChallengeLoadsSlowly)), + @"Present Single Select Challenge": NSStringFromSelector(@selector(presentSingleSelectChallenge)), + @"Present Multi Select Challenge": NSStringFromSelector(@selector(presentMultiSelectChallenge)), + @"Present OOB Challenge": NSStringFromSelector(@selector(presentOOBChallenge)), + @"Present HTML Challenge": NSStringFromSelector(@selector(presentHTMLChallenge)), + @"Present Progress View": NSStringFromSelector(@selector(presentProgressView)), + }; + for (NSString *key in [buttonTitleToSelectorMapping keysSortedByValueUsingComparator:^(NSString *a, NSString *b) { + return [a compare:b]; + }]) { + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + [button addTarget:self action:NSSelectorFromString(buttonTitleToSelectorMapping[key]) forControlEvents:UIControlEventTouchUpInside]; + button.titleLabel.numberOfLines = 0; + button.titleLabel.textAlignment = NSTextAlignmentCenter; + [button setTitle:key forState:UIControlStateNormal]; + [containerView addArrangedSubview:button]; + } + [containerView addArrangedSubview:[UIView new]]; +} + +- (void)toggleDarkMode { + if (self.isDarkMode) { + self.customization = [STDSUICustomization defaultSettings]; + self.isDarkMode = false; + } else { + self.customization = [STDSUICustomization defaultSettings]; + // Navigation bar + self.customization.navigationBarCustomization = [STDSNavigationBarCustomization new]; + self.customization.navigationBarCustomization.headerText = @"Authentication"; + self.customization.navigationBarCustomization.buttonText = @"Nope"; + self.customization.navigationBarCustomization.textColor = UIColor.whiteColor; + self.customization.navigationBarCustomization.barStyle = UIBarStyleBlack; + + // General + self.customization.backgroundColor = UIColor.blackColor; + self.customization.activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; + self.customization.preferredStatusBarStyle = UIStatusBarStyleLightContent; + self.customization.footerCustomization = [STDSFooterCustomization new]; + self.customization.footerCustomization.backgroundColor = [UIColor colorWithRed:.08 green:.08 blue:.08 alpha:1]; + self.customization.footerCustomization.headingFont = [UIFont boldSystemFontOfSize:15]; + self.customization.footerCustomization.headingTextColor = [UIColor colorWithRed:.95 green:.95 blue:.95 alpha:1]; + self.customization.footerCustomization.textColor = [UIColor colorWithRed:.90 green:.90 blue:.90 alpha:1]; + + // Cancel button + STDSButtonCustomization *cancelButtonCustomization = [STDSButtonCustomization defaultSettingsForButtonType:STDSUICustomizationButtonTypeCancel]; + cancelButtonCustomization.textColor = UIColor.grayColor; + cancelButtonCustomization.titleStyle = STDSButtonTitleStyleUppercase; + [self.customization setButtonCustomization:cancelButtonCustomization forType:STDSUICustomizationButtonTypeCancel]; + + // Text + self.customization.labelCustomization.headingTextColor = UIColor.whiteColor; + self.customization.labelCustomization.textColor = UIColor.whiteColor; + + // Text field + self.customization.textFieldCustomization.keyboardAppearance = UIKeyboardAppearanceDark; + self.customization.textFieldCustomization.textColor = UIColor.whiteColor; + self.customization.textFieldCustomization.borderColor = UIColor.whiteColor; + + // Radio/Checkbox + self.customization.selectionCustomization.secondarySelectedColor = UIColor.lightGrayColor; + self.customization.selectionCustomization.unselectedBorderColor = UIColor.blackColor; + self.customization.selectionCustomization.unselectedBackgroundColor = UIColor.darkGrayColor; + + self.isDarkMode = true; + } +} + +- (void)presentTextChallenge { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:NO resendCode:NO]]; +} + +- (void)presentTextChallengeWithWhitelist { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:YES resendCode:NO]]; +} + +- (void)presentTextChallengeWithResendCode { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:NO resendCode:YES]]; +} + +- (void)presentTextChallengeWithResendCodeAndWhitelist { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:YES resendCode:YES]]; +} + +- (void)presentTextChallengeLoadsSlowly { + self.shouldLoadSlowly = YES; + STDSProgressViewController *progressVC = [[STDSProgressViewController alloc] initWithDirectoryServer:STDSDirectoryServerULTestEC uiCustomization:self.customization analyticsDelegate:nil didCancel:^{}]; + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:progressVC]; + [self.navigationController presentViewController:navigationController animated:YES completion:nil]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:NO resendCode:NO]]; + }); + +} + +- (void)presentSingleSelectChallenge { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject singleSelectChallengeResponse]]; +} + +- (void)presentMultiSelectChallenge { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject multiSelectChallengeResponse]]; +} + +- (void)presentOOBChallenge { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject OOBChallengeResponse]]; +} + +- (void)presentHTMLChallenge { + [self presentChallengeForChallengeResponse:[STDSChallengeResponseObject HTMLChallengeResponse]]; +} + +- (void)presentProgressView { + __weak typeof(self) weakSelf = self; + UIViewController *vc = [[STDSProgressViewController alloc] initWithDirectoryServer:STDSDirectoryServerULTestEC + uiCustomization:self.customization + analyticsDelegate:nil + didCancel:^{ + [weakSelf dismissViewControllerAnimated:YES completion:nil]; + }]; + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:vc]; + [self.navigationController presentViewController:navigationController animated:YES completion:nil]; +} + +- (void)presentChallengeForChallengeResponse:(id)challengeResponse { + STDSChallengeResponseViewController *challengeResponseViewController = [[STDSChallengeResponseViewController alloc] initWithUICustomization:self.customization + imageLoader:self.imageLoader + directoryServer:STDSDirectoryServerULTestEC + analyticsDelegate:nil]; + challengeResponseViewController.delegate = self; + + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:challengeResponseViewController]; + [self.navigationController presentViewController:navigationController animated:YES completion:nil]; + // Simulate what `STDSTransaction` does + [challengeResponseViewController setLoading]; + NSUInteger delay = self.shouldLoadSlowly ? 5 : 0; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [challengeResponseViewController setChallengeResponse:challengeResponse animated:YES]; + }); +} + +#pragma mark - STDSChallengeResponseViewControllerDelegate + +- (void)challengeResponseViewController:(nonnull STDSChallengeResponseViewController *)viewController didSubmitHTMLForm:(nonnull NSString *)form { + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)challengeResponseViewController:(nonnull STDSChallengeResponseViewController *)viewController didSubmitInput:(nonnull NSString *)userInput whitelistSelection:(nonnull id)whitelistSelection { + [viewController setLoading]; + NSUInteger delay = self.shouldLoadSlowly ? 5 : 0; + self.shouldLoadSlowly = NO; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [viewController setChallengeResponse:[STDSChallengeResponseObject OOBChallengeResponse] animated:YES]; + }); +} + +- (void)challengeResponseViewController:(nonnull STDSChallengeResponseViewController *)viewController didSubmitSelection:(nonnull NSArray> *)selection whitelistSelection:(nonnull id)whitelistSelection { + [viewController setLoading]; + [viewController setChallengeResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:YES resendCode:YES] animated:YES]; +} + +- (void)challengeResponseViewControllerDidCancel:(nonnull STDSChallengeResponseViewController *)viewController { + self.shouldLoadSlowly = NO; + [self.navigationController dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)challengeResponseViewControllerDidOOBContinue:(nonnull STDSChallengeResponseViewController *)viewController whitelistSelection:(nonnull id)whitelistSelection { + [viewController setLoading]; + [viewController setChallengeResponse:[STDSChallengeResponseObject singleSelectChallengeResponse] animated:YES]; +} + +- (void)challengeResponseViewControllerDidRequestResend:(nonnull STDSChallengeResponseViewController *)viewController { +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2DemoUI/Sources/main.m b/Stripe3DS2/Stripe3DS2DemoUI/Sources/main.m new file mode 100644 index 00000000..ca445d07 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUI/Sources/main.m @@ -0,0 +1,16 @@ +// +// main.m +// Stripe3DS2DemoUI +// +// Created by Andrew Harrison on 2/27/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Stripe3DS2/Stripe3DS2DemoUITests/Info.plist b/Stripe3DS2/Stripe3DS2DemoUITests/Info.plist new file mode 100644 index 00000000..afb93bf7 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUITests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UILaunchStoryboardName + LaunchScreen + + diff --git a/Stripe3DS2/Stripe3DS2DemoUITests/STDSChallengeResponseViewControllerSnapshotTests.m b/Stripe3DS2/Stripe3DS2DemoUITests/STDSChallengeResponseViewControllerSnapshotTests.m new file mode 100644 index 00000000..e5b5691f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2DemoUITests/STDSChallengeResponseViewControllerSnapshotTests.m @@ -0,0 +1,117 @@ +// +// STDSChallengeResponseViewControllerSnapshotTests.m +// Stripe3DS2DemoUITests +// +// Created by Andrew Harrison on 3/28/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +@import iOSSnapshotTestCaseCore; + +#import + +#import "STDSChallengeResponseViewController.h" +#import "STDSChallengeResponseObject+TestObjects.h" + +/** + Calls FBSnapshotVerifyView with a default 2% per-pixel color differentiation, as M1 and Intel machines render shadows differently. + @param view The view to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + */ +#define STPSnapshotVerifyView(view__, identifier__) \ +FBSnapshotVerifyViewWithPixelOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0.02, 0) + +@interface STDSChallengeResponseViewControllerSnapshotTests: FBSnapshotTestCase + +@end + +@implementation STDSChallengeResponseViewControllerSnapshotTests + +- (void)setUp { + [super setUp]; + + /// Recorded on an iPhone 12 Mini running iOS 15.4 +// self.recordMode = YES; +} + +- (void)testVerifyTextChallengeDesign { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:[STDSChallengeResponseObject textChallengeResponseWithWhitelist:NO resendCode:NO] directoryServer:STDSDirectoryServerCustom]; + [challengeResponseViewController view]; + + [self waitForChallengeResponseTimer]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"TextChallengeResponse"); +} + +- (void)testVerifySingleSelectDesign { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:[STDSChallengeResponseObject singleSelectChallengeResponse] directoryServer:STDSDirectoryServerCustom]; + [challengeResponseViewController view]; + + [self waitForChallengeResponseTimer]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"SingleSelectResponse"); +} + +- (void)testVerifyMultiSelectDesign { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:[STDSChallengeResponseObject multiSelectChallengeResponse] directoryServer:STDSDirectoryServerCustom]; + [challengeResponseViewController view]; + + [self waitForChallengeResponseTimer]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"MultiSelectResponse"); +} + +- (void)testVerifyOOBDesign { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:[STDSChallengeResponseObject OOBChallengeResponse] directoryServer:STDSDirectoryServerCustom]; + [challengeResponseViewController view]; + + [self waitForChallengeResponseTimer]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"OOBResponse"); +} + +- (void)testLoadingAmex { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:nil directoryServer:STDSDirectoryServerAmex]; + [challengeResponseViewController view]; + [challengeResponseViewController setLoading]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"LoadingAmex"); +} + +- (void)testLoadingDiscover { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:nil directoryServer:STDSDirectoryServerDiscover]; + [challengeResponseViewController view]; + [challengeResponseViewController setLoading]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"LoadingDiscover"); +} + +- (void)testLoadingMastercard { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:nil directoryServer:STDSDirectoryServerMastercard]; + [challengeResponseViewController view]; + [challengeResponseViewController setLoading]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"LoadingMastercard"); +} + +- (void)testLoadingVisa { + STDSChallengeResponseViewController *challengeResponseViewController = [self challengeResponseViewControllerForResponse:nil directoryServer:STDSDirectoryServerVisa]; + [challengeResponseViewController view]; + [challengeResponseViewController setLoading]; + + STPSnapshotVerifyView(challengeResponseViewController.view, @"LoadingVisa"); +} + +- (STDSChallengeResponseViewController *)challengeResponseViewControllerForResponse:(id)response directoryServer:(STDSDirectoryServer)directoryServer { + STDSImageLoader *imageLoader = [[STDSImageLoader alloc] initWithURLSession:NSURLSession.sharedSession]; + + STDSChallengeResponseViewController *vc = [[STDSChallengeResponseViewController alloc] initWithUICustomization:[STDSUICustomization defaultSettings] imageLoader:imageLoader directoryServer:directoryServer analyticsDelegate:nil]; + [vc setChallengeResponse:response animated:NO]; + return vc; +} + +- (void)waitForChallengeResponseTimer { + (void)[XCTWaiter waitForExpectations:@[[self expectationWithDescription:@""]] timeout:2.5]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Resources/Info.plist b/Stripe3DS2/Stripe3DS2Resources/Info.plist new file mode 100644 index 00000000..7607d027 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Resources/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSHumanReadableCopyright + Copyright © 2020 Stripe. All rights reserved. + NSPrincipalClass + + + diff --git a/Stripe3DS2/Stripe3DS2Tests/Info.plist b/Stripe3DS2/Stripe3DS2Tests/Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Stripe3DS2/Stripe3DS2Tests/JSON/ARes.json b/Stripe3DS2/Stripe3DS2Tests/JSON/ARes.json new file mode 100644 index 00000000..8bcadb57 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/JSON/ARes.json @@ -0,0 +1,16 @@ +{ + "dsTransID": "4e4750e7-6ab5-45a4-accf-9c668ed3b5a7", + "acsTransID": "fa695a82-a48c-455d-9566-a652058dda27", + "p_messageVersion": "1.0.5", + "acsOperatorID": "acsOperatorUL", + "sdkTransID": "D77EB83F-F317-4E29-9852-EBAAB55515B7", + "eci": "00", + "dsReferenceNumber": "3DS_LOA_DIS_PPFU_020100_00010", + "acsReferenceNumber": "3DS_LOA_ACS_PPFU_020100_00009", + "threeDSServerTransID": "fc7a39de-dc41-4b65-ba76-a322769b2efc", + "messageVersion": "2.2.0", + "authenticationValue": "AABBCCDDEEFFAABBCCDDEEFFAAA=", + "messageType": "pArs", + "transStatus": "C", + "acsChallengeMandated": "NO" +} diff --git a/Stripe3DS2/Stripe3DS2Tests/JSON/CRes.json b/Stripe3DS2/Stripe3DS2Tests/JSON/CRes.json new file mode 100644 index 00000000..990444d8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/JSON/CRes.json @@ -0,0 +1,31 @@ +{ + "threeDSServerTransID": "8a880dc0-d2d2-4067-bcb1-b08d1690b26e", + "acsTransID": "d7c1ee99-9478-44a6-b1f2-391e29c6b340", + "acsUiType": "01", + "challengeAddInfo": "Additional information to be shown.", + "challengeCompletionInd": "N", + "challengeInfoHeader": "Header information", + "challengeInfoLabel": "One-time-password", + "challengeInfoText": "Please enter the received one-time-password", + "challengeInfoTextIndicator": "N", + "expandInfoLabel": "Additional instructions", + "expandInfoText": "The issuer will send you via SMS a one-time password. Please enter the value in the designated input field above and press continue to complete the 3-D Secure authentication process.", + "issuerImage": { + "medium": "https://acs.com/medium_image.svg", + "high": "https://acs.com/high_image.svg", + "extraHigh": "https://acs.com/extraHigh_image.svg" + }, + "messageType": "CRes", + "messageVersion": "2.2.0", + "psImage": { + "medium": "https://ds.com/medium_image.svg", + "high": "https://ds.com/high_image.svg", + "extraHigh": "https://ds.com/extraHigh_image.svg" + }, + "resendInformationLabel": "Send new One-time-password", + "sdkTransID": "b2385523-a66c-4907-ac3c-91848e8c0067", + "submitAuthenticationLabel": "Continue", + "whyInfoLabel": "Why using 3-D Secure?", + "whyInfoText": "Some explanation about why using 3-D Secure is an excellent idea as part of an online payment transaction", + "acsCounterAtoS": "001" +} diff --git a/Stripe3DS2/Stripe3DS2Tests/JSON/ErrorMessage.json b/Stripe3DS2/Stripe3DS2Tests/JSON/ErrorMessage.json new file mode 100644 index 00000000..e978a69f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/JSON/ErrorMessage.json @@ -0,0 +1,13 @@ +{ + "threeDSServerTransID": "6afa6072-9412-446b-9673-2f98b3ee98a2", + "acsTransID": "375d90ad-3873-498b-9133-380cbbc8d99d", + "dsTransID": "0b470d4f-fdf8-429f-9147-505b1a589883", + "errorCode": "203", + "errorComponent": "A", + "errorDescription": "Data element not in the required format. Not numeric or wrong length.", + "errorDetail": "billAddrCountry,billAddrPostCode,dsURL", + "errorMessageType": "AReq", + "messageType": "Erro", + "messageVersion": "2.2.0", + "sdkTransID": "b2385523-a66c-4907-ac3c-91848e8c0067" +} diff --git a/Stripe3DS2/Stripe3DS2Tests/NSDictionary+DecodingHelpersTest.m b/Stripe3DS2/Stripe3DS2Tests/NSDictionary+DecodingHelpersTest.m new file mode 100644 index 00000000..846c5268 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/NSDictionary+DecodingHelpersTest.m @@ -0,0 +1,312 @@ +// +// NSDictionary+DecodingHelpersTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/28/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "NSDictionary+DecodingHelpers.h" +#import "STDSStripe3DS2Error.h" +#import "NSError+Stripe3DS2.h" + +@interface JSONDecodableTestObject : NSObject +@property (nonatomic, copy) NSString *value; +@end + +@implementation JSONDecodableTestObject + ++ (instancetype)decodedObjectFromJSON:(NSDictionary *)json error:(NSError * _Nullable __autoreleasing *)outError { + NSString *value = [json _stds_stringForKey:@"key" required:YES error:outError]; + if (outError && *outError) { + return nil; + } + JSONDecodableTestObject *obj = [[self alloc] init]; + obj.value = value; + return obj; +} + +@end + +@interface NSDictionary_DecodingHelpersTest : XCTestCase + +@end + +@implementation NSDictionary_DecodingHelpersTest + +- (void)testMissingRequiredKey { + // Every getter should fail the same way if the key is not present + NSDictionary *json = @{}; + NSError *expectedError = [NSError _stds_missingJSONFieldError:@"key"]; + NSError *error; + id value; + + value = [json _stds_stringForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_stringForKey:@"key" validator:^BOOL (NSString *value) { + return NO; + } required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_boolForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_arrayForKey:@"key" arrayElementType:[JSONDecodableTestObject class] required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_urlForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_dictionaryForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); +} + +- (void)testInvalidType { + // Every getter should fail the same way if the value is not the expected type + NSDictionary *json = @{@"key": [NSObject new]}; + NSError *expectedError = [NSError _stds_invalidJSONFieldError:@"key"]; + NSError *error; + id value; + + value = [json _stds_stringForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_stringForKey:@"key" validator:^BOOL (NSString *value) { + return NO; + } required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_boolForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_arrayForKey:@"key" arrayElementType:[JSONDecodableTestObject class] required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_urlForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + value = [json _stds_dictionaryForKey:@"key" required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); +} + +#pragma mark NSString + +- (void)testString { + NSDictionary *json = [self _basicJSONDictionary]; + NSError *error; + NSString *value = [json _stds_stringForKey:@"key" required:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(value); + XCTAssertEqualObjects(value, @"value"); +} + +- (void)testEmptyString { + NSDictionary *json = @{@"key": @""}; + NSError *expectedError = [NSError _stds_missingJSONFieldError:@"key"]; + NSError *error; + id value; + + // Required empty string should produce a missing error + value = [json _stds_stringForKey:@"key" required:YES error:&error]; + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); + + // Not required empty string should produce an invalid error + expectedError = [NSError _stds_invalidJSONFieldError:@"key"]; + value = [json _stds_stringForKey:@"key" required:NO error:&error]; + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); +} + +- (void)testInvalidValueString { + NSDictionary *json = [self _basicJSONDictionary]; + NSError *expectedError = [NSError _stds_invalidJSONFieldError:@"key"]; + NSError *error; + NSString *value = [json _stds_stringForKey:@"key" validator:^BOOL (NSString *value) { + return NO; + } required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); +} + +#pragma mark NSArray + +- (void)testArray { + NSDictionary *json = @{ + @"key": @[@{@"key": @"value"}] + }; + NSError *error; + NSArray *array = [json _stds_arrayForKey:@"key" arrayElementType:[JSONDecodableTestObject class] required:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(array); + if (array.count == 1) { + XCTAssertEqualObjects([array[0] class], [JSONDecodableTestObject class]); + XCTAssertEqualObjects(array[0].value, @"value"); + } else { + XCTFail(@"Array was not populated"); + } +} + +- (void)testInvalidElementTypeArray { + NSDictionary *json = @{ + @"key": @[@"value1", @"value2"] + }; + NSError *expectedError = [NSError _stds_invalidJSONFieldError:@"key"]; + NSError *error; + NSArray *value = [json _stds_arrayForKey:@"key" arrayElementType:[JSONDecodableTestObject class] required:YES error:&error]; + XCTAssertNotNil(error); + if (error) { + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } else { + XCTFail(@"Error should have a value"); + } + XCTAssertNil(value); +} + +#pragma mark NSDictionary + +- (void)testDictionary { + NSDictionary *nestedJSON = [self _basicJSONDictionary]; + NSDictionary *json = @{ + @"key": nestedJSON + }; + NSError *error; + NSDictionary *value = [json _stds_dictionaryForKey:@"key" required:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(value); + XCTAssertEqualObjects(value, nestedJSON); +} + +#pragma mark NSURL + +- (void)testURL { + NSDictionary *json = @{@"key": @"www.stripe.com"}; + NSError *error; + NSURL *value = [json _stds_urlForKey:@"key" required:YES error:&error]; + XCTAssertNil(error); + XCTAssertNotNil(value); + XCTAssertEqualObjects(value, [NSURL URLWithString:@"www.stripe.com"]); +} + +#pragma mark BOOL + +- (void)testBOOL { + NSDictionary *json = @{@"key": @(YES)}; + NSError *error; + BOOL value = [json _stds_boolForKey:@"key" required:YES error:&error]; + XCTAssertNil(error); + XCTAssertEqual(value, YES); +} + +#pragma mark Helpers + +- (NSDictionary *)_basicJSONDictionary { + return @{@"key": @"value"}; +} + + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/NSString+EmptyCheckingTests.m b/Stripe3DS2/Stripe3DS2Tests/NSString+EmptyCheckingTests.m new file mode 100644 index 00000000..49024547 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/NSString+EmptyCheckingTests.m @@ -0,0 +1,32 @@ +// +// NSString+EmptyCheckingTests.m +// Stripe3DS2Tests +// +// Created by Andrew Harrison on 3/4/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "NSString+EmptyChecking.h" + +@interface NSString_EmptyCheckingTests : XCTestCase + +@end + +@implementation NSString_EmptyCheckingTests + +- (void)testStringIsEmpty { + XCTAssertTrue([NSString _stds_isStringEmpty:@""]); + XCTAssertTrue([NSString _stds_isStringEmpty:@" "]); + XCTAssertTrue([NSString _stds_isStringEmpty:@"\n"]); + XCTAssertTrue([NSString _stds_isStringEmpty:@"\t"]); +} + +- (void)testStringIsNotEmpty { + XCTAssertFalse([NSString _stds_isStringEmpty:@"Hello"]); + XCTAssertFalse([NSString _stds_isStringEmpty:@","]); + XCTAssertFalse([NSString _stds_isStringEmpty:@"\\n"]); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSACSNetworkingManagerTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSACSNetworkingManagerTest.m new file mode 100644 index 00000000..33fca13f --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSACSNetworkingManagerTest.m @@ -0,0 +1,54 @@ +// +// STDSACSNetworkingManagerTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 4/18/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSStripe3DS2Error.h" +#import "STDSACSNetworkingManager.h" +#import "STDSTestJSONUtils.h" +#import "STDSErrorMessage.h" +#import "STDSChallengeResponseObject.h" + +@interface STDSACSNetworkingManager (Private) +- (nullable id)decodeJSON:(NSDictionary *)dict error:(NSError * _Nullable *)outError; +@end + +@interface STDSACSNetworkingManagerTest : XCTestCase + +@end + +@implementation STDSACSNetworkingManagerTest + +- (void)testDecodeJSON { + STDSACSNetworkingManager *manager = [[STDSACSNetworkingManager alloc] init]; + NSError *error; + id decoded; + + // Unknown message type + NSDictionary *unknownMessageDict = @{@"messageType": @"foo"}; + decoded = [manager decodeJSON:unknownMessageDict error:&error]; + XCTAssertEqual(error.code, STDSErrorCodeUnknownMessageType); + XCTAssertNil(decoded); + error = nil; + + // Error Message type + NSDictionary *errorMessageDict = [STDSTestJSONUtils jsonNamed:@"ErrorMessage"]; + decoded = [manager decodeJSON:errorMessageDict error:&error]; + XCTAssertEqual(error.code, STDSErrorCodeReceivedErrorMessage); + XCTAssertTrue([error.userInfo[STDSStripe3DS2ErrorMessageErrorKey] isKindOfClass:[STDSErrorMessage class]]); + XCTAssertNil(decoded); + error = nil; + + // ChallengeResponse message type + NSDictionary *challengeResponseDict = [STDSTestJSONUtils jsonNamed:@"CRes"]; + decoded = [manager decodeJSON:challengeResponseDict error:&error]; + XCTAssertNil(error); + XCTAssertTrue([decoded isKindOfClass:[STDSChallengeResponseObject class]]); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSAuthenticationRequestParametersTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSAuthenticationRequestParametersTest.m new file mode 100644 index 00000000..7e701da8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSAuthenticationRequestParametersTest.m @@ -0,0 +1,41 @@ +// +// STDSAuthenticationRequestParametersTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSAuthenticationRequestParameters.h" +#import "STDSJSONEncoder.h" + +@interface STDSAuthenticationRequestParametersTest : XCTestCase + +@end + +@implementation STDSAuthenticationRequestParametersTest + +#pragma mark - STDSJSONEncodable + +- (void)testPropertyNamesToJSONKeysMapping { + STDSAuthenticationRequestParameters *params = [STDSAuthenticationRequestParameters new]; + + NSDictionary *mapping = [STDSAuthenticationRequestParameters propertyNamesToJSONKeysMapping]; + + for (NSString *propertyName in [mapping allKeys]) { + XCTAssertFalse([propertyName containsString:@":"]); + XCTAssert([params respondsToSelector:NSSelectorFromString(propertyName)]); + } + + for (NSString *formFieldName in [mapping allValues]) { + XCTAssert([formFieldName isKindOfClass:[NSString class]]); + XCTAssert([formFieldName length] > 0); + } + + XCTAssertEqual([[mapping allValues] count], [[NSSet setWithArray:[mapping allValues]] count]); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:[STDSJSONEncoder dictionaryForObject:params]]); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSAuthenticationResponseTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSAuthenticationResponseTests.m new file mode 100644 index 00000000..7dac3fd1 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSAuthenticationResponseTests.m @@ -0,0 +1,32 @@ +// +// STDSAuthenticationResponseTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 5/20/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSAuthenticationResponseObject.h" +#import "STDSTestJSONUtils.h" + +@interface STDSAuthenticationResponseTests : XCTestCase + +@end + +@implementation STDSAuthenticationResponseTests + +- (void)testInitWithJSON { + NSError *error = nil; + STDSAuthenticationResponseObject *ares = [STDSAuthenticationResponseObject decodedObjectFromJSON:[STDSTestJSONUtils jsonNamed:@"ARes"] error:&error]; + + XCTAssertNil(error); + XCTAssertNotNil(ares, @"Failed to create an ares parsed from JSON"); + + id authResponse = STDSAuthenticationResponseFromJSON([STDSTestJSONUtils jsonNamed:@"ARes"]); + XCTAssertNotNil(authResponse, @"Failed to create an ares parsed from JSON"); + XCTAssert(authResponse.isChallengeRequired, @"ares did not indicate that a challenge was required"); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSBase64URLEncodingTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSBase64URLEncodingTests.m new file mode 100644 index 00000000..25642a5d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSBase64URLEncodingTests.m @@ -0,0 +1,59 @@ +// +// STDSBase64URLEncodingTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "NSData+JWEHelpers.h" +#import "NSString+JWEHelpers.h" + +@interface STDSBase64URLEncodingTests : XCTestCase + +@end + +@implementation STDSBase64URLEncodingTests + +// test cases from https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41 + +- (void)testEncodingDataToString { + { + Byte bytes[5] = {3, 236, 255, 224, 193}; + NSData *data = [NSData dataWithBytes:bytes length:5]; + XCTAssertEqualObjects([data _stds_base64URLEncodedString], @"A-z_4ME"); + } + + { + Byte bytes[30] = {123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32, 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125}; + NSData *data = [NSData dataWithBytes:bytes length:30]; + XCTAssertEqualObjects([data _stds_base64URLEncodedString], @"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"); + } + + { + Byte bytes[70] = {123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, + 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, + 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, + 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, + 111, 116, 34, 58, 116, 114, 117, 101, 125}; + NSData *data = [NSData dataWithBytes:bytes length:70]; + XCTAssertEqualObjects([data _stds_base64URLEncodedString], @"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"); + } + +} + +- (void)testEncodingString { + XCTAssertEqualObjects([@"{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}" _stds_base64URLEncodedString], @"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"); + + XCTAssertEqualObjects([@"{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}" _stds_base64URLEncodedString], @"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"); +} + +- (void)testDecodingString { + XCTAssertEqualObjects([@"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" _stds_base64URLDecodedString], @"{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}"); + + XCTAssertEqualObjects([ @"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" _stds_base64URLDecodedString], @"{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}"); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSChallengeParametersTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSChallengeParametersTests.m new file mode 100644 index 00000000..8b430d10 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSChallengeParametersTests.m @@ -0,0 +1,56 @@ +// +// STDSChallengeParametersTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 2/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSAuthenticationResponseObject.h" +#import "STDSChallengeParameters.h" + +@interface TestAuthResponse: STDSAuthenticationResponseObject + +@end + +@interface STDSChallengeParametersTests : XCTestCase + +@end + +@implementation STDSChallengeParametersTests + +- (void)testInitWithAuthResponse { + STDSChallengeParameters *params = [[STDSChallengeParameters alloc] initWithAuthenticationResponse:[[TestAuthResponse alloc] init]]; + + XCTAssertEqual(params.threeDSServerTransactionID, @"test_threeDSServerTransactionID", @"Failed to set test_threeDSServerTransactionID"); + XCTAssertEqual(params.acsTransactionID, @"test_acsTransactionID", @"Failed to set test_acsTransactionID"); + XCTAssertEqual(params.acsReferenceNumber, @"test_acsReferenceNumber", @"Failed to set test_acsReferenceNumber"); + XCTAssertEqual(params.acsSignedContent, @"test_acsSignedContent", @"Failed to set test_acsSignedContent"); + XCTAssertNil(params.threeDSRequestorAppURL, @"Should not have set threeDSRequestorAppURL"); +} + +@end + +#pragma mark - TestAuthResponse + +@implementation TestAuthResponse + +- (NSString *)threeDSServerTransactionID { + return @"test_threeDSServerTransactionID"; +} + +- (NSString *)acsTransactionID { + return @"test_acsTransactionID"; +} + +- (NSString *)acsReferenceNumber { + return @"test_acsReferenceNumber"; +} + +- (NSString *)acsSignedContent { + return @"test_acsSignedContent"; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSChallengeRequestParametersTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSChallengeRequestParametersTest.m new file mode 100644 index 00000000..e97b030e --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSChallengeRequestParametersTest.m @@ -0,0 +1,71 @@ +// +// STDSChallengeRequestParametersTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 4/1/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSChallengeRequestParameters.h" +#import "STDSJSONEncoder.h" + +@interface STDSChallengeRequestParametersTest : XCTestCase + +@end + +@implementation STDSChallengeRequestParametersTest + +#pragma mark - STDSJSONEncodable + +- (void)testPropertyNamesToJSONKeysMapping { + STDSChallengeRequestParameters *params = [[STDSChallengeRequestParameters alloc] initWithThreeDSServerTransactionIdentifier:@"server id" + acsTransactionIdentifier:@"acs id" + messageVersion:@"message version" + sdkTransactionIdentifier:@"sdk id" + requestorAppUrl:@"requestor app url" + sdkCounterStoA:0]; + + NSDictionary *mapping = [STDSChallengeRequestParameters propertyNamesToJSONKeysMapping]; + + for (NSString *propertyName in [mapping allKeys]) { + XCTAssertFalse([propertyName containsString:@":"]); + XCTAssert([params respondsToSelector:NSSelectorFromString(propertyName)]); + } + + for (NSString *formFieldName in [mapping allValues]) { + XCTAssert([formFieldName isKindOfClass:[NSString class]]); + XCTAssert([formFieldName length] > 0); + } + + XCTAssertEqual([[mapping allValues] count], [[NSSet setWithArray:[mapping allValues]] count]); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:[STDSJSONEncoder dictionaryForObject:params]]); +} + +- (void)testNextChallengeRequestParametersIncrementsCounter { + STDSChallengeRequestParameters *params = [[STDSChallengeRequestParameters alloc] initWithThreeDSServerTransactionIdentifier:@"server id" + acsTransactionIdentifier:@"acs id" + messageVersion:@"message version" + sdkTransactionIdentifier:@"sdk id" + requestorAppUrl:@"requestor app url" + sdkCounterStoA:0]; + for (NSInteger i = 0; i < 1000; i++) { + XCTAssertEqual(params.sdkCounterStoA.length, 3); + XCTAssertEqual(params.sdkCounterStoA.integerValue, i); + params = [params nextChallengeRequestParametersByIncrementCounter]; + } +} + +- (void)testEmptyChallengeDataEntryField { + STDSChallengeRequestParameters *params = [[STDSChallengeRequestParameters alloc] initWithThreeDSServerTransactionIdentifier:@"server id" + acsTransactionIdentifier:@"acs id" + messageVersion:@"message version" + sdkTransactionIdentifier:@"sdk id" + requestorAppUrl:@"requestor app url" + sdkCounterStoA:0]; + params.challengeDataEntry = @""; + XCTAssertNil(params.challengeDataEntry); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSChallengeResponseObjectTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSChallengeResponseObjectTest.m new file mode 100644 index 00000000..5f2616ad --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSChallengeResponseObjectTest.m @@ -0,0 +1,79 @@ +// +// STDSChallengeResponseObjectTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSChallengeResponseObject.h" +#import "STDSTestJSONUtils.h" +#import "NSError+Stripe3DS2.h" + +@interface STDSChallengeResponseObjectTest : XCTestCase + +@end + +@implementation STDSChallengeResponseObjectTest + +- (void)testSuccessfulDecode { + NSDictionary *json = [STDSTestJSONUtils jsonNamed:@"CRes"]; + NSError *error; + STDSChallengeResponseObject *cr = [STDSChallengeResponseObject decodedObjectFromJSON:json error:&error]; + + XCTAssertNil(error); + XCTAssertNotNil(cr); + + XCTAssertEqualObjects(cr.threeDSServerTransactionID, @"8a880dc0-d2d2-4067-bcb1-b08d1690b26e"); + XCTAssertEqualObjects(cr.acsTransactionID, @"d7c1ee99-9478-44a6-b1f2-391e29c6b340"); + XCTAssertEqual(cr.acsUIType, STDSACSUITypeText); + XCTAssertEqual(cr.challengeCompletionIndicator, NO); + XCTAssertEqualObjects(cr.challengeInfoHeader, @"Header information"); + XCTAssertEqualObjects(cr.challengeInfoLabel, @"One-time-password"); + XCTAssertEqualObjects(cr.challengeInfoText, @"Please enter the received one-time-password"); + XCTAssertEqual(cr.showChallengeInfoTextIndicator, NO); + XCTAssertEqualObjects(cr.expandInfoLabel, @"Additional instructions"); + XCTAssertEqualObjects(cr.expandInfoText, @"The issuer will send you via SMS a one-time password. Please enter the value in the designated input field above and press continue to complete the 3-D Secure authentication process."); + XCTAssertEqualObjects(cr.issuerImage.mediumDensityURL, [NSURL URLWithString:@"https://acs.com/medium_image.svg"]); + XCTAssertEqualObjects(cr.issuerImage.highDensityURL, [NSURL URLWithString:@"https://acs.com/high_image.svg"]); + XCTAssertEqualObjects(cr.issuerImage.extraHighDensityURL, [NSURL URLWithString:@"https://acs.com/extraHigh_image.svg"]); + XCTAssertEqualObjects(cr.messageType, @"CRes"); + XCTAssertEqualObjects(cr.messageVersion, @"2.2.0"); + XCTAssertEqualObjects(cr.paymentSystemImage.mediumDensityURL, [NSURL URLWithString:@"https://ds.com/medium_image.svg"]); + XCTAssertEqualObjects(cr.paymentSystemImage.highDensityURL, [NSURL URLWithString:@"https://ds.com/high_image.svg"]); + XCTAssertEqualObjects(cr.paymentSystemImage.extraHighDensityURL, [NSURL URLWithString:@"https://ds.com/extraHigh_image.svg"]); + XCTAssertEqualObjects(cr.resendInformationLabel, @"Send new One-time-password"); + XCTAssertEqualObjects(cr.sdkTransactionID, @"b2385523-a66c-4907-ac3c-91848e8c0067"); + XCTAssertEqualObjects(cr.submitAuthenticationLabel, @"Continue"); + XCTAssertEqualObjects(cr.whyInfoLabel, @"Why using 3-D Secure?"); + XCTAssertEqualObjects(cr.whyInfoText, @"Some explanation about why using 3-D Secure is an excellent idea as part of an online payment transaction"); + XCTAssertEqualObjects(cr.acsCounterACStoSDK, @"001"); +} + +- (void)testMissingFields { + NSArray *requiredFields = @[ + @"threeDSServerTransID", + @"acsCounterAtoS", + @"acsTransID", + @"acsUiType", + @"challengeCompletionInd", + @"messageType", + @"messageVersion", + @"sdkTransID", + ]; + + for (NSString *field in requiredFields) { + NSMutableDictionary *response = [[STDSTestJSONUtils jsonNamed:@"CRes"] mutableCopy]; + [response removeObjectForKey:field]; + + NSError *error; + NSError *expectedError = [NSError _stds_missingJSONFieldError:field]; + XCTAssertNil([STDSChallengeResponseObject decodedObjectFromJSON:response error:&error]); + XCTAssertNotNil(error); + XCTAssertEqual(error.code, expectedError.code); + XCTAssertEqualObjects(error.userInfo, expectedError.userInfo); + } +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSConfigParametersTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSConfigParametersTests.m new file mode 100644 index 00000000..28114985 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSConfigParametersTests.m @@ -0,0 +1,67 @@ +// +// STDSConfigParametersTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 2/13/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSConfigParameters.h" +#import "STDSInvalidInputException.h" + +@interface STDSConfigParametersTests : XCTestCase + +@end + +@implementation STDSConfigParametersTests + +- (void)testStandardParameters { + STDSConfigParameters *defaultParameters = [[STDSConfigParameters alloc] initWithStandardParameters]; + XCTAssertNotNil(defaultParameters, @"Should return a non-nil instance"); +} + +- (void)testAddRead { + STDSConfigParameters *parameters = [[STDSConfigParameters alloc] init]; + XCTAssertNoThrow([parameters addParameterNamed:@"testName" withValue:@"testValue"], @"Should not throw with non-nil name and value."); + NSString *paramValue = nil; + XCTAssertNoThrow(paramValue = [parameters parameterValue:@"testName"], @"Should not throw with non-nil name."); + XCTAssertEqual(paramValue, @"testValue", @"Returned value does not match expectation."); +} + +- (void)testDefaultGroup { + STDSConfigParameters *parameters = [[STDSConfigParameters alloc] init]; + XCTAssertNoThrow([parameters addParameterNamed:@"testName" withValue:@"testValue"], @"Should not throw with non-nil name and value."); + XCTAssertNoThrow([parameters addParameterNamed:@"testName" withValue:@"testValue2" toGroup:@"otherGroup"], @"Should not throw with non-nil name, value, and group."); + NSString *paramValue = nil; + XCTAssertNoThrow(paramValue = [parameters parameterValue:@"testName"], @"Should not throw with non-nil name."); + XCTAssertEqual(paramValue, @"testValue", @"Returned value does not match expectation. Should default to default group's value."); + XCTAssertNoThrow(paramValue = [parameters parameterValue:@"testName" inGroup:@"otherGroup"], @"Should not throw with non-nil name and group name."); + XCTAssertEqual(paramValue, @"testValue2", @"Returned value does not match expectation. Should read from custom group."); +} + +- (void)testExceptions { + STDSConfigParameters *parameters = [[STDSConfigParameters alloc] init]; + XCTAssertNoThrow([parameters addParameterNamed:@"testParam" withValue:@"testValue" toGroup:nil], @"Should not throw with nil group."); + + XCTAssertThrowsSpecific([parameters addParameterNamed:@"testParam" withValue:@"value2"], STDSInvalidInputException, @"Should throw STDSInvalidInputException if trying to override testParam value."); + [parameters addParameterNamed:@"testParam" withValue:@"testValue" toGroup:@"otherGroup"]; + XCTAssertThrowsSpecific([parameters addParameterNamed:@"testParam" withValue:@"value2" toGroup:@"otherGroup"], STDSInvalidInputException, @"Should throw STDSInvalidInputException if trying to override testParam value in non-default group."); +} + +- (void)testRemove { + STDSConfigParameters *parameters = [[STDSConfigParameters alloc] init]; + + [parameters addParameterNamed:@"testParam" withValue:@"testValue"]; + [parameters addParameterNamed:@"testParam" withValue:@"testValue2" toGroup:@"otherGroup"]; + XCTAssertEqual([parameters removeParameterNamed:@"testParam"], @"testValue", @"Should return testValue when removing."); + XCTAssertNil([parameters parameterValue:@"testParam"]); + XCTAssertNotNil([parameters parameterValue:@"testParam" inGroup:@"otherGroup"], @"Should only remove param in specified group."); + + XCTAssertNil([parameters removeParameterNamed:@"testParam"], @"Should return nil if removing a non-existant parameter."); + + XCTAssertEqual([parameters removeParameterNamed:@"testParam" fromGroup:@"otherGroup"], @"testValue2", @"Should return group-specific value when removing."); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSDeviceInformationManagerTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSDeviceInformationManagerTests.m new file mode 100644 index 00000000..eaa3a514 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSDeviceInformationManagerTests.m @@ -0,0 +1,33 @@ +// +// STDSDeviceInformationManagerTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 1/24/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDeviceInformation.h" +#import "STDSDeviceInformationManager.h" +#import "STDSWarning.h" + +@interface STDSDeviceInformationManagerTests : XCTestCase + +@end + +@implementation STDSDeviceInformationManagerTests + +- (void)testDeviceInformation { + STDSDeviceInformation *deviceInformation = [STDSDeviceInformationManager deviceInformationWithWarnings:@[] ignoringRestrictions:NO]; + XCTAssertEqualObjects(deviceInformation.dictionaryValue[@"DV"], @"1.6", @"Device data version check."); + XCTAssertNotNil(deviceInformation.dictionaryValue[@"DD"], @"Device data should be non-nil"); + XCTAssertNotNil(deviceInformation.dictionaryValue[@"DPNA"], @"Param not available should be non-nil in simulator"); + XCTAssertNil(deviceInformation.dictionaryValue[@"SW"]); + + deviceInformation = [STDSDeviceInformationManager deviceInformationWithWarnings:@[[[STDSWarning alloc] initWithIdentifier:@"WARNING_1" message:@"" severity:STDSWarningSeverityMedium], [[STDSWarning alloc] initWithIdentifier:@"WARNING_2" message:@"" severity:STDSWarningSeverityMedium], ] ignoringRestrictions:NO]; + NSArray *warningIDs = @[@"WARNING_1", @"WARNING_2"]; + XCTAssertEqualObjects(deviceInformation.dictionaryValue[@"SW"], warningIDs, @"Failed to set warning identifiers correctly"); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSDeviceInformationParameterTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSDeviceInformationParameterTests.m new file mode 100644 index 00000000..df56c47b --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSDeviceInformationParameterTests.m @@ -0,0 +1,213 @@ +// +// STDSDeviceInformationParameterTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 1/24/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDeviceInformationParameter+Private.h" + +@interface STDSDeviceInformationParameterTests : XCTestCase + +@end + +@implementation STDSDeviceInformationParameterTests + +- (void)testNoPermissions { + STDSDeviceInformationParameter *noPermissionParam = [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"NoPermissionID" + permissionCheck:^BOOL{ + return NO; + } + valueCheck:^id _Nullable{ + XCTFail(@"Should not try to collect value if we don't have permission for it"); + return @"fail"; + }]; + [noPermissionParam collectIgnoringRestrictions:YES withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + XCTAssertFalse(collected, @"Should not have collected a param we don't have permission for."); + XCTAssertTrue([value isKindOfClass:[NSString class]], @"No permission value should be a string."); + XCTAssertEqualObjects(value, @"RE03", @"Returned value should be 'RE03' for param with missing permissions."); + }]; +} + +- (void)testNoValue { + STDSDeviceInformationParameter *noValueParam = [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"NoValueID" + permissionCheck:^BOOL{ + return YES; + } + valueCheck:^id _Nullable{ + return nil; + }]; + [noValueParam collectIgnoringRestrictions:YES withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + XCTAssertFalse(collected, @"Should not have collected a param we don't have a value for."); + XCTAssertTrue([value isKindOfClass:[NSString class]], @"No value value should be a string."); + XCTAssertEqualObjects(value, @"RE02", @"Returned value should be 'RE02' for param with unavailable value."); + }]; +} + +- (void)testCollect { + __block BOOL permissionCheckCalled = NO; + __block BOOL valueCheckCalled = NO; + __block BOOL collectedHandlerCalled = NO; + + STDSDeviceInformationParameter *param = [[STDSDeviceInformationParameter alloc] initWithIdentifier:@"ParamID" + permissionCheck:^BOOL{ + XCTAssertFalse(valueCheckCalled); + permissionCheckCalled = YES; + return YES; + } + valueCheck:^id _Nullable{ + XCTAssertTrue(permissionCheckCalled); + valueCheckCalled = YES; + return @"param_val"; + }]; + [param collectIgnoringRestrictions:YES withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + XCTAssertTrue(collected, @"Should have marked value as collected."); + XCTAssertEqualObjects(value, @"param_val", @"Inaccurate returned value."); + XCTAssertTrue(permissionCheckCalled); + XCTAssertTrue(valueCheckCalled); + collectedHandlerCalled = YES; + }]; + + // This check tests that collect is synchronous for now + XCTAssertTrue(collectedHandlerCalled); + + // reset so the permission before value check doesn't fail on the second call + permissionCheckCalled = NO; + valueCheckCalled = NO; + + // make sure the ignoreRestrictions param is respected + [param collectIgnoringRestrictions:NO withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + XCTAssertFalse(collected, @"Should not have marked value as collected."); + XCTAssertFalse(permissionCheckCalled, @"Restrictions shouldn't even check the runtime permission."); + XCTAssertFalse(valueCheckCalled, @"Should not have tried to get the value."); + XCTAssertEqualObjects(value, @"RE01", @"Should return market restricted code as the value."); + }]; +} + +- (void)testAllParameters { + NSArray *allParams = [STDSDeviceInformationParameter allParameters]; + XCTAssertEqual(allParams.count, 30, @"iOS should collect 30 separate parameters."); + NSMutableSet *allParamIdentifiers = [[NSMutableSet alloc] init]; + for (STDSDeviceInformationParameter *param in allParams) { + [param collectIgnoringRestrictions:YES withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + [allParamIdentifiers addObject:identifier]; + }]; + } + XCTAssertEqual(allParamIdentifiers.count, allParams.count, @"Sanity check that there are not duplicate identifiers."); + NSArray *expectedIdentifiers = @[ + @"C001", + @"C002", + @"C003", + @"C004", + @"C005", + @"C006", + @"C008", + @"C009", + @"C010", + @"C011", + @"C012", + @"C013", + @"C014", + @"C015", + @"C017", + @"I001", + @"I002", + @"I003", + @"I004", + @"I005", + @"I006", + @"I007", + @"I008", + @"I009", + @"I010", + @"I011", + @"I012", + @"I013", + @"I014", + @"I015" + ]; + for (NSString *identifier in expectedIdentifiers) { + XCTAssertTrue([allParamIdentifiers containsObject:identifier], @"Missing identifier %@", identifier); + } +} + +- (void)testOnlyApprovedIdentifiers { + NSArray *allParams = [STDSDeviceInformationParameter allParameters]; + NSMutableSet *collectedParameterIdentifiers = [[NSMutableSet alloc] init]; + for (STDSDeviceInformationParameter *param in allParams) { + [param collectIgnoringRestrictions:NO withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + + if (collected) { + [collectedParameterIdentifiers addObject:identifier]; + } + }]; + } + NSArray *expectedIdentifiers = @[ + @"C001", + @"C002", + @"C003", + @"C004", + @"C005", + @"C006", + @"C008", + ]; + XCTAssertEqual(collectedParameterIdentifiers.count, expectedIdentifiers.count, @"Should only have collected the expected amount."); + + for (NSString *identifier in expectedIdentifiers) { + XCTAssertTrue([collectedParameterIdentifiers containsObject:identifier], @"Missing identifier %@", identifier); + } +} + +- (void)testIdentifiersAccurate { + NSDictionary *expectedIdentifiers = @{ + @"C001": [STDSDeviceInformationParameter platform], + @"C002": [STDSDeviceInformationParameter deviceModel], + @"C003": [STDSDeviceInformationParameter OSName], + @"C004": [STDSDeviceInformationParameter OSVersion], + @"C005": [STDSDeviceInformationParameter locale], + @"C006": [STDSDeviceInformationParameter timeZone], + @"C008": [STDSDeviceInformationParameter screenResolution], + @"C009": [STDSDeviceInformationParameter deviceName], + @"C010": [STDSDeviceInformationParameter IPAddress], + @"C011": [STDSDeviceInformationParameter latitude], + @"C012": [STDSDeviceInformationParameter longitude], + @"I001": [STDSDeviceInformationParameter identiferForVendor], + @"I002": [STDSDeviceInformationParameter userInterfaceIdiom], + @"I003": [STDSDeviceInformationParameter familyNames], + @"I004": [STDSDeviceInformationParameter fontNamesForFamilyName], + @"I005": [STDSDeviceInformationParameter systemFont], + @"I006": [STDSDeviceInformationParameter labelFontSize], + @"I007": [STDSDeviceInformationParameter buttonFontSize], + @"I008": [STDSDeviceInformationParameter smallSystemFontSize], + @"I009": [STDSDeviceInformationParameter systemFontSize], + @"I010": [STDSDeviceInformationParameter systemLocale], + @"I011": [STDSDeviceInformationParameter availableLocaleIdentifiers], + @"I012": [STDSDeviceInformationParameter preferredLanguages], + @"I013": [STDSDeviceInformationParameter defaultTimeZone], + }; + + [expectedIdentifiers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, STDSDeviceInformationParameter * _Nonnull obj, BOOL * _Nonnull stop) { + [obj collectIgnoringRestrictions:YES withHandler:^(BOOL collected, NSString * _Nonnull identifier, id _Nonnull value) { + XCTAssertEqualObjects(key, identifier); + }]; + }]; +} + +#pragma mark - App ID + +- (void)testSDKAppIdentifier { + // xctest in Xcode 13+ uses the Xcode version for the current app id string, previous versions are empty + NSString *appIdentifierKeyPrefix = @"STDSStripe3DS2AppIdentifierKey"; + NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"] ?: @""; + NSString *appIdentifierUserDefaultsKey = [appIdentifierKeyPrefix stringByAppendingString:appVersion]; + + [[NSUserDefaults standardUserDefaults] removeObjectForKey:appIdentifierUserDefaultsKey]; + NSString *appId = [STDSDeviceInformationParameter sdkAppIdentifier]; + XCTAssertNotNil(appId); + XCTAssertEqualObjects(appId, [[NSUserDefaults standardUserDefaults] stringForKey:appIdentifierUserDefaultsKey]); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSDirectoryServerCertificateTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSDirectoryServerCertificateTests.m new file mode 100644 index 00000000..43e7bab5 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSDirectoryServerCertificateTests.m @@ -0,0 +1,811 @@ +// +// STDSDirectoryServerCertificateTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 3/28/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDirectoryServerCertificate+Internal.h" +#import "STDSJSONWebSignature.h" +#import "NSString+JWEHelpers.h" + +@interface STDSDirectoryServerCertificateTests : XCTestCase + +@end + +@implementation STDSDirectoryServerCertificateTests + +- (void)testCertificateForDirectoryServer { + NSArray *directoryServers = @[ + @(STDSDirectoryServerULTestRSA), + @(STDSDirectoryServerULTestEC), + @(STDSDirectoryServerSTPTestRSA), + @(STDSDirectoryServerSTPTestEC), + @(STDSDirectoryServerAmex), + @(STDSDirectoryServerDiscover), + @(STDSDirectoryServerMastercard), + @(STDSDirectoryServerVisa), + @(STDSDirectoryServerCustom), + @(STDSDirectoryServerUnknown), + ]; + + for (NSNumber *directoryServerNum in directoryServers) { + STDSDirectoryServer directoryServer = (STDSDirectoryServer)[directoryServerNum integerValue]; + switch (directoryServer) { + case STDSDirectoryServerULTestRSA: + case STDSDirectoryServerULTestEC: + case STDSDirectoryServerSTPTestRSA: + case STDSDirectoryServerSTPTestEC: + case STDSDirectoryServerAmex: + case STDSDirectoryServerCartesBancaires: + case STDSDirectoryServerDiscover: + case STDSDirectoryServerMastercard: + case STDSDirectoryServerVisa: + XCTAssertNotNil([STDSDirectoryServerCertificate certificateForDirectoryServer:directoryServer], @"Failed creating certificate for type %@", directoryServerNum); + break; + + case STDSDirectoryServerCustom: + case STDSDirectoryServerUnknown: + XCTAssertNil([STDSDirectoryServerCertificate certificateForDirectoryServer:directoryServer], @"Should return nil for STDSDirectoryServerUnknown"); + break; + } + } +} + +- (void)testKeyType { + NSArray *directoryServers = @[ + @(STDSDirectoryServerULTestRSA), + @(STDSDirectoryServerULTestEC), + @(STDSDirectoryServerSTPTestRSA), + @(STDSDirectoryServerSTPTestEC), + @(STDSDirectoryServerAmex), + @(STDSDirectoryServerCartesBancaires), + @(STDSDirectoryServerDiscover), + @(STDSDirectoryServerMastercard), + @(STDSDirectoryServerVisa), + @(STDSDirectoryServerCustom), + @(STDSDirectoryServerUnknown), + ]; + + for (NSNumber *directoryServerNum in directoryServers) { + STDSDirectoryServer directoryServer = (STDSDirectoryServer)[directoryServerNum integerValue]; + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate certificateForDirectoryServer:directoryServer]; + switch (directoryServer) { + case STDSDirectoryServerULTestRSA: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerULTestRSA"); + break; + + case STDSDirectoryServerULTestEC: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeEC, @"Incorrect key type for STDSDirectoryServerULTestEC"); + break; + + case STDSDirectoryServerSTPTestRSA: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerSTPTestRSA"); + break; + + case STDSDirectoryServerSTPTestEC: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeEC, @"Incorrect key type for STDSDirectoryServerSTPTestEC"); + break; + + case STDSDirectoryServerAmex: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerAmex"); + break; + + case STDSDirectoryServerCartesBancaires: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerCartesBancaires"); + break; + + case STDSDirectoryServerDiscover: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerDiscover"); + break; + + case STDSDirectoryServerMastercard: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerMastercard"); + break; + + case STDSDirectoryServerVisa: + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Incorrect key type for STDSDirectoryServerVisa"); + break; + + case STDSDirectoryServerCustom: + case STDSDirectoryServerUnknown: + // asserts + break; + } + } +} + +- (void)testRSA_OAEP_SHA256Encryption { + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate certificateForDirectoryServer:STDSDirectoryServerSTPTestRSA]; + if (certificate != nil) { + + XCTAssertNotNil([certificate encryptDataUsingRSA_OAEP_SHA256:[@"In nature's infinite book of secrecy a little I can read." dataUsingEncoding:NSUTF8StringEncoding]]); + } else { + XCTFail(@"Failed loading certificate for %@", NSStringFromSelector(_cmd)); + } +} + +- (void)testCustomCertificate { + NSString *certificateString = @"MIIDXTCCAkWgAwIBAgIQbS4C4BSig7uuJ5uDpeT4VjANBgkqhkiG9w0BAQsFADBH" + "MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEX" + "MBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE0ODQ5WhcNMjcxMjMx" + "MTQwMDAwWjBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYH" + "ZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwggEiMA0GCSqGSIb3DQEB" + "AQUAA4IBDwAwggEKAoIBAQCfgQ+0A4Jz0CWR5Ac/MdK2ABuCzttNkvBQFl1Hz8q4" + "o8Qct3isdVN5P475dXaNGiN02HElZMO813uepDRUSJlAfP8AmZIKkxokxEFIUqsp" + "vbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0CmvnJ1AuseqQceKDdEGit1pnoCP6gEe" + "oUQdik97tOl7459V8d3UTpxLozUVlwPU00tgPmUUek8j1tPAmWx17e6EaoLRkK4Q" + "eDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++eJQUArC4IuoqdLYFjB2r+bNKdstj" + "uH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzmAd2s0/nPAgMBAAGjRTBDMBIGA1Ud" + "EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTDgwKdvAPq" + "bbCmehDaw0PwavI83jANBgkqhkiG9w0BAQsFAAOCAQEAOUcKqpzNQ6lr0PbDSsns" + "D6onfi+8j3TD0xG0zBSf+8G4zs8Zb6vzzQ5qHKgfr4aeen8Pw0cw2KKUJ2dFaBqj" + "n3/6/MIZbgaBvXKUbmY8xCxKQ+tOFc3KWIu4pSaO50tMPJjU/lP35bv19AA9vs9M" + "TKY2qLf88bmoNYT3W8VSDcB58KBHa7HVIPx7BUUtSyb2N2Jqx5AOiYy4NarhB3hV" + "ftkZBmCzi2Qw50KWIgTFYcIVeRTx3Js/F0IuEdgZHBK2gmO7fdM7+QKYm83401vl" + "YRNCXfIZ0H9E1V3NddqJuqIutdUajckSzMhXdNCJqfI4FAQAymTWGL3/lZyr/30x" + "Fg=="; + + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate customCertificateWithData:[[NSData alloc] initWithBase64EncodedString:certificateString options:0]]; + XCTAssertNotNil(certificate, @"Failed to create certificate from string."); + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Parsed incorrect key type from custom certificate"); +} + +- (void)testCustomCertificateWithString { + // Using ds-amex.pm.PEM + NSString *certificateString = @"MIIE0TCCA7mgAwIBAgIUXbeqM1duFcHk4dDBwT8o7Ln5wX8wDQYJKoZIhvcNAQEL" + "BQAwXjELMAkGA1UEBhMCVVMxITAfBgNVBAoTGEFtZXJpY2FuIEV4cHJlc3MgQ29t" + "cGFueTEsMCoGA1UEAxMjQW1lcmljYW4gRXhwcmVzcyBTYWZla2V5IElzc3Vpbmcg" + "Q0EwHhcNMTgwMjIxMjM0OTMxWhcNMjAwMjIxMjM0OTMwWjCB0DELMAkGA1UEBhMC" + "VVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9yazE/MD0GA1UE" + "ChM2QW1lcmljYW4gRXhwcmVzcyBUcmF2ZWwgUmVsYXRlZCBTZXJ2aWNlcyBDb21w" + "YW55LCBJbmMuMTkwNwYDVQQLEzBHbG9iYWwgTmV0d29yayBUZWNobm9sb2d5IC0g" + "TmV0d29yayBBUEkgUGxhdGZvcm0xHzAdBgNVBAMTFlNESy5TYWZlS2V5LkVuY3J5" + "cHRLZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSFF9kTYbwRrxX" + "C6WcJJYio5TZDM62+CnjQRfggV3GMI+xIDtMIN8LL/jbWBTycu97vrNjNNv+UPhI" + "WzhFDdUqyRfrY337A39uE8k1xhdDI3dNeZz6xgq8r9hn2NBou78YPBKidpN5oiHn" + "TxcFq1zudut2fmaldaa9a4ZKgIQo+02heiJfJ8XNWkoWJ17GcjJ59UU8C1KF/y1G" + "ymYO5ha2QRsVZYI17+ZFsqnpcXwK4Mr6RQKV6UimmO0nr5++CgvXfekcWAlLV6Xq" + "juACWi3kw0haepaX/9qHRu1OSyjzWNcSVZ0On6plB5Lq6Y9ylgmxDrv+zltz3MrT" + "K7txIAFFAgMBAAGjggESMIIBDjAMBgNVHRMBAf8EAjAAMCEGA1UdEQQaMBiCFlNE" + "Sy5TYWZlS2V5LkVuY3J5cHRLZXkwRQYJKwYBBAGCNxQCBDgeNgBBAE0ARQBYAF8A" + "UwBBAEYARQBLAEUAWQAyAF8ARABTAF8ARQBOAEMAUgBZAFAAVABJAE8ATjAOBgNV" + "HQ8BAf8EBAMCBJAwHwYDVR0jBBgwFoAU7k/rXuVMhTBxB1zSftPgmLFuDIgwRAYD" + "VR0fBD0wOzA5oDegNYYzaHR0cDovL2FtZXhzay5jcmwuY29tLXN0cm9uZy1pZC5u" + "ZXQvYW1leHNhZmVrZXkuY3JsMB0GA1UdDgQWBBQHclVTo5nwZGH8labJ2F2P45xi" + "fDANBgkqhkiG9w0BAQsFAAOCAQEAWY6b77VBoGLs3k5vOqSU7QRqT+4v6y77T8LA" + "BKrSZ58DiVZWVyDSxyftQUiRRgFHt2gTN0yfJTP50Fyp84nCEWC0tugZ4iIhgPss" + "HzL+4/u4eG/MTzK2ESxvPgr6YHajyuU+GXA89u8+bsFrFmojOjhTgFKli7YUeV/0" + "xoiYZf2utlns800ofJrcrfiFoqE6PvK4Od0jpeMgfSKv71nK5ihA1+wTk76ge1fs" + "PxL23hEdRpWW11ofaLfJGkLFXMM3/LHSXWy7HhsBgDELdzLSHU4VkSv8yTOZxsRO" + "ByxdC5v3tXGcK56iQdtKVPhFGOOEBugw7AcuRzv3f1GhvzAQZg=="; + STDSDirectoryServerCertificate *certificate = certificate = [STDSDirectoryServerCertificate customCertificateWithString:certificateString]; + XCTAssertNotNil(certificate, @"Failed to create certificate from string."); + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Parsed incorrect key type from custom certificate"); + + // 3ds2.rsa.encryption.crt + certificateString = @"-----BEGIN CERTIFICATE-----" + "MIIFrjCCBJagAwIBAgIQB2rJmsHVwbONd36WP9QPrTANBgkqhkiG9w0BAQsFADBx" + "MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl" + "cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xIjAgBgNVBAMTGVZpc2EgZUNv" + "bW1lcmNlIElzc3VpbmcgQ0EwHhcNMTcxMTAyMjIyMzEwWhcNMjAxMTAzMDAyMzEw" + "WjCBoTEYMBYGA1UEBxMPSGlnaGxhbmRzIFJhbmNoMREwDwYDVQQIEwhDb2xvcmFk" + "bzELMAkGA1UEBhMCVVMxDTALBgNVBAoTBFZJU0ExLzAtBgNVBAsTJlZpc2EgSW50" + "ZXJuYXRpb25hbCBTZXJ2aWNlIEFzc29jaWF0aW9uMSUwIwYDVQQDExwzZHMyLnJz" + "YS5lbmNyeXB0aW9uLnZpc2EuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" + "CgKCAQEAst+HGfPPsX3p6HHEQ9YzourlQj16Nscmm13Cp7cZe4dZB2oWnJqZ7oh/" + "pEoEoOAxBw1x4NFgXKTKdHAeu3VBNVw8SwMTdIC+X16VV+3VIyPbUvJXFp3QoR8W" + "UwPB3F1Lb9SMFNS95boYDZKIOdPW0cP1dRi7pFugsBUZDCP/H3nFfBFHMCBoga+P" + "3AHGj5y8RVpv0hS9jaIsYjX+i58B61OGCB7D0AiADNZJuFzw2+xpNkt6NJJF66FP" + "O8qIh8xR2xGVDf7TtCbss/CugLRgSqKab9YRB8/TBTcy5bxj6O8HD6aL2zGLcMY9" + "dCobXxCodLEtMjJdVL8N+iZrsI2gtwIDAQABo4ICDzCCAgswEwYDVR0lBAwwCgYI" + "KwYBBQUHAwEwZQYIKwYBBQUHAQEEWTBXMCUGCCsGAQUFBzABhhlodHRwOi8vb2Nz" + "cC52aXNhLmNvbS9vY3NwMC4GCCsGAQUFBzAChiJodHRwOi8vZW5yb2xsLnZpc2Fj" + "YS5jb20vZWNvbW0uY2VyMB8GA1UdIwQYMBaAFN/DKlUuL0I6ekCdkqD3R3nXj4eK" + "MAwGA1UdEwEB/wQCMAAwgcoGA1UdHwSBwjCBvzAooCagJIYiaHR0cDovL0Vucm9s" + "bC52aXNhY2EuY29tL2VDb21tLmNybDCBkqCBj6CBjIaBiWxkYXA6Ly9FbnJvbGwu" + "dmlzYWNhLmNvbTozODkvY249VmlzYSBlQ29tbWVyY2UgSXNzdWluZyBDQSxjPVVT" + "LG91PVZpc2EgSW50ZXJuYXRpb25hbCBTZXJ2aWNlIEFzc29jaWF0aW9uLG89VklT" + "QT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MA4GA1UdDwEB/wQEAwIFoDAnBgNV" + "HREEIDAeghwzZHMyLnJzYS5lbmNyeXB0aW9uLnZpc2EuY29tMB0GA1UdDgQWBBT8" + "m2pDtUtY13f/3NCOmexHavP5zDA5BgNVHSAEMjAwMC4GBWeBAwEBMCUwIwYIKwYB" + "BQUHAgEWF2h0dHA6Ly93d3cudmlzYS5jb20vcGtpMA0GCSqGSIb3DQEBCwUAA4IB" + "AQCcCUhU7KnHUDuLXqwSuzC8lWWCcEqPRgPPzY3mgBUg9ya0p+v7QF2BG77tpygK" + "E2yDPkOE8trzYeMi7TCuvKgZvUXDSOka8SId9QleMBlo2pzNi0vKKBG8+E7qmGaf" + "etQHVaoFvhg24/e7y8q89VYNKfLXn8TWMUOJdTQoNP+4bHcCnBvWWUcI2LlyEog1" + "2FDSG8hgP3cpw+0B2Hace9BQGR7ZgTIJAANEHZ54QGOYdxZEcDS5IEpKZlN8INs/" + "NKJyCkqP09VA4NO/WHaGFAXtgoLmjlA9Kal+4ieJPKijVDxcHVv/uPSfVQJ0/vCa" + "udJGOXV9q4VteupwLxfOGW8w" + "-----END CERTIFICATE-----"; + + certificateString = @"-----BEGIN CERTIFICATE-----\n" + "MIIFrjCCBJagAwIBAgIQB2rJmsHVwbONd36WP9QPrTANBgkqhkiG9w0BAQsFADBx\n" + "MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl\n" + "cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xIjAgBgNVBAMTGVZpc2EgZUNv\n" + "bW1lcmNlIElzc3VpbmcgQ0EwHhcNMTcxMTAyMjIyMzEwWhcNMjAxMTAzMDAyMzEw\n" + "WjCBoTEYMBYGA1UEBxMPSGlnaGxhbmRzIFJhbmNoMREwDwYDVQQIEwhDb2xvcmFk\n" + "bzELMAkGA1UEBhMCVVMxDTALBgNVBAoTBFZJU0ExLzAtBgNVBAsTJlZpc2EgSW50\n" + "ZXJuYXRpb25hbCBTZXJ2aWNlIEFzc29jaWF0aW9uMSUwIwYDVQQDExwzZHMyLnJz\n" + "YS5lbmNyeXB0aW9uLnZpc2EuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" + "CgKCAQEAst+HGfPPsX3p6HHEQ9YzourlQj16Nscmm13Cp7cZe4dZB2oWnJqZ7oh/\n" + "pEoEoOAxBw1x4NFgXKTKdHAeu3VBNVw8SwMTdIC+X16VV+3VIyPbUvJXFp3QoR8W\n" + "UwPB3F1Lb9SMFNS95boYDZKIOdPW0cP1dRi7pFugsBUZDCP/H3nFfBFHMCBoga+P\n" + "3AHGj5y8RVpv0hS9jaIsYjX+i58B61OGCB7D0AiADNZJuFzw2+xpNkt6NJJF66FP\n" + "O8qIh8xR2xGVDf7TtCbss/CugLRgSqKab9YRB8/TBTcy5bxj6O8HD6aL2zGLcMY9\n" + "dCobXxCodLEtMjJdVL8N+iZrsI2gtwIDAQABo4ICDzCCAgswEwYDVR0lBAwwCgYI\n" + "KwYBBQUHAwEwZQYIKwYBBQUHAQEEWTBXMCUGCCsGAQUFBzABhhlodHRwOi8vb2Nz\n" + "cC52aXNhLmNvbS9vY3NwMC4GCCsGAQUFBzAChiJodHRwOi8vZW5yb2xsLnZpc2Fj\n" + "YS5jb20vZWNvbW0uY2VyMB8GA1UdIwQYMBaAFN/DKlUuL0I6ekCdkqD3R3nXj4eK\n" + "MAwGA1UdEwEB/wQCMAAwgcoGA1UdHwSBwjCBvzAooCagJIYiaHR0cDovL0Vucm9s\n" + "bC52aXNhY2EuY29tL2VDb21tLmNybDCBkqCBj6CBjIaBiWxkYXA6Ly9FbnJvbGwu\n" + "dmlzYWNhLmNvbTozODkvY249VmlzYSBlQ29tbWVyY2UgSXNzdWluZyBDQSxjPVVT\n" + "LG91PVZpc2EgSW50ZXJuYXRpb25hbCBTZXJ2aWNlIEFzc29jaWF0aW9uLG89VklT\n" + "QT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MA4GA1UdDwEB/wQEAwIFoDAnBgNV\n" + "HREEIDAeghwzZHMyLnJzYS5lbmNyeXB0aW9uLnZpc2EuY29tMB0GA1UdDgQWBBT8\n" + "m2pDtUtY13f/3NCOmexHavP5zDA5BgNVHSAEMjAwMC4GBWeBAwEBMCUwIwYIKwYB\n" + "BQUHAgEWF2h0dHA6Ly93d3cudmlzYS5jb20vcGtpMA0GCSqGSIb3DQEBCwUAA4IB\n" + "AQCcCUhU7KnHUDuLXqwSuzC8lWWCcEqPRgPPzY3mgBUg9ya0p+v7QF2BG77tpygK\n" + "E2yDPkOE8trzYeMi7TCuvKgZvUXDSOka8SId9QleMBlo2pzNi0vKKBG8+E7qmGaf\n" + "etQHVaoFvhg24/e7y8q89VYNKfLXn8TWMUOJdTQoNP+4bHcCnBvWWUcI2LlyEog1\n" + "2FDSG8hgP3cpw+0B2Hace9BQGR7ZgTIJAANEHZ54QGOYdxZEcDS5IEpKZlN8INs/\n" + "NKJyCkqP09VA4NO/WHaGFAXtgoLmjlA9Kal+4ieJPKijVDxcHVv/uPSfVQJ0/vCa\n" + "udJGOXV9q4VteupwLxfOGW8w\n" + "-----END CERTIFICATE-----\n"; + certificate = [STDSDirectoryServerCertificate customCertificateWithString:certificateString]; + XCTAssertNotNil(certificate, @"Failed to create certificate from string."); + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Parsed incorrect key type from custom certificate"); + + certificateString = @"-----BEGIN CERTIFICATE-----" + "-----END CERTIFICATE-----"; + certificate = [STDSDirectoryServerCertificate customCertificateWithString:certificateString]; + XCTAssertNil(certificate, @"Should not return a valid value with only anchors"); + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Parsed incorrect key type from custom certificate"); + certificateString = @"-----END CERTIFICATE-----"; + + certificate = [STDSDirectoryServerCertificate customCertificateWithString:certificateString]; + XCTAssertNil(certificate, @"Should not return a valid value with only suffix anchor"); + XCTAssertEqual(certificate.keyType, STDSDirectoryServerKeyTypeRSA, @"Parsed incorrect key type from custom certificate"); +} + +- (void)testCertificateChainValidation { + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *certificateString = @"MIIDXTCCAkWgAwIBAgIQbS4C4BSig7uuJ5uDpeT4VjANBgkqhkiG9w0BAQsFADBH" + "MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEX" + "MBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE0ODQ5WhcNMjcxMjMx" + "MTQwMDAwWjBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYH" + "ZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwggEiMA0GCSqGSIb3DQEB" + "AQUAA4IBDwAwggEKAoIBAQCfgQ+0A4Jz0CWR5Ac/MdK2ABuCzttNkvBQFl1Hz8q4" + "o8Qct3isdVN5P475dXaNGiN02HElZMO813uepDRUSJlAfP8AmZIKkxokxEFIUqsp" + "vbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0CmvnJ1AuseqQceKDdEGit1pnoCP6gEe" + "oUQdik97tOl7459V8d3UTpxLozUVlwPU00tgPmUUek8j1tPAmWx17e6EaoLRkK4Q" + "eDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++eJQUArC4IuoqdLYFjB2r+bNKdstj" + "uH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzmAd2s0/nPAgMBAAGjRTBDMBIGA1Ud" + "EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTDgwKdvAPq" + "bbCmehDaw0PwavI83jANBgkqhkiG9w0BAQsFAAOCAQEAOUcKqpzNQ6lr0PbDSsns" + "D6onfi+8j3TD0xG0zBSf+8G4zs8Zb6vzzQ5qHKgfr4aeen8Pw0cw2KKUJ2dFaBqj" + "n3/6/MIZbgaBvXKUbmY8xCxKQ+tOFc3KWIu4pSaO50tMPJjU/lP35bv19AA9vs9M" + "TKY2qLf88bmoNYT3W8VSDcB58KBHa7HVIPx7BUUtSyb2N2Jqx5AOiYy4NarhB3hV" + "ftkZBmCzi2Qw50KWIgTFYcIVeRTx3Js/F0IuEdgZHBK2gmO7fdM7+QKYm83401vl" + "YRNCXfIZ0H9E1V3NddqJuqIutdUajckSzMhXdNCJqfI4FAQAymTWGL3/lZyr/30x" + "Fg=="; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-string-concatenation" + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSArray *certChain = @[@"MIIDeTCCAmGgAwIBAgIQbS4C4BSig7uuJ5uDpeT4WDANB" + "gkqhkiG9w0BAQsFADBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBG" + "RYHZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE1NDAyW" + "hcNMjcxMjMxMTMzMDAwWjBIMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyL" + "GQBGRYHZXhhbXBsZTEYMBYGA1UEAwwPUlNBIEV4YW1wbGUgQUNTMIIBIjANBgkqhkiG9" + "w0BAQEFAAOCAQ8AMIIBCgKCAQEAkNrPIBDXMU6fcyv5i+QHQAQ+K8gsC3HJb7FYhYaw8" + "hXbNJa+t8q0lDKwLZgQXYV+ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6" + "Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s+hwx1IU5AT+AIelNqBgcF2vE5W25/S" + "GGBoaROVdUYxqETDggM1z5cKV4ZjDZ8+lh4oVB07bkac6LQdHpJUUySH/Er20DXx30Ky" + "i97PciXKTS+QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy+7dg7z" + "41Rp6D0ZeEvtaUp4bX4aKraL4rTfwIDAQABo2AwXjAMBgNVHRMBAf8EAjAAMA4GA1UdD" + "wEB/wQEAwIHgDAdBgNVHQ4EFgQUktwf6ZpTCxjYKw/BLW6PeiNX4swwHwYDVR0jBBgwF" + "oAUw4MCnbwD6m2wpnoQ2sND8GryPN4wDQYJKoZIhvcNAQELBQADggEBAGuNHxv/BR6j7" + "lCPysm1uhrbjBOqdrhJMR/Id4dB2GtdEScl3irGPmXyQ2SncTWhNfsgsKDZWp5Bk7+Ot" + "nty0eNUMk3hZEqgYjxhzau048XHbsfGvoJaMGZZNTwUvTUz2hkkhgpx9yQAKIA2LzFKc" + "gYhelPu4GW5rtEuxu3IS6WYy3D1GtF3naEWkjUra8hQOhOl2S+CYHmRd6lGkXykVDajM" + "gd2AJFzXdKLxTt0OYrWDGlUSzGACRBCd5xbRmATIldtccaGqDN1cNWv0I/bPN8EpKS6B" + "0WaZcPasItKWpDC85Jw1GrDxdhwoKHoxtSG+odiTwB5zLbrn2OsRE5bV7E="]; +#pragma clang diagnostic pop + + XCTAssertTrue([STDSDirectoryServerCertificate _verifyCertificateChain:certChain withRootCertificates:@[certificateString]], @"Failed to verify certificate chain."); + certChain = @[@"junk_data", @"more_junk"]; + XCTAssertFalse([STDSDirectoryServerCertificate _verifyCertificateChain:certChain withRootCertificates:@[certificateString]], @"Verified certificate chain with invalid certificates."); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-string-concatenation" + // Test with valid certificates in the chain, but that are not related to the root certificate + // ref. https://tools.ietf.org/html/rfc7515 + certChain = @[ + @"MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVM" + "xITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR2" + "8gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExM" + "TYwMTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UE" + "CBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWR" + "keS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYW" + "RkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlc" + "nRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJ" + "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTt" + "wY6vj3D3HKrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqV" + "Tr9vcyOdQmVZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aL" + "GbqGmu75RpRSgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo" + "7RJlbmr2EkRTcDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgW" + "JCJjPOq8lh8BJ6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAw" + "EAAaOCATIwggEuMB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVH" + "SMEGDAWgBTSxLDSkdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEA" + "MDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWR" + "keS5jb20wRgYDVR0fBD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2" + "RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVH" + "SAAMDgwNgYIKwYBBQUHAgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j" + "b20vcmVwb3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggE" + "BANKGwOy9+aG2Z+5mC6IGOgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPI" + "UyIXvJxwqoJKSQ3kbTJSMUA2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL" + "5CkKSkB2XIsKd83ASe8T+5o0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9" + "p0iRFEUOOjZv2kWzRaJBydTXRE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsx" + "uxN89txJx9OjxUUAiKEngHUuHqDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZ" + "EjYx8WnM25sgVjOuH0aBsXBTWVU+4=", + @"MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Z" + "hbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIE" + "luYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb" + "24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8x" + "IDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDY" + "yMFoXDTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZS" + "BHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgM" + "iBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN" + "ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XC" + "APVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux" + "6wwdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLO" + "tXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWo" + "riMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZ" + "Eewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ" + "4EFgQU0sSw0pHUTBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBu" + "zEkMCIGA1UEBxMbVmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQK" + "Ew5WYWxpQ2VydCwgSW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2x" + "pY3kgVmFsaWRhdGlvbiBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudm" + "FsaWNlcnQuY29tLzEgMB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CA" + "QEwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGG" + "F2h0dHA6Ly9vY3NwLmdvZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA" + "6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybD" + "BLBgNVHSAERDBCMEAGBFUdIAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZ" + "mljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjAN" + "BgkqhkiG9w0BAQUFAAOBgQC1QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+" + "Sn1eocSxI0YGyeR+sBjUZsE4OWBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgM" + "QLARzLrUc+cb53S8wGd9D0VmsfSxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j" + "09VZw==", + @"MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ" + "0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNT" + "AzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0a" + "G9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkq" + "hkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE" + "5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTm" + "V0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZ" + "XJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQD" + "ExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9" + "AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5a" + "vIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zf" + "N1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwb" + "P7RfZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQU" + "AA4GBADt/UG9vUJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQ" + "C1u+mNr0HZDzTuIYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMM" + "j4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd", + @"MIIDeTCCAmGgAwIBAgIQbS4C4BSig7uuJ5uDpeT4WDANB" + "gkqhkiG9w0BAQsFADBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBG" + "RYHZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE1NDAyW" + "hcNMjcxMjMxMTMzMDAwWjBIMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyL" + "GQBGRYHZXhhbXBsZTEYMBYGA1UEAwwPUlNBIEV4YW1wbGUgQUNTMIIBIjANBgkqhkiG9" + "w0BAQEFAAOCAQ8AMIIBCgKCAQEAkNrPIBDXMU6fcyv5i+QHQAQ+K8gsC3HJb7FYhYaw8" + "hXbNJa+t8q0lDKwLZgQXYV+ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6" + "Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s+hwx1IU5AT+AIelNqBgcF2vE5W25/S" + "GGBoaROVdUYxqETDggM1z5cKV4ZjDZ8+lh4oVB07bkac6LQdHpJUUySH/Er20DXx30Ky" + "i97PciXKTS+QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy+7dg7z" + "41Rp6D0ZeEvtaUp4bX4aKraL4rTfwIDAQABo2AwXjAMBgNVHRMBAf8EAjAAMA4GA1UdD" + "wEB/wQEAwIHgDAdBgNVHQ4EFgQUktwf6ZpTCxjYKw/BLW6PeiNX4swwHwYDVR0jBBgwF" + "oAUw4MCnbwD6m2wpnoQ2sND8GryPN4wDQYJKoZIhvcNAQELBQADggEBAGuNHxv/BR6j7" + "lCPysm1uhrbjBOqdrhJMR/Id4dB2GtdEScl3irGPmXyQ2SncTWhNfsgsKDZWp5Bk7+Ot" + "nty0eNUMk3hZEqgYjxhzau048XHbsfGvoJaMGZZNTwUvTUz2hkkhgpx9yQAKIA2LzFKc" + "gYhelPu4GW5rtEuxu3IS6WYy3D1GtF3naEWkjUra8hQOhOl2S+CYHmRd6lGkXykVDajM" + "gd2AJFzXdKLxTt0OYrWDGlUSzGACRBCd5xbRmATIldtccaGqDN1cNWv0I/bPN8EpKS6B" + "0WaZcPasItKWpDC85Jw1GrDxdhwoKHoxtSG+odiTwB5zLbrn2OsRE5bV7E=",]; + XCTAssertFalse([STDSDirectoryServerCertificate _verifyCertificateChain:certChain withRootCertificates:@[certificateString]], @"Verified invalid certificate chain."); + + // stripe.com's cert chain retrieved by https://whatsmychaincert.com/ + // If below test cases start failing, the certs may have expired and should be replaced with newer ones + // from whatsmychaincert.com, select Generate Cert Chain, include root for stripe.com + // print the entire certificate chain with `openssl crl2pkcs7 -nocrl -certfile | openssl pkcs7 -print_certs -text` + // The root is the last printed certificate and the cert chain should be ordered with the top most at the end of certChain + + certChain = @[ + @"MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j" + "ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL" + "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3" + "LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW" + "YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC" + "ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY" + "uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/" + "LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy" + "/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh" + "cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k" + "8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB" + "Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF" + "BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp" + "Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy" + "dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2" + "MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j" + "b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW" + "gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh" + "hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg" + "4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa" + "2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs" + "1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1" + "oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn" + "8TUoE6smftX3eg==", + + @"MIIHQDCCBiigAwIBAgIQD2ygPziYarLZojJGDizjjzANBgkqhkiG9w0BAQsFADB1" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk" + "IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTIxMTAyMDAwMDAwMFoXDTIyMDIwMjIz" + "NTk1OVowgcYxHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB" + "BAGCNzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQF" + "Ewc0Njc1NTA2MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQG" + "A1UEBxMNU2FuIEZyYW5jaXNjbzEUMBIGA1UEChMLU3RyaXBlLCBJbmMxEzARBgNV" + "BAMTCnN0cmlwZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1" + "ZyRTERjtA28e16WX4AUBBbXE8YTA6F5RNp5NEy2px6GF2LbjR8TobVfJoj63+CXR" + "W4uox0TQV526ZnPKbvYpAilYxEc9/fJTPS3lDJ4EgKRZlZ97UrGY8dzOR2aebpw4" + "xYnN8gYXFHeISG1ieQnnyZUck5HUF1FX3rcWh8y7doNLL9cvIt5+R6yLqeTZkCX3" + "eIAvANuhdN++nqTM7JoFSViZa8VNQ18wviB5Hw+VWdpZXF9SQmWpP4M7pgDUYTVa" + "xIsywBiisQBExh5NXEhSAboTtqxmt5IADSQx9E2tp5u3oY2Ql3YRGmYnfe1y7QKD" + "PdEVMRa4aFod+w9qe2OfAgMBAAGjggN4MIIDdDAfBgNVHSMEGDAWgBQ901Cl1qCt" + "7vNKYApl0yHU+PjWDzAdBgNVHQ4EFgQUzjnGrO9r9NbeN6cRkzoAtaKNZLowJQYD" + "VR0RBB4wHIIKc3RyaXBlLmNvbYIOd3d3LnN0cmlwZS5jb20wDgYDVR0PAQH/BAQD" + "AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSg" + "MqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzMu" + "Y3JsMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2" + "ZXItZzMuY3JsMEoGA1UdIARDMEEwCwYJYIZIAYb9bAIBMDIGBWeBDAEBMCkwJwYI" + "KwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBiAYIKwYBBQUH" + "AQEEfDB6MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wUgYI" + "KwYBBQUHMAKGRmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNI" + "QTJFeHRlbmRlZFZhbGlkYXRpb25TZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCC" + "AX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHYAKXm+8J45OSHwVnOfY6V35b5XfZxg" + "Cvj5TV0mXCVdx4QAAAF8n1z3KwAABAMARzBFAiAWda1cPqRI3YujJxhh3ahOlUoN" + "L+bpZFA30lL+L3UB1AIhAIAX2FUC4uyshmrtgnnq7OcmIzsOEVW7LDlFUlfokAbW" + "AHUAUaOw9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN/tSLBeUAAAF8n1z3JAAABAMA" + "RjBEAiAPAv+vPLXXB1hB5S+MIrWXIIIsa0u+cPIARVuMEQPz5gIgLbrsj2bNmxYF" + "mfvwsORC61rCPouzzFkvQGy81lz0L38AdwBByMqx3yJGShDGoToJQodeTjGLGwPr" + "60vHaPCQYpYG9gAAAXyfXPahAAAEAwBIMEYCIQCdieTsguvk9YFGdpMPsSp4CJYY" + "1cn1lTiYQQBwzKSqogIhAOyyQKx4fhNqtWOiXUYhPaQtYLS7Ey4TeOlmm7U3OktO" + "MA0GCSqGSIb3DQEBCwUAA4IBAQBmZNhqh9q/u4hNPX7fPhIXTkHJD+B30kefLTOK" + "OA/ZlMzGdvrGaJAJNzQzZL0gTEf3x+D9yjOv8shBIfblugTTa/+ulDbSzjM7qrn6" + "nfojxtLdJ90UvkDCeJ30+Mn+FRahAeLN1jVAgXAUKTgU8QoAM0URZuLh/yWSoMzh" + "kx9PiDtEJR6DMoVVOPTf9Yk3iUBr//KDzVMbz5aJFDO+2fUjGqVmW+3rapYfealY" + "LNz/SmaO8S5U4z9A3xkwXSWxlEKkboKvngSdhNgYyPeHTmNKhcmFnnA9q2LvAl9u" + "UUhp5kZA9EKInRZiVl6i7z1RNhxe2RISbq1MlZi/raZYACt8", + ]; +#pragma clang diagnostic pop + + // root + NSString *rootCertificateString = @"MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3" + "d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j" + "ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL" + "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3" + "LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug" + "RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm" + "+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW" + "PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM" + "xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB" + "Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3" + "hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg" + "EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF" + "MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA" + "FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec" + "nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z" + "eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF" + "hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2" + "Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe" + "vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep" + "+OkuE6N36B9K"; + + // mastercard DS (from mastercard.der) + NSString *dsCertificateString = @"MIIFtTCCA52gAwIBAgIQJqSRaPua/6cpablmVDHWUDANBgkqhkiG9w0BAQsFADB6" + "MQswCQYDVQQGEwJVUzETMBEGA1UEChMKTWFzdGVyQ2FyZDEoMCYGA1UECxMfTWFz" + "dGVyQ2FyZCBJZGVudGl0eSBDaGVjayBHZW4gMzEsMCoGA1UEAxMjUFJEIE1hc3Rl" + "ckNhcmQgM0RTMiBBY3F1aXJlciBTdWIgQ0EwHhcNMTgxMTIwMTQ1MzIzWhcNMjEx" + "MTIwMTQ1MzIzWjBxMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUTWFzdGVyQ2FyZCBX" + "b3JsZHdpZGUxGzAZBgNVBAsTEmdhdGV3YXktZW5jcnlwdGlvbjEmMCQGA1UEAxMd" + "M2RzMi5kaXJlY3RvcnkubWFzdGVyY2FyZC5jb20wggEiMA0GCSqGSIb3DQEBAQUA" + "A4IBDwAwggEKAoIBAQCFlZjqbbL9bDKOzZFawdbyfQcezVEUSDCWWsYKw/V6co9A" + "GaPBUsGgzxF6+EDgVj3vYytgSl8xFvVPsb4ZJ6BJGvimda8QiIyrX7WUxQMB3hyS" + "BOPf4OB72CP+UkaFNR6hdlO5ofzTmB2oj1FdLGZmTN/sj6ZoHkn2Zzums8QAHFjv" + "FjspKUYCmms91gpNpJPUUztn0N1YMWVFpFMytahHIlpiGqTDt4314F7sFABLxzFr" + "Dmcqhf623SPV3kwQiLVWOvewO62ItYUFgHwle2dq76YiKrUv1C7vADSk2Am4gqwv" + "7dcCnFeM2AHbBFBa1ZBRQXosuXVw8ZcQqfY8m4iNAgMBAAGjggE+MIIBOjAOBgNV" + "HQ8BAf8EBAMCAygwCQYDVR0TBAIwADAfBgNVHSMEGDAWgBSakqJUx4CN/s5W4wMU" + "/17uSLhFuzBIBggrBgEFBQcBAQQ8MDowOAYIKwYBBQUHMAGGLGh0dHA6Ly9vY3Nw" + "LnBraS5pZGVudGl0eWNoZWNrLm1hc3RlcmNhcmQuY29tMCgGA1UdEQQhMB+CHTNk" + "czIuZGlyZWN0b3J5Lm1hc3RlcmNhcmQuY29tMGkGA1UdHwRiMGAwXqBcoFqGWGh0" + "dHA6Ly9jcmwucGtpLmlkZW50aXR5Y2hlY2subWFzdGVyY2FyZC5jb20vOWE5MmEy" + "NTRjNzgwOGRmZWNlNTZlMzAzMTRmZjVlZWU0OGI4NDViYi5jcmwwHQYDVR0OBBYE" + "FHxN6+P0r3+dFWmi/+pDQ8JWaCbuMA0GCSqGSIb3DQEBCwUAA4ICAQAtwW8siyCi" + "mhon1WUAUmufZ7bbegf3cTOafQh77NvA0xgVeloELUNCwsSSZgcOIa4Zgpsa0xi5" + "fYxXsPLgVPLM0mBhTOD1DnPu1AAm32QVelHe6oB98XxbkQlHGXeOLs62PLtDZd94" + "7pm08QMVb+MoCnHLaBLV6eKhKK+SNrfcxr33m0h3v2EMoiJ6zCvp8HgIHEhVpleU" + "8H2Uo5YObatb/KUHgtp2z0vEfyGhZR7hrr48vUQpfVGBABsCV0aqUkPxtAXWfQo9" + "1N9B7H3EIcSjbiUz5vkj9YeDSyJIi0Y/IZbzuNMsz2cRi1CWLl37w2fe128qWxYq" + "Y/k+Y4HX7uYchB8xPaZR4JczCvg1FV2JrkOcFvElVXWSMpBbe2PS6OMr3XxrHjzp" + "DyM9qvzge0Ai9+rq8AyGoG1dP2Ay83Ndlgi42X3yl1uEUW2feGojCQQCFFArazEj" + "LUkSlrB2kA12SWAhsqqQwnBLGSTp7PqPZeWkluQVXS0sbj0878kTra6TjG3U+KqO" + "JCj8v6G380qIkAXe1xMHHNQ6GS59HZMeBPYkK2y5hmh/JVo4bRfK7Ya3blBSBfB8" + "AVWQ5GqVWklvXZsQLN7FH/fMIT3y8iE1W19Ua4whlhvn7o/aYWOkHr1G2xyh8BHj" + "7H63A2hjcPlW/ZAJSTuBZUClAhsNohH2Jg=="; + + XCTAssertTrue([STDSDirectoryServerCertificate _verifyCertificateChain:certChain withRootCertificates:@[rootCertificateString]], @"Failed to verify w/ root certificate."); + XCTAssertFalse([STDSDirectoryServerCertificate _verifyCertificateChain:certChain withRootCertificates:@[dsCertificateString]], @"Should not verify w/ DS certificate."); + NSArray *bothCertificateStrings = @[dsCertificateString, rootCertificateString]; + XCTAssertTrue([STDSDirectoryServerCertificate _verifyCertificateChain:certChain withRootCertificates:bothCertificateStrings], @"Failed to verify w/ root certificate and ds certificate."); +} + +- (void)testVerifyPS256Signature { + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *jwsString = @"eyJhbGciOiJQUzI1NiIsIng1YyI6WyJNSUlEZVRDQ0FtR2dBd0lCQWdJUWJTNEM0QlNp" + "Zzd1dUo1dURwZVQ0V0RBTkJna3Foa2lHOXcwQkFRc0ZBREJITVJNd0VRWUtDWkltaVpQ" + "eUxHUUJHUllEWTI5dE1SY3dGUVlLQ1pJbWlaUHlMR1FCR1JZSFpYaGhiWEJzWlRFWE1C" + "VUdBMVVFQXd3T1VsTkJJRVY0WVcxd2JHVWdSRk13SGhjTk1UY3hNVEl4TVRFMU5EQXlX" + "aGNOTWpjeE1qTXhNVE16TURBd1dqQklNUk13RVFZS0NaSW1pWlB5TEdRQkdSWURZMjl0" + "TVJjd0ZRWUtDWkltaVpQeUxHUUJHUllIWlhoaGJYQnNaVEVZTUJZR0ExVUVBd3dQVWxO" + "QklFVjRZVzF3YkdVZ1FVTlRNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1J" + "SUJDZ0tDQVFFQWtOclBJQkRYTVU2ZmN5djVpK1FIUUFRK0s4Z3NDM0hKYjdGWWhZYXc4" + "aFhiTkphK3Q4cTBsREt3TFpnUVhZVitmZld4WEp2NUdHcmxaRTRHVTUybGZNRWVnVER6" + "WVRyUlEzdGVwZ0tGak1HZzZJeTZma2wxWk5zeDJnRW9uc25sU2hmekE5R0p3UlRtdEtQ" + "Ymsxcytod3gxSVU1QVQrQUllbE5xQmdjRjJ2RTVXMjUvU0dHQm9hUk9WZFVZeHFFVERn" + "Z00xejVjS1Y0WmpEWjgrbGg0b1ZCMDdia2FjNkxRZEhwSlVVeVNIL0VyMjBEWHgzMEt5" + "aTk3UGNpWEtUUytRS1hubW04aXZ5UkNtdXgyMlpvUFVpbmQyQktDNU9pRzRNd0FMaGFM" + "MloyazhDc1JkZnkrN2RnN3o0MVJwNkQwWmVFdnRhVXA0Ylg0YUtyYUw0clRmd0lEQVFB" + "Qm8yQXdYakFNQmdOVkhSTUJBZjhFQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBZEJn" + "TlZIUTRFRmdRVWt0d2Y2WnBUQ3hqWUt3L0JMVzZQZWlOWDRzd3dId1lEVlIwakJCZ3dG" + "b0FVdzRNQ25id0Q2bTJ3cG5vUTJzTkQ4R3J5UE40d0RRWUpLb1pJaHZjTkFRRUxCUUFE" + "Z2dFQkFHdU5IeHYvQlI2ajdsQ1B5c20xdWhyYmpCT3FkcmhKTVIvSWQ0ZEIyR3RkRVNj" + "bDNpckdQbVh5UTJTbmNUV2hOZnNnc0tEWldwNUJrNytPdG50eTBlTlVNazNoWkVxZ1lq" + "eGh6YXUwNDhYSGJzZkd2b0phTUdaWk5Ud1V2VFV6Mmhra2hncHg5eVFBS0lBMkx6Rktj" + "Z1loZWxQdTRHVzVydEV1eHUzSVM2V1l5M0QxR3RGM25hRVdralVyYThoUU9oT2wyUytD" + "WUhtUmQ2bEdrWHlrVkRhak1nZDJBSkZ6WGRLTHhUdDBPWXJXREdsVVN6R0FDUkJDZDV4" + "YlJtQVRJbGR0Y2NhR3FETjFjTld2MEkvYlBOOEVwS1M2QjBXYVpjUGFzSXRLV3BEQzg1" + "SncxR3JEeGRod29LSG94dFNHK29kaVR3QjV6TGJybjJPc1JFNWJWN0U9Il19" + "." + "eyJBQ1MgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFUKSI6eyJrdHkiOiJFQyIsImNydiI6" + "IlAtMjU2IiwieCI6Im1QVUtUX2JBV0dISWhnMFRwampxVnNQMXJYV1F1X3Z3Vk9ISHRO" + "a2RZb0EiLCJ5IjoiOEJRQXNJbUdlQVM0NmZ5V3c1TWhmR1RUMElqQnBGdzJTUzM0RHY0" + "SXJzIix9LCJTREsgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFDKSI6eyJrdHkiOiJFQyIs" + "ImNydiI6IlAtMjU2IiwieCI6IlplMmxvU1Yzd3Jyb0tVTl80emh3R2hDcW8zWGh1MXRk" + "NFFqZVE1d0lWUjAiLCJ5IjoiSGxMdGRYQVJZX2Y1NUEzZm56UWJQY202aGdyMzRNcDhw" + "LW51elFDRTBadyIsfSwiQUNTIFVSTCI6Imh0dHA6Ly9hY3NzZXJ2ZXIuZG9tYWlubmFt" + "ZS5jb20ifQ" + "." + "OiiD5pbwe_0wrG_j61LmUxidBzTbUHyZXrKE9efRylEgMJy0axYJLOUshIloiKMexPdo" + "yDdpZV5pMQR588q-" + "uuUfssKpDXj3dCrIlUCEwgs4yfIBAUwd2NHoDg20w25ek0NPH9RV_T867DAXIjDWyd2I" + "y0ZFvJeHSrRskyPY75PA_mAUIpahaW20rxJHHMyGuWx2byKxnIx6G14vXCOPp1xEv49K" + "xKWrgFLS3_GtVYuNDqQC5pleHd4drLKSbq1bjwqEM1osYEZvw9y-" + "f1vQYxAQ8GJEti9F_309GVCZSWe1oyNDY51mo-" + "BwiyojoCDPxQDOTp6g4lp656tUQNW16g"; + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:jwsString]; + + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *certificateString = @"MIIDXTCCAkWgAwIBAgIQbS4C4BSig7uuJ5uDpeT4VjANBgkqhkiG9w0BAQsFADBH" + "MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEX" + "MBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE0ODQ5WhcNMjcxMjMx" + "MTQwMDAwWjBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYH" + "ZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwggEiMA0GCSqGSIb3DQEB" + "AQUAA4IBDwAwggEKAoIBAQCfgQ+0A4Jz0CWR5Ac/MdK2ABuCzttNkvBQFl1Hz8q4" + "o8Qct3isdVN5P475dXaNGiN02HElZMO813uepDRUSJlAfP8AmZIKkxokxEFIUqsp" + "vbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0CmvnJ1AuseqQceKDdEGit1pnoCP6gEe" + "oUQdik97tOl7459V8d3UTpxLozUVlwPU00tgPmUUek8j1tPAmWx17e6EaoLRkK4Q" + "eDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++eJQUArC4IuoqdLYFjB2r+bNKdstj" + "uH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzmAd2s0/nPAgMBAAGjRTBDMBIGA1Ud" + "EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTDgwKdvAPq" + "bbCmehDaw0PwavI83jANBgkqhkiG9w0BAQsFAAOCAQEAOUcKqpzNQ6lr0PbDSsns" + "D6onfi+8j3TD0xG0zBSf+8G4zs8Zb6vzzQ5qHKgfr4aeen8Pw0cw2KKUJ2dFaBqj" + "n3/6/MIZbgaBvXKUbmY8xCxKQ+tOFc3KWIu4pSaO50tMPJjU/lP35bv19AA9vs9M" + "TKY2qLf88bmoNYT3W8VSDcB58KBHa7HVIPx7BUUtSyb2N2Jqx5AOiYy4NarhB3hV" + "ftkZBmCzi2Qw50KWIgTFYcIVeRTx3Js/F0IuEdgZHBK2gmO7fdM7+QKYm83401vl" + "YRNCXfIZ0H9E1V3NddqJuqIutdUajckSzMhXdNCJqfI4FAQAymTWGL3/lZyr/30x" + "Fg=="; + + XCTAssertTrue([STDSDirectoryServerCertificate verifyJSONWebSignature:jws withRootCertificates:@[certificateString]], @"Failed to validate correct PS256 signature."); +} + +- (void)testVerifyPS256SignatureFail { + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *jwsString = @"eyJhbGciOiJQUzI1NiIsIng1YyI6WyJNSUlEZVRDQ0FtR2dBd0lCQWdJUWJTNEM0QlNp" + "Zzd1dUo1dURwZVQ0V0RBTkJna3Foa2lHOXcwQkFRc0ZBREJITVJNd0VRWUtDWkltaVpQ" + "eUxHUUJHUllEWTI5dE1SY3dGUVlLQ1pJbWlaUHlMR1FCR1JZSFpYaGhiWEJzWlRFWE1C" + "VUdBMVVFQXd3T1VsTkJJRVY0WVcxd2JHVWdSRk13SGhjTk1UY3hNVEl4TVRFMU5EQXlX" + "aGNOTWpjeE1qTXhNVE16TURBd1dqQklNUk13RVFZS0NaSW1pWlB5TEdRQkdSWURZMjl0" + "TVJjd0ZRWUtDWkltaVpQeUxHUUJHUllIWlhoaGJYQnNaVEVZTUJZR0ExVUVBd3dQVWxO" + "QklFVjRZVzF3YkdVZ1FVTlRNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1J" + "SUJDZ0tDQVFFQWtOclBJQkRYTVU2ZmN5djVpK1FIUUFRK0s4Z3NDM0hKYjdGWWhZYXc4" + "aFhiTkphK3Q4cTBsREt3TFpnUVhZVitmZld4WEp2NUdHcmxaRTRHVTUybGZNRWVnVER6" + "WVRyUlEzdGVwZ0tGak1HZzZJeTZma2wxWk5zeDJnRW9uc25sU2hmekE5R0p3UlRtdEtQ" + "Ymsxcytod3gxSVU1QVQrQUllbE5xQmdjRjJ2RTVXMjUvU0dHQm9hUk9WZFVZeHFFVERn" + "Z00xejVjS1Y0WmpEWjgrbGg0b1ZCMDdia2FjNkxRZEhwSlVVeVNIL0VyMjBEWHgzMEt5" + "aTk3UGNpWEtUUytRS1hubW04aXZ5UkNtdXgyMlpvUFVpbmQyQktDNU9pRzRNd0FMaGFM" + "MloyazhDc1JkZnkrN2RnN3o0MVJwNkQwWmVFdnRhVXA0Ylg0YUtyYUw0clRmd0lEQVFB" + "Qm8yQXdYakFNQmdOVkhSTUJBZjhFQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBZEJn" + "TlZIUTRFRmdRVWt0d2Y2WnBUQ3hqWUt3L0JMVzZQZWlOWDRzd3dId1lEVlIwakJCZ3dG" + "b0FVdzRNQ25id0Q2bTJ3cG5vUTJzTkQ4R3J5UE40d0RRWUpLb1pJaHZjTkFRRUxCUUFE" + "Z2dFQkFHdU5IeHYvQlI2ajdsQ1B5c20xdWhyYmpCT3FkcmhKTVIvSWQ0ZEIyR3RkRVNj" + "bDNpckdQbVh5UTJTbmNUV2hOZnNnc0tEWldwNUJrNytPdG50eTBlTlVNazNoWkVxZ1lq" + "eGh6YXUwNDhYSGJzZkd2b0phTUdaWk5Ud1V2VFV6Mmhra2hncHg5eVFBS0lBMkx6Rktj" + "Z1loZWxQdTRHVzVydEV1eHUzSVM2V1l5M0QxR3RGM25hRVdralVyYThoUU9oT2wyUytD" + "WUhtUmQ2bEdrWHlrVkRhak1nZDJBSkZ6WGRLTHhUdDBPWXJXREdsVVN6R0FDUkJDZDV4" + "YlJtQVRJbGR0Y2NhR3FETjFjTld2MEkvYlBOOEVwS1M2QjBXYVpjUGFzSXRLV3BEQzg1" + "SncxR3JEeGRod29LSG94dFNHK29kaVR3QjV6TGJybjJPc1JFNWJWN0U9Il19" + "." + "eyJBQ1MgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFUKSI6eyJrdHkiOiJFQyIsImNydiI6" + "IlAtMjU2IiwieCI6Im1QVUtUX2JBV0dISWhnMFRwampxVnNQMXJYV1F1X3Z3Vk9ISHRO" + "a2RZb0EiLCJ5IjoiOEJRQXNJbUdlQVM0NmZ5V3c1TWhmR1RUMElqQnBGdzJTUzM0RHY0" + "SXJzIix9LCJTREsgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFDKSI6eyJrdHkiOiJFQyIs" + "ImNydiI6IlAtMjU2IiwieCI6IlplMmxvU1Yzd3Jyb0tVTl80emh3R2hDcW8zWGh1MXRk" + "NFFqZVE1d0lWUjAiLCJ5IjoiSGxMdGRYQVJZX2Y1NUEzZm56UWJQY202aGdyMzRNcDhw" + "LW51elFDRTBadyIsfSwiQUNTIFVSTCI6Imh0dHA6Ly9hY3NzZXJ2ZXIuZG9tYWlubmFt" + "ZS5jb20ifQ" + "." + "OiiD5pbwe_0wrG_j61LmUxidBzTbUHyZXrKE9efRylEgMJy0axYJLOUshIloiKMexPdo" + "yDdpZV5pMQR588q-" + "uuUfssKpDXj3dCrIlUCEwgs4yfIBAUwd2NHoDg20w25ek0NPH9RV_T867DAXIjDWyd2I" + "y0ZFvJeHSrRskyPY75PA_mAUIpahaW20rxJHHMyGuWx2byKxnIx6G14vXCOPp1xEv49K" + "xKWrgFLS3_GtVYuNDqQC5pleHd4drLKSbq1bjwqEM1osYEZvw9y-" + "f1vQYxAQ8GJEti9F_309GVCZSWe1oyNEY51mo-" // This is all the same as testVerifyPS256Signature, except this line where "NDY51mo-" became "NEY51mo-" + "BwiyojoCDPxQDOTp6g4lp656tUQNW16g"; + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:jwsString]; + + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *certificateString = @"MIIDXTCCAkWgAwIBAgIQbS4C4BSig7uuJ5uDpeT4VjANBgkqhkiG9w0BAQsFADBH" + "MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEX" + "MBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE0ODQ5WhcNMjcxMjMx" + "MTQwMDAwWjBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYH" + "ZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwggEiMA0GCSqGSIb3DQEB" + "AQUAA4IBDwAwggEKAoIBAQCfgQ+0A4Jz0CWR5Ac/MdK2ABuCzttNkvBQFl1Hz8q4" + "o8Qct3isdVN5P475dXaNGiN02HElZMO813uepDRUSJlAfP8AmZIKkxokxEFIUqsp" + "vbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0CmvnJ1AuseqQceKDdEGit1pnoCP6gEe" + "oUQdik97tOl7459V8d3UTpxLozUVlwPU00tgPmUUek8j1tPAmWx17e6EaoLRkK4Q" + "eDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++eJQUArC4IuoqdLYFjB2r+bNKdstj" + "uH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzmAd2s0/nPAgMBAAGjRTBDMBIGA1Ud" + "EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTDgwKdvAPq" + "bbCmehDaw0PwavI83jANBgkqhkiG9w0BAQsFAAOCAQEAOUcKqpzNQ6lr0PbDSsns" + "D6onfi+8j3TD0xG0zBSf+8G4zs8Zb6vzzQ5qHKgfr4aeen8Pw0cw2KKUJ2dFaBqj" + "n3/6/MIZbgaBvXKUbmY8xCxKQ+tOFc3KWIu4pSaO50tMPJjU/lP35bv19AA9vs9M" + "TKY2qLf88bmoNYT3W8VSDcB58KBHa7HVIPx7BUUtSyb2N2Jqx5AOiYy4NarhB3hV" + "ftkZBmCzi2Qw50KWIgTFYcIVeRTx3Js/F0IuEdgZHBK2gmO7fdM7+QKYm83401vl" + "YRNCXfIZ0H9E1V3NddqJuqIutdUajckSzMhXdNCJqfI4FAQAymTWGL3/lZyr/30x" + "Fg=="; + + XCTAssertFalse([STDSDirectoryServerCertificate verifyJSONWebSignature:jws withRootCertificates:@[certificateString]], @"Incorrectly validated PS256 signature."); +} + +- (void)testVerifyES256Signature { + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *jwsString = @"eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlDclRDQ0FaV2dBd0lCQWdJUWJTNEM0QlNp" + "Zzd1dUo1dURwZVQ0V1RBTkJna3Foa2lHOXcwQkFRc0ZBREJITVJNd0VRWUtDWkltaVpQ" + "eUxHUUJHUllEWTI5dE1SY3dGUVlLQ1pJbWlaUHlMR1FCR1JZSFpYaGhiWEJzWlRFWE1C" + "VUdBMVVFQXd3T1VsTkJJRVY0WVcxd2JHVWdSRk13SGhjTk1UY3hNVEl4TVRVME16STNX" + "aGNOTWpjeE1qTXhNVE16TURBd1dqQkhNUk13RVFZS0NaSW1pWlB5TEdRQkdSWURZMjl0" + "TVJjd0ZRWUtDWkltaVpQeUxHUUJHUllIWlhoaGJYQnNaVEVYTUJVR0ExVUVBd3dPUlVN" + "Z1JYaGhiWEJzWlNCQlExTXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FB" + "VGZvZml3YzZBaXUxWWc1dkc5ZUtYSGVEQ1ZoOWgzVk1xTjIveUoxQ1dHVWlwOEJqOHEr" + "ZXJPbzc0dHQ2akVUTXpBYVRwekxaNW9HRFpjZVpmR21NN2RvMkF3WGpBTUJnTlZIUk1C" + "QWY4RUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQWRCZ05WSFE0RUZnUVUwQVd0REhS" + "L3ZsUXJSQXo0YUtnSkJsbkZqRXN3SHdZRFZSMGpCQmd3Rm9BVXc0TUNuYndENm0yd3Bu" + "b1Eyc05EOEdyeVBONHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRXFsRVJld1VDZUV0" + "dEFrQzBGMTZIamp4ZnYxV2E4bmFEbWFSTDk5UTAvcXFVTjh3MHF3cEFQRjd3bjJhZkxm" + "YUdkKzV1WkViMVROWXdWOUF3OUwvczNCY1NURVJJbDZPRVduK3g3Y3RPbUh5MnZ2N21p" + "dGFVcmlsZUdvZGVubS9mYURkeTVWZ0tZaitLc01WTTJzTlZhZWtYK1Qwc3dBQ1g5Qjkw" + "dW5aeGE2MjU2dDJPSjJRVjV6dTNzWU8xTjBqOXY3K3lGK0ZneDAxNE5ydzcvWHQ4SUxH" + "RjU4TnhiUWhraGtmV1NmSHRhRTVtb0JBYldSdUZURmJrQmY0NVNLZTBVTWlVNUxhYzl4" + "STBPN1hDRCt6TkI1bXdzNE5PMkFZdnl4SHE5WCthNjRJaFhjbFhuZ1BRTXJVcU1vTFdJ" + "MTY2Z1JKU3ZRRVdzSUxJVXR4MndzaVlzPSJdfQ" + "." + "eyJBQ1MgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFUKSI6eyJrdHkiOiJFQyIsImNydiI6" + "IlAtMjU2IiwieCI6Im1QVUtUX2JBV0dISWhnMFRwampxVnNQMXJYV1F1X3Z3Vk9ISHRO" + "a2RZb0EiLCJ5IjoiOEJRQXNJbUdlQVM0NmZ5V3c1TWhmR1RUMElqQnBGdzJTUzM0RHY0" + "SXJzIix9LCJTREsgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFDKSI6eyJrdHkiOiJFQyIs" + "ImNydiI6IlAtMjU2IiwieCI6IlplMmxvU1Yzd3Jyb0tVTl80emh3R2hDcW8zWGh1MXRk" + "NFFqZVE1d0lWUjAiLCJ5IjoiSGxMdGRYQVJZX2Y1NUEzZm56UWJQY202aGdyMzRNcDhw" + "LW51elFDRTBadyIsfSwiQUNTIFVSTCI6Imh0dHA6Ly9hY3NzZXJ2ZXIuZG9tYWlubmFt" + "ZS5jb20ifQ" + "." + "KMNtxy3eZMDnVRK_UZvFtRY8fDuAAHJXHCaKncoViB3dy56u5VOT0XnB8K-" + "0nWwFhwWmwU1xGVwimBcfxNplCA"; + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:jwsString]; + + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *certificateString = @"MIIDXTCCAkWgAwIBAgIQbS4C4BSig7uuJ5uDpeT4VjANBgkqhkiG9w0BAQsFADBH" + "MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEX" + "MBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE0ODQ5WhcNMjcxMjMx" + "MTQwMDAwWjBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYH" + "ZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwggEiMA0GCSqGSIb3DQEB" + "AQUAA4IBDwAwggEKAoIBAQCfgQ+0A4Jz0CWR5Ac/MdK2ABuCzttNkvBQFl1Hz8q4" + "o8Qct3isdVN5P475dXaNGiN02HElZMO813uepDRUSJlAfP8AmZIKkxokxEFIUqsp" + "vbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0CmvnJ1AuseqQceKDdEGit1pnoCP6gEe" + "oUQdik97tOl7459V8d3UTpxLozUVlwPU00tgPmUUek8j1tPAmWx17e6EaoLRkK4Q" + "eDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++eJQUArC4IuoqdLYFjB2r+bNKdstj" + "uH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzmAd2s0/nPAgMBAAGjRTBDMBIGA1Ud" + "EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTDgwKdvAPq" + "bbCmehDaw0PwavI83jANBgkqhkiG9w0BAQsFAAOCAQEAOUcKqpzNQ6lr0PbDSsns" + "D6onfi+8j3TD0xG0zBSf+8G4zs8Zb6vzzQ5qHKgfr4aeen8Pw0cw2KKUJ2dFaBqj" + "n3/6/MIZbgaBvXKUbmY8xCxKQ+tOFc3KWIu4pSaO50tMPJjU/lP35bv19AA9vs9M" + "TKY2qLf88bmoNYT3W8VSDcB58KBHa7HVIPx7BUUtSyb2N2Jqx5AOiYy4NarhB3hV" + "ftkZBmCzi2Qw50KWIgTFYcIVeRTx3Js/F0IuEdgZHBK2gmO7fdM7+QKYm83401vl" + "YRNCXfIZ0H9E1V3NddqJuqIutdUajckSzMhXdNCJqfI4FAQAymTWGL3/lZyr/30x" + "Fg=="; + + XCTAssertTrue([STDSDirectoryServerCertificate verifyJSONWebSignature:jws withRootCertificates:@[certificateString]], @"Failed to verify valid ES256 signature."); +} + +- (void)testVerifyES256SignatureFail { + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *jwsString = @"eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlDclRDQ0FaV2dBd0lCQWdJUWJTNEM0QlNp" + "Zzd1dUo1dURwZVQ0V1RBTkJna3Foa2lHOXcwQkFRc0ZBREJITVJNd0VRWUtDWkltaVpQ" + "eUxHUUJHUllEWTI5dE1SY3dGUVlLQ1pJbWlaUHlMR1FCR1JZSFpYaGhiWEJzWlRFWE1C" + "VUdBMVVFQXd3T1VsTkJJRVY0WVcxd2JHVWdSRk13SGhjTk1UY3hNVEl4TVRVME16STNX" + "aGNOTWpjeE1qTXhNVE16TURBd1dqQkhNUk13RVFZS0NaSW1pWlB5TEdRQkdSWURZMjl0" + "TVJjd0ZRWUtDWkltaVpQeUxHUUJHUllIWlhoaGJYQnNaVEVYTUJVR0ExVUVBd3dPUlVN" + "Z1JYaGhiWEJzWlNCQlExTXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FB" + "VGZvZml3YzZBaXUxWWc1dkc5ZUtYSGVEQ1ZoOWgzVk1xTjIveUoxQ1dHVWlwOEJqOHEr" + "ZXJPbzc0dHQ2akVUTXpBYVRwekxaNW9HRFpjZVpmR21NN2RvMkF3WGpBTUJnTlZIUk1C" + "QWY4RUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQWRCZ05WSFE0RUZnUVUwQVd0REhS" + "L3ZsUXJSQXo0YUtnSkJsbkZqRXN3SHdZRFZSMGpCQmd3Rm9BVXc0TUNuYndENm0yd3Bu" + "b1Eyc05EOEdyeVBONHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRXFsRVJld1VDZUV0" + "dEFrQzBGMTZIamp4ZnYxV2E4bmFEbWFSTDk5UTAvcXFVTjh3MHF3cEFQRjd3bjJhZkxm" + "YUdkKzV1WkViMVROWXdWOUF3OUwvczNCY1NURVJJbDZPRVduK3g3Y3RPbUh5MnZ2N21p" + "dGFVcmlsZUdvZGVubS9mYURkeTVWZ0tZaitLc01WTTJzTlZhZWtYK1Qwc3dBQ1g5Qjkw" + "dW5aeGE2MjU2dDJPSjJRVjV6dTNzWU8xTjBqOXY3K3lGK0ZneDAxNE5ydzcvWHQ4SUxH" + "RjU4TnhiUWhraGtmV1NmSHRhRTVtb0JBYldSdUZURmJrQmY0NVNLZTBVTWlVNUxhYzl4" + "STBPN1hDRCt6TkI1bXdzNE5PMkFZdnl4SHE5WCthNjRJaFhjbFhuZ1BRTXJVcU1vTFdJ" + "MTY2Z1JKU3ZRRVdzSUxJVXR4MndzaVlzPSJdfQ" + "." + "eyJBQ1MgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFUKSI6eyJrdHkiOiJFQyIsImNydiI6" + "IlAtMjU2IiwieCI6Im1QVUtUX2JBV0dISWhnMFRwampxVnNQMXJYV1F1X3Z3Vk9ISHRO" + "a2RZb0EiLCJ5IjoiOEJRQXNJbUdlQVM0NmZ5V3c1TWhmR1RUMElqQnBGdzJTUzM0RHY0" + "SXJzIix9LCJTREsgRXBoZW1lcmFsIFB1YmxpYyBLZXkgKFFDKSI6eyJrdHkiOiJFQyIs" + "ImNydiI6IlAtMjU2IiwieCI6IlplMmxvU1Yzd3Jyb0tVTl80emh3R2hDcW8zWGh1MXRk" + "NFFqZVE1d0lWUjAiLCJ5IjoiSGxMdGRYQVJZX2Y1NUEzZm56UWJQY202aGdyMzRNcDhw" + "LW51elFDRTBadyIsfSwiQUNTIFVSTCI6Imh0dHA6Ly9hY3NzZXJ2ZXIuZG9tYWlubMFt" // This is all the same as testVerifyPS256Signature, except this line where "bmFt" became "bMFt" + "ZS5jb20ifQ" + "." + "KMNtxy3eZMDnVRK_UZvFtRY8fDuAAHJXHCaKncoViB3dy56u5VOT0XnB8K-" + "0nWwFhwWmwU1xGVwimBcfxNplCA"; + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:jwsString]; + + // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSString *certificateString = @"MIIDXTCCAkWgAwIBAgIQbS4C4BSig7uuJ5uDpeT4VjANBgkqhkiG9w0BAQsFADBH" + "MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEX" + "MBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwHhcNMTcxMTIxMTE0ODQ5WhcNMjcxMjMx" + "MTQwMDAwWjBHMRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYH" + "ZXhhbXBsZTEXMBUGA1UEAwwOUlNBIEV4YW1wbGUgRFMwggEiMA0GCSqGSIb3DQEB" + "AQUAA4IBDwAwggEKAoIBAQCfgQ+0A4Jz0CWR5Ac/MdK2ABuCzttNkvBQFl1Hz8q4" + "o8Qct3isdVN5P475dXaNGiN02HElZMO813uepDRUSJlAfP8AmZIKkxokxEFIUqsp" + "vbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0CmvnJ1AuseqQceKDdEGit1pnoCP6gEe" + "oUQdik97tOl7459V8d3UTpxLozUVlwPU00tgPmUUek8j1tPAmWx17e6EaoLRkK4Q" + "eDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++eJQUArC4IuoqdLYFjB2r+bNKdstj" + "uH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzmAd2s0/nPAgMBAAGjRTBDMBIGA1Ud" + "EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTDgwKdvAPq" + "bbCmehDaw0PwavI83jANBgkqhkiG9w0BAQsFAAOCAQEAOUcKqpzNQ6lr0PbDSsns" + "D6onfi+8j3TD0xG0zBSf+8G4zs8Zb6vzzQ5qHKgfr4aeen8Pw0cw2KKUJ2dFaBqj" + "n3/6/MIZbgaBvXKUbmY8xCxKQ+tOFc3KWIu4pSaO50tMPJjU/lP35bv19AA9vs9M" + "TKY2qLf88bmoNYT3W8VSDcB58KBHa7HVIPx7BUUtSyb2N2Jqx5AOiYy4NarhB3hV" + "ftkZBmCzi2Qw50KWIgTFYcIVeRTx3Js/F0IuEdgZHBK2gmO7fdM7+QKYm83401vl" + "YRNCXfIZ0H9E1V3NddqJuqIutdUajckSzMhXdNCJqfI4FAQAymTWGL3/lZyr/30x" + "Fg=="; + + XCTAssertFalse([STDSDirectoryServerCertificate verifyJSONWebSignature:jws withRootCertificates:@[certificateString]], @"Verified invalid ES256 signature."); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSEllipticCurvePointTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSEllipticCurvePointTests.m new file mode 100644 index 00000000..a315dcd8 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSEllipticCurvePointTests.m @@ -0,0 +1,42 @@ +// +// STDSEllipticCurvePointTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 4/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "NSString+JWEHelpers.h" +#import "STDSEllipticCurvePoint.h" + +@interface STDSEllipticCurvePointTests : XCTestCase + +@end + +@implementation STDSEllipticCurvePointTests + +- (void)testInitWithJWK { + + STDSEllipticCurvePoint *ecPoint = [[STDSEllipticCurvePoint alloc] initWithJWK:@{ // ref. EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + @"kty":@"EC", + @"crv":@"P-256", + @"x":@"mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA", + @"y":@"8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs", + }]; + + XCTAssertNotNil(ecPoint, @"Failed to create point with valid jwk"); + XCTAssertEqualObjects(ecPoint.x, [@"mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA" _stds_base64URLDecodedData], @"Parsed incorrect x-coordinate"); + XCTAssertEqualObjects(ecPoint.y, [@"8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs" _stds_base64URLDecodedData], @"Parsed incorrect y-coordinate"); + + ecPoint = [[STDSEllipticCurvePoint alloc] initWithJWK:@{ + @"kty":@"EC", + @"crv":@"P-128", + @"x":@"mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA", + @"y":@"8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs", + }]; + XCTAssertNil(ecPoint, @"Shoud return nil for non P-256 curve."); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSEphemeralKeyPairTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSEphemeralKeyPairTests.m new file mode 100644 index 00000000..5d8cb194 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSEphemeralKeyPairTests.m @@ -0,0 +1,41 @@ +// +// STDSEphemeralKeyPairTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 3/26/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSDirectoryServerCertificate.h" +#import "STDSEllipticCurvePoint.h" +#import "STDSEphemeralKeyPair+Testing.h" +#import "STDSSecTypeUtilities.h" + +@interface STDSEphemeralKeyPairTests : XCTestCase + +@end + +@implementation STDSEphemeralKeyPairTests + +- (void)testCreateEpehemeralKeyPair { + STDSEphemeralKeyPair *keyPair = [STDSEphemeralKeyPair ephemeralKeyPair]; + XCTAssertNotNil(keyPair.publicKeyJWK, @"Failed to create a valid public key JWK"); + STDSEphemeralKeyPair *keyPair2 = [STDSEphemeralKeyPair ephemeralKeyPair]; + XCTAssertNotEqual(keyPair.publicKeyJWK, keyPair2.publicKeyJWK, @"Failed sanity check that two different ephemeral key pairs don't have the same public key JWK."); +} + +- (void)testDiffieHellmanSharedSecret { + // values from EMVCo_3DS_-AppBased_CryptoExamples_082018.pdf + NSDictionary *jwk = [NSJSONSerialization JSONObjectWithData:[@"{\"kty\":\"EC\",\"crv\":\"P-256\",\"kid\":\"UUIDkeyidentifierforDS-EC\", \"x\":\"2_v-MuNZccqwM7PXlakW9oHLP5XyrjMG1UVS8OxYrgA\", \"y\":\"rm1ktLmFIsP2R0YyJGXtsCbaTUesUK31Xc04tHJRolc\"}" dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL]; + + STDSEllipticCurvePoint *publicKey = [[STDSEllipticCurvePoint alloc] initWithJWK:jwk]; + STDSEphemeralKeyPair *keyPair = [STDSEphemeralKeyPair testKeyPair]; + NSData *secret = [keyPair createSharedSecretWithEllipticCurveKey:publicKey]; + const unsigned char expectedSecretBytes[] = {0x5C, 0x32, 0xBC, 0x13, 0xF8, 0xEC, 0xEB, 0x14, 0x8A, 0xBA, 0xF2, 0xA6, 0xB9, 0xDD, 0x1F, 0x68, 0x91, 0xBB, 0x2A, 0x80, 0xAB, 0x09, 0x34, 0x7C, 0x64, 0x06, 0x82, 0x31, 0xA5, 0x9E, 0x8C, 0xA2}; + NSData *expectedSecret = [[NSData alloc] initWithBytes:expectedSecretBytes length:32]; + XCTAssertEqualObjects(secret, expectedSecret, @"Generated incorrect shared secret value"); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSErrorMessageTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSErrorMessageTest.m new file mode 100644 index 00000000..4ff3bff4 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSErrorMessageTest.m @@ -0,0 +1,61 @@ +// +// STDSErrorMessageTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSErrorMessage.h" +#import "STDSJSONEncoder.h" +#import "STDSTestJSONUtils.h" + +@interface STDSErrorMessageTest : XCTestCase + +@end + +@implementation STDSErrorMessageTest + +#pragma mark - STDSJSONDecodable + +- (void)testSuccessfulDecode { + NSDictionary *json = [STDSTestJSONUtils jsonNamed:@"ErrorMessage"]; + NSError *error; + STDSErrorMessage *errorMessage = [STDSErrorMessage decodedObjectFromJSON:json error:&error]; + + XCTAssertNil(error); + XCTAssertNotNil(errorMessage); + XCTAssertEqualObjects(errorMessage.errorCode, @"203"); + XCTAssertEqualObjects(errorMessage.errorComponent, @"A"); + XCTAssertEqualObjects(errorMessage.errorDescription, @"Data element not in the required format. Not numeric or wrong length."); + XCTAssertEqualObjects(errorMessage.errorDetails, @"billAddrCountry,billAddrPostCode,dsURL"); + XCTAssertEqualObjects(errorMessage.errorMessageType, @"AReq"); + XCTAssertEqualObjects(errorMessage.messageVersion, @"2.2.0"); + +} + +#pragma mark - STDSJSONEncodable + +- (void)testPropertyNamesToJSONKeysMapping { + STDSErrorMessage *params = [STDSErrorMessage new]; + + NSDictionary *mapping = [STDSErrorMessage propertyNamesToJSONKeysMapping]; + + for (NSString *propertyName in [mapping allKeys]) { + XCTAssertFalse([propertyName containsString:@":"]); + XCTAssert([params respondsToSelector:NSSelectorFromString(propertyName)]); + } + + for (NSString *formFieldName in [mapping allValues]) { + XCTAssert([formFieldName isKindOfClass:[NSString class]]); + XCTAssert([formFieldName length] > 0); + } + + XCTAssertEqual([[mapping allValues] count], [[NSSet setWithArray:[mapping allValues]] count]); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:[STDSJSONEncoder dictionaryForObject:params]]); +} + + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSJSONEncoderTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSJSONEncoderTest.m new file mode 100644 index 00000000..a42c70d9 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSJSONEncoderTest.m @@ -0,0 +1,201 @@ +// +// STDSJSONEncoderTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/25/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSJSONEncoder.h" + +#pragma mark - STDSJSONEncodableObject + +@interface STDSJSONEncodableObject : NSObject +@property (nonatomic, copy) NSString *testProperty; +@property (nonatomic, copy) NSArray *testArrayProperty; +@property (nonatomic, copy) NSDictionary *testDictionaryProperty; +@property (nonatomic) STDSJSONEncodableObject *testNestedObjectProperty; +@end + +@implementation STDSJSONEncodableObject + ++ (NSDictionary *)propertyNamesToJSONKeysMapping { + return @{ + NSStringFromSelector(@selector(testProperty)): @"test_property", + NSStringFromSelector(@selector(testArrayProperty)): @"test_array_property", + NSStringFromSelector(@selector(testDictionaryProperty)): @"test_dictionary_property", + NSStringFromSelector(@selector(testNestedObjectProperty)): @"test_nested_property", + }; +} + +@end + +#pragma mark - STDSJSONEncoderTest + +@interface STDSJSONEncoderTest : XCTestCase +@end + +@implementation STDSJSONEncoderTest + +- (void)testEmptyEncodableObject { + STDSJSONEncodableObject *object = [STDSJSONEncodableObject new]; + XCTAssertEqualObjects([STDSJSONEncoder dictionaryForObject:object], @{}); +} + +- (void)testNestedObject { + STDSJSONEncodableObject *object = [STDSJSONEncodableObject new]; + STDSJSONEncodableObject *nestedObject = [STDSJSONEncodableObject new]; + nestedObject.testProperty = @"nested_object_property"; + object.testProperty = @"object_property"; + object.testNestedObjectProperty = nestedObject; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:object]; + NSDictionary *expected = @{ + @"test_property": @"object_property", + @"test_nested_property": @{ + @"test_property": @"nested_object_property", + } + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); +} + +- (void)testSerializeDeserialize { + STDSJSONEncodableObject *object = [STDSJSONEncodableObject new]; + object.testProperty = @"test"; + NSDictionary *expected = @{@"test_property": @"test"}; + NSData *data = [NSJSONSerialization dataWithJSONObject:[STDSJSONEncoder dictionaryForObject:object] options:0 error:nil]; + id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + XCTAssertEqualObjects(expected, jsonObject); +} + +- (void)testBoolAndNumbers { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + testObject.testArrayProperty = @[@0, + @1, + [NSNumber numberWithBool:NO], + [[NSNumber alloc] initWithBool:YES], + @YES]; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_array_property": @[ + @0, + @1, + [NSNumber numberWithBool:NO], + [[NSNumber alloc] initWithBool:YES], + @YES], + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); + +} + +#pragma mark NSArray + +- (void)testArrayValueEmpty { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + testObject.testProperty = @"success"; + testObject.testArrayProperty = @[]; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_property": @"success", + @"test_array_property": @[] + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); +} + +- (void)testArrayValue { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + testObject.testProperty = @"success"; + testObject.testArrayProperty = @[@1, @2, @3]; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_property": @"success", + @"test_array_property": @[@1, @2, @3] + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); + +} + +- (void)testArrayOfEncodable { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + + STDSJSONEncodableObject *inner1 = [STDSJSONEncodableObject new]; + inner1.testProperty = @"inner1"; + STDSJSONEncodableObject *inner2 = [STDSJSONEncodableObject new]; + inner2.testArrayProperty = @[@"inner2"]; + + testObject.testArrayProperty = @[inner1, inner2]; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_array_property": @[ + @{ + @"test_property": @"inner1" + }, + @{ + @"test_array_property": @[@"inner2"] + } + ] + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); +} + +#pragma mark NSDictionary + +- (void)testDictionaryValueEmpty { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + testObject.testProperty = @"success"; + testObject.testDictionaryProperty = @{}; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_property": @"success", + @"test_dictionary_property": @{} + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); +} + +- (void)testDictionaryValue { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + testObject.testProperty = @"success"; + testObject.testDictionaryProperty = @{@"foo": @"bar"}; + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_property": @"success", + @"test_dictionary_property": @{@"foo": @"bar"} + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); + +} + +- (void)testDictionaryOfEncodable { + STDSJSONEncodableObject *testObject = [STDSJSONEncodableObject new]; + + STDSJSONEncodableObject *inner1 = [STDSJSONEncodableObject new]; + inner1.testProperty = @"inner1"; + STDSJSONEncodableObject *inner2 = [STDSJSONEncodableObject new]; + inner2.testArrayProperty = @[@"inner2"]; + + testObject.testDictionaryProperty = @{@"one": inner1, @"two": inner2}; + + NSDictionary *jsonDictionary = [STDSJSONEncoder dictionaryForObject:testObject]; + NSDictionary *expected = @{ + @"test_dictionary_property": @{ + @"one": @{ + @"test_property": @"inner1" + }, + @"two": @{ + @"test_array_property": @[@"inner2"] + } + } + }; + XCTAssertEqualObjects(jsonDictionary, expected); + XCTAssertTrue([NSJSONSerialization isValidJSONObject:jsonDictionary]); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSJSONWebEncryptionTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSJSONWebEncryptionTests.m new file mode 100644 index 00000000..9e7cf928 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSJSONWebEncryptionTests.m @@ -0,0 +1,119 @@ +// +// STDSJSONWebEncryptionTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 1/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSStripe3DS2Error.h" +#import "NSString+JWEHelpers.h" +#import "STDSDirectoryServer.h" +#import "STDSDirectoryServerCertificate.h" +#import "STDSJSONWebEncryption.h" +#import "STDSSecTypeUtilities.h" + +@interface STDSJSONWebEncryptionTests : XCTestCase + +@end + +@implementation STDSJSONWebEncryptionTests + +- (void)testEncryption { + NSDictionary *json = @{@"a": @(0), @"b": @[@(0), @(1), @(2)], @"c": @{@"d": @"val"}}; + NSError *error = nil; + + NSString *encrypted = [STDSJSONWebEncryption encryptJSON:json forDirectoryServer:STDSDirectoryServerSTPTestRSA error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with ul_test cert."); + XCTAssertNil(error, @"Successful encryption should not populate error."); + + encrypted = [STDSJSONWebEncryption encryptJSON:json forDirectoryServer:STDSDirectoryServerSTPTestEC error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with ec_test cert."); + XCTAssertNil(error, @"Successful encryption should not populate error."); + + encrypted = [STDSJSONWebEncryption encryptJSON:json forDirectoryServer:STDSDirectoryServerULTestRSA error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with STDSDirectoryServerULTestRSA."); + XCTAssertNil(error, @"Successful encryption should not populate error."); + + encrypted = [STDSJSONWebEncryption encryptJSON:json forDirectoryServer:STDSDirectoryServerULTestEC error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with STDSDirectoryServerULTestEC."); + XCTAssertNil(error, @"Successful encryption should not populate error."); + + encrypted = [STDSJSONWebEncryption encryptJSON:json forDirectoryServer:STDSDirectoryServerUnknown error:&error]; + XCTAssertNil(encrypted, @"Invalid server ID should return nil."); + XCTAssertEqual(error.code, STDSErrorCodeDecryptionVerification, @"Failed encryption should provide a STDSErrorCodeDecryptionVerification error."); +} + +- (void)testEncryptionWithCustomCertificate { + NSDictionary *json = @{@"a": @(0), @"b": @[@(0), @(1), @(2)], @"c": @{@"d": @"val"}}; + NSError *error = nil; + // Using ds-amex.pm.PEM + NSString *certificateString = @"MIIE0TCCA7mgAwIBAgIUXbeqM1duFcHk4dDBwT8o7Ln5wX8wDQYJKoZIhvcNAQEL" + "BQAwXjELMAkGA1UEBhMCVVMxITAfBgNVBAoTGEFtZXJpY2FuIEV4cHJlc3MgQ29t" + "cGFueTEsMCoGA1UEAxMjQW1lcmljYW4gRXhwcmVzcyBTYWZla2V5IElzc3Vpbmcg" + "Q0EwHhcNMTgwMjIxMjM0OTMxWhcNMjAwMjIxMjM0OTMwWjCB0DELMAkGA1UEBhMC" + "VVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9yazE/MD0GA1UE" + "ChM2QW1lcmljYW4gRXhwcmVzcyBUcmF2ZWwgUmVsYXRlZCBTZXJ2aWNlcyBDb21w" + "YW55LCBJbmMuMTkwNwYDVQQLEzBHbG9iYWwgTmV0d29yayBUZWNobm9sb2d5IC0g" + "TmV0d29yayBBUEkgUGxhdGZvcm0xHzAdBgNVBAMTFlNESy5TYWZlS2V5LkVuY3J5" + "cHRLZXkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSFF9kTYbwRrxX" + "C6WcJJYio5TZDM62+CnjQRfggV3GMI+xIDtMIN8LL/jbWBTycu97vrNjNNv+UPhI" + "WzhFDdUqyRfrY337A39uE8k1xhdDI3dNeZz6xgq8r9hn2NBou78YPBKidpN5oiHn" + "TxcFq1zudut2fmaldaa9a4ZKgIQo+02heiJfJ8XNWkoWJ17GcjJ59UU8C1KF/y1G" + "ymYO5ha2QRsVZYI17+ZFsqnpcXwK4Mr6RQKV6UimmO0nr5++CgvXfekcWAlLV6Xq" + "juACWi3kw0haepaX/9qHRu1OSyjzWNcSVZ0On6plB5Lq6Y9ylgmxDrv+zltz3MrT" + "K7txIAFFAgMBAAGjggESMIIBDjAMBgNVHRMBAf8EAjAAMCEGA1UdEQQaMBiCFlNE" + "Sy5TYWZlS2V5LkVuY3J5cHRLZXkwRQYJKwYBBAGCNxQCBDgeNgBBAE0ARQBYAF8A" + "UwBBAEYARQBLAEUAWQAyAF8ARABTAF8ARQBOAEMAUgBZAFAAVABJAE8ATjAOBgNV" + "HQ8BAf8EBAMCBJAwHwYDVR0jBBgwFoAU7k/rXuVMhTBxB1zSftPgmLFuDIgwRAYD" + "VR0fBD0wOzA5oDegNYYzaHR0cDovL2FtZXhzay5jcmwuY29tLXN0cm9uZy1pZC5u" + "ZXQvYW1leHNhZmVrZXkuY3JsMB0GA1UdDgQWBBQHclVTo5nwZGH8labJ2F2P45xi" + "fDANBgkqhkiG9w0BAQsFAAOCAQEAWY6b77VBoGLs3k5vOqSU7QRqT+4v6y77T8LA" + "BKrSZ58DiVZWVyDSxyftQUiRRgFHt2gTN0yfJTP50Fyp84nCEWC0tugZ4iIhgPss" + "HzL+4/u4eG/MTzK2ESxvPgr6YHajyuU+GXA89u8+bsFrFmojOjhTgFKli7YUeV/0" + "xoiYZf2utlns800ofJrcrfiFoqE6PvK4Od0jpeMgfSKv71nK5ihA1+wTk76ge1fs" + "PxL23hEdRpWW11ofaLfJGkLFXMM3/LHSXWy7HhsBgDELdzLSHU4VkSv8yTOZxsRO" + "ByxdC5v3tXGcK56iQdtKVPhFGOOEBugw7AcuRzv3f1GhvzAQZg=="; + STDSDirectoryServerCertificate *certificate = [STDSDirectoryServerCertificate customCertificateWithData:[[NSData alloc] initWithBase64EncodedString:certificateString options:0]]; + + NSString *encrypted = [STDSJSONWebEncryption encryptJSON:json + withCertificate:certificate + directoryServerID:@"custom_test" + serverKeyID:nil + error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with custom cert."); + XCTAssertNil(error, @"Successful encryption should not populate error."); +} + +- (void)testDirectEncryptionWithKey { + NSDictionary *json = @{@"a": @(0), @"b": @[@(0), @(1), @(2)], @"c": @{@"d": @"val"}}; + NSError *error = nil; + NSData *contentEncryptionKey = STDSCryptoRandomData(32); + NSString *encrypted = [STDSJSONWebEncryption directEncryptJSON:json + withContentEncryptionKey:contentEncryptionKey + forACSTransactionID:@"acs_id" + error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with direct encryption key."); + XCTAssertNil(error, @"Successful encryption should not populate error."); +} + +- (void)testDecryption { + NSDictionary *json = @{@"a": @(0), @"b": @[@(0), @(1), @(2)], @"c": @{@"d": @"val"}}; + NSError *error = nil; + NSData *contentEncryptionKey = STDSCryptoRandomData(32); + NSString *encrypted = [STDSJSONWebEncryption directEncryptJSON:json + withContentEncryptionKey:contentEncryptionKey + forACSTransactionID:@"acs_id" + error:&error]; + XCTAssertNotNil(encrypted, @"Should successfully encrypt with direct encryption key."); + XCTAssertNil(error, @"Successful encryption should not populate error."); + + NSDictionary *decrypted = [STDSJSONWebEncryption decryptData:[encrypted dataUsingEncoding:NSUTF8StringEncoding] + withContentEncryptionKey:contentEncryptionKey + error:&error]; + XCTAssertEqualObjects(decrypted, json, @"Decrypted is not equal to the encrypted json"); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSJSONWebSignatureTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSJSONWebSignatureTests.m new file mode 100644 index 00000000..c4587102 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSJSONWebSignatureTests.m @@ -0,0 +1,78 @@ +// +// STDSJSONWebSignatureTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 4/2/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "NSString+JWEHelpers.h" +#import "STDSEllipticCurvePoint.h" +#import "STDSJSONWebSignature.h" + +@interface STDSJSONWebSignatureTests : XCTestCase + +@end + +@implementation STDSJSONWebSignatureTests + +- (void)testInitES256 { + + // generated a private ec key and certificate, plugged into jwt.io with default sample payload. + // This certificate will expire in 2030 but as this test doesn't cover certificate validity + // it shouldn't start failing + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:@"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUh3TUlHV0Fna0ErdEM1LzJxV1RqRXdDZ1lJS29aSXpqMEVBd0l3QURBZUZ3MHlNVEF4TURReU1UQTBNamxhRncwek1UQXhNREl5TVRBME1qbGFNQUF3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFSV3oram42NUJ0T012ZHlIS2N2akJlQlNEWkgycjFSVHdqbVlTaTlSL3pwQm51UTRFaU1uQ3FmTVBXaVpxQjRRZGJBZDBFN29INTBWcHVaMVAwODdHTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDSVFETTVRbHRDTFhEeEpvTG1EVXRqREgxZEJQVHBUVG1jS2pjOHlodVp1VHU2UUloQVBEU0cvN3plV09NdkhxNUpaWk8zd3JQeVBhTFlVNHBCcGpWTS95YzQ5MDciXX0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.71MhQ7FJavv1nQ7Boujfp7K0iBEYFGSGLZ3osnL9KAY9scF95Hf7ZMQ8I1JSgnGl227UY96is80MlbTijOOxsg"]; + + XCTAssertNotNil(jws, @"Failed to create jws object"); + + XCTAssertEqual(jws.algorithm, STDSJSONWebSignatureAlgorithmES256, @"Parsed incorrect algorithm"); + + NSData *digest = [@"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUh3TUlHV0Fna0ErdEM1LzJxV1RqRXdDZ1lJS29aSXpqMEVBd0l3QURBZUZ3MHlNVEF4TURReU1UQTBNamxhRncwek1UQXhNREl5TVRBME1qbGFNQUF3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFSV3oram42NUJ0T012ZHlIS2N2akJlQlNEWkgycjFSVHdqbVlTaTlSL3pwQm51UTRFaU1uQ3FmTVBXaVpxQjRRZGJBZDBFN29INTBWcHVaMVAwODdHTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDSVFETTVRbHRDTFhEeEpvTG1EVXRqREgxZEJQVHBUVG1jS2pjOHlodVp1VHU2UUloQVBEU0cvN3plV09NdkhxNUpaWk8zd3JQeVBhTFlVNHBCcGpWTS95YzQ5MDciXX0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0" dataUsingEncoding:NSUTF8StringEncoding]; + XCTAssertEqualObjects(jws.digest, digest, @"Parsed payload incorrectly."); + NSData *signature = [@"71MhQ7FJavv1nQ7Boujfp7K0iBEYFGSGLZ3osnL9KAY9scF95Hf7ZMQ8I1JSgnGl227UY96is80MlbTijOOxsg" _stds_base64URLDecodedData]; + XCTAssertEqualObjects(jws.signature, signature, @"Parsed signature incorrectly."); + NSData *payload = [@"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0" _stds_base64URLDecodedData]; + XCTAssertEqualObjects(jws.payload, payload, @"Parsed payload incorrectly."); + + XCTAssertNotNil(jws.ellipticCurvePoint, @"Failed to parse elliptic curve point."); + XCTAssertNotNil(jws.certificateChain, @"Must have certificate chain."); + + const unsigned char keyBytes[] = {0x11, 0x5b, 0x3f, 0xa3, 0x9f, 0xae, 0x41, 0xb4, 0xe3, 0x2f, 0x77, 0x21, 0xca, 0x72, + 0xf8, 0xc1, 0x78, 0x14, 0x83, 0x64, 0x7d, 0xab, 0xd5, 0x14, 0xf0, 0x8e, 0x66, 0x12, 0x8b, + 0xd4, 0x7f, 0xce, 0x90, 0x67, 0xb9, 0x0e, 0x04, 0x88, 0xc9, 0xc2, 0xa9, 0xf3, 0x0f, 0x5a, + 0x26, 0x6a, 0x07, 0x84, 0x1d, 0x6c, 0x07, 0x74, 0x13, 0xba, 0x07, 0xe7, 0x45, 0x69, 0xb9, + 0x9d, 0x4f, 0xd3, 0xce, 0xc6}; + size_t keyLength = sizeof(keyBytes)/2; + NSData *coordinateX = [NSData dataWithBytes:keyBytes length:keyLength]; + NSData *coordinateY = [NSData dataWithBytes:keyBytes + keyLength length:keyLength]; + + XCTAssertEqualObjects(jws.ellipticCurvePoint.x, coordinateX, @"Incorrect x-point."); + XCTAssertEqualObjects(jws.ellipticCurvePoint.y, coordinateY, @"Incorrect y-point."); +} + +- (void)testInitPS256 { + + // test jws strings generated from jwt.io + STDSJSONWebSignature *jws = [[STDSJSONWebSignature alloc] initWithString:@"eyJhbGciOiJQUzI1NiIsIng1YyI6WyJNSUkiLCJNSUkyIl19.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.CDB63_lCSBlmrIfZBvn6w4rDKJgkmKhFe4mfR-xUnfxf9N0g4vZa0R9lFG5pjVkThq9CX-p-vM_64wG4bAC53VlXOk6DhjzN0LTCo1nB81rd8DgqMH4SkLFy3wP-Xe0akRmXE8iHmv63ip7d2LGQVCD38xwXOnoBUVANCrcsC0Iur1TTEXaEfT6ACwg3V1YTu-vygNdbhYZOC_Q9ESbaoPxOQfumXnD44m1EN_FV3d-uQJx1Rn6w3AkDw34P3KunLrwOMJt1mbkWzb66VDVsIxegc4N8TjJTzvxmCk841wUae3kZ97_HPIEfil3ewv80hZstEE2hcEXJbdBfsxsSqg"]; + + XCTAssertNotNil(jws, @"Failed to create jws object"); + + XCTAssertEqual(jws.algorithm, STDSJSONWebSignatureAlgorithmPS256, @"Parsed incorrect algorithm"); + + NSData *digest = [@"eyJhbGciOiJQUzI1NiIsIng1YyI6WyJNSUkiLCJNSUkyIl19.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0" dataUsingEncoding:NSUTF8StringEncoding]; + XCTAssertEqualObjects(jws.digest, digest, @"Parsed payload incorrectly."); + NSData *signature = [@"CDB63_lCSBlmrIfZBvn6w4rDKJgkmKhFe4mfR-xUnfxf9N0g4vZa0R9lFG5pjVkThq9CX-p-vM_64wG4bAC53VlXOk6DhjzN0LTCo1nB81rd8DgqMH4SkLFy3wP-Xe0akRmXE8iHmv63ip7d2LGQVCD38xwXOnoBUVANCrcsC0Iur1TTEXaEfT6ACwg3V1YTu-vygNdbhYZOC_Q9ESbaoPxOQfumXnD44m1EN_FV3d-uQJx1Rn6w3AkDw34P3KunLrwOMJt1mbkWzb66VDVsIxegc4N8TjJTzvxmCk841wUae3kZ97_HPIEfil3ewv80hZstEE2hcEXJbdBfsxsSqg" _stds_base64URLDecodedData]; + XCTAssertEqualObjects(jws.signature, signature, @"Parsed signature incorrectly."); + NSData *payload = [@"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0" _stds_base64URLDecodedData]; + XCTAssertEqualObjects(jws.payload, payload, @"Parsed payload incorrectly."); + + XCTAssertNil(jws.ellipticCurvePoint, @"Should not create elliptic curve point."); + + NSArray *certChain = @[@"MII", @"MII2"]; + XCTAssertEqualObjects(jws.certificateChain, certChain, @"Failed to parse x5c correctly."); + + +} +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSSecTypeUtilitiesTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSSecTypeUtilitiesTests.m new file mode 100644 index 00000000..9c4c3b2d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSSecTypeUtilitiesTests.m @@ -0,0 +1,142 @@ +// +// STDSSecTypeUtilitiesTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 1/28/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "NSString+JWEHelpers.h" +#import "STDSSecTypeUtilities.h" + +@interface STDSSecTypeUtilitiesTests : XCTestCase + +@end + +@implementation STDSSecTypeUtilitiesTests + +- (void)testDirectoryServerForID { + XCTAssertEqual(STDSDirectoryServerForID(@"ul_test"), STDSDirectoryServerSTPTestRSA, @"ul_test should map to STDSDirectoryServerSTPTestRSA."); + XCTAssertEqual(STDSDirectoryServerForID(@"ec_test"), STDSDirectoryServerSTPTestEC, @"ec_test should map to STDSDirectoryServerSTPTestEC."); + XCTAssertEqual(STDSDirectoryServerForID(@"F055545342"), STDSDirectoryServerULTestRSA, @"F055545342 should map to STDSDirectoryServerULTestRSA."); + XCTAssertEqual(STDSDirectoryServerForID(@"F155545342"), STDSDirectoryServerULTestEC, @"F155545342 should map to STDSDirectoryServerULTestEC."); + + XCTAssertEqual(STDSDirectoryServerForID(@"junk"), STDSDirectoryServerUnknown, @"junk server ID should map to STDSDirectoryServerUnknown."); +} + +- (void)testCertificateForServer { + SecCertificateRef certificate = NULL; + + certificate = STDSCertificateForServer(STDSDirectoryServerSTPTestRSA); + XCTAssertTrue(certificate != NULL, @"Unable to load STDSDirectoryServerSTPTestRSA certificate."); + if (certificate != NULL) { + CFRelease(certificate); + } + certificate = STDSCertificateForServer(STDSDirectoryServerSTPTestEC); + XCTAssertTrue(certificate != NULL, @"Unable to load STDSDirectoryServerSTPTestEC certificate."); + if (certificate != NULL) { + CFRelease(certificate); + } + certificate = STDSCertificateForServer(STDSDirectoryServerUnknown); + if (certificate != NULL) { + XCTFail(@"Should not have an unknown certificate."); + CFRelease(certificate); + } +} + +- (void)testCopyPublicRSAKey { + SecCertificateRef certificate = STDSCertificateForServer(STDSDirectoryServerSTPTestRSA); + if (certificate != NULL) { + SecKeyRef publicKey = SecCertificateCopyKey(certificate); + if (publicKey != NULL) { + CFRelease(publicKey); + } else { + XCTFail(@"Unable to load public key from certificate"); + } + + CFRelease(certificate); + } else { + XCTFail(@"Failed loading certificate for %@", NSStringFromSelector(_cmd)); + } +} + +- (void)testCopyPublicECKey { + SecCertificateRef certificate = STDSCertificateForServer(STDSDirectoryServerSTPTestEC); + if (certificate != NULL) { + SecKeyRef publicKey = SecCertificateCopyKey(certificate); + if (publicKey != NULL) { + CFRelease(publicKey); + } else { + XCTFail(@"Unable to load public key from certificate"); + } + + CFRelease(certificate); + } else { + XCTFail(@"Failed loading certificate for %@", NSStringFromSelector(_cmd)); + } +} + +- (void)testCopyKeyTypeRSA { + SecCertificateRef certificate = STDSCertificateForServer(STDSDirectoryServerSTPTestRSA); + if (certificate != NULL) { + CFStringRef keyType = STDSSecCertificateCopyPublicKeyType(certificate); + if (keyType != NULL) { + XCTAssertTrue(CFStringCompare(keyType, kSecAttrKeyTypeRSA, 0) == kCFCompareEqualTo, @"Key type is incorrect"); + CFRelease(keyType); + } else { + XCTFail(@"Failed to copy key type."); + } + CFRelease(certificate); + } else { + XCTFail(@"Failed loading certificate for %@", NSStringFromSelector(_cmd)); + } +} + +- (void)testCopyKeyTypeEC { + SecCertificateRef certificate = STDSCertificateForServer(STDSDirectoryServerSTPTestEC); + if (certificate != NULL) { + CFStringRef keyType = STDSSecCertificateCopyPublicKeyType(certificate); + if (keyType != NULL) { + XCTAssertTrue(CFStringCompare(keyType, kSecAttrKeyTypeECSECPrimeRandom, 0) == kCFCompareEqualTo, @"Key type is incorrect"); + CFRelease(keyType); + } else { + XCTFail(@"Failed to copy key type."); + } + CFRelease(certificate); + } else { + XCTFail(@"Failed loading certificate for %@", NSStringFromSelector(_cmd)); + } +} + +- (void)testRandomData { + // We're not actually going to implement randomness tests... just sanity + NSData *data1 = STDSCryptoRandomData(32); + NSData *data2 = STDSCryptoRandomData(32); + + XCTAssertNotNil(data1); + XCTAssertTrue(data1.length == 32, @"Random data is not correct length."); + XCTAssertNotEqualObjects(data1, data2, @"Sanity check: two random data's should not equate to equal (unless you get reeeeallly unlucky."); + XCTAssertTrue(STDSCryptoRandomData(12).length == 12, @"Random data is not correct length."); + XCTAssertNotNil(STDSCryptoRandomData(0), @"Empty random data should return empty data."); + XCTAssertTrue(STDSCryptoRandomData(0).length == 0, @"Empty random data should have length 0"); +} + +- (void)testConcatKDFWithSHA256 { + NSData *data = STDSCreateConcatKDFWithSHA256(STDSCryptoRandomData(32), 256, @"acs_identifier"); + XCTAssertNotNil(data, @"Failed to concat KDF and hash."); + XCTAssertEqual(data.length, 256, @"Concat returned data of incorrect length"); +} + + +- (void)testVerifyEllipticCurveP256 { + NSData *payload = [@"eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *signature = [@"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" _stds_base64URLDecodedData]; + + NSData *coordinateX = [@"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU" _stds_base64URLDecodedData]; + NSData *coordinateY = [@"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0" _stds_base64URLDecodedData]; + + XCTAssertTrue(STDSVerifyEllipticCurveP256Signature(coordinateX, coordinateY, payload, signature)); +} +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSSynchronousLocationManagerTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSSynchronousLocationManagerTests.m new file mode 100644 index 00000000..65e2f9a2 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSSynchronousLocationManagerTests.m @@ -0,0 +1,28 @@ +// +// STDSSynchronousLocationManagerTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 1/24/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSSynchronousLocationManager.h" + +@interface STDSSynchronousLocationManagerTests : XCTestCase + +@end + +@implementation STDSSynchronousLocationManagerTests + +- (void)testLocationFetchIsSynchronous { + id originalLocation = [[NSObject alloc] init]; + id location = originalLocation; + + location = [[STDSSynchronousLocationManager sharedManager] deviceLocation]; + // tests that location gets synchronously updated (even if it's to nil due to permissions while running tests) + XCTAssertNotEqual(originalLocation, location); +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSTestJSONUtils.h b/Stripe3DS2/Stripe3DS2Tests/STDSTestJSONUtils.h new file mode 100644 index 00000000..c05e729a --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSTestJSONUtils.h @@ -0,0 +1,19 @@ +// +// STDSTestJSONUtils.h +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface STDSTestJSONUtils : NSObject + ++ (NSDictionary *)jsonNamed:(NSString *)name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSTestJSONUtils.m b/Stripe3DS2/Stripe3DS2Tests/STDSTestJSONUtils.m new file mode 100644 index 00000000..f742e1fc --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSTestJSONUtils.m @@ -0,0 +1,54 @@ +// +// STDSTestJSONUtils.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/29/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "STDSTestJSONUtils.h" + +@implementation STDSTestJSONUtils + ++ (NSDictionary *)jsonNamed:(NSString *)name { + NSData *data = [self dataFromJSONFile:name]; + if (data != nil) { + return [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)kNilOptions error:nil]; + } + return nil; +} + ++ (NSBundle *)testBundle { + return [NSBundle bundleForClass:[STDSTestJSONUtils class]]; +} + ++ (NSData *)dataFromJSONFile:(NSString *)name { + NSBundle *bundle = [self testBundle]; + NSString *path = [bundle pathForResource:name ofType:@"json"]; + + if (!path) { + // Missing JSON file + return nil; + } + + NSError *error = nil; + NSString *jsonString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; + + if (!jsonString) { + // File read error + return nil; + } + + // Strip all lines that begin with `//` + NSMutableArray *jsonLines = [[NSMutableArray alloc] init]; + + for (NSString *line in [jsonString componentsSeparatedByString:@"\n"]) { + if (![line hasPrefix:@"//"]) { + [jsonLines addObject:line]; + } + } + + return [[jsonLines componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSThreeDS2ServiceTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSThreeDS2ServiceTests.m new file mode 100644 index 00000000..237b5127 --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSThreeDS2ServiceTests.m @@ -0,0 +1,62 @@ +// +// STDSThreeDS2ServiceTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 1/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSAlreadyInitializedException.h" +#import "STDSConfigParameters.h" +#import "STDSInvalidInputException.h" +#import "STDSThreeDS2Service.h" +#import "STDSNotInitializedException.h" + +@interface STDSThreeDS2ServiceTests : XCTestCase + +@end + +@implementation STDSThreeDS2ServiceTests + +- (void)testInitialize { + STDSThreeDS2Service *service = [[STDSThreeDS2Service alloc] init]; + XCTAssertNoThrow([service initializeWithConfig:[[STDSConfigParameters alloc] init] + locale:nil + uiSettings:nil], + @"Should not throw with valid input and first call to initialize"); + + XCTAssertThrowsSpecific([service initializeWithConfig:[[STDSConfigParameters alloc] init] + locale:nil + uiSettings:nil], + STDSAlreadyInitializedException, + @"Should throw STDSAlreadyInitializedException if called again with valid input."); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + XCTAssertThrowsSpecific([service initializeWithConfig:nil + locale:nil + uiSettings:nil], + STDSInvalidInputException, + @"Should throw STDSInvalidInputException for nil config even if already initialized."); + + service = [[STDSThreeDS2Service alloc] init]; + XCTAssertThrowsSpecific([service initializeWithConfig:nil + locale:nil + uiSettings:nil], + STDSInvalidInputException, + @"Should throw STDSInvalidInputException for nil config on first initialize."); +#pragma clang diagnostic pop + + XCTAssertNoThrow([service initializeWithConfig:[[STDSConfigParameters alloc] init] + locale:nil + uiSettings:nil], + @"Should not throw with valid input and first call to initialize even after invalid input"); + + service = [[STDSThreeDS2Service alloc] init]; + XCTAssertThrowsSpecific(service.warnings, STDSNotInitializedException); + +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSTransactionTest.m b/Stripe3DS2/Stripe3DS2Tests/STDSTransactionTest.m new file mode 100644 index 00000000..3dd1187c --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSTransactionTest.m @@ -0,0 +1,157 @@ +// +// STDSTransactionTest.m +// Stripe3DS2Tests +// +// Created by Yuki Tokuhiro on 3/22/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSTransaction+Private.h" +#import "STDSInvalidInputException.h" +#import "STDSChallengeParameters.h" +#import "STDSChallengeStatusReceiver.h" +#import "STDSRuntimeErrorEvent.h" +#import "STDSProtocolErrorEvent.h" +#import "STDSStripe3DS2Error.h" +#import "STDSTransaction.h" +#import "STDSErrorMessage.h" +#import "NSError+Stripe3DS2.h" + +@interface STDSTransaction (Private) +@property (nonatomic, weak) id challengeStatusReceiver; + +- (void)_handleError:(NSError *)error; +- (NSString *)_sdkAppIdentifier; +@end + +@interface STDSTransactionTest : XCTestCase +@property (nonatomic, copy) void (^didErrorWithProtocolErrorEvent)(STDSProtocolErrorEvent *); +@property (nonatomic, copy) void (^didErrorWithRuntimeErrorEvent)(STDSRuntimeErrorEvent *); +@end + +@implementation STDSTransactionTest + +- (void)tearDown { + self.didErrorWithRuntimeErrorEvent = nil; + self.didErrorWithProtocolErrorEvent = nil; + [super tearDown]; +} + +#pragma mark - Timeout + +- (void)testTimeoutBelow5Throws { + STDSTransaction *transaction = [STDSTransaction new]; + STDSChallengeParameters *challengeParameters = [[STDSChallengeParameters alloc] init]; + XCTAssertThrowsSpecific([transaction doChallengeWithViewController:[UIViewController new] + challengeParameters:challengeParameters + challengeStatusReceiver:self + timeout:4 * 60], STDSInvalidInputException); +} + +- (void)testTimeoutFires { + STDSTransaction *transaction = [STDSTransaction new]; + STDSChallengeParameters *challengeParameters = [[STDSChallengeParameters alloc] init]; + + // Assert timer is scheduled to fire 5 minutes from now, give or take 1 second + NSInteger timeout = 300; + [transaction doChallengeWithViewController:[UIViewController new] + challengeParameters:challengeParameters + challengeStatusReceiver:self + timeout:timeout]; + XCTAssertTrue(transaction.timeoutTimer.isValid); + NSTimeInterval secondsIntoTheFutureTimerWillFire = [transaction.timeoutTimer.fireDate timeIntervalSinceNow]; + XCTAssertLessThanOrEqual(fabs(secondsIntoTheFutureTimerWillFire - (timeout)), 1); +} + +#pragma mark - Error Handling + +- (void)testHandleUnknownMessageTypeError { + NSError *error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain code:STDSErrorCodeUnknownMessageType userInfo:nil]; + [self _expectProtocolErrorEventForError:error validator:^(STDSProtocolErrorEvent *protocolErrorEvent) { + XCTAssertEqualObjects(protocolErrorEvent.errorMessage.errorCode, @"101"); + }]; +} + +- (void)testHandleInvalidJSONError { + NSError *error = [NSError _stds_invalidJSONFieldError:@"invalid field"]; + [self _expectProtocolErrorEventForError:error validator:^(STDSProtocolErrorEvent *protocolErrorEvent) { + XCTAssertEqualObjects(protocolErrorEvent.errorMessage.errorCode, @"203"); + XCTAssertEqualObjects(protocolErrorEvent.errorMessage.errorDetails, @"invalid field"); + }]; +} + +- (void)testHandleMissingJSONError { + NSError *error = [NSError _stds_missingJSONFieldError:@"missing field"]; + [self _expectProtocolErrorEventForError:error validator:^(STDSProtocolErrorEvent *protocolErrorEvent) { + XCTAssertEqualObjects(protocolErrorEvent.errorMessage.errorCode, @"201"); + XCTAssertEqualObjects(protocolErrorEvent.errorMessage.errorDetails, @"missing field"); + }]; +} + +- (void)testHandleReceivedErrorMessage { + STDSErrorMessage *receivedErrorMessage = [[STDSErrorMessage alloc] initWithErrorCode:@"" errorComponent:@"" errorDescription:@"" errorDetails:nil messageVersion:@"" acsTransactionIdentifier:@"" errorMessageType:@""]; + NSError *error = [NSError errorWithDomain:STDSStripe3DS2ErrorDomain + code:STDSErrorCodeReceivedErrorMessage + userInfo:@{STDSStripe3DS2ErrorMessageErrorKey: receivedErrorMessage}]; + + [self _expectProtocolErrorEventForError:error validator:^(STDSProtocolErrorEvent *protocolErrorEvent) { + XCTAssertEqualObjects(protocolErrorEvent.errorMessage, receivedErrorMessage); + }]; +} + +- (void)testHandleNetworkConnectionLostError { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorNetworkConnectionLost + userInfo:nil]; + [self _expectRuntimeErrorEventForError:error validator:^(STDSRuntimeErrorEvent *runtimeErrorEvent) { + XCTAssertEqualObjects(runtimeErrorEvent.errorCode, [@(NSURLErrorNetworkConnectionLost) stringValue]); + }]; +} + +- (void)_expectProtocolErrorEventForError:(NSError *)error validator:(void (^)(STDSProtocolErrorEvent *))protocolErrorEventChecker { + STDSTransaction *transaction = [STDSTransaction new]; + transaction.challengeStatusReceiver = self; + XCTestExpectation *expectation = [self expectationWithDescription:@"Call didErrorWithProtocolErrorEvent"]; + self.didErrorWithProtocolErrorEvent = ^(STDSProtocolErrorEvent *protocolErrorEvent) { + protocolErrorEventChecker(protocolErrorEvent); + [expectation fulfill]; + }; + [transaction _handleError:error]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)_expectRuntimeErrorEventForError:(NSError *)error validator:(void (^)(STDSRuntimeErrorEvent *))runtimeErrorEventChecker { + STDSTransaction *transaction = [STDSTransaction new]; + transaction.challengeStatusReceiver = self; + XCTestExpectation *expectation = [self expectationWithDescription:@"Call didErrorWithRuntimeErrorEvent"]; + self.didErrorWithRuntimeErrorEvent = ^(STDSRuntimeErrorEvent *runtimeErrorEvent) { + runtimeErrorEventChecker(runtimeErrorEvent); + [expectation fulfill]; + }; + [transaction _handleError:error]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +#pragma mark - STDSChallengeStatusReceiver + +- (void)transaction:(nonnull STDSTransaction *)transaction didCompleteChallengeWithCompletionEvent:(nonnull STDSCompletionEvent *)completionEvent {} + +- (void)transaction:(nonnull STDSTransaction *)transaction didErrorWithProtocolErrorEvent:(nonnull STDSProtocolErrorEvent *)protocolErrorEvent { + if (self.didErrorWithProtocolErrorEvent) { + self.didErrorWithProtocolErrorEvent(protocolErrorEvent); + } +} + +- (void)transaction:(nonnull STDSTransaction *)transaction didErrorWithRuntimeErrorEvent:(nonnull STDSRuntimeErrorEvent *)runtimeErrorEvent { + if (self.didErrorWithRuntimeErrorEvent) { + self.didErrorWithRuntimeErrorEvent(runtimeErrorEvent); + } +} + +- (void)transactionDidCancel:(nonnull STDSTransaction *)transaction {} + +- (void)transactionDidTimeOut:(nonnull STDSTransaction *)transaction {} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSUICustomizationTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSUICustomizationTests.m new file mode 100644 index 00000000..9d231acf --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSUICustomizationTests.m @@ -0,0 +1,249 @@ +// +// STDSUICustomizationTests.m +// Stripe3DS2Tests +// +// Created by Andrew Harrison on 3/14/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import +#import "STDSUICustomization.h" + +@interface STDSUICustomizationTests : XCTestCase + +@end + +@implementation STDSUICustomizationTests + +// The following helper methods return customization objects with properties different than the default. + +- (STDSNavigationBarCustomization *)_customNavigationBar { + STDSNavigationBarCustomization *custom = [STDSNavigationBarCustomization new]; + custom.font = [UIFont italicSystemFontOfSize:1]; + custom.textColor = UIColor.blueColor; + custom.barTintColor = UIColor.redColor; + custom.barStyle = UIBarStyleBlack; + custom.translucent = NO; + custom.headerText = @"foo"; + custom.buttonText = @"bar"; + return custom; +} + +- (STDSLabelCustomization *)_customLabel { + STDSLabelCustomization *custom = [STDSLabelCustomization new]; + custom.font = [UIFont italicSystemFontOfSize:1]; + custom.textColor = UIColor.blueColor; + custom.headingTextColor = UIColor.redColor; + custom.headingFont = [UIFont italicSystemFontOfSize:2]; + return custom; +} + +- (STDSTextFieldCustomization *)_customTextField { + STDSTextFieldCustomization *custom = [STDSTextFieldCustomization new]; + custom.font = [UIFont italicSystemFontOfSize:1]; + custom.textColor = UIColor.blueColor; + custom.borderWidth = -1; + custom.borderColor = UIColor.redColor; + custom.cornerRadius = -8; + custom.keyboardAppearance = UIKeyboardAppearanceAlert; + custom.placeholderTextColor = UIColor.greenColor; + return custom; +} + +- (STDSButtonCustomization *)_customButton { + STDSButtonCustomization *custom = [[STDSButtonCustomization alloc] initWithBackgroundColor:UIColor.redColor cornerRadius:-1]; + custom.font = [UIFont italicSystemFontOfSize:1]; + custom.textColor = UIColor.blueColor; + custom.titleStyle = STDSButtonTitleStyleLowercase; + return custom; +} + +- (STDSFooterCustomization *)_customFooter { + STDSFooterCustomization *custom = [STDSFooterCustomization new]; + custom.font = [UIFont italicSystemFontOfSize:1]; + custom.textColor = UIColor.blueColor; + custom.backgroundColor = UIColor.redColor; + custom.chevronColor = UIColor.greenColor; + custom.headingTextColor = UIColor.grayColor; + custom.headingFont = [UIFont italicSystemFontOfSize:2]; + return custom; +} + +- (STDSSelectionCustomization *)_customSelection { + STDSSelectionCustomization *custom = [STDSSelectionCustomization new]; + custom.primarySelectedColor = UIColor.redColor; + custom.secondarySelectedColor = UIColor.blueColor; + custom.unselectedBorderColor = UIColor.brownColor; + custom.unselectedBackgroundColor = UIColor.cyanColor; + return custom; +} + +#pragma mark - Copying + +- (void)testUICustomizationDeepCopy { + // Make a STDSUICustomization instance with all non-default properties + STDSButtonCustomization *submitButtonCustomization = [self _customButton]; + STDSButtonCustomization *continueButtonCustomization = [self _customButton]; + continueButtonCustomization.cornerRadius = -2; + STDSButtonCustomization *nextButtonCustomization = [self _customButton]; + nextButtonCustomization.cornerRadius = -3; + STDSButtonCustomization *cancelButtonCustomization = [self _customButton]; + cancelButtonCustomization.cornerRadius = -4; + STDSButtonCustomization *resendButtonCustomization = [self _customButton]; + resendButtonCustomization.cornerRadius = -5; + + STDSNavigationBarCustomization *navigationBarCustomization = [self _customNavigationBar]; + STDSLabelCustomization *labelCustomization = [self _customLabel]; + STDSTextFieldCustomization *textFieldCustomization = [self _customTextField]; + STDSFooterCustomization *footerCustomization = [self _customFooter]; + STDSSelectionCustomization *selectionCustomization = [self _customSelection]; + + STDSUICustomization *uiCustomization = [[STDSUICustomization alloc] init]; + uiCustomization.footerCustomization = footerCustomization; + uiCustomization.selectionCustomization = selectionCustomization; + [uiCustomization setButtonCustomization:submitButtonCustomization forType:STDSUICustomizationButtonTypeSubmit]; + [uiCustomization setButtonCustomization:continueButtonCustomization forType:STDSUICustomizationButtonTypeContinue]; + [uiCustomization setButtonCustomization:nextButtonCustomization forType:STDSUICustomizationButtonTypeNext]; + [uiCustomization setButtonCustomization:cancelButtonCustomization forType:STDSUICustomizationButtonTypeCancel]; + [uiCustomization setButtonCustomization:resendButtonCustomization forType:STDSUICustomizationButtonTypeResend]; + uiCustomization.navigationBarCustomization = navigationBarCustomization; + uiCustomization.labelCustomization = labelCustomization; + uiCustomization.textFieldCustomization = textFieldCustomization; + uiCustomization.backgroundColor = UIColor.redColor; + uiCustomization.activityIndicatorViewStyle = UIActivityIndicatorViewStyleLarge; + uiCustomization.blurStyle = UIBlurEffectStyleDark; + uiCustomization.preferredStatusBarStyle = UIStatusBarStyleLightContent; + + STDSUICustomization *copy = [uiCustomization copy]; + XCTAssertNotNil([copy buttonCustomizationForButtonType:STDSUICustomizationButtonTypeNext]); + XCTAssertNotNil(copy.navigationBarCustomization); + XCTAssertNotNil(copy.labelCustomization); + XCTAssertNotNil(copy.textFieldCustomization); + XCTAssertNotNil(copy.footerCustomization); + XCTAssertNotNil(copy.selectionCustomization); + + /// The pointers do not reference the same objects. + XCTAssertNotEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeSubmit], [copy buttonCustomizationForButtonType:STDSUICustomizationButtonTypeSubmit]); + XCTAssertNotEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeContinue], [copy buttonCustomizationForButtonType:STDSUICustomizationButtonTypeContinue]); + XCTAssertNotEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeNext], [copy buttonCustomizationForButtonType:STDSUICustomizationButtonTypeNext]); + XCTAssertNotEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeCancel], [copy buttonCustomizationForButtonType:STDSUICustomizationButtonTypeCancel]); + XCTAssertNotEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeResend], [copy buttonCustomizationForButtonType:STDSUICustomizationButtonTypeResend]); + XCTAssertNotEqual(uiCustomization.navigationBarCustomization, copy.navigationBarCustomization); + XCTAssertNotEqual(uiCustomization.labelCustomization, copy.labelCustomization); + XCTAssertNotEqual(uiCustomization.textFieldCustomization, copy.textFieldCustomization); + XCTAssertNotEqual(uiCustomization.footerCustomization, copy.footerCustomization); + XCTAssertNotEqual(uiCustomization.selectionCustomization, copy.selectionCustomization); + + /// The properties have been successfully copied. + XCTAssertEqualObjects(uiCustomization.backgroundColor, copy.backgroundColor); + XCTAssertEqual(uiCustomization.activityIndicatorViewStyle, copy.activityIndicatorViewStyle); + XCTAssertEqual(uiCustomization.blurStyle, copy.blurStyle); + // A different test case will cover that our custom classes implemented copy correctly; just sanity check one property here + XCTAssertEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeSubmit].cornerRadius, submitButtonCustomization.cornerRadius); + XCTAssertEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeContinue].cornerRadius, continueButtonCustomization.cornerRadius); + XCTAssertEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeNext].cornerRadius, nextButtonCustomization.cornerRadius); + XCTAssertEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeCancel].cornerRadius, cancelButtonCustomization.cornerRadius); + XCTAssertEqual([uiCustomization buttonCustomizationForButtonType:STDSUICustomizationButtonTypeResend].cornerRadius, resendButtonCustomization.cornerRadius); + XCTAssertEqualObjects(uiCustomization.navigationBarCustomization.font, copy.navigationBarCustomization.font); + XCTAssertEqualObjects(uiCustomization.labelCustomization.font, copy.labelCustomization.font); + XCTAssertEqualObjects(uiCustomization.textFieldCustomization.font, copy.textFieldCustomization.font); + XCTAssertEqualObjects(uiCustomization.footerCustomization.font, copy.footerCustomization.font); + XCTAssertEqualObjects(uiCustomization.selectionCustomization.primarySelectedColor, copy.selectionCustomization.primarySelectedColor); + XCTAssertEqual(uiCustomization.preferredStatusBarStyle, copy.preferredStatusBarStyle); +} + +- (void)testButtonCustomizationIsCopied { + STDSButtonCustomization *buttonCustomization = [self _customButton]; + + /// The pointers do not reference the same objects. + STDSButtonCustomization *copy = [buttonCustomization copy]; + XCTAssertNotEqual(buttonCustomization, copy); + + /// The properties have been successfully copied. + XCTAssertEqual(buttonCustomization.cornerRadius, copy.cornerRadius); + XCTAssertEqual(buttonCustomization.backgroundColor, copy.backgroundColor); + XCTAssertEqual(buttonCustomization.font, copy.font); + XCTAssertEqual(buttonCustomization.textColor, copy.textColor); + XCTAssertEqual(buttonCustomization.titleStyle, buttonCustomization.titleStyle); +} + +- (void)testNavigationBarCustomizationIsCopied { + STDSNavigationBarCustomization *navigationBarCustomization = [self _customNavigationBar]; + + /// The pointers do not reference the same objects. + STDSNavigationBarCustomization *copy = [navigationBarCustomization copy]; + XCTAssertNotEqual(navigationBarCustomization, copy); + + /// The properties have been successfully copied. + XCTAssertEqualObjects(navigationBarCustomization.headerText, copy.headerText); + XCTAssertEqualObjects(navigationBarCustomization.buttonText, copy.buttonText); + XCTAssertEqualObjects(navigationBarCustomization.barTintColor, copy.barTintColor); + XCTAssertEqualObjects(navigationBarCustomization.font, copy.font); + XCTAssertEqualObjects(navigationBarCustomization.textColor, copy.textColor); + XCTAssertEqual(navigationBarCustomization.barStyle, copy.barStyle); + XCTAssertEqual(navigationBarCustomization.translucent, copy.translucent); +} + +- (void)testLabelCustomizationIsCopied { + STDSLabelCustomization *labelCustomization = [self _customLabel]; + + /// The pointers do not reference the same objects. + STDSLabelCustomization *copy = [labelCustomization copy]; + XCTAssertNotEqual(labelCustomization, copy); + + /// The properties have been successfully copied. + XCTAssertEqualObjects(labelCustomization.headingTextColor, copy.headingTextColor); + XCTAssertEqualObjects(labelCustomization.headingFont, copy.headingFont); + XCTAssertEqualObjects(labelCustomization.font, copy.font); + XCTAssertEqualObjects(labelCustomization.textColor, copy.textColor); +} + +- (void)testTextFieldCustomizationIsCopied { + STDSTextFieldCustomization *textFieldCustomization = [self _customTextField]; + + /// The pointers do not reference the same objects. + STDSTextFieldCustomization *copy = [textFieldCustomization copy]; + XCTAssertNotEqual(textFieldCustomization, copy); + + /// The properties have been successfully copied. + XCTAssertEqual(textFieldCustomization.borderWidth, copy.borderWidth); + XCTAssertEqualObjects(textFieldCustomization.borderColor, copy.borderColor); + XCTAssertEqual(textFieldCustomization.cornerRadius, copy.cornerRadius); + XCTAssertEqualObjects(textFieldCustomization.font, copy.font); + XCTAssertEqualObjects(textFieldCustomization.textColor, copy.textColor); + XCTAssertEqual(textFieldCustomization.keyboardAppearance, copy.keyboardAppearance); + XCTAssertEqualObjects(textFieldCustomization.placeholderTextColor, copy.placeholderTextColor); +} + +- (void)testFooterCustomizationIsCopied { + STDSFooterCustomization *footerCustomization = [self _customFooter]; + + /// The pointers do not reference the same objects. + STDSFooterCustomization *copy = [footerCustomization copy]; + XCTAssertNotEqual(footerCustomization, copy); + + /// The properties have been successfully copied. + XCTAssertEqualObjects(footerCustomization.textColor, copy.textColor); + XCTAssertEqualObjects(footerCustomization.font, copy.font); + XCTAssertEqualObjects(footerCustomization.backgroundColor, copy.backgroundColor); + XCTAssertEqualObjects(footerCustomization.chevronColor, copy.chevronColor); + XCTAssertEqualObjects(footerCustomization.headingTextColor, copy.headingTextColor); + XCTAssertEqualObjects(footerCustomization.headingFont, copy.headingFont); +} + +- (void)testSelectionCustomizationIsCopied { + STDSSelectionCustomization *customization = [self _customSelection]; + + /// The pointers do not reference the same objects. + STDSSelectionCustomization *copy = [customization copy]; + XCTAssertNotEqual(customization, copy); + + /// The properties have been successfully copied. + XCTAssertEqualObjects(customization.primarySelectedColor, copy.primarySelectedColor); + XCTAssertEqualObjects(customization.secondarySelectedColor, copy.secondarySelectedColor); + XCTAssertEqualObjects(customization.unselectedBorderColor, copy.unselectedBorderColor); + XCTAssertEqualObjects(customization.unselectedBackgroundColor, copy.unselectedBackgroundColor); + +} + +@end diff --git a/Stripe3DS2/Stripe3DS2Tests/STDSWarningTests.m b/Stripe3DS2/Stripe3DS2Tests/STDSWarningTests.m new file mode 100644 index 00000000..ec559c5d --- /dev/null +++ b/Stripe3DS2/Stripe3DS2Tests/STDSWarningTests.m @@ -0,0 +1,26 @@ +// +// STDSWarningTests.m +// Stripe3DS2Tests +// +// Created by Cameron Sabol on 2/12/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import "STDSWarning.h" + +@interface STDSWarningTests : XCTestCase + +@end + +@implementation STDSWarningTests + +- (void)testWarning { + STDSWarning *warning = [[STDSWarning alloc] initWithIdentifier:@"test_id" message:@"test_message" severity:STDSWarningSeverityMedium]; + XCTAssertEqual(warning.identifier, @"test_id", @"Identifier was not set correctly."); + XCTAssertEqual(warning.message, @"test_message", @"Message was not set correctly."); + XCTAssertEqual(warning.severity, STDSWarningSeverityMedium, @"Severity was not set correctly."); +} + +@end diff --git a/Stripe3DS2/exported_symbols.txt b/Stripe3DS2/exported_symbols.txt new file mode 100644 index 00000000..981c8756 --- /dev/null +++ b/Stripe3DS2/exported_symbols.txt @@ -0,0 +1,13 @@ +_OBJC_CLASS_$_STDSButtonCustomization +_OBJC_CLASS_$_STDSChallengeParameters +_OBJC_CLASS_$_STDSConfigParameters +_OBJC_CLASS_$_STDSFooterCustomization +_OBJC_CLASS_$_STDSJSONEncoder +_OBJC_CLASS_$_STDSLabelCustomization +_OBJC_CLASS_$_STDSNavigationBarCustomization +_OBJC_CLASS_$_STDSSelectionCustomization +_OBJC_CLASS_$_STDSTextFieldCustomization +_OBJC_CLASS_$_STDSThreeDS2Service +_OBJC_CLASS_$_STDSUICustomization +_OBJC_CLASS_$_STDSSwiftTryCatch +_STDSAuthenticationResponseFromJSON diff --git a/StripeApplePay/README.md b/StripeApplePay/README.md new file mode 100644 index 00000000..9c225745 --- /dev/null +++ b/StripeApplePay/README.md @@ -0,0 +1,36 @@ +# Stripe Apple Pay iOS SDK + +StripeApplePay is a lightweight Apple Pay SDK intended for building App Clips or other size-constrained apps. + +## Table of contents + + +- [Stripe Apple Pay iOS SDK](#stripe-apple-pay-ios-sdk) +- [Table of contents](#table-of-contents) + - [Requirements](#requirements) + - [Getting started](#getting-started) + - [Integration](#integration) + - [Example](#example) + - [Manual linking](#manual-linking) + + + +## Requirements + +The Stripe Apple Pay SDK is compatible with apps targeting iOS 13.0 or above. + +## Getting started + +### Integration + +Get started with our [📚 Apple Pay integration guide](https://stripe.com/docs/apple-pay?platform=ios) and [example project](../Example/AppClipExample), or [📘 browse the SDK reference](https://stripe.dev/stripe-ios/stripe-applepay/index.html) for fine-grained documentation of all the classes and methods in the SDK. + +### Example + +[AppClipExample](../Example/AppClipExample) – This example demonstrates how to offer Apple Pay in an App Clip. + +## Manual linking + +If you link this library manually, use a version from our [releases](https://github.com/stripe/stripe-ios/releases) page and make sure to embed all of the following frameworks: +- `StripeCore.xcframework` +- `StripeApplePay.xcframework` diff --git a/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj b/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj new file mode 100644 index 00000000..61dad09b --- /dev/null +++ b/StripeApplePay/StripeApplePay.xcodeproj/project.pbxproj @@ -0,0 +1,618 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 01AA289840E0AC9229A8CF63 /* StripeApplePay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29BFCC7B0FCEA743A857B51F /* StripeApplePay.framework */; }; + 09EB0F7E346CB22144515E67 /* SetupIntent+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DDE851AD7450BE70381337 /* SetupIntent+API.swift */; }; + 1990346BA0B39ADD47210E18 /* StripeApplePay.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C1D3421B1B2BB91FAA66620 /* StripeApplePay.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 22E1F50D066A294A316052ED /* PaymentIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2398CA91B601B5FC7C8FB48 /* PaymentIntent.swift */; }; + 234FA38DC46927B23871A75D /* SetupIntentParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D7CF2B75A797D6B2B5A04B4 /* SetupIntentParams.swift */; }; + 2D6CD6872A00B6FE0243C3F5 /* PKPayment+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 906F7217DBF694BF42F23458 /* PKPayment+Stripe.swift */; }; + 2EBB230815383A8402D71146 /* STPPaymentMethodFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 905182496A07DE4F056A3EAA /* STPPaymentMethodFunctionalTest.swift */; }; + 313F5F7B2B0BE5A600BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F7A2B0BE5A600BD98A9 /* Docs.docc */; }; + 43682CEAB00A93868FA3188A /* SetupIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC2A354F50C5D563436A0068 /* SetupIntent.swift */; }; + 463680AADB8CED6E962CD45A /* PaymentIntentParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 478361D29DF10F2B43F7A1D2 /* PaymentIntentParams.swift */; }; + 4AA6A66246DD30798E5CC5F7 /* PaymentIntent+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EAD8979A20E239E16257E4 /* PaymentIntent+API.swift */; }; + 4ADC5356764DC5E3F1C1D51B /* CardBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DBC2BC8C0A60983D0948A11 /* CardBrand.swift */; }; + 4B2DE4109D876CCBDC53C2A3 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62ECC1AA5583E4104C073B63 /* StripeCore.framework */; }; + 4CE57B5BF79D1515F27A18A3 /* Address.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF86CF1EFE4AC1A46F055202 /* Address.swift */; }; + 505A82AADE15E2B2B99C5D32 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 594F0478E94E6FA2F551EA0A /* Token.swift */; }; + 526AE8381F232C9FEABFFDCD /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8ED017D12DE81D3ABD013768 /* OHHTTPStubsSwift */; }; + 53BA33D02E07DFF1393DF0E4 /* PaymentMethod+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264583DBFFA42C864220D7FF /* PaymentMethod+API.swift */; }; + 59AEBEB856DB8A0118F1D0DE /* STPAnalyticsClient+Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20A70C7D203A80129DBB9304 /* STPAnalyticsClient+Payments.swift */; }; + 59C1DB9EF052987BD20B65A3 /* BillingDetails+ApplePay.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FD028203455E50A637227C /* BillingDetails+ApplePay.swift */; }; + 62AFEE5A1E32BD84588CA233 /* STPTelemetryClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B4DF82BA51CC68265B0795 /* STPTelemetryClientTest.swift */; }; + 7C7C92AFED77FC4C5D26DC36 /* BillingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C563CA8E1D005A453C762703 /* BillingDetails.swift */; }; + 848843F1145350ABF540D69F /* ShippingDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08710B3CD45DE9DCE2055A3 /* ShippingDetails.swift */; }; + 90286A48FA6C350BDEC227D0 /* TelemetryInjectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 446F888DB3EC0FC1FE9B9377 /* TelemetryInjectionTest.swift */; }; + 9F50D23599CD0B1270F8C295 /* STPApplePayContext+LegacySupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023208C7E3B3044F553DFDD /* STPApplePayContext+LegacySupport.swift */; }; + A69598FE8095AFA9898297A9 /* Token+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81AB96E6201D534220ABB9D /* Token+API.swift */; }; + A79510332C88568637C9E867 /* STPAnalyticsClient+ApplePayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E9055B54A5634B636570E3 /* STPAnalyticsClient+ApplePayTest.swift */; }; + AB48B0CBFFEFC46E01C76B6C /* STPAPIClient+PaymentsCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894B7D67F2364A12DD133CF4 /* STPAPIClient+PaymentsCore.swift */; }; + BCC7454DB40673493DF940F5 /* STPAnalyticsClient+PaymentsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD5C52C679274D830AA5B93 /* STPAnalyticsClient+PaymentsAPI.swift */; }; + C0D4B1753F584397DD1AD946 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87A189B89110D3A8EF052425 /* XCTest.framework */; }; + C6552A9ADEA160B9BBAF3A10 /* STPApplePayContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4AEF10AF7900AF7ED0EDE21 /* STPApplePayContext.swift */; }; + C6A333FBB72EE91849DD6202 /* STPAPIClient+ApplePay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855AB5D51581CE6ABCCD2493 /* STPAPIClient+ApplePay.swift */; }; + CB78059C8EB6A072C30A98DA /* STPTelemetryClientFunctionalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B7AA620790EC4257132D738 /* STPTelemetryClientFunctionalTest.swift */; }; + CECAB0CB1CE4D7BD1EF897F0 /* Blocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5439AD0D1236F6A21EE93CB3 /* Blocks.swift */; }; + D8D613D3A85B81F8C4386E08 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = CB64080D8A41D33BC3DEEAF8 /* OHHTTPStubs */; }; + E1E7B153B169D0A6363ADD4B /* PaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D1E8A9F849655BAC63DB2FD /* PaymentMethod.swift */; }; + EEF84AC6C1EBF27BF6AAC0BF /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FA663325484F2D515613494 /* StripeCoreTestUtils.framework */; }; + F42BC784EDBD141C90E74A5F /* PKContact+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133A7483EDB9F1B1CFFDB9ED /* PKContact+Stripe.swift */; }; + F59D5A3F2C5849CD465062A5 /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88664A990EE5D99076E88987 /* StripeCore+Import.swift */; }; + FADFD3B7E3832928E254FAF1 /* PaymentMethodParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 468654E242DFE2F85FF422EB /* PaymentMethodParams.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6DAB385DAFF8DCB87110CC7E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 95898E5CA0A1871DDFFB66B0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 747C9FE1743C0B4444303569; + remoteInfo = StripeApplePay; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F937383B644A63BD5FC4A081 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + FF3CB7A978895136380088BB /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0B7AA620790EC4257132D738 /* STPTelemetryClientFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTelemetryClientFunctionalTest.swift; sourceTree = ""; }; + 0C1D3421B1B2BB91FAA66620 /* StripeApplePay.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeApplePay.h; sourceTree = ""; }; + 0FA663325484F2D515613494 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 133A7483EDB9F1B1CFFDB9ED /* PKContact+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKContact+Stripe.swift"; sourceTree = ""; }; + 20A70C7D203A80129DBB9304 /* STPAnalyticsClient+Payments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+Payments.swift"; sourceTree = ""; }; + 229446797CC5FB07DB344D81 /* StripeApplePayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeApplePayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 264583DBFFA42C864220D7FF /* PaymentMethod+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentMethod+API.swift"; sourceTree = ""; }; + 29BFCC7B0FCEA743A857B51F /* StripeApplePay.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeApplePay.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 30DDE851AD7450BE70381337 /* SetupIntent+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SetupIntent+API.swift"; sourceTree = ""; }; + 313F5F7A2B0BE5A600BD98A9 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; + 3D7CF2B75A797D6B2B5A04B4 /* SetupIntentParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupIntentParams.swift; sourceTree = ""; }; + 40D5ED47331313B6AD8B6F46 /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 425B19195E4B86F77229252D /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + 446F888DB3EC0FC1FE9B9377 /* TelemetryInjectionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInjectionTest.swift; sourceTree = ""; }; + 468654E242DFE2F85FF422EB /* PaymentMethodParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodParams.swift; sourceTree = ""; }; + 478361D29DF10F2B43F7A1D2 /* PaymentIntentParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentIntentParams.swift; sourceTree = ""; }; + 49B0926F8F65BC0102B99BF4 /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + 4EE8157F3536C238AC337337 /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + 5439AD0D1236F6A21EE93CB3 /* Blocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Blocks.swift; sourceTree = ""; }; + 594F0478E94E6FA2F551EA0A /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; + 62ECC1AA5583E4104C073B63 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 67711D1BC01BA83323EA1F6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 6D1E8A9F849655BAC63DB2FD /* PaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethod.swift; sourceTree = ""; }; + 7023208C7E3B3044F553DFDD /* STPApplePayContext+LegacySupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPApplePayContext+LegacySupport.swift"; sourceTree = ""; }; + 79B089D69DBA1A0BCF4503B6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 855AB5D51581CE6ABCCD2493 /* STPAPIClient+ApplePay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+ApplePay.swift"; sourceTree = ""; }; + 87A189B89110D3A8EF052425 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 88664A990EE5D99076E88987 /* StripeCore+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeCore+Import.swift"; sourceTree = ""; }; + 894B7D67F2364A12DD133CF4 /* STPAPIClient+PaymentsCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+PaymentsCore.swift"; sourceTree = ""; }; + 8DBC2BC8C0A60983D0948A11 /* CardBrand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardBrand.swift; sourceTree = ""; }; + 905182496A07DE4F056A3EAA /* STPPaymentMethodFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodFunctionalTest.swift; sourceTree = ""; }; + 906F7217DBF694BF42F23458 /* PKPayment+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKPayment+Stripe.swift"; sourceTree = ""; }; + 93EAD8979A20E239E16257E4 /* PaymentIntent+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentIntent+API.swift"; sourceTree = ""; }; + BDAEEEB96953ECBB9C68CF1C /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + C08710B3CD45DE9DCE2055A3 /* ShippingDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingDetails.swift; sourceTree = ""; }; + C563CA8E1D005A453C762703 /* BillingDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BillingDetails.swift; sourceTree = ""; }; + CB01F67DF47C468825F8FF6A /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + CC2A354F50C5D563436A0068 /* SetupIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupIntent.swift; sourceTree = ""; }; + D2398CA91B601B5FC7C8FB48 /* PaymentIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentIntent.swift; sourceTree = ""; }; + D81AB96E6201D534220ABB9D /* Token+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Token+API.swift"; sourceTree = ""; }; + E4AEF10AF7900AF7ED0EDE21 /* STPApplePayContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPApplePayContext.swift; sourceTree = ""; }; + E4B4DF82BA51CC68265B0795 /* STPTelemetryClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTelemetryClientTest.swift; sourceTree = ""; }; + E6E9055B54A5634B636570E3 /* STPAnalyticsClient+ApplePayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+ApplePayTest.swift"; sourceTree = ""; }; + F4FD028203455E50A637227C /* BillingDetails+ApplePay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BillingDetails+ApplePay.swift"; sourceTree = ""; }; + FBD5C52C679274D830AA5B93 /* STPAnalyticsClient+PaymentsAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+PaymentsAPI.swift"; sourceTree = ""; }; + FF86CF1EFE4AC1A46F055202 /* Address.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Address.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2A690CB3AEAD3A05D0DF3D47 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B2DE4109D876CCBDC53C2A3 /* StripeCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 92797DEFFA4201115E38DEAD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C0D4B1753F584397DD1AD946 /* XCTest.framework in Frameworks */, + 01AA289840E0AC9229A8CF63 /* StripeApplePay.framework in Frameworks */, + EEF84AC6C1EBF27BF6AAC0BF /* StripeCoreTestUtils.framework in Frameworks */, + D8D613D3A85B81F8C4386E08 /* OHHTTPStubs in Frameworks */, + 526AE8381F232C9FEABFFDCD /* OHHTTPStubsSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0D2F315747B25043D0135CBC /* Analytics */ = { + isa = PBXGroup; + children = ( + 20A70C7D203A80129DBB9304 /* STPAnalyticsClient+Payments.swift */, + FBD5C52C679274D830AA5B93 /* STPAnalyticsClient+PaymentsAPI.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 18E783E8B2360F08F2FCDEB9 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 87A189B89110D3A8EF052425 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1A99D698FD7EE445DA2366ED /* StripeApplePay */ = { + isa = PBXGroup; + children = ( + 313F5F7A2B0BE5A600BD98A9 /* Docs.docc */, + B7FC50BAFE8B7A10FA7EA283 /* Source */, + 67711D1BC01BA83323EA1F6B /* Info.plist */, + 0C1D3421B1B2BB91FAA66620 /* StripeApplePay.h */, + ); + path = StripeApplePay; + sourceTree = ""; + }; + 2F76FD687FF6507E5B2C1A3F /* ApplePayContext */ = { + isa = PBXGroup; + children = ( + 855AB5D51581CE6ABCCD2493 /* STPAPIClient+ApplePay.swift */, + E4AEF10AF7900AF7ED0EDE21 /* STPApplePayContext.swift */, + 7023208C7E3B3044F553DFDD /* STPApplePayContext+LegacySupport.swift */, + ); + path = ApplePayContext; + sourceTree = ""; + }; + 3581723D3A3FF58EE3E59033 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 40D5ED47331313B6AD8B6F46 /* Project-Debug.xcconfig */, + CB01F67DF47C468825F8FF6A /* Project-Release.xcconfig */, + 425B19195E4B86F77229252D /* StripeiOS Tests-Debug.xcconfig */, + BDAEEEB96953ECBB9C68CF1C /* StripeiOS Tests-Release.xcconfig */, + 49B0926F8F65BC0102B99BF4 /* StripeiOS-Debug.xcconfig */, + 4EE8157F3536C238AC337337 /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 3E8665C11A450F2FB7D2F2E7 /* Models */ = { + isa = PBXGroup; + children = ( + FF86CF1EFE4AC1A46F055202 /* Address.swift */, + C563CA8E1D005A453C762703 /* BillingDetails.swift */, + 8DBC2BC8C0A60983D0948A11 /* CardBrand.swift */, + D2398CA91B601B5FC7C8FB48 /* PaymentIntent.swift */, + 478361D29DF10F2B43F7A1D2 /* PaymentIntentParams.swift */, + 6D1E8A9F849655BAC63DB2FD /* PaymentMethod.swift */, + 468654E242DFE2F85FF422EB /* PaymentMethodParams.swift */, + CC2A354F50C5D563436A0068 /* SetupIntent.swift */, + 3D7CF2B75A797D6B2B5A04B4 /* SetupIntentParams.swift */, + C08710B3CD45DE9DCE2055A3 /* ShippingDetails.swift */, + 594F0478E94E6FA2F551EA0A /* Token.swift */, + ); + path = Models; + sourceTree = ""; + }; + 56479504C3EB58BC9E0C860A = { + isa = PBXGroup; + children = ( + DC6E0D845E48183E2C732ECF /* Project */, + 18E783E8B2360F08F2FCDEB9 /* Frameworks */, + 7C21384B46F40CB3F23DD515 /* Products */, + ); + sourceTree = ""; + }; + 6FAB736FE07EDFA07F922A75 /* API */ = { + isa = PBXGroup; + children = ( + 3E8665C11A450F2FB7D2F2E7 /* Models */, + 93EAD8979A20E239E16257E4 /* PaymentIntent+API.swift */, + 264583DBFFA42C864220D7FF /* PaymentMethod+API.swift */, + 30DDE851AD7450BE70381337 /* SetupIntent+API.swift */, + D81AB96E6201D534220ABB9D /* Token+API.swift */, + ); + path = API; + sourceTree = ""; + }; + 7261423B1B83AF12EE2FEA40 /* PaymentsCore */ = { + isa = PBXGroup; + children = ( + 0B7AA620790EC4257132D738 /* STPTelemetryClientFunctionalTest.swift */, + E4B4DF82BA51CC68265B0795 /* STPTelemetryClientTest.swift */, + ); + path = PaymentsCore; + sourceTree = ""; + }; + 7C21384B46F40CB3F23DD515 /* Products */ = { + isa = PBXGroup; + children = ( + 29BFCC7B0FCEA743A857B51F /* StripeApplePay.framework */, + 229446797CC5FB07DB344D81 /* StripeApplePayTests.xctest */, + 62ECC1AA5583E4104C073B63 /* StripeCore.framework */, + 0FA663325484F2D515613494 /* StripeCoreTestUtils.framework */, + ); + name = Products; + sourceTree = ""; + }; + 823E8B8D6F322BF2BCC4C030 /* PaymentsCore */ = { + isa = PBXGroup; + children = ( + 0D2F315747B25043D0135CBC /* Analytics */, + 6FAB736FE07EDFA07F922A75 /* API */, + 9FE0B80D161B8DB9C70902BF /* Categories */, + ); + path = PaymentsCore; + sourceTree = ""; + }; + 9FE0B80D161B8DB9C70902BF /* Categories */ = { + isa = PBXGroup; + children = ( + 894B7D67F2364A12DD133CF4 /* STPAPIClient+PaymentsCore.swift */, + ); + path = Categories; + sourceTree = ""; + }; + A4676BE706A785EA50A1FEBE /* StripeApplePayTests */ = { + isa = PBXGroup; + children = ( + 7261423B1B83AF12EE2FEA40 /* PaymentsCore */, + 79B089D69DBA1A0BCF4503B6 /* Info.plist */, + E6E9055B54A5634B636570E3 /* STPAnalyticsClient+ApplePayTest.swift */, + 905182496A07DE4F056A3EAA /* STPPaymentMethodFunctionalTest.swift */, + 446F888DB3EC0FC1FE9B9377 /* TelemetryInjectionTest.swift */, + ); + path = StripeApplePayTests; + sourceTree = ""; + }; + B7FC50BAFE8B7A10FA7EA283 /* Source */ = { + isa = PBXGroup; + children = ( + 2F76FD687FF6507E5B2C1A3F /* ApplePayContext */, + E1B6C6692BA02E5DD1AED20B /* Extensions */, + 823E8B8D6F322BF2BCC4C030 /* PaymentsCore */, + 5439AD0D1236F6A21EE93CB3 /* Blocks.swift */, + 88664A990EE5D99076E88987 /* StripeCore+Import.swift */, + ); + path = Source; + sourceTree = ""; + }; + DC6E0D845E48183E2C732ECF /* Project */ = { + isa = PBXGroup; + children = ( + 3581723D3A3FF58EE3E59033 /* BuildConfigurations */, + 1A99D698FD7EE445DA2366ED /* StripeApplePay */, + A4676BE706A785EA50A1FEBE /* StripeApplePayTests */, + ); + name = Project; + sourceTree = ""; + }; + E1B6C6692BA02E5DD1AED20B /* Extensions */ = { + isa = PBXGroup; + children = ( + F4FD028203455E50A637227C /* BillingDetails+ApplePay.swift */, + 133A7483EDB9F1B1CFFDB9ED /* PKContact+Stripe.swift */, + 906F7217DBF694BF42F23458 /* PKPayment+Stripe.swift */, + ); + path = Extensions; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 82FBB6E9E55342EADEEE1865 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1990346BA0B39ADD47210E18 /* StripeApplePay.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 747C9FE1743C0B4444303569 /* StripeApplePay */ = { + isa = PBXNativeTarget; + buildConfigurationList = 49AD325F0339BB78EE36ABC4 /* Build configuration list for PBXNativeTarget "StripeApplePay" */; + buildPhases = ( + 82FBB6E9E55342EADEEE1865 /* Headers */, + 0FA9CF39340C39235D831C53 /* Sources */, + 52997B8984C3A02398E230F4 /* Resources */, + FF3CB7A978895136380088BB /* Embed Frameworks */, + 2A690CB3AEAD3A05D0DF3D47 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeApplePay; + productName = StripeApplePay; + productReference = 29BFCC7B0FCEA743A857B51F /* StripeApplePay.framework */; + productType = "com.apple.product-type.framework"; + }; + ACAFA21CF224F80EFAEFDC2F /* StripeApplePayTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 48EC33D43FBA14658B0E9567 /* Build configuration list for PBXNativeTarget "StripeApplePayTests" */; + buildPhases = ( + C5D7D85B45B4D8BECDF5B7CD /* Sources */, + 200F36028817645AC1730398 /* Resources */, + F937383B644A63BD5FC4A081 /* Embed Frameworks */, + 92797DEFFA4201115E38DEAD /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9D2CBE0177D04009899ECFED /* PBXTargetDependency */, + ); + name = StripeApplePayTests; + packageProductDependencies = ( + CB64080D8A41D33BC3DEEAF8 /* OHHTTPStubs */, + 8ED017D12DE81D3ABD013768 /* OHHTTPStubsSwift */, + ); + productName = StripeApplePayTests; + productReference = 229446797CC5FB07DB344D81 /* StripeApplePayTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 95898E5CA0A1871DDFFB66B0 /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = 2182982C71AEF088A6D2AA6A /* Build configuration list for PBXProject "StripeApplePay" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 56479504C3EB58BC9E0C860A; + packageReferences = ( + B45F7C9F63270FAF880F5EEF /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + ); + productRefGroup = 7C21384B46F40CB3F23DD515 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 747C9FE1743C0B4444303569 /* StripeApplePay */, + ACAFA21CF224F80EFAEFDC2F /* StripeApplePayTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 200F36028817645AC1730398 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 52997B8984C3A02398E230F4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0FA9CF39340C39235D831C53 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C6A333FBB72EE91849DD6202 /* STPAPIClient+ApplePay.swift in Sources */, + 9F50D23599CD0B1270F8C295 /* STPApplePayContext+LegacySupport.swift in Sources */, + C6552A9ADEA160B9BBAF3A10 /* STPApplePayContext.swift in Sources */, + CECAB0CB1CE4D7BD1EF897F0 /* Blocks.swift in Sources */, + 59C1DB9EF052987BD20B65A3 /* BillingDetails+ApplePay.swift in Sources */, + 313F5F7B2B0BE5A600BD98A9 /* Docs.docc in Sources */, + F42BC784EDBD141C90E74A5F /* PKContact+Stripe.swift in Sources */, + 2D6CD6872A00B6FE0243C3F5 /* PKPayment+Stripe.swift in Sources */, + 4CE57B5BF79D1515F27A18A3 /* Address.swift in Sources */, + 7C7C92AFED77FC4C5D26DC36 /* BillingDetails.swift in Sources */, + 4ADC5356764DC5E3F1C1D51B /* CardBrand.swift in Sources */, + 22E1F50D066A294A316052ED /* PaymentIntent.swift in Sources */, + 463680AADB8CED6E962CD45A /* PaymentIntentParams.swift in Sources */, + E1E7B153B169D0A6363ADD4B /* PaymentMethod.swift in Sources */, + FADFD3B7E3832928E254FAF1 /* PaymentMethodParams.swift in Sources */, + 43682CEAB00A93868FA3188A /* SetupIntent.swift in Sources */, + 234FA38DC46927B23871A75D /* SetupIntentParams.swift in Sources */, + 848843F1145350ABF540D69F /* ShippingDetails.swift in Sources */, + 505A82AADE15E2B2B99C5D32 /* Token.swift in Sources */, + 4AA6A66246DD30798E5CC5F7 /* PaymentIntent+API.swift in Sources */, + 53BA33D02E07DFF1393DF0E4 /* PaymentMethod+API.swift in Sources */, + 09EB0F7E346CB22144515E67 /* SetupIntent+API.swift in Sources */, + A69598FE8095AFA9898297A9 /* Token+API.swift in Sources */, + 59AEBEB856DB8A0118F1D0DE /* STPAnalyticsClient+Payments.swift in Sources */, + BCC7454DB40673493DF940F5 /* STPAnalyticsClient+PaymentsAPI.swift in Sources */, + AB48B0CBFFEFC46E01C76B6C /* STPAPIClient+PaymentsCore.swift in Sources */, + F59D5A3F2C5849CD465062A5 /* StripeCore+Import.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C5D7D85B45B4D8BECDF5B7CD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CB78059C8EB6A072C30A98DA /* STPTelemetryClientFunctionalTest.swift in Sources */, + 62AFEE5A1E32BD84588CA233 /* STPTelemetryClientTest.swift in Sources */, + A79510332C88568637C9E867 /* STPAnalyticsClient+ApplePayTest.swift in Sources */, + 2EBB230815383A8402D71146 /* STPPaymentMethodFunctionalTest.swift in Sources */, + 90286A48FA6C350BDEC227D0 /* TelemetryInjectionTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 9D2CBE0177D04009899ECFED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeApplePay; + target = 747C9FE1743C0B4444303569 /* StripeApplePay */; + targetProxy = 6DAB385DAFF8DCB87110CC7E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 13B57D9AA89321957F3ACF3A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BDAEEEB96953ECBB9C68CF1C /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeApplePayTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeApplePayTests; + PRODUCT_NAME = StripeApplePayTests; + SDKROOT = iphoneos; + }; + name = Release; + }; + 15219003E9DE09D774FB72BB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4EE8157F3536C238AC337337 /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeApplePay/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-apple-pay"; + PRODUCT_NAME = StripeApplePay; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; + 6EEB3D72619B3E4C89798C09 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 49B0926F8F65BC0102B99BF4 /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeApplePay/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-apple-pay"; + PRODUCT_NAME = StripeApplePay; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; + 73AE406DB5197038572F40A7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CB01F67DF47C468825F8FF6A /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 7CA6A9C0F1D4C842935D7CEC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40D5ED47331313B6AD8B6F46 /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + EB1D00A86E16D71A6153C2A0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 425B19195E4B86F77229252D /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeApplePayTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeApplePayTests; + PRODUCT_NAME = StripeApplePayTests; + SDKROOT = iphoneos; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2182982C71AEF088A6D2AA6A /* Build configuration list for PBXProject "StripeApplePay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7CA6A9C0F1D4C842935D7CEC /* Debug */, + 73AE406DB5197038572F40A7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 48EC33D43FBA14658B0E9567 /* Build configuration list for PBXNativeTarget "StripeApplePayTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EB1D00A86E16D71A6153C2A0 /* Debug */, + 13B57D9AA89321957F3ACF3A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 49AD325F0339BB78EE36ABC4 /* Build configuration list for PBXNativeTarget "StripeApplePay" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6EEB3D72619B3E4C89798C09 /* Debug */, + 15219003E9DE09D774FB72BB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + B45F7C9F63270FAF880F5EEF /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davidme-stripe/OHHTTPStubs"; + requirement = { + branch = "stripe-mock"; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 8ED017D12DE81D3ABD013768 /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; + CB64080D8A41D33BC3DEEAF8 /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 95898E5CA0A1871DDFFB66B0 /* Project object */; +} diff --git a/StripeApplePay/StripeApplePay.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeApplePay/StripeApplePay.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeApplePay/StripeApplePay.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeApplePay/StripeApplePay.xcodeproj/xcshareddata/xcschemes/StripeApplePay.xcscheme b/StripeApplePay/StripeApplePay.xcodeproj/xcshareddata/xcschemes/StripeApplePay.xcscheme new file mode 100644 index 00000000..64ccb761 --- /dev/null +++ b/StripeApplePay/StripeApplePay.xcodeproj/xcshareddata/xcschemes/StripeApplePay.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeApplePay/StripeApplePay/Docs.docc/StripeApplePay.md b/StripeApplePay/StripeApplePay/Docs.docc/StripeApplePay.md new file mode 100644 index 00000000..8d6de7aa --- /dev/null +++ b/StripeApplePay/StripeApplePay/Docs.docc/StripeApplePay.md @@ -0,0 +1,3 @@ +# ``StripeApplePay`` + +Placeholder diff --git a/StripeApplePay/StripeApplePay/Info.plist b/StripeApplePay/StripeApplePay/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeApplePay/StripeApplePay/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPAPIClient+ApplePay.swift b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPAPIClient+ApplePay.swift new file mode 100644 index 00000000..d00717db --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPAPIClient+ApplePay.swift @@ -0,0 +1,44 @@ +// +// STPAPIClient+ApplePay.swift +// StripeApplePay +// +// Created by Jack Flintermann on 12/19/14. +// Copyright © 2014 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +/// STPAPIClient extensions to create Stripe Tokens, Sources, or PaymentMethods from Apple Pay PKPayment objects. +extension STPAPIClient { + /// Converts Stripe errors into the appropriate Apple Pay error, for use in `PKPaymentAuthorizationResult`. + /// If the error can be fixed by the customer within the Apple Pay sheet, we return an NSError that can be displayed in the Apple Pay sheet. + /// Otherwise, the original error is returned, resulting in the Apple Pay sheet being dismissed. You should display the error message to the customer afterwards. + /// Currently, we convert billing address related errors into a PKPaymentError that helpfully points to the billing address field in the Apple Pay sheet. + /// Note that Apple Pay should prevent most card errors (e.g. invalid CVC, expired cards) when you add a card to the wallet. + /// - Parameter stripeError: An error from the Stripe SDK. + @objc(pkPaymentErrorForStripeError:) + public class func pkPaymentError(forStripeError stripeError: Error?) -> Error? { + guard let stripeError = stripeError else { + return nil + } + + if (stripeError as NSError).domain == STPError.stripeDomain + && ((stripeError as NSError).userInfo[STPError.cardErrorCodeKey] as? String + == STPCardErrorCode.incorrectZip.rawValue) + { + var userInfo = (stripeError as NSError).userInfo + var errorCode: PKPaymentError.Code = .unknownError + errorCode = .billingContactInvalidError + userInfo[PKPaymentErrorKey.postalAddressUserInfoKey.rawValue] = + CNPostalAddressPostalCodeKey + return NSError( + domain: STPError.stripeDomain, + code: errorCode.rawValue, + userInfo: userInfo + ) + } + return stripeError + } +} diff --git a/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext+LegacySupport.swift b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext+LegacySupport.swift new file mode 100644 index 00000000..c71eb499 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext+LegacySupport.swift @@ -0,0 +1,55 @@ +// +// STPApplePayContext+LegacySupport.swift +// StripeApplePay +// +// Created by David Estes on 1/25/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit + +/// Internal Apple Pay class. Do not use. +/// :nodoc: +@objc @_spi(STP) public class _stpinternal_ApplePayContextDidCreatePaymentMethodStorage: NSObject { + @_spi(STP) public weak var delegate: _stpinternal_STPApplePayContextDelegateBase? + @_spi(STP) public var context: STPApplePayContext + @_spi(STP) public var paymentMethod: StripeAPI.PaymentMethod + @_spi(STP) public var paymentInformation: PKPayment + @_spi(STP) public var completion: STPIntentClientSecretCompletionBlock + + @_spi(STP) public init( + delegate: _stpinternal_STPApplePayContextDelegateBase, + context: STPApplePayContext, + paymentMethod: StripeAPI.PaymentMethod, + paymentInformation: PKPayment, + completion: @escaping STPIntentClientSecretCompletionBlock + ) { + self.delegate = delegate + self.context = context + self.paymentMethod = paymentMethod + self.paymentInformation = paymentInformation + self.completion = completion + } +} + +/// Internal Apple Pay class. Do not use. +/// :nodoc: +@objc @_spi(STP) public class _stpinternal_ApplePayContextDidCompleteStorage: NSObject { + @_spi(STP) public weak var delegate: _stpinternal_STPApplePayContextDelegateBase? + @_spi(STP) public var context: STPApplePayContext + @_spi(STP) public var status: STPApplePayContext.PaymentStatus + @_spi(STP) public var error: Error? + + @_spi(STP) public init( + delegate: _stpinternal_STPApplePayContextDelegateBase, + context: STPApplePayContext, + status: STPApplePayContext.PaymentStatus, + error: Error? + ) { + self.delegate = delegate + self.context = context + self.status = status + self.error = error + } +} diff --git a/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext.swift b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext.swift new file mode 100644 index 00000000..9fa84a3d --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/ApplePayContext/STPApplePayContext.swift @@ -0,0 +1,824 @@ +// +// STPApplePayContext.swift +// StripeApplePay +// +// Created by Yuki Tokuhiro on 2/20/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import ObjectiveC +import PassKit +@_spi(STP) import StripeCore + +/// :nodoc: +@objc public protocol _stpinternal_STPApplePayContextDelegateBase: NSObjectProtocol { + /// Called when the user selects a new shipping method. The delegate should determine + /// shipping costs based on the shipping method and either the shipping address supplied in the original + /// PKPaymentRequest or the address fragment provided by the last call to paymentAuthorizationController: + /// didSelectShippingContact:completion:. + /// You must invoke the completion block with an updated array of PKPaymentSummaryItem objects. + @objc(applePayContext:didSelectShippingMethod:handler:) + optional func applePayContext( + _ context: STPApplePayContext, + didSelect shippingMethod: PKShippingMethod, + handler: @escaping (_ update: PKPaymentRequestShippingMethodUpdate) -> Void + ) + + /// Called when the user has selected a new shipping address. You should inspect the + /// address and must invoke the completion block with an updated array of PKPaymentSummaryItem objects. + /// @note To maintain privacy, the shipping information is anonymized. For example, in the United States it only includes the city, state, and zip code. This provides enough information to calculate shipping costs, without revealing sensitive information until the user actually approves the purchase. + /// Receive full shipping information in the paymentInformation passed to `applePayContext:didCreatePaymentMethod:paymentInformation:completion:` + @objc optional func applePayContext( + _ context: STPApplePayContext, + didSelectShippingContact contact: PKContact, + handler: @escaping (_ update: PKPaymentRequestShippingContactUpdate) -> Void + ) + + /// Called when the user has entered or updated a coupon code. You should validate the + /// coupon and must invoke the completion block with a PKPaymentRequestCouponCodeUpdate object. + @available(iOS 15.0, *) + @objc optional func applePayContext( + _ context: STPApplePayContext, + didChangeCouponCode couponCode: String, + handler completion: @escaping (PKPaymentRequestCouponCodeUpdate) -> Void + ) + + /// Optionally configure additional information on your PKPaymentAuthorizationResult. + /// This closure will be called after the PaymentIntent or SetupIntent is confirmed, but before + /// the Apple Pay sheet has been closed. + /// In your implementation, you can configure the PKPaymentAuthorizationResult to add custom fields, such as `orderDetails`. + /// See https://developer.apple.com/documentation/passkit/pkpaymentauthorizationresult for all configuration options. + /// This method is optional. If you implement this, you must call the handler block with the PKPaymentAuthorizationResult on the main queue. + /// WARNING: If you do not call the completion handler, your app will hang until the Apple Pay sheet times out. + @objc optional func applePayContext( + _ context: STPApplePayContext, + willCompleteWithResult authorizationResult: PKPaymentAuthorizationResult, + handler: @escaping (_ authorizationResult: PKPaymentAuthorizationResult) -> Void + ) +} + +/// Implement the required methods of this delegate to supply a PaymentIntent to ApplePayContext and be notified of the completion of the Apple Pay payment. +/// You may also implement the optional delegate methods to handle shipping methods and shipping address changes e.g. to verify you can ship to the address, or update the payment amount. +public protocol ApplePayContextDelegate: _stpinternal_STPApplePayContextDelegateBase { + /// Called after the customer has authorized Apple Pay. Implement this method to call the completion block with the client secret of a PaymentIntent or SetupIntent. + /// - Parameters: + /// - paymentMethod: The PaymentMethod that represents the customer's Apple Pay payment method. + /// If you create the PaymentIntent with confirmation_method=manual, pass `paymentMethod.id` as the payment_method and confirm=true. Otherwise, you can ignore this parameter. + /// - paymentInformation: The underlying PKPayment created by Apple Pay. + /// If you create the PaymentIntent with confirmation_method=manual, you can collect shipping information using its `shippingContact` and `shippingMethod` properties. + /// - completion: Call this with the PaymentIntent or SetupIntent client secret, or the error that occurred creating the PaymentIntent or SetupIntent. + func applePayContext( + _ context: STPApplePayContext, + didCreatePaymentMethod paymentMethod: StripeAPI.PaymentMethod, + paymentInformation: PKPayment, + completion: @escaping STPIntentClientSecretCompletionBlock + ) + + /// Called after the Apple Pay sheet is dismissed with the result of the payment. + /// Your implementation could stop a spinner and display a receipt view or error to the customer, for example. + /// - Parameters: + /// - status: The status of the payment + /// - error: The error that occurred, if any. + func applePayContext( + _ context: STPApplePayContext, + didCompleteWith status: STPApplePayContext.PaymentStatus, + error: Error? + ) +} + +/// A helper class that implements Apple Pay. +/// Usage looks like this: +/// 1. Initialize this class with a PKPaymentRequest describing the payment request (amount, line items, required shipping info, etc) +/// 2. Call presentApplePayOnViewController:completion: to present the Apple Pay sheet and begin the payment process +/// 3 (optional): If you need to respond to the user changing their shipping information/shipping method, implement the optional delegate methods +/// 4. When the user taps 'Buy', this class uses the PaymentIntent that you supply in the applePayContext:didCreatePaymentMethod:completion: delegate method to complete the payment +/// 5. After payment completes/errors and the sheet is dismissed, this class informs you in the applePayContext:didCompleteWithStatus: delegate method +/// - seealso: https://stripe.com/docs/apple-pay#native for a full guide +/// - seealso: ApplePayExampleViewController for an example +@objc(STPApplePayContext) +public class STPApplePayContext: NSObject, PKPaymentAuthorizationControllerDelegate { + enum Error: Swift.Error { + case invalidFinalState + } + /// A special string that can be passed in place of a intent client secret to force showing success and return a PaymentState of `success`. + /// - Note: ⚠️ If provided, the SDK performs no action to complete the payment or setup - it doesn't confirm a PaymentIntent or SetupIntent or handle next actions. + /// You should only use this if your integration can't create a PaymentIntent or SetupIntent. It is your responsibility to ensure that you only pass this value if the payment or set up is successful. + @_spi(STP) public static let COMPLETE_WITHOUT_CONFIRMING_INTENT = "COMPLETE_WITHOUT_CONFIRMING_INTENT" + + /// Initializes this class. + /// @note This may return nil if the request is invalid e.g. the user is restricted by parental controls, or can't make payments on any of the request's supported networks + /// @note If using Swift, using ApplePayContextDelegate is recommended over STPApplePayContextDelegate. + /// - Parameters: + /// - paymentRequest: The payment request to use with Apple Pay. + /// - delegate: The delegate. + @objc(initWithPaymentRequest:delegate:) + public required init?( + paymentRequest: PKPaymentRequest, + delegate: _stpinternal_STPApplePayContextDelegateBase? + ) { + STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPApplePayContext.self) + let canMakePayments: Bool = { + if #available(iOS 15.0, *) { + // On iOS 15+, Apple Pay can be displayed even though there are no cards because Apple added the ability for customers to add cards in the payment sheet (see WWDC '21 "What's new in Wallet and Apple Pay") + return PKPaymentAuthorizationController.canMakePayments() + } else { + return PKPaymentAuthorizationController.canMakePayments(usingNetworks: StripeAPI.supportedPKPaymentNetworks()) + } + }() + + assert(!paymentRequest.merchantIdentifier.isEmpty, "You must set `merchantIdentifier` on your payment request.") + guard + canMakePayments, + !paymentRequest.merchantIdentifier.isEmpty, + // PKPaymentAuthorizationController's docs incorrectly state: + // "If the user can’t make payments on any of the payment request’s supported networks, initialization fails and this method returns nil." + // In actuality, this initializer is non-nullable. To make sure we return nil when the request is invalid, we'll use PKPaymentAuthorizationViewController's initializer, which *is* nullable. + PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) != nil + else { + return nil + } + authorizationController = PKPaymentAuthorizationController(paymentRequest: paymentRequest) + + self.delegate = delegate + + super.init() + authorizationController?.delegate = self + } + + private var presentationWindow: UIWindow? + + /// Presents the Apple Pay sheet from the key window, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// - Parameters: + /// - completion: Called after the Apple Pay sheet is presented + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) + @objc(presentApplePayWithCompletion:) + public func presentApplePay(completion: STPVoidBlock? = nil) { + #if os(visionOS) + // This isn't great: We should encourage the use of presentApplePay(from window:) instead. + let windows = UIApplication.shared.connectedScenes + .compactMap { ($0 as? UIWindowScene)?.windows } + .flatMap { $0 } + .sorted { firstWindow, _ in firstWindow.isKeyWindow } + let window = windows.first + #else + let window = UIApplication.shared.windows.first { $0.isKeyWindow } + #endif + self.presentApplePay(from: window, completion: completion) + } + + /// Presents the Apple Pay sheet from the specified window, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// - Parameters: + /// - window: The UIWindow to host the Apple Pay sheet + /// - completion: Called after the Apple Pay sheet is presented + @objc(presentApplePayFromWindow:completion:) + public func presentApplePay(from window: UIWindow?, completion: STPVoidBlock? = nil) { + presentationWindow = window + guard !didPresentApplePay, let applePayController = self.authorizationController else { + assert( + false, + "This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay." + ) + return + } + didPresentApplePay = true + + // This instance (and the associated Objective-C bridge object, if any) must live so + // that the apple pay sheet is dismissed; until then, the app is effectively frozen. + objc_setAssociatedObject( + applePayController, + UnsafeRawPointer(&kApplePayContextAssociatedObjectKey), + self, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + + applePayController.present { (_) in + DispatchQueue.main.async { + completion?() + } + } + } + + /// Presents the Apple Pay sheet from the specified view controller, starting the payment process. + /// @note This method should only be called once; create a new instance of STPApplePayContext every time you present Apple Pay. + /// @deprecated A presenting UIViewController is no longer needed. Use presentApplePay(completion:) instead. + /// - Parameters: + /// - viewController: The UIViewController instance to present the Apple Pay sheet on + /// - completion: Called after the Apple Pay sheet is presented + @objc(presentApplePayOnViewController:completion:) + @available( + *, + deprecated, + message: "Use `presentApplePay(completion:)` instead.", + renamed: "presentApplePay(completion:)" + ) + public func presentApplePay( + on viewController: UIViewController, + completion: STPVoidBlock? = nil + ) { + let window = viewController.viewIfLoaded?.window + presentApplePay(from: window, completion: completion) + } + + /// The API Client to use to make requests. + /// Defaults to `STPAPIClient.shared` + @objc public var apiClient: STPAPIClient = STPAPIClient.shared + /// ApplePayContext passes this to the /confirm endpoint for PaymentIntents if it did not collect shipping details itself. + /// :nodoc: + @_spi(STP) public var shippingDetails: StripeAPI.ShippingDetails? + private weak var delegate: _stpinternal_STPApplePayContextDelegateBase? + @objc var authorizationController: PKPaymentAuthorizationController? + @_spi(STP) public var returnUrl: String? + + @_spi(STP) @frozen public enum ConfirmType { + case client + case server + /// The merchant backend used the special string instead of a intent client secret, so we completed the payment without confirming an intent. + case none + } + /// Tracks where the call to confirm the PaymentIntent or SetupIntent happened. + @_spi(STP) public var confirmType: ConfirmType? + // Internal state + private var paymentState: PaymentState = .notStarted + private var error: Swift.Error? + /// YES if the flow cancelled or timed out. This toggles which delegate method (didFinish or didAuthorize) calls our didComplete delegate method + private var didCancelOrTimeoutWhilePending = false + private var didPresentApplePay = false + + /// :nodoc: + @objc public override func responds(to aSelector: Selector!) -> Bool { + // ApplePayContextDelegate exposes methods that map 1:1 to PKPaymentAuthorizationControllerDelegate methods + // We want this method to return YES for these methods IFF they are implemented by our delegate + + // Why not simply implement the methods to call their equivalents on self.delegate? + // The implementation of e.g. didSelectShippingMethod must call the completion block. + // If the user does not implement e.g. didSelectShippingMethod, we don't know the correct PKPaymentSummaryItems to pass to the completion block + // (it may have changed since we were initialized due to another delegate method) + if let equivalentDelegateSelector = _delegateToAppleDelegateMapping()[aSelector] { + return delegate?.responds(to: equivalentDelegateSelector) ?? false + } else { + return super.responds(to: aSelector) + } + } + + // MARK: - Private Helper + func _delegateToAppleDelegateMapping() -> [Selector: Selector] { + typealias pkDidSelectShippingMethodSignature = + (any PKPaymentAuthorizationControllerDelegate) -> ( + ( + PKPaymentAuthorizationController, + PKShippingMethod, + @escaping (PKPaymentRequestShippingMethodUpdate) -> Void + ) -> Void + )? + let pk_didSelectShippingMethod = #selector( + (PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( + _: + didSelectShippingMethod: + handler: + )) as pkDidSelectShippingMethodSignature) + let stp_didSelectShippingMethod = #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didSelect:handler:)) + let pk_didSelectShippingContact = #selector( + PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( + _: + didSelectShippingContact: + handler: + )) + let stp_didSelectShippingContact = #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext( + _: + didSelectShippingContact: + handler: + )) + + var delegateToAppleDelegateMapping = [ + pk_didSelectShippingMethod: stp_didSelectShippingMethod, + pk_didSelectShippingContact: stp_didSelectShippingContact, + ] + + if #available(iOS 15.0, *) { + // On iOS 15+, Apple Pay can now accept coupon codes directly, so we need to broker the + // new coupon delegate functions between the host app and Apple Pay. + let pk_didChangeCouponCode = #selector( + PKPaymentAuthorizationControllerDelegate.paymentAuthorizationController( + _: + didChangeCouponCode: + handler: + )) + let stp_didChangeCouponCode = #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext( + _: + didChangeCouponCode: + handler: + )) + + delegateToAppleDelegateMapping[pk_didChangeCouponCode] = stp_didChangeCouponCode + } + + return delegateToAppleDelegateMapping + } + + func _end() { + if let authorizationController = authorizationController { + objc_setAssociatedObject( + authorizationController, + UnsafeRawPointer(&kApplePayContextAssociatedObjectKey), + nil, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + } + authorizationController = nil + delegate = nil + } + + func _shippingDetails(from payment: PKPayment) -> StripeAPI.ShippingDetails? { + guard let address = payment.shippingContact?.postalAddress, + let name = payment.shippingContact?.name + else { + // The shipping address street and name are required parameters for a valid .ShippingDetails + // Return `shippingDetails` instead + return shippingDetails + } + + let addressParams = StripeAPI.ShippingDetails.Address( + city: address.city, + country: address.isoCountryCode, + line1: address.street, + postalCode: address.postalCode, + state: address.state + ) + + let formatter = PersonNameComponentsFormatter() + formatter.style = .long + let shippingParams = StripeAPI.ShippingDetails( + address: addressParams, + name: formatter.string(from: name), + phone: payment.shippingContact?.phoneNumber?.stringValue + ) + + return shippingParams + } + + // MARK: - PKPaymentAuthorizationControllerDelegate + /// :nodoc: + @objc(paymentAuthorizationController:didAuthorizePayment:handler:) + public func paymentAuthorizationController( + _ controller: PKPaymentAuthorizationController, + didAuthorizePayment payment: PKPayment, + handler completion: @escaping (PKPaymentAuthorizationResult) -> Void + ) { + // Some observations (on iOS 12 simulator): + // - The docs say localizedDescription can be shown in the Apple Pay sheet, but I haven't seen this. + // - If you call the completion block w/ a status of .failure and an error, the user is prompted to try again. + + _completePayment(with: payment) { status, error in + let errors = [STPAPIClient.pkPaymentError(forStripeError: error)].compactMap({ $0 }) + let result = PKPaymentAuthorizationResult(status: status, errors: errors) + if self.delegate?.responds( + to: #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext( + _: + willCompleteWithResult: + handler: + )) + ) + ?? false + { + self.delegate?.applePayContext?( + self, + willCompleteWithResult: result, + handler: { newResult in + completion(newResult) + } + ) + } else { + completion(result) + } + } + } + + /// :nodoc: + @objc + public func paymentAuthorizationController( + _ controller: PKPaymentAuthorizationController, + didSelectShippingMethod shippingMethod: PKShippingMethod, + handler completion: @escaping (PKPaymentRequestShippingMethodUpdate) -> Void + ) { + if delegate?.responds( + to: #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didSelect:handler:)) + ) + ?? false + { + delegate?.applePayContext?(self, didSelect: shippingMethod, handler: completion) + } + } + + /// :nodoc: + @objc + public func paymentAuthorizationController( + _ controller: PKPaymentAuthorizationController, + didSelectShippingContact contact: PKContact, + handler completion: @escaping (PKPaymentRequestShippingContactUpdate) -> Void + ) { + if delegate?.responds( + to: #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext( + _: + didSelectShippingContact: + handler: + )) + ) ?? false { + delegate?.applePayContext?(self, didSelectShippingContact: contact, handler: completion) + } + } + + /// :nodoc: + @available(iOS 15.0, *) + @objc + public func paymentAuthorizationController( + _ controller: PKPaymentAuthorizationController, + didChangeCouponCode couponCode: String, + handler completion: @escaping (PKPaymentRequestCouponCodeUpdate) -> Void) { + + if delegate?.responds( + to: #selector( + _stpinternal_STPApplePayContextDelegateBase.applePayContext(_:didChangeCouponCode:handler:)) + ) ?? false { + delegate?.applePayContext?(self, didChangeCouponCode: couponCode, handler: completion) + } + } + + /// :nodoc: + @objc public func paymentAuthorizationControllerDidFinish( + _ controller: PKPaymentAuthorizationController + ) { + // Note: If you don't dismiss the VC, the UI disappears, the VC blocks interaction, and this method gets called again. + // Note: This method is called if the user cancels (taps outside the sheet) or Apple Pay times out (empirically 30 seconds) + switch paymentState { + case .notStarted: + controller.dismiss { + stpDispatchToMainThreadIfNecessary { + self.callDidCompleteDelegate(status: .userCancellation, error: nil) + self._end() + } + } + case .pending: + // We can't cancel a pending payment. If we dismiss the VC now, the customer might interact with the app and miss seeing the result of the payment - risking a double charge, chargeback, etc. + // Instead, we'll dismiss and notify our delegate when the payment finishes. + didCancelOrTimeoutWhilePending = true + case .error: + controller.dismiss { + stpDispatchToMainThreadIfNecessary { + self.callDidCompleteDelegate(status: .error, error: self.error) + self._end() + } + } + case .success: + controller.dismiss { + stpDispatchToMainThreadIfNecessary { + self.callDidCompleteDelegate(status: .success, error: nil) + self._end() + } + } + } + } + + /// :nodoc: + @objc public func presentationWindow( + for controller: PKPaymentAuthorizationController + ) -> UIWindow? { + return presentationWindow + } + + // MARK: - Helpers + func _completePayment( + with payment: PKPayment, + completion: @escaping (PKPaymentAuthorizationStatus, Swift.Error?) -> Void + ) { + // Helper to handle annoying logic around "Do I call completion block or dismiss + call delegate?" + let handleFinalState: ((PaymentState, Swift.Error?) -> Void) = { state, error in + switch state { + case .error: + self.paymentState = .error + self.error = error + if self.didCancelOrTimeoutWhilePending { + self.authorizationController?.dismiss { + DispatchQueue.main.async { + self.callDidCompleteDelegate(status: .error, error: self.error) + self._end() + } + } + } else { + completion(PKPaymentAuthorizationStatus.failure, error) + } + return + case .success: + self.paymentState = .success + if self.didCancelOrTimeoutWhilePending { + self.authorizationController?.dismiss { + DispatchQueue.main.async { + self.callDidCompleteDelegate(status: .success, error: nil) + self._end() + } + } + } else { + completion(PKPaymentAuthorizationStatus.success, nil) + } + return + case .pending, .notStarted: + let errorAnalytic = ErrorAnalytic(event: .unexpectedApplePayError, + error: Error.invalidFinalState) + STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) + stpAssertionFailure("Invalid final state") + return + } + } + + // 1. Create PaymentMethod + StripeAPI.PaymentMethod.create(apiClient: apiClient, payment: payment) { result in + guard let paymentMethod = try? result.get(), self.authorizationController != nil else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + + let paymentMethodCompletion: STPIntentClientSecretCompletionBlock = { + clientSecret, + intentCreationError in + guard let clientSecret = clientSecret, intentCreationError == nil, + self.authorizationController != nil + else { + handleFinalState(.error, intentCreationError) + return + } + + guard clientSecret != STPApplePayContext.COMPLETE_WITHOUT_CONFIRMING_INTENT else { + self.confirmType = STPApplePayContext.ConfirmType.none + handleFinalState(.success, nil) + return + } + + if StripeAPI.SetupIntentConfirmParams.isClientSecretValid(clientSecret) { + // 3a. Retrieve the SetupIntent and see if we need to confirm it client-side + StripeAPI.SetupIntent.get(apiClient: self.apiClient, clientSecret: clientSecret) + { + result in + guard let setupIntent = try? result.get(), + self.authorizationController != nil + else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + + switch setupIntent.status { + case .requiresConfirmation, .requiresAction, .requiresPaymentMethod: + self.confirmType = .client + // 4a. Confirm the SetupIntent + self.paymentState = .pending // After this point, we can't cancel + var confirmParams = StripeAPI.SetupIntentConfirmParams( + clientSecret: clientSecret + ) + confirmParams.paymentMethod = paymentMethod.id + confirmParams.useStripeSdk = true + confirmParams.returnUrl = self.returnUrl + + StripeAPI.SetupIntent.confirm( + apiClient: self.apiClient, + params: confirmParams + ) { + result in + guard let setupIntent = try? result.get(), + self.authorizationController != nil, + setupIntent.status == .succeeded + else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + + handleFinalState(.success, nil) + } + case .succeeded: + self.confirmType = .server + handleFinalState(.success, nil) + case .canceled, .processing, .unknown, .unparsable, .none: + handleFinalState( + .error, + Self.makeUnknownError( + message: + "The SetupIntent is in an unexpected state: \(setupIntent.status!)" + ) + ) + } + } + } else { + let paymentIntentClientSecret = clientSecret + // 3b. Retrieve the PaymentIntent and see if we need to confirm it client-side + StripeAPI.PaymentIntent.get( + apiClient: self.apiClient, + clientSecret: paymentIntentClientSecret + ) { result in + guard let paymentIntent = try? result.get(), + self.authorizationController != nil + else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + + if paymentIntent.confirmationMethod == .automatic + && (paymentIntent.status == .requiresPaymentMethod + || paymentIntent.status == .requiresConfirmation) + { + self.confirmType = .client + // 4b. Confirm the PaymentIntent + + var paymentIntentParams = StripeAPI.PaymentIntentParams( + clientSecret: paymentIntentClientSecret + ) + paymentIntentParams.paymentMethod = paymentMethod.id + paymentIntentParams.useStripeSdk = true + // If a merchant attaches shipping to the PI on their server, the /confirm endpoint will error if we update shipping with a “requires secret key” error message. + // To accommodate this, don't attach if our shipping is the same as the PI's shipping + if paymentIntent.shipping != self._shippingDetails(from: payment) { + paymentIntentParams.shipping = self._shippingDetails(from: payment) + } + + self.paymentState = .pending // After this point, we can't cancel + + // We don't use PaymentHandler because we can't handle next actions as-is - we'd need to dismiss the Apple Pay VC. + StripeAPI.PaymentIntent.confirm( + apiClient: self.apiClient, + params: paymentIntentParams + ) { + result in + guard let postConfirmPI = try? result.get(), + postConfirmPI.status == .succeeded + || postConfirmPI.status == .requiresCapture + else { + if case .failure(let error) = result { + handleFinalState(.error, error) + } else { + handleFinalState(.error, nil) + } + return + } + handleFinalState(.success, nil) + } + } else if paymentIntent.status == .succeeded + || paymentIntent.status == .requiresCapture + { + self.confirmType = .server + handleFinalState(.success, nil) + } else { + let unknownError = Self.makeUnknownError( + message: + "The PaymentIntent is in an unexpected state. If you pass confirmation_method = manual when creating the PaymentIntent, also pass confirm = true. If server-side confirmation fails, double check you are passing the error back to the client." + ) + handleFinalState(.error, unknownError) + } + } + } + } + // 2. Fetch PaymentIntent/SetupIntent client secret from delegate + let legacyDelegateSelector = NSSelectorFromString( + "applePayContext:didCreatePaymentMethod:paymentInformation:completion:" + ) + if let delegate = self.delegate { + if let delegate = delegate as? ApplePayContextDelegate { + delegate.applePayContext( + self, + didCreatePaymentMethod: paymentMethod, + paymentInformation: payment, + completion: paymentMethodCompletion + ) + } else if delegate.responds(to: legacyDelegateSelector), + let helperClass = NSClassFromString("STPApplePayContextLegacyHelper") + { + let legacyStorage = _stpinternal_ApplePayContextDidCreatePaymentMethodStorage( + delegate: delegate, + context: self, + paymentMethod: paymentMethod, + paymentInformation: payment, + completion: paymentMethodCompletion + ) + helperClass.performDidCreatePaymentMethod(legacyStorage) + } else { + assertionFailure( + "An STPApplePayContext's delegate must conform to ApplePayContextDelegate or STPApplePayContextDelegate." + ) + } + } + } + } + + func callDidCompleteDelegate(status: PaymentStatus, error: Swift.Error?) { + if let delegate = self.delegate { + if let delegate = delegate as? ApplePayContextDelegate { + delegate.applePayContext(self, didCompleteWith: status, error: error) + } else if delegate.responds( + to: NSSelectorFromString("applePayContext:didCompleteWithStatus:error:") + ) { + if let helperClass = NSClassFromString("STPApplePayContextLegacyHelper") { + let legacyStorage = _stpinternal_ApplePayContextDidCompleteStorage( + delegate: delegate, + context: self, + status: status, + error: error + ) + helperClass.performDidComplete(legacyStorage) + } + } else { + assertionFailure( + "An STPApplePayContext's delegate must conform to ApplePayContextDelegate or STPApplePayContextDelegate." + ) + } + } + + } + + @_spi(STP) public static func makeUnknownError(message: String) -> NSError { + let userInfo = [ + NSLocalizedDescriptionKey: NSError.stp_unexpectedErrorMessage(), + STPError.errorMessageKey: message, + ] + return NSError( + domain: STPError.STPPaymentHandlerErrorDomain, + code: STPPaymentHandlerErrorCodeIntentStatusErrorCode, + userInfo: userInfo + ) + } + + /// This is STPPaymentHandlerErrorCode.intentStatusErrorCode.rawValue, which we don't want to vend from this framework. + fileprivate static let STPPaymentHandlerErrorCodeIntentStatusErrorCode = 3 + + enum PaymentState { + case notStarted + case pending + case error + case success + } + + /// An enum representing the status of a payment requested from the user. + @frozen public enum PaymentStatus { + /// The payment succeeded. + case success + /// The payment failed due to an unforeseen error, such as the user's Internet connection being offline. + case error + /// The user cancelled the payment (for example, by hitting "cancel" in the Apple Pay dialog). + case userCancellation + } +} + +/// :nodoc: +@_spi(STP) extension STPApplePayContext: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier: String { + return "STPApplePayContext" + } +} + +/// :nodoc: +class ModernApplePayContext: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier: String { + return "ModernApplePayContext" + } +} + +private var kSTPApplePayContextAssociatedObjectKey = 0 +enum STPPaymentState: Int { + case notStarted + case pending + case error + case success +} + +private class _stpinternal_STPApplePayContextLegacyHelper: NSObject { + @objc class func performDidCreatePaymentMethod( + _ storage: _stpinternal_ApplePayContextDidCreatePaymentMethodStorage + ) { + // Placeholder to allow this to be called on AnyObject + } + @objc class func performDidComplete(_ storage: _stpinternal_ApplePayContextDidCompleteStorage) { + // Placeholder to allow this to be called on AnyObject + } +} + +private var kApplePayContextAssociatedObjectKey = 0 diff --git a/StripeApplePay/StripeApplePay/Source/Blocks.swift b/StripeApplePay/StripeApplePay/Source/Blocks.swift new file mode 100644 index 00000000..e434acbc --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Blocks.swift @@ -0,0 +1,18 @@ +// +// Blocks.swift +// StripeApplePay +// +// Created by David Estes on 1/6/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// An empty block, called with no arguments, returning nothing. +public typealias STPVoidBlock = () -> Void + +/// A block to be run with the client secret of a PaymentIntent or SetupIntent. +/// - Parameters: +/// - clientSecret: The client secret of the PaymentIntent or SetupIntent. See https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret +/// - error: The error that occurred when creating the Intent, or nil if none occurred. +public typealias STPIntentClientSecretCompletionBlock = (String?, Error?) -> Void diff --git a/StripeApplePay/StripeApplePay/Source/Extensions/BillingDetails+ApplePay.swift b/StripeApplePay/StripeApplePay/Source/Extensions/BillingDetails+ApplePay.swift new file mode 100644 index 00000000..8e1ed5c2 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Extensions/BillingDetails+ApplePay.swift @@ -0,0 +1,101 @@ +// +// BillingDetails+ApplePay.swift +// StripeApplePay +// +// Created by David Estes on 8/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeContact { + /// Initializes a new Contact with data from an PassKit contact. + /// - Parameter contact: The PassKit contact you want to populate the Contact from. + /// - Returns: A new Contact with data copied from the passed in contact. + init( + pkContact contact: PKContact + ) { + let nameComponents = contact.name + if let nameComponents = nameComponents { + givenName = stringIfHasContentsElseNil(nameComponents.givenName) + familyName = stringIfHasContentsElseNil(nameComponents.familyName) + + name = stringIfHasContentsElseNil( + PersonNameComponentsFormatter.localizedString(from: nameComponents, style: .default) + ) + } + email = stringIfHasContentsElseNil(contact.emailAddress) + if let phoneNumber = contact.phoneNumber { + phone = sanitizedPhoneString(from: phoneNumber) + } else { + phone = nil + } + setAddressFromCNPostal(contact.postalAddress) + } + + private func sanitizedPhoneString(from phoneNumber: CNPhoneNumber) -> String? { + return stringIfHasContentsElseNil( + STPNumericStringValidator.sanitizedNumericString(for: phoneNumber.stringValue) + ) + } + + private mutating func setAddressFromCNPostal(_ address: CNPostalAddress?) { + line1 = stringIfHasContentsElseNil(address?.street) + city = stringIfHasContentsElseNil(address?.city) + state = stringIfHasContentsElseNil(address?.state) + postalCode = stringIfHasContentsElseNil(address?.postalCode) + country = stringIfHasContentsElseNil(address?.isoCountryCode.uppercased()) + } +} + +extension StripeAPI.BillingDetails { + init?( + from payment: PKPayment + ) { + var billingDetails: StripeAPI.BillingDetails? + if payment.billingContact != nil { + billingDetails = StripeAPI.BillingDetails() + if let billingContact = payment.billingContact { + let billingAddress = StripeContact(pkContact: billingContact) + billingDetails?.name = billingAddress.name + billingDetails?.email = billingAddress.email + billingDetails?.phone = billingAddress.phone + if billingContact.postalAddress != nil { + billingDetails?.address = StripeAPI.BillingDetails.Address( + contact: billingAddress + ) + } + } + } + + // The phone number and email in the "Contact" panel in the Apple Pay dialog go into the shippingContact, + // not the billingContact. To work around this, we should fill the billingDetails' email and phone + // number from the shippingDetails. + if payment.shippingContact != nil { + var shippingAddress: StripeContact? + if let shippingContact = payment.shippingContact { + shippingAddress = StripeContact(pkContact: shippingContact) + } + if billingDetails?.email == nil && shippingAddress?.email != nil { + if billingDetails == nil { + billingDetails = StripeAPI.BillingDetails() + } + billingDetails?.email = shippingAddress?.email + } + if billingDetails?.phone == nil && shippingAddress?.phone != nil { + if billingDetails == nil { + billingDetails = StripeAPI.BillingDetails() + } + billingDetails?.phone = shippingAddress?.phone + } + } + + if let billingDetails = billingDetails { + self = billingDetails + } else { + return nil + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/Extensions/PKContact+Stripe.swift b/StripeApplePay/StripeApplePay/Source/Extensions/PKContact+Stripe.swift new file mode 100644 index 00000000..03db9af0 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Extensions/PKContact+Stripe.swift @@ -0,0 +1,26 @@ +// +// PKContact+Stripe.swift +// StripeApplePay +// +// Created by David Estes on 11/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit + +extension PKContact { + @_spi(STP) public var addressParams: [AnyHashable: Any] { + var params: [AnyHashable: Any] = [:] + let stpAddress = StripeContact(pkContact: self) + + params["name"] = stpAddress.name + params["address_line1"] = stpAddress.line1 + params["address_city"] = stpAddress.city + params["address_state"] = stpAddress.state + params["address_zip"] = stpAddress.postalCode + params["address_country"] = stpAddress.country + + return params + } +} diff --git a/StripeApplePay/StripeApplePay/Source/Extensions/PKPayment+Stripe.swift b/StripeApplePay/StripeApplePay/Source/Extensions/PKPayment+Stripe.swift new file mode 100644 index 00000000..f3280e50 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/Extensions/PKPayment+Stripe.swift @@ -0,0 +1,32 @@ +// +// PKPayment+Stripe.swift +// StripeApplePay +// +// Created by Ben Guo on 7/2/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +import PassKit + +extension PKPayment { + /// Returns true if the instance is a payment from the simulator. + @_spi(STP) public func stp_applepay_isSimulated() -> Bool { + return token.transactionIdentifier == "Simulated Identifier" + } + + /// Returns a fake transaction identifier with the expected ~-separated format. + @_spi(STP) public class func stp_applepay_testTransactionIdentifier() -> String { + var uuid = UUID().uuidString + uuid = uuid.replacingOccurrences(of: "~", with: "") + + // Simulated cards don't have enough info yet. For now, use a fake Visa number + let number = "4242424242424242" + + // Without the original PKPaymentRequest, we'll need to use fake data here. + let amount = NSDecimalNumber(string: "0") + let cents = NSNumber(value: amount.multiplying(byPowerOf10: 2).intValue).stringValue + let currency = "USD" + let identifier = ["ApplePayStubs", number, cents, currency, uuid].joined(separator: "~") + return identifier + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Address.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Address.swift new file mode 100644 index 00000000..dc80d6dd --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Address.swift @@ -0,0 +1,43 @@ +// +// Address.swift +// StripeApplePay +// +// Created by David Estes on 8/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// An internal struct for handling contacts. This is not encodable/decodable for use with the Stripe API. +struct StripeContact { + /// The user's full name (e.g. "Jane Doe") + public var name: String? + + /// The first line of the user's street address (e.g. "123 Fake St") + public var line1: String? + + /// The apartment, floor number, etc of the user's street address (e.g. "Apartment 1A") + public var line2: String? + + /// The city in which the user resides (e.g. "San Francisco") + public var city: String? + + /// The state in which the user resides (e.g. "CA") + public var state: String? + + /// The postal code in which the user resides (e.g. "90210") + public var postalCode: String? + + /// The ISO country code of the address (e.g. "US") + public var country: String? + + /// The phone number of the address (e.g. "8885551212") + public var phone: String? + + /// The email of the address (e.g. "jane@doe.com") + public var email: String? + + internal var givenName: String? + internal var familyName: String? +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/BillingDetails.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/BillingDetails.swift new file mode 100644 index 00000000..1871ec79 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/BillingDetails.swift @@ -0,0 +1,67 @@ +// +// BillingDetails.swift +// StripeApplePay +// +// Created by David Estes on 7/15/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + /// Billing information associated with a `STPPaymentMethod` that may be used or required by particular types of payment methods. + /// - seealso: https://stripe.com/docs/api/payment_methods/object#payment_method_object-billing_details + public struct BillingDetails: UnknownFieldsCodable { + /// Billing address. + public var address: Address? + + /// The billing address, a property sent in a PaymentMethod response + public struct Address: UnknownFieldsCodable { + /// The first line of the user's street address (e.g. "123 Fake St") + public var line1: String? + + /// The apartment, floor number, etc of the user's street address (e.g. "Apartment 1A") + public var line2: String? + + /// The city in which the user resides (e.g. "San Francisco") + public var city: String? + + /// The state in which the user resides (e.g. "CA") + public var state: String? + + /// The postal code in which the user resides (e.g. "90210") + public var postalCode: String? + + /// The ISO country code of the address (e.g. "US") + public var country: String? + + public var _additionalParametersStorage: NonEncodableParameters? + public var _allResponseFieldsStorage: NonEncodableParameters? + } + + /// Email address. + public var email: String? + /// Full name. + public var name: String? + /// Billing phone number (including extension). + public var phone: String? + + public var _additionalParametersStorage: NonEncodableParameters? + public var _allResponseFieldsStorage: NonEncodableParameters? + } + +} + +extension StripeAPI.BillingDetails.Address { + init( + contact: StripeContact + ) { + self.city = contact.city + self.country = contact.country + self.line1 = contact.line1 + self.line2 = contact.line2 + self.postalCode = contact.postalCode + self.state = contact.state + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/CardBrand.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/CardBrand.swift new file mode 100644 index 00000000..0ca26890 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/CardBrand.swift @@ -0,0 +1,33 @@ +// +// CardBrand.swift +// StripeApplePay +// +// Created by David Estes on 4/14/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension StripeAPI { + /// The various card brands to which a payment card can belong. + enum CardBrand: String, SafeEnumCodable { + /// Visa card + case visa = "Visa" + /// American Express card + case amex = "American Express" + /// Mastercard card + case mastercard = "MasterCard" + /// Discover card + case discover = "Discover" + /// JCB card + case JCB = "JCB" + /// Diners Club card + case dinersClub = "Diners Club" + /// UnionPay card + case unionPay = "UnionPay" + /// An unknown card brand type + case unknown = "Unknown" + /// An unparsable card brand + case unparsable + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntent.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntent.swift new file mode 100644 index 00000000..24cbfcb2 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntent.swift @@ -0,0 +1,149 @@ +// +// PaymentIntent.swift +// StripeApplePay +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct PaymentIntent: UnknownFieldsDecodable { + // TODO: (MOBILESDK-468) Add modern bindings for more PaymentIntent fields + /// The Stripe ID of the PaymentIntent. + @_spi(STP) public let id: String + + /// The client secret used to fetch this PaymentIntent + @_spi(STP) public let clientSecret: String + + /// Amount intended to be collected by this PaymentIntent. + @_spi(STP) public let amount: Int + + /// If status is `.canceled`, when the PaymentIntent was canceled. + @_spi(STP) public let canceledAt: Date? + + /// Capture method of this PaymentIntent + @_spi(STP) public let captureMethod: CaptureMethod + + /// Confirmation method of this PaymentIntent + @_spi(STP) public let confirmationMethod: ConfirmationMethod + + /// When the PaymentIntent was created. + @_spi(STP) public let created: Date + + /// The currency associated with the PaymentIntent. + @_spi(STP) public let currency: String + + /// The `description` field of the PaymentIntent. + /// An arbitrary string attached to the object. Often useful for displaying to users. + @_spi(STP) public let stripeDescription: String? + + /// Whether or not this PaymentIntent was created in livemode. + @_spi(STP) public let livemode: Bool + + /// Email address that the receipt for the resulting payment will be sent to. + @_spi(STP) public let receiptEmail: String? + + /// The Stripe ID of the Source used in this PaymentIntent. + @_spi(STP) public let sourceId: String? + + /// The Stripe ID of the PaymentMethod used in this PaymentIntent. + @_spi(STP) public let paymentMethodId: String? + + /// Status of the PaymentIntent + @_spi(STP) public let status: Status + + /// Shipping information for this PaymentIntent. + @_spi(STP) public let shipping: ShippingDetails? + + /// Status types for a PaymentIntent + @frozen @_spi(STP) public enum Status: String, SafeEnumCodable { + /// Unknown status + case unknown + /// This PaymentIntent requires a PaymentMethod or Source + case requiresPaymentMethod = "requires_payment_method" + /// This PaymentIntent requires a Source + /// Deprecated: Use STPPaymentIntentStatusRequiresPaymentMethod instead. + @available( + *, + deprecated, + message: "Use STPPaymentIntentStatus.requiresPaymentMethod instead", + renamed: "STPPaymentIntentStatus.requiresPaymentMethod" + ) + case requiresSource = "requires_source" + /// This PaymentIntent needs to be confirmed + case requiresConfirmation = "requires_confirmation" + /// The selected PaymentMethod or Source requires additional authentication steps. + /// Additional actions found via `next_action` + case requiresAction = "requires_action" + /// The selected Source requires additional authentication steps. + /// Additional actions found via `next_source_action` + /// Deprecated: Use STPPaymentIntentStatusRequiresAction instead. + @available( + *, + deprecated, + message: "Use STPPaymentIntentStatus.requiresAction instead", + renamed: "STPPaymentIntentStatus.requiresAction" + ) + case requiresSourceAction = "requires_source_action" + /// Stripe is processing this PaymentIntent + case processing + /// The payment has succeeded + case succeeded + /// Indicates the payment must be captured, for STPPaymentIntentCaptureMethodManual + case requiresCapture = "requires_capture" + /// This PaymentIntent was canceled and cannot be changed. + case canceled + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @frozen @_spi(STP) public enum ConfirmationMethod: String, SafeEnumCodable { + /// Unknown confirmation method + case unknown + /// Confirmed via publishable key + case manual + /// Confirmed via secret key + case automatic + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @frozen @_spi(STP) public enum CaptureMethod: String, SafeEnumCodable { + /// Unknown capture method + case unknown + /// The PaymentIntent will be automatically captured + case automatic + /// The PaymentIntent must be manually captured once it has the status + /// `.requiresCapture` + case manual + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @_spi(STP) public var _allResponseFieldsStorage: NonEncodableParameters? + } +} + +extension StripeAPI.PaymentIntent { + /// Helper function for extracting PaymentIntent id from the Client Secret. + /// This avoids having to pass around both the id and the secret. + /// - Parameter clientSecret: The `client_secret` from the PaymentIntent + internal static func id(fromClientSecret clientSecret: String) -> String? { + // see parseClientSecret from stripe-js-v3 + let components = clientSecret.components(separatedBy: "_secret_") + if components.count >= 2 && components[0].hasPrefix("pi_") { + return components[0] + } else { + return nil + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntentParams.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntentParams.swift new file mode 100644 index 00000000..5dfe11a8 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentIntentParams.swift @@ -0,0 +1,101 @@ +// +// PaymentIntentParams.swift +// StripeApplePay +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct PaymentIntentParams: UnknownFieldsEncodable { + /// The client secret of the PaymentIntent. Required + @_spi(STP) public let clientSecret: String + + @_spi(STP) public init( + clientSecret: String + ) { + self.clientSecret = clientSecret + } + + @_spi(STP) public var id: String? { + return PaymentIntent.id(fromClientSecret: clientSecret) + } + + /// Provide a supported `PaymentMethodParams` object, and Stripe will create a + /// PaymentMethod during PaymentIntent confirmation. + /// @note alternative to `paymentMethodId` + @_spi(STP) public var paymentMethodData: PaymentMethodParams? + + /// Provide an already created PaymentMethod's id, and it will be used to confirm the PaymentIntent. + /// @note alternative to `paymentMethodParams` + @_spi(STP) public var paymentMethod: String? + + /// Provide an already created Source's id, and it will be used to confirm the PaymentIntent. + @_spi(STP) public var sourceId: String? + + /// Email address that the receipt for the resulting payment will be sent to. + @_spi(STP) public var receiptEmail: String? + + /// `@YES` to save this PaymentIntent’s PaymentMethod or Source to the associated Customer, + /// if the PaymentMethod/Source is not already attached. + /// This should be a boolean NSNumber, so that it can be `nil` + @_spi(STP) public var savePaymentMethod: Bool? + + /// The URL to redirect your customer back to after they authenticate or cancel + /// their payment on the payment method’s app or site. + /// This should probably be a URL that opens your iOS app. + @_spi(STP) public var returnURL: String? + + /// When provided, this property indicates how you intend to use the payment method that your customer provides after the current payment completes. + /// If applicable, additional authentication may be performed to comply with regional legislation or network rules required to enable the usage of the same payment method for additional payments. + @_spi(STP) public var setupFutureUsage: SetupFutureUsage? + + /// A boolean number to indicate whether you intend to use the Stripe SDK's functionality to handle any PaymentIntent next actions. + /// If set to false, STPPaymentIntent.nextAction will only ever contain a redirect url that can be opened in a webview or mobile browser. + /// When set to true, the nextAction may contain information that the Stripe SDK can use to perform native authentication within your + /// app. + @_spi(STP) public var useStripeSdk: Bool? + + /// Shipping information. + @_spi(STP) public var shipping: ShippingDetails? + + /// Indicates how you intend to use the payment method that your customer provides after the current payment completes. + /// If applicable, additional authentication may be performed to comply with regional legislation or network rules required to enable the usage of the same payment method for additional payments. + /// - seealso: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-setup_future_usage + @frozen @_spi(STP) public enum SetupFutureUsage: String, SafeEnumCodable { + /// Unknown value. Update your SDK, or use `allResponseFields` for custom handling. + case unknown + /// No value was provided. + case none + /// Indicates you intend to only reuse the payment method when the customer is in your checkout flow. + case onSession + /// Indicates you intend to reuse the payment method when the customer may or may not be in your checkout flow. + case offSession + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } +} + +extension StripeAPI.PaymentIntentParams { + static internal let isClientSecretValidRegex: NSRegularExpression = try! NSRegularExpression( + pattern: "^pi_[^_]+_secret_[^_]+$", + options: [] + ) + + @_spi(STP) public static func isClientSecretValid(_ clientSecret: String) -> Bool { + return + (isClientSecretValidRegex.numberOfMatches( + in: clientSecret, + options: .anchored, + range: NSRange(location: 0, length: clientSecret.count) + )) == 1 + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethod.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethod.swift new file mode 100644 index 00000000..235e47e2 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethod.swift @@ -0,0 +1,176 @@ +// +// PaymentMethod.swift +// StripeApplePay +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + /// PaymentMethod objects represent your customer's payment instruments. They can be used with PaymentIntents to collect payments. + /// - seealso: https://stripe.com/docs/api/payment_methods + public struct PaymentMethod: UnknownFieldsDecodable { + /// The Stripe ID of the PaymentMethod. + public let id: String + + /// Time at which the object was created. Measured in seconds since the Unix epoch. + public var created: Date? + /// `YES` if the object exists in live mode or the value `NO` if the object exists in test mode. + public var livemode = false + + /// The type of the PaymentMethod. The corresponding, similarly named property contains additional information specific to the PaymentMethod type. + /// e.g. if the type is `Card`, the `card` property is also populated. + public var type: PaymentMethodType? + + /// The type of the PaymentMethod. + @frozen public enum PaymentMethodType: String, SafeEnumCodable { + /// A card payment method. + case card + /// An unknown type. + case unknown + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + /// Billing information associated with the PaymentMethod that may be used or required by particular types of payment methods. + public var billingDetails: BillingDetails? + /// The ID of the Customer to which this PaymentMethod is saved. Nil when the PaymentMethod has not been saved to a Customer. + public var customerId: String? + /// If this is a card PaymentMethod (ie `self.type == .card`), this contains additional details. + public var card: Card? + + /// :nodoc: + public struct Card: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + /// The issuer of the card. + public private(set) var brand: Brand = .unknown + + /// The various card brands to which a payment card can belong. + @frozen public enum Brand: String, SafeEnumCodable { + /// Visa + case visa + /// American Express + case amex + /// Mastercard + case mastercard + /// Discover + case discover + /// JCB + case jcb + /// Diners Club + case diners + /// UnionPay + case unionpay + /// An unknown card brand + case unknown + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + /// Two-letter ISO code representing the country of the card. + public private(set) var country: String? + /// Two-digit number representing the card’s expiration month. + public private(set) var expMonth: Int + /// Four-digit number representing the card’s expiration year. + public private(set) var expYear: Int + /// Card funding type. Can be credit, debit, prepaid, or unknown. + public private(set) var funding: String? + /// The last four digits of the card. + public private(set) var last4: String? + /// Uniquely identifies this particular card number. You can use this attribute to check whether two customers who’ve signed up with you are using the same card number, for example. + public private(set) var fingerprint: String? + + /// Contains information about card networks that can be used to process the payment. + public private(set) var networks: Networks? + + /// Contains details on how this Card maybe be used for 3D Secure authentication. + public private(set) var threeDSecureUsage: ThreeDSecureUsage? + + /// If this Card is part of a Card Wallet, this contains the details of the Card Wallet. + public private(set) var wallet: Wallet? + + public struct Networks: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// All available networks for the card. + public private(set) var available: [String]? + /// The preferred network for the card if one exists. + public private(set) var preferred: String? + } + + /// Contains details on how a `Card` may be used for 3D Secure authentication. + public struct ThreeDSecureUsage: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// `true` if 3D Secure is supported on this card. + public private(set) var supported = false + } + + public struct Wallet: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + /// The type of the Card Wallet. A matching property is populated if the type is `.masterpass` or `.visaCheckout` containing additional information specific to the Card Wallet type. + public private(set) var type: WalletType = .unknown + /// Contains additional Masterpass information, if the type of the Card Wallet is `STPPaymentMethodCardWalletTypeMasterpass` + public private(set) var masterpass: Masterpass? + /// Contains additional Visa Checkout information, if the type of the Card Wallet is `STPPaymentMethodCardWalletTypeVisaCheckout` + public private(set) var visaCheckout: VisaCheckout? + + /// The type of Card Wallet. + @frozen public enum WalletType: String, SafeEnumCodable { + /// Amex Express Checkout + case amexExpressCheckout = "amex_express_checkout" + /// Apple Pay + case applePay = "apple_pay" + /// Google Pay + case googlePay = "google_pay" + /// Masterpass + case masterpass = "masterpass" + /// Samsung Pay + case samsungPay = "samsung_pay" + /// Visa Checkout + case visaCheckout = "visa_checkout" + /// An unknown Card Wallet type. + case unknown = "unknown" + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + public struct Masterpass: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var email: String? + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var name: String? + /// Owner’s verified billing address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var billingAddress: BillingDetails.Address? + /// Owner’s verified shipping address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var shippingAddress: BillingDetails.Address? + } + + /// A Visa Checkout Card Wallet + /// - seealso: https://stripe.com/docs/visa-checkout + public struct VisaCheckout: UnknownFieldsDecodable { + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var email: String? + /// Owner’s verified email. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var name: String? + /// Owner’s verified billing address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var billingAddress: BillingDetails.Address? + /// Owner’s verified shipping address. Values are verified or provided by the payment method directly (and if supported) at the time of authorization or settlement. + public private(set) var shippingAddress: BillingDetails.Address? + + public var _allResponseFieldsStorage: NonEncodableParameters? + } + } + } + + public var _allResponseFieldsStorage: NonEncodableParameters? + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethodParams.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethodParams.swift new file mode 100644 index 00000000..c790a9a7 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/PaymentMethodParams.swift @@ -0,0 +1,73 @@ +// +// PaymentMethodParams.swift +// StripeApplePay +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + /// An object representing parameters used to create a PaymentMethod object. + /// - seealso: https://stripe.com/docs/api/payment_methods/create + @_spi(STP) public struct PaymentMethodParams: UnknownFieldsEncodable { + /// The type of payment method. + /// The associated property will contain additional information (e.g. `type == .card` means `card` should also be populated). + @_spi(STP) public var type: PaymentMethod.PaymentMethodType + + /// If this is a card PaymentMethod, this contains the user’s card details. + @_spi(STP) public var card: Card? + + /// Billing information associated with the PaymentMethod that may be used or required by particular types of payment methods. + @_spi(STP) public var billingDetails: BillingDetails? + + /// Used internally to identify the version of the SDK sending the request + @_spi(STP) public var paymentUserAgent: String? = { + return PaymentsSDKVariant.paymentUserAgent + }() + + /// :nodoc: + @_spi(STP) public struct Card: UnknownFieldsEncodable { + /// The card number, as a string without any separators. Ex. "4242424242424242" + @_spi(STP) public var number: String? + /// Number representing the card's expiration month. Ex. 1 + @_spi(STP) public var expMonth: Int? + /// Two- or four-digit number representing the card's expiration year. + @_spi(STP) public var expYear: Int? + /// For backwards compatibility, you can alternatively set this as a Stripe token (e.g., for Apple Pay) + @_spi(STP) public var token: String? + /// Card security code. It is highly recommended to always include this value. + @_spi(STP) public var cvc: String? + + /// The last 4 digits of the card's number, if it's been set, otherwise nil. + @_spi(STP) public var last4: String? { + if number != nil && (number?.count ?? 0) >= 4 { + return (number as NSString?)?.substring(from: (number?.count ?? 0) - 4) + } else { + return nil + } + } + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } +} + +extension StripeAPI.PaymentMethodParams.Card: CustomStringConvertible, CustomDebugStringConvertible, + CustomLeafReflectable +{ + @_spi(STP) public var debugDescription: String { + return description + } + + @_spi(STP) public var description: String { + return "Card \(last4 ?? "")" + } + + @_spi(STP) public var customMirror: Mirror { + return Mirror(reflecting: self.description) + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntent.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntent.swift new file mode 100644 index 00000000..a8369479 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntent.swift @@ -0,0 +1,58 @@ +// +// SetupIntent.swift +// StripeApplePay +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct SetupIntent: UnknownFieldsDecodable { + @_spi(STP) public let id: String + // TODO: (MOBILESDK-467) Add modern bindings for more SetupIntent fields + @_spi(STP) public let status: SetupIntentStatus? + + /// Status types for an STPSetupIntent + @frozen @_spi(STP) public enum SetupIntentStatus: String, SafeEnumCodable { + /// Unknown status + case unknown + /// This SetupIntent requires a PaymentMethod + case requiresPaymentMethod = "requires_payment_method" + /// This SetupIntent needs to be confirmed + case requiresConfirmation = "requires_confirmation" + /// The selected PaymentMethod requires additional authentication steps. + /// Additional actions found via the `nextAction` property of `STPSetupIntent` + case requiresAction = "requires_action" + /// Stripe is processing this SetupIntent + case processing + /// The SetupIntent has succeeded + case succeeded + /// This SetupIntent was canceled and cannot be changed. + case canceled + + case unparsable + // TODO: This is @frozen because of a bug in the Xcode 12.2 Swift compiler. + // Remove @frozen after Xcode 12.2 support has been dropped. + } + + @_spi(STP) public var _allResponseFieldsStorage: NonEncodableParameters? + } +} + +extension StripeAPI.SetupIntent { + /// Helper function for extracting SetupIntent id from the Client Secret. + /// This avoids having to pass around both the id and the secret. + /// - Parameter clientSecret: The `client_secret` from the SetupIntent + internal static func id(fromClientSecret clientSecret: String) -> String? { + // see parseClientSecret from stripe-js-v3 + let components = clientSecret.components(separatedBy: "_secret_") + if components.count >= 2 && components[0].hasPrefix("seti_") { + return components[0] + } else { + return nil + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntentParams.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntentParams.swift new file mode 100644 index 00000000..72d750b1 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/SetupIntentParams.swift @@ -0,0 +1,66 @@ +// +// SetupIntentParams.swift +// StripeApplePay +// +// Created by David Estes on 6/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct SetupIntentConfirmParams: UnknownFieldsEncodable { + /// Generated by by Editor -> Refactor -> Generate Memberwise Initializer + @_spi(STP) public init( + clientSecret: String, + paymentMethodData: StripeAPI.PaymentMethodParams? = nil, + paymentMethod: String? = nil, + returnUrl: String? = nil, + useStripeSdk: Bool? = nil, + _additionalParametersStorage: NonEncodableParameters? = nil + ) { + self.clientSecret = clientSecret + self.paymentMethodData = paymentMethodData + self.paymentMethod = paymentMethod + self.returnUrl = returnUrl + self.useStripeSdk = useStripeSdk + self._additionalParametersStorage = _additionalParametersStorage + } + + /// The client secret of the SetupIntent. Required. + @_spi(STP) public let clientSecret: String + /// Provide a supported `PaymentMethodParams` object, and Stripe will create a + /// PaymentMethod during PaymentIntent confirmation. + /// @note alternative to `paymentMethodId` + @_spi(STP) public var paymentMethodData: PaymentMethodParams? + /// Provide an already created PaymentMethod's id, and it will be used to confirm the SetupIntent. + /// @note alternative to `paymentMethodParams` + @_spi(STP) public var paymentMethod: String? + /// The URL to redirect your customer back to after they authenticate or cancel + /// their payment on the payment method’s app or site. + /// This should probably be a URL that opens your iOS app. + @_spi(STP) public var returnUrl: String? + /// A boolean number to indicate whether you intend to use the Stripe SDK's functionality to handle any SetupIntent next actions. + /// If set to false, SetupIntent.nextAction will only ever contain a redirect url that can be opened in a webview or mobile browser. + /// When set to true, the nextAction may contain information that the Stripe SDK can use to perform native authentication within your + /// app. + @_spi(STP) public var useStripeSdk: Bool? + + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + + // MARK: - Utilities + static private let regex = try! NSRegularExpression( + pattern: "^seti_[^_]+_secret_[^_]+$", + options: [] + ) + @_spi(STP) public static func isClientSecretValid(_ clientSecret: String) -> Bool { + return + (regex.numberOfMatches( + in: clientSecret, + options: .anchored, + range: NSRange(location: 0, length: clientSecret.count) + )) == 1 + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/ShippingDetails.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/ShippingDetails.swift new file mode 100644 index 00000000..cec8a3bb --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/ShippingDetails.swift @@ -0,0 +1,93 @@ +// +// ShippingDetails.swift +// StripeApplePay +// +// Created by Yuki Tokuhiro on 8/4/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + @_spi(STP) public struct ShippingDetails: UnknownFieldsCodable, Equatable { + @_spi(STP) public init( + address: StripeAPI.ShippingDetails.Address, + name: String, + carrier: String? = nil, + phone: String? = nil, + trackingNumber: String? = nil, + _allResponseFieldsStorage: NonEncodableParameters? = nil, + _additionalParametersStorage: NonEncodableParameters? = nil + ) { + self.address = address + self.name = name + self.carrier = carrier + self.phone = phone + self.trackingNumber = trackingNumber + self._allResponseFieldsStorage = _allResponseFieldsStorage + self._additionalParametersStorage = _additionalParametersStorage + } + + /// Shipping address. + @_spi(STP) public var address: Address + + /// Recipient name. + @_spi(STP) public var name: String + + /// The delivery service that shipped a physical product, such as Fedex, UPS, USPS, etc. + @_spi(STP) public var carrier: String? + + /// Recipient phone (including extension). + @_spi(STP) public var phone: String? + + /// The tracking number for a physical product, obtained from the delivery service. If multiple tracking numbers were generated for this purchase, please separate them with commas. + @_spi(STP) public var trackingNumber: String? + + @_spi(STP) public var _allResponseFieldsStorage: NonEncodableParameters? + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + + @_spi(STP) public struct Address: UnknownFieldsCodable, Equatable { + @_spi(STP) public init( + city: String? = nil, + country: String? = nil, + line1: String, + line2: String? = nil, + postalCode: String? = nil, + state: String? = nil, + _allResponseFieldsStorage: NonEncodableParameters? = nil, + _additionalParametersStorage: NonEncodableParameters? = nil + ) { + self.city = city + self.country = country + self.line1 = line1 + self.line2 = line2 + self.postalCode = postalCode + self.state = state + self._allResponseFieldsStorage = _allResponseFieldsStorage + self._additionalParametersStorage = _additionalParametersStorage + } + + /// City/District/Suburb/Town/Village. + @_spi(STP) public var city: String? + + /// Two-letter country code (ISO 3166-1 alpha-2). + @_spi(STP) public var country: String? + + /// Address line 1 (Street address/PO Box/Company name). + @_spi(STP) public var line1: String + + /// Address line 2 (Apartment/Suite/Unit/Building). + @_spi(STP) public var line2: String? + + /// ZIP or postal code. + @_spi(STP) public var postalCode: String? + + /// State/County/Province/Region. + @_spi(STP) public var state: String? + + @_spi(STP) public var _allResponseFieldsStorage: NonEncodableParameters? + @_spi(STP) public var _additionalParametersStorage: NonEncodableParameters? + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Token.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Token.swift new file mode 100644 index 00000000..c3a3bba2 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Models/Token.swift @@ -0,0 +1,130 @@ +// +// Token.swift +// StripeApplePay +// +// Created by David Estes on 7/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeAPI { + // Internal note: @_spi(StripeApplePayTokenization) is intended for limited public use. See https://docs.google.com/document/d/1Z9bTUBvDDufoqTaQeI3A0Cxdsoj_D0IkxdWX-GB-RTQ + @_spi(StripeApplePayTokenization) public struct Token: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? + + /// The value of the token. You can store this value on your server and use it to make charges and customers. + /// - seealso: https://stripe.com/docs/payments/charges-api + public let id: String + /// Whether or not this token was created in livemode. Will be YES if you used your Live Publishable Key, and NO if you used your Test Publishable Key. + var livemode: Bool + /// The type of this token. + var type: TokenType + + /// Possible Token types + enum TokenType: String, SafeEnumCodable { + /// Account token type + case account + /// Bank account token type + case bankAccount = "bank_account" + /// Card token type + case card + /// PII token type + case PII = "pii" + /// CVC update token type + case cvcUpdate = "cvc_update" + case unparsable + } + + /// The credit card details that were used to create the token. Will only be set if the token was created via a credit card or Apple Pay, otherwise it will be + /// nil. + var card: Card? + // /// The bank account details that were used to create the token. Will only be set if the token was created with a bank account, otherwise it will be nil. + // Not yet implemented. + // var bankAccount: BankAccount? + /// When the token was created. + var created: Date? + + struct Card: UnknownFieldsDecodable { + var _allResponseFieldsStorage: NonEncodableParameters? + + /// The last 4 digits of the card. + var last4: String + /// For cards made with Apple Pay, this refers to the last 4 digits of the + /// "Device Account Number" for the tokenized card. For regular cards, it will + /// be nil. + var dynamicLast4: String? + /// Whether or not the card originated from Apple Pay. + var isApplePayCard: Bool { + return (allResponseFields["tokenization_method"] as? String) == "apple_pay" + } + /// The card's expiration month. 1-indexed (i.e. 1 == January) + var expMonth: Int + /// The card's expiration year. + var expYear: Int + /// The cardholder's name. + var name: String? + + /// City/District/Suburb/Town/Village. + var addressCity: String? + + /// Billing address country, if provided when creating card. + var addressCountry: String? + + /// Address line 1 (Street address/PO Box/Company name). + var addressLine1: String? + + /// If address_line1 was provided, results of the check. + var addressLine1Check: AddressCheck? + + /// Results of an address check. + enum AddressCheck: String, SafeEnumCodable { + case pass + case fail + case unavailable + case unchecked + case unparsable + } + + /// Address line 2 (Apartment/Suite/Unit/Building). + var addressLine2: String? + + /// State/County/Province/Region. + var addressState: String? + + /// ZIP or postal code. + var addressZip: String? + + /// If address_zip was provided, results of the check. + var addressZipCheck: AddressCheck? + + /// The issuer of the card. + var brand: CardBrand = .unknown + + /// The funding source for the card (credit, debit, prepaid, or other) + var funding: FundingType = .unknown + + /// The various funding sources for a payment card. + enum FundingType: String, SafeEnumCodable { + /// Debit card funding + case debit + /// Credit card funding + case credit + /// Prepaid card funding + case prepaid + /// An other or unknown type of funding source. + case unknown + case unparsable + } + + /// Two-letter ISO code representing the issuing country of the card. + var country: String? + /// This is only applicable when tokenizing debit cards to issue payouts to managed + /// accounts. You should not set it otherwise. The card can then be used as a + /// transfer destination for funds in this currency. + var currency: String? + } + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentIntent+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentIntent+API.swift new file mode 100644 index 00000000..76c49cf6 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentIntent+API.swift @@ -0,0 +1,85 @@ +// +// PaymentIntent+API.swift +// StripeApplePay +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI.PaymentIntent { + /// A callback to be run with a PaymentIntent response from the Stripe API. + /// - Parameters: + /// - paymentIntent: The Stripe PaymentIntent from the response. Will be nil if an error occurs. - seealso: PaymentIntent + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(STP) public typealias PaymentIntentCompletionBlock = ( + Result + ) -> Void + + /// Retrieves the PaymentIntent object using the given secret. - seealso: https://stripe.com/docs/api#retrieve_payment_intent + /// - Parameters: + /// - secret: The client secret of the payment intent to be retrieved. Cannot be nil. + /// - completion: The callback to run with the returned PaymentIntent object, or an error. + @_spi(STP) public static func get( + apiClient: STPAPIClient = .shared, + clientSecret: String, + completion: @escaping PaymentIntentCompletionBlock + ) { + assert( + StripeAPI.PaymentIntentParams.isClientSecretValid(clientSecret), + "`secret` format does not match expected client secret formatting." + ) + guard let identifier = StripeAPI.PaymentIntent.id(fromClientSecret: clientSecret) else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)" + let parameters: [String: String] = ["client_secret": clientSecret] + + apiClient.get(resource: endpoint, parameters: parameters, completion: completion) + } + + /// Confirms the PaymentIntent object with the provided params object. + /// At a minimum, the params object must include the `clientSecret`. + /// - seealso: https://stripe.com/docs/api#confirm_payment_intent + /// @note Use the `confirmPayment:withAuthenticationContext:completion:` method on `PaymentHandler` instead + /// of calling this method directly. It handles any authentication necessary for you. - seealso: https://stripe.com/docs/payments/3d-secure + /// - Parameters: + /// - paymentIntentParams: The `PaymentIntentParams` to pass to `/confirm` + /// - completion: The callback to run with the returned PaymentIntent object, or an error. + @_spi(STP) public static func confirm( + apiClient: STPAPIClient = .shared, + params: StripeAPI.PaymentIntentParams, + completion: @escaping PaymentIntentCompletionBlock + ) { + assert( + StripeAPI.PaymentIntentParams.isClientSecretValid(params.clientSecret), + "`paymentIntentParams.clientSecret` format does not match expected client secret formatting." + ) + + guard let identifier = StripeAPI.PaymentIntent.id(fromClientSecret: params.clientSecret) + else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)/confirm" + + let type = params.paymentMethodData?.type.rawValue + STPAnalyticsClient.sharedClient.logPaymentIntentConfirmationAttempt( + paymentMethodType: type + ) + + // Add telemetry + var paramsWithTelemetry = params + if let pmAdditionalParams = paramsWithTelemetry.paymentMethodData?.additionalParameters { + paramsWithTelemetry.paymentMethodData?.additionalParameters = STPTelemetryClient.shared + .paramsByAddingTelemetryFields(toParams: pmAdditionalParams) + } + + apiClient.post(resource: endpoint, object: paramsWithTelemetry, completion: completion) + } + + static let Resource = "payment_intents" +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentMethod+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentMethod+API.swift new file mode 100644 index 00000000..f48e682c --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/PaymentMethod+API.swift @@ -0,0 +1,61 @@ +// +// PaymentMethod+API.swift +// StripeApplePay +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeAPI.PaymentMethod { + /// A callback to be run with a PaymentMethod response from the Stripe API. + /// - Parameters: + /// - paymentMethod: The Stripe PaymentMethod from the response. Will be nil if an error occurs. - seealso: PaymentMethod + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(STP) public typealias PaymentMethodCompletionBlock = ( + Result + ) -> Void + + static func create( + apiClient: STPAPIClient = .shared, + params: StripeAPI.PaymentMethodParams, + completion: @escaping PaymentMethodCompletionBlock + ) { + STPAnalyticsClient.sharedClient.logPaymentMethodCreationAttempt( + paymentMethodType: params.type.rawValue + ) + apiClient.post(resource: Resource, object: params, completion: completion) + } + + /// Converts a PKPayment object into a Stripe Payment Method using the Stripe API. + /// - Parameters: + /// - payment: The user's encrypted payment information as returned from a PKPaymentAuthorizationController. Cannot be nil. + /// - completion: The callback to run with the returned Stripe source (and any errors that may have occurred). + @_spi(STP) public static func create( + apiClient: STPAPIClient = .shared, + payment: PKPayment, + completion: @escaping PaymentMethodCompletionBlock + ) { + StripeAPI.Token.create(apiClient: apiClient, payment: payment) { (result) in + guard let token = try? result.get() else { + if case .failure(let error) = result { + completion(.failure(error)) + } else { + completion(.failure(NSError.stp_genericConnectionError())) + } + return + } + var cardParams = StripeAPI.PaymentMethodParams.Card() + cardParams.token = token.id + let billingDetails = StripeAPI.BillingDetails(from: payment) + var paymentMethodParams = StripeAPI.PaymentMethodParams(type: .card, card: cardParams) + paymentMethodParams.billingDetails = billingDetails + Self.create(apiClient: apiClient, params: paymentMethodParams, completion: completion) + } + } + + static let Resource = "payment_methods" +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/SetupIntent+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/SetupIntent+API.swift new file mode 100644 index 00000000..74fbfd82 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/SetupIntent+API.swift @@ -0,0 +1,84 @@ +// +// SetupIntent+API.swift +// StripeApplePay +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI.SetupIntent { + /// A callback to be run with a SetupIntent response from the Stripe API. + /// - Parameters: + /// - setupIntent: The Stripe SetupIntent from the response. Will be nil if an error occurs. - seealso: SetupIntent + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(STP) public typealias SetupIntentCompletionBlock = (Result) + -> Void + + /// Retrieves the SetupIntent object using the given secret. - seealso: https://stripe.com/docs/api/setup_intents/retrieve + /// - Parameters: + /// - secret: The client secret of the SetupIntent to be retrieved. Cannot be nil. + /// - completion: The callback to run with the returned SetupIntent object, or an error. + @_spi(STP) public static func get( + apiClient: STPAPIClient = .shared, + clientSecret: String, + completion: @escaping SetupIntentCompletionBlock + ) { + assert( + StripeAPI.SetupIntentConfirmParams.isClientSecretValid(clientSecret), + "`secret` format does not match expected client secret formatting." + ) + guard let identifier = StripeAPI.SetupIntent.id(fromClientSecret: clientSecret) else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)" + let parameters: [String: String] = ["client_secret": clientSecret] + + apiClient.get(resource: endpoint, parameters: parameters, completion: completion) + } + + /// Confirms the SetupIntent object with the provided params object. + /// At a minimum, the params object must include the `clientSecret`. + /// - seealso: https://stripe.com/docs/api/setup_intents/confirm + /// @note Use the `confirmSetupIntent:withAuthenticationContext:completion:` method on `PaymentHandler` instead + /// of calling this method directly. It handles any authentication necessary for you. - seealso: https://stripe.com/docs/payments/3d-secure + /// - Parameters: + /// - setupIntentParams: The `SetupIntentConfirmParams` to pass to `/confirm` + /// - completion: The callback to run with the returned PaymentIntent object, or an error. + @_spi(STP) public static func confirm( + apiClient: STPAPIClient = .shared, + params: StripeAPI.SetupIntentConfirmParams, + completion: @escaping SetupIntentCompletionBlock + ) { + assert( + StripeAPI.SetupIntentConfirmParams.isClientSecretValid(params.clientSecret), + "`setupIntentConfirmParams.clientSecret` format does not match expected client secret formatting." + ) + + guard let identifier = StripeAPI.SetupIntent.id(fromClientSecret: params.clientSecret) + else { + completion(.failure(StripeError.invalidRequest)) + return + } + let endpoint = "\(Resource)/\(identifier)/confirm" + + let type = params.paymentMethodData?.type.rawValue + STPAnalyticsClient.sharedClient.logSetupIntentConfirmationAttempt( + paymentMethodType: type + ) + + // Add telemetry + var paramsWithTelemetry = params + if let pmAdditionalParams = paramsWithTelemetry.paymentMethodData?.additionalParameters { + paramsWithTelemetry.paymentMethodData?.additionalParameters = STPTelemetryClient.shared + .paramsByAddingTelemetryFields(toParams: pmAdditionalParams) + } + + apiClient.post(resource: endpoint, object: paramsWithTelemetry, completion: completion) + } + + static let Resource = "setup_intents" +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Token+API.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Token+API.swift new file mode 100644 index 00000000..d6088387 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/API/Token+API.swift @@ -0,0 +1,92 @@ +// +// Token+API.swift +// StripeApplePay +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit +@_spi(STP) import StripeCore + +extension StripeAPI.Token { + /// A callback to be run with a token response from the Stripe API. + /// - Parameters: + /// - token: The Stripe token from the response. Will be nil if an error occurs. - seealso: STPToken + /// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. + @_spi(StripeApplePayTokenization) public typealias TokenCompletionBlock = (Result) -> Void + + /// Converts a PKPayment object into a Stripe token using the Stripe API. + /// - Parameters: + /// - payment: The user's encrypted payment information as returned from a PKPaymentAuthorizationController. Cannot be nil. + /// - completion: The callback to run with the returned Stripe token (and any errors that may have occurred). + @_spi(StripeApplePayTokenization) public static func create( + apiClient: STPAPIClient = .shared, + payment: PKPayment, + completion: @escaping TokenCompletionBlock + ) { + // Internal note: @_spi(StripeApplePayTokenization) is intended for limited public use. See https://docs.google.com/document/d/1Z9bTUBvDDufoqTaQeI3A0Cxdsoj_D0IkxdWX-GB-RTQ + let params = payment.stp_tokenParameters(apiClient: apiClient) + create( + apiClient: apiClient, + parameters: params, + completion: completion + ) + } + + static func create( + apiClient: STPAPIClient = .shared, + parameters: [String: Any], + completion: @escaping TokenCompletionBlock + ) { + let tokenType = STPAnalyticsClient.tokenType(fromParameters: parameters) + var mutableParams = parameters + STPTelemetryClient.shared.addTelemetryFields(toParams: &mutableParams) + mutableParams = STPAPIClient.paramsAddingPaymentUserAgent(mutableParams) + STPAnalyticsClient.sharedClient.logTokenCreationAttempt(tokenType: tokenType) + apiClient.post(resource: Resource, parameters: mutableParams, completion: completion) + STPTelemetryClient.shared.sendTelemetryData() + } + + static let Resource = "tokens" +} + +extension PKPayment { + func stp_tokenParameters(apiClient: STPAPIClient) -> [String: Any] { + let paymentString = String(data: self.token.paymentData, encoding: .utf8) + var payload: [String: Any] = [:] + payload["pk_token"] = paymentString + if let billingContact = self.billingContact { + payload["card"] = billingContact.addressParams + } + + assert( + !((paymentString?.count ?? 0) == 0 + && apiClient.publishableKey?.hasPrefix("pk_live") ?? false), + "The pk_token is empty. Using Apple Pay with an iOS Simulator while not in Stripe Test Mode will always fail." + ) + + let paymentInstrumentName = self.token.paymentMethod.displayName + if let paymentInstrumentName = paymentInstrumentName { + payload["pk_token_instrument_name"] = paymentInstrumentName + } + + let paymentNetwork = self.token.paymentMethod.network + if let paymentNetwork = paymentNetwork { + // Note: As of SDK 20.0.0, this will return `PKPaymentNetwork(_rawValue: MasterCard)`. + // We're intentionally leaving it this way: See RUN_MOBILESDK-125. + payload["pk_token_payment_network"] = paymentNetwork + } + + var transactionIdentifier = self.token.transactionIdentifier + if transactionIdentifier != "" { + if self.stp_applepay_isSimulated() { + transactionIdentifier = PKPayment.stp_applepay_testTransactionIdentifier() + } + payload["pk_token_transaction_id"] = transactionIdentifier + } + + return payload + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+Payments.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+Payments.swift new file mode 100644 index 00000000..ecd52639 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+Payments.swift @@ -0,0 +1,27 @@ +// +// STPAnalyticsClient+Payments.swift +// StripeApplePay +// +// Created by David Estes on 1/24/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// An analytic specific to payments that serializes payment-specific +/// information into its params. +@_spi(STP) public protocol PaymentAnalytic: Analytic { + var additionalParams: [String: Any] { get } +} + +@_spi(STP) extension PaymentAnalytic { + public var params: [String: Any] { + var params = additionalParams + + params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) + params["ocr_type"] = PaymentsSDKVariant.ocrTypeString + params["pay_var"] = PaymentsSDKVariant.variant + return params + } +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+PaymentsAPI.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+PaymentsAPI.swift new file mode 100644 index 00000000..745eecb8 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Analytics/STPAnalyticsClient+PaymentsAPI.swift @@ -0,0 +1,67 @@ +// +// STPAnalyticsClient+PaymentsAPI.swift +// StripeApplePay +// +// Created by David Estes on 1/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension STPAnalyticsClient { + // MARK: - Log events + + func logPaymentMethodCreationAttempt(paymentMethodType: String?) { + log( + analytic: PaymentAPIAnalytic( + event: .paymentMethodCreation, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown", + ] + ) + ) + } + + func logTokenCreationAttempt(tokenType: String?) { + log( + analytic: PaymentAPIAnalytic( + event: .tokenCreation, + additionalParams: [ + "token_type": tokenType ?? "unknown", + ] + ) + ) + } + + func logPaymentIntentConfirmationAttempt( + paymentMethodType: String? + ) { + log( + analytic: PaymentAPIAnalytic( + event: .paymentMethodIntentCreation, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown", + ] + ) + ) + } + + func logSetupIntentConfirmationAttempt( + paymentMethodType: String? + ) { + log( + analytic: PaymentAPIAnalytic( + event: .setupIntentConfirmationAttempt, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown", + ] + ) + ) + } +} + +struct PaymentAPIAnalytic: PaymentAnalytic { + let event: STPAnalyticEvent + let additionalParams: [String: Any] +} diff --git a/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/STPAPIClient+PaymentsCore.swift b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/STPAPIClient+PaymentsCore.swift new file mode 100644 index 00000000..2ed46cf8 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/PaymentsCore/Categories/STPAPIClient+PaymentsCore.swift @@ -0,0 +1,20 @@ +// +// STPAPIClient+PaymentsCore.swift +// StripeApplePay +// +// Created by David Estes on 1/25/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension STPAPIClient { + @_spi(STP) public class func paramsAddingPaymentUserAgent( + _ params: [String: Any] + ) -> [String: Any] { + var newParams = params + newParams["payment_user_agent"] = PaymentsSDKVariant.paymentUserAgent + return newParams + } +} diff --git a/StripeApplePay/StripeApplePay/Source/StripeCore+Import.swift b/StripeApplePay/StripeApplePay/Source/StripeCore+Import.swift new file mode 100644 index 00000000..123a8e98 --- /dev/null +++ b/StripeApplePay/StripeApplePay/Source/StripeCore+Import.swift @@ -0,0 +1,10 @@ +// +// StripeCore+Import.swift +// StripeApplePay +// +// Created by David Estes on 11/15/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_exported import StripeCore diff --git a/StripeApplePay/StripeApplePay/StripeApplePay.h b/StripeApplePay/StripeApplePay/StripeApplePay.h new file mode 100644 index 00000000..e6b9e0f8 --- /dev/null +++ b/StripeApplePay/StripeApplePay/StripeApplePay.h @@ -0,0 +1,18 @@ +// +// StripeApplePay.h +// StripeApplePay +// +// Created by David Estes on 11/8/21. +// + +#import + +//! Project version number for StripeApplePay. +FOUNDATION_EXPORT double StripeApplePayVersionNumber; + +//! Project version string for StripeApplePay. +FOUNDATION_EXPORT const unsigned char StripeApplePayVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeApplePay/StripeApplePayTests/Info.plist b/StripeApplePay/StripeApplePayTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientFunctionalTest.swift b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientFunctionalTest.swift new file mode 100644 index 00000000..9f70bfb5 --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientFunctionalTest.swift @@ -0,0 +1,67 @@ +// +// STPTelemetryClientFunctionalTest.swift +// StripeApplePayTests +// +// Created by Yuki Tokuhiro on 5/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +// swift-format-ignore +@testable @_spi(STP) import StripeApplePay + +// swift-format-ignore +@testable @_spi(STP) import StripeCore + +class STPTelemetryClientFunctionalTest: XCTestCase { + func testSendFraudDetectionData() { + // Sending telemetry without any FraudDetectionData... + FraudDetectionData.shared.sid = nil + FraudDetectionData.shared.sidCreationDate = nil + FraudDetectionData.shared.muid = nil + FraudDetectionData.shared.guid = nil + let sendTelemetry1 = expectation(description: "") + STPTelemetryClient.shared.sendTelemetryData(forceSend: true) { _ in + sendTelemetry1.fulfill() + } + waitForExpectations(timeout: 10, handler: nil) + // ...populates FraudDetectionData + let sid = FraudDetectionData.shared.sid + let muid = FraudDetectionData.shared.muid + let guid = FraudDetectionData.shared.guid + XCTAssertNotNil(sid) + XCTAssertNotNil(muid) + XCTAssertNotNil(guid) + + let sendTelemetry2 = expectation(description: "") + // Sending telemetry again... + STPTelemetryClient.shared.sendTelemetryData(forceSend: true) { _ in + sendTelemetry2.fulfill() + } + // ...gives the same FraudDetectionData + XCTAssertEqual(FraudDetectionData.shared.sid, sid) + XCTAssertEqual(FraudDetectionData.shared.muid, muid) + XCTAssertEqual(FraudDetectionData.shared.guid, guid) + guard let sidCreationDate = FraudDetectionData.shared.sidCreationDate else { + XCTFail() + return + } + // sanity check creation date looks right + XCTAssertTrue(sidCreationDate > Date(timeIntervalSinceNow: -10)) + waitForExpectations(timeout: 10, handler: nil) + + // Expiring the FraudDetectionData + FraudDetectionData.shared.sidCreationDate = Date(timeIntervalSinceNow: -999999) + let sendTelemetry3 = expectation(description: "") + // ...and sending telemetry again + STPTelemetryClient.shared.sendTelemetryData(forceSend: true) { _ in + sendTelemetry3.fulfill() + } + waitForExpectations(timeout: 10, handler: nil) + // ...gives the same muid and guid but different sid + XCTAssertEqual(FraudDetectionData.shared.muid, muid) + XCTAssertEqual(FraudDetectionData.shared.guid, guid) + XCTAssertNotEqual(FraudDetectionData.shared.sid, sid) + } +} diff --git a/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientTest.swift b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientTest.swift new file mode 100644 index 00000000..8a730008 --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/PaymentsCore/STPTelemetryClientTest.swift @@ -0,0 +1,75 @@ +// +// STPTelemetryClientTest.swift +// StripeApplePayTests +// +// Created by Yuki Tokuhiro on 9/24/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import XCTest + +// swift-format-ignore +@testable @_spi(STP) import StripeApplePay + +// swift-format-ignore +@testable @_spi(STP) import StripeCore + +class STPTelemetryClientTest: XCTestCase { + + func testAddTelemetryData() { + let sut = STPTelemetryClient.shared + var params: [String: Any] = [ + "foo": "bar" + ] + let exp = expectation(description: "delay") + DispatchQueue.main.asyncAfter( + deadline: DispatchTime.now() + Double(Int64(0.1 * Double(NSEC_PER_SEC))) + / Double(NSEC_PER_SEC), + execute: { + sut.addTelemetryFields(toParams: ¶ms) + XCTAssertNotNil(params) + exp.fulfill() + } + ) + waitForExpectations(timeout: 2, handler: nil) + } + + func testAdvancedFraudSignalsSwitch() { + XCTAssertTrue(StripeAPI.advancedFraudSignalsEnabled) + StripeAPI.advancedFraudSignalsEnabled = false + XCTAssertFalse(StripeAPI.advancedFraudSignalsEnabled) + } + + func testAddTelemetryFieldsWhenFraudDetectionDataEmpty() { + // Should not add any fields if fraudDetectionData is empty + FraudDetectionData.shared.reset() + var params: [String: Any] = [:] + STPTelemetryClient.shared.addTelemetryFields(toParams: ¶ms) + XCTAssertTrue(params.isEmpty) + } + + func testAddTelemetryFieldsWhenSIDExpired() { + // Should add muid, but not add sid if it's expired + var params: [String: Any] = [:] + FraudDetectionData.shared.sid = "expired" + FraudDetectionData.shared.sidCreationDate = Date(timeInterval: -30 * 60, since: Date()) + FraudDetectionData.shared.muid = "muid value" + FraudDetectionData.shared.guid = "guid value" + STPTelemetryClient.shared.addTelemetryFields(toParams: ¶ms) + XCTAssertEqual(params["muid"] as? String, "muid value") + XCTAssertEqual(params["guid"] as? String, "guid value") + XCTAssertNil(params["sid"] as? String) + } + + func testAddTelemetryFields() { + var params: [String: Any] = [:] + FraudDetectionData.shared.sid = "sid value" + FraudDetectionData.shared.muid = "muid value" + FraudDetectionData.shared.guid = "guid value" + FraudDetectionData.shared.sidCreationDate = Date() + STPTelemetryClient.shared.addTelemetryFields(toParams: ¶ms) + XCTAssertEqual(params["muid"] as? String, "muid value") + XCTAssertEqual(params["sid"] as? String, "sid value") + XCTAssertEqual(params["guid"] as? String, "guid value") + } +} diff --git a/StripeApplePay/StripeApplePayTests/STPAnalyticsClient+ApplePayTest.swift b/StripeApplePay/StripeApplePayTests/STPAnalyticsClient+ApplePayTest.swift new file mode 100644 index 00000000..bed3eb7e --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/STPAnalyticsClient+ApplePayTest.swift @@ -0,0 +1,29 @@ +// +// STPAnalyticsClient+ApplePayTest.swift +// StripeApplePayTests +// +// Created by David Estes on 2/3/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +// swift-format-ignore +@_spi(STP) @testable import StripeApplePay + +// swift-format-ignore +@_spi(STP) @testable import StripeCore + +class STPAnalyticsClientApplePayTest: XCTestCase { + func testApplePaySDKVariantPayload() throws { + // setup + let analytic = PaymentAPIAnalytic( + event: .paymentMethodCreation, + additionalParams: [:] + ) + let client = STPAnalyticsClient() + let payload = client.payload(from: analytic) + XCTAssertEqual("applepay", payload["pay_var"] as? String) + } +} diff --git a/StripeApplePay/StripeApplePayTests/STPPaymentMethodFunctionalTest.swift b/StripeApplePay/StripeApplePayTests/STPPaymentMethodFunctionalTest.swift new file mode 100644 index 00000000..d309d66f --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/STPPaymentMethodFunctionalTest.swift @@ -0,0 +1,97 @@ +// +// STPPaymentMethodFunctionalTest.swift +// StripeApplePayTests +// +// Created by David Estes on 8/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore // for StripeError +import StripeCoreTestUtils +import XCTest + +// swift-format-ignore +@_spi(STP) @testable import StripeApplePay + +let STPTestingDefaultPublishableKey = "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6" +public let STPTestingNetworkRequestTimeout: TimeInterval = 8 + +class STPPaymentMethodModernTest: XCTestCase { + func testCreateCardPaymentMethod() { + let expectation = self.expectation(description: "Created") + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + var params = StripeAPI.PaymentMethodParams(type: .card) + var card = StripeAPI.PaymentMethodParams.Card() + card.number = "4242424242424242" + card.expYear = 28 + card.expMonth = 12 + card.cvc = "100" + var billingAddress = StripeAPI.BillingDetails.Address() + billingAddress.city = "San Francisco" + billingAddress.country = "US" + billingAddress.line1 = "150 Townsend St" + billingAddress.line2 = "4th Floor" + billingAddress.postalCode = "94103" + billingAddress.state = "CA" + + var billingDetails = StripeAPI.BillingDetails() + billingDetails.address = billingAddress + billingDetails.email = "email@email.com" + billingDetails.name = "Isaac Asimov" + billingDetails.phone = "555-555-5555" + + params.card = card + params.billingDetails = billingDetails + + StripeAPI.PaymentMethod.create(apiClient: apiClient, params: params) { result in + let paymentMethod = try! result.get() + XCTAssertEqual(paymentMethod.card?.last4, "4242") + expectation.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testCreateCardPaymentMethodWithAdditionalAPIStuff() { + let expectation = self.expectation(description: "Created") + let apiClient = STPAPIClient(publishableKey: STPTestingDefaultPublishableKey) + var params = StripeAPI.PaymentMethodParams(type: .card) + var card = StripeAPI.PaymentMethodParams.Card() + card.number = "4242424242424242" + card.expYear = 28 + card.expMonth = 12 + card.cvc = "100" + var billingAddress = StripeAPI.BillingDetails.Address() + billingAddress.city = "San Francisco" + billingAddress.country = "US" + billingAddress.line1 = "150 Townsend St" + billingAddress.line2 = "4th Floor" + billingAddress.postalCode = "94103" + billingAddress.state = "CA" + billingAddress.additionalParameters = ["invalid_thing": "yes"] + + var billingDetails = StripeAPI.BillingDetails() + billingDetails.address = billingAddress + billingDetails.email = "email@email.com" + billingDetails.name = "Isaac Asimov" + billingDetails.phone = "555-555-5555" + + params.card = card + params.billingDetails = billingDetails + + StripeAPI.PaymentMethod.create(apiClient: apiClient, params: params) { result in + do { + _ = try result.get() + XCTFail("This request should fail") + } catch { + let stripeError = error as? StripeError + if case .apiError(let apiError) = stripeError { + XCTAssertEqual(apiError.code, "parameter_unknown") + XCTAssertEqual(apiError.param, "billing_details[address][invalid_thing]") + expectation.fulfill() + } + } + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/StripeApplePay/StripeApplePayTests/TelemetryInjectionTest.swift b/StripeApplePay/StripeApplePayTests/TelemetryInjectionTest.swift new file mode 100644 index 00000000..b337a440 --- /dev/null +++ b/StripeApplePay/StripeApplePayTests/TelemetryInjectionTest.swift @@ -0,0 +1,95 @@ +// +// TelemetryInjectionTest.swift +// StripeApplePayTests +// +// Created by David Estes on 2/9/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import AVFoundation +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +// swift-format-ignore +@_spi(STP) @testable import StripeApplePay + +// swift-format-ignore +@_spi(STP) @testable import StripeCore + +class TelemetryInjectionTest: APIStubbedTestCase { + func testIntentConfirmAddsTelemetry() { + let apiClient = stubbedAPIClient() + + let piTelemetryExpectation = self.expectation(description: "saw pi telemetry") + let siTelemetryExpectation = self.expectation(description: "saw si telemetry") + + // As an implementation detail, OHHTTPStubs will run this block in `canInitWithRequest` in addition to + // `initWithRequest`. So it could be called more times than we expect. + // We don't have control over this behavior (CFNetwork drives it), so let's not worry + // about overfulfillment. + piTelemetryExpectation.assertForOverFulfill = false + siTelemetryExpectation.assertForOverFulfill = false + + stub { urlRequest in + if urlRequest.url!.absoluteString.contains("_intent") { + let ua = urlRequest.queryItems!.first(where: { + $0.name == "payment_method_data[payment_user_agent]" + })!.value! + XCTAssertTrue(ua.hasPrefix("stripe-ios/")) + let muid = urlRequest.queryItems!.first(where: { + $0.name == "payment_method_data[muid]" + })!.value! + let guid = urlRequest.queryItems!.first(where: { + $0.name == "payment_method_data[guid]" + })!.value! + XCTAssertNotNil(muid) + XCTAssertNotNil(guid) + if urlRequest.url!.absoluteString.contains("payment_intent") { + piTelemetryExpectation.fulfill() + } + if urlRequest.url!.absoluteString.contains("setup_intent") { + siTelemetryExpectation.fulfill() + } + return true + } + return false + } response: { _ in + // We don't care about the response + return HTTPStubsResponse() + } + + var params = StripeAPI.PaymentMethodParams(type: .card) + var card = StripeAPI.PaymentMethodParams.Card() + card.number = "4242424242424242" + card.expYear = 28 + card.expMonth = 12 + card.cvc = "100" + params.card = card + + // Set up telemetry data + StripeAPI.advancedFraudSignalsEnabled = true + FraudDetectionData.shared.sid = "sid" + FraudDetectionData.shared.muid = "muid" + FraudDetectionData.shared.guid = "guid" + FraudDetectionData.shared.sidCreationDate = Date() + + let piExpectation = self.expectation(description: "PI Confirmed") + var pip = StripeAPI.PaymentIntentParams(clientSecret: "pi_123_secret_abc") + pip.paymentMethodData = params + StripeAPI.PaymentIntent.confirm(apiClient: apiClient, params: pip) { _ in + piExpectation.fulfill() + } + + let siExpectation = self.expectation(description: "SI Confirmed") + var sip = StripeAPI.SetupIntentConfirmParams(clientSecret: "seti_123_secret_abc") + sip.paymentMethodData = params + StripeAPI.SetupIntent.confirm(apiClient: apiClient, params: sip) { _ in + siExpectation.fulfill() + } + + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } +} diff --git a/StripeCameraCore/StripeCameraCore.xcodeproj/project.pbxproj b/StripeCameraCore/StripeCameraCore.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ccfeffb1 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore.xcodeproj/project.pbxproj @@ -0,0 +1,640 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 0485C4A62D9444E75877E170 /* CameraExifMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76A4D11F53602B77F9F5B2E /* CameraExifMetadata.swift */; }; + 064567ECD2B71E5EB7D3A201 /* MockCameraPermissionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832A750C892D9D3FC54D9CF3 /* MockCameraPermissionsManager.swift */; }; + 06FEEA450541E2D36D5A10FC /* CameraPermissionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0F925920743B648B04C949 /* CameraPermissionsManager.swift */; }; + 0F02BB3F9F7936D7BB593226 /* Torch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D750C5508AB271C099351E42 /* Torch.swift */; }; + 10BCE429AA79F7F0D1A9F2D3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7158E127DC7294CDABDEF5B8 /* XCTest.framework */; }; + 1EF9F2AB3E43769AB2BA6D04 /* StripeCameraCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2720B7BD13954418A8AC52E5 /* StripeCameraCoreTestUtils.framework */; }; + 28F23B4322876ED2BAC8C933 /* StripeCameraCoreTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DDD1039F321BAF147F162F7D /* StripeCameraCoreTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A1AAD8E5A589E4E74B02A35 /* MockSimulatorCameraSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCE205F4CCF2D486967BA25 /* MockSimulatorCameraSession.swift */; }; + 2CEF8A27C8864A6198C69A5D /* CVPixelBuffer+StripeCameraCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0B9FDC8622F6CDF9F8A4B4 /* CVPixelBuffer+StripeCameraCore.swift */; }; + 48B09ECBED820F3097779208 /* CGRect+StripeCameraCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B4106DF455718394E209C00 /* CGRect+StripeCameraCore.swift */; }; + 5F57E0BF10039F7055013066 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7158E127DC7294CDABDEF5B8 /* XCTest.framework */; }; + 6D44C32189C0CB409C9E4712 /* StripeCameraCore.h in Headers */ = {isa = PBXBuildFile; fileRef = E7FB32269B8E36C3CBDB370F /* StripeCameraCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 88A1AF2B173B4055D8F44A8C /* MockAppSettingsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F04D387EEF3A5D07ECD1F7E /* MockAppSettingsHelper.swift */; }; + 8F3073987F30B9B8DDF0D956 /* MockTestCameraSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8091DB42296B7EC745567AF7 /* MockTestCameraSession.swift */; }; + 9AFBF50F47562CB34903064E /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E7BCD0896ADAFFF5CCA738E /* StripeCore.framework */; }; + 9CE6EE572439AD5B22B77294 /* CameraPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79D46F44C7BC55B9B595AEF /* CameraPreviewView.swift */; }; + 9FC42D068D1719AB28C3068F /* AppSettingsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DFFB1E2F90176D156E315B /* AppSettingsHelper.swift */; }; + A95255494DF27A7CAE9A70CE /* CameraSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD8FEBAE0069BE88B75B0C5 /* CameraSession.swift */; }; + AB24784F49EB99E7C36509BD /* CGRect_StripeCameraCoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5B6BCFF035A9E214EA9EA0 /* CGRect_StripeCameraCoreTest.swift */; }; + D2186D6E491660D0C2A9D577 /* StripeCameraCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DBC37790A9F68168A89095B /* StripeCameraCore.framework */; }; + D9687FAD52D1896F28309580 /* UIImage+Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA969E606F5A3829BC9D4445 /* UIImage+Buffer.swift */; }; + E01A054861E5D82D18C7D224 /* StripeCameraCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DBC37790A9F68168A89095B /* StripeCameraCore.framework */; }; + F181EEBCE3D2CAD043DAF0F0 /* UIDeviceOrientation+StripeCameraCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9BDABE163D20DF20F2237D /* UIDeviceOrientation+StripeCameraCore.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0321B7B71CC9BDDCF236B375 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B93DF1E4460962AFB28CF144 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B50782C89D54809DFFCF80D0; + remoteInfo = StripeCameraCore; + }; + 55BD94DFEA7738F26C23A545 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B93DF1E4460962AFB28CF144 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6A948E338F0BF55DBFD83170; + remoteInfo = StripeCameraCoreTestUtils; + }; + BA0100EB31091CFA64BF12A6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B93DF1E4460962AFB28CF144 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B50782C89D54809DFFCF80D0; + remoteInfo = StripeCameraCore; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2F2CDDE07BFFE88706C6A908 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + BCA0A45A41520FB95F4E8D10 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + F97E2A90DA9F982FC293A4A9 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0A0B9FDC8622F6CDF9F8A4B4 /* CVPixelBuffer+StripeCameraCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CVPixelBuffer+StripeCameraCore.swift"; sourceTree = ""; }; + 0F4174046507759A9668F4EE /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + 17E9A614AF4543DB62E7F5B4 /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + 226FED2248EE3F96F217BEE9 /* StripeCameraCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeCameraCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2720B7BD13954418A8AC52E5 /* StripeCameraCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCameraCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2B5B6BCFF035A9E214EA9EA0 /* CGRect_StripeCameraCoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRect_StripeCameraCoreTest.swift; sourceTree = ""; }; + 3B4106DF455718394E209C00 /* CGRect+StripeCameraCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+StripeCameraCore.swift"; sourceTree = ""; }; + 3D0F925920743B648B04C949 /* CameraPermissionsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPermissionsManager.swift; sourceTree = ""; }; + 44E9A4900C4B8D08A617488C /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + 48DFFB1E2F90176D156E315B /* AppSettingsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsHelper.swift; sourceTree = ""; }; + 4E0D85BEF8C269A46D5ECEAC /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + 5DFD03E78034CFB59E244199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 609D9ADB9E9898694A2A67FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 6DBC37790A9F68168A89095B /* StripeCameraCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCameraCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7158E127DC7294CDABDEF5B8 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 7D9BDABE163D20DF20F2237D /* UIDeviceOrientation+StripeCameraCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDeviceOrientation+StripeCameraCore.swift"; sourceTree = ""; }; + 8091DB42296B7EC745567AF7 /* MockTestCameraSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTestCameraSession.swift; sourceTree = ""; }; + 832A750C892D9D3FC54D9CF3 /* MockCameraPermissionsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCameraPermissionsManager.swift; sourceTree = ""; }; + 83596C8ED3829B17A93490A6 /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + 8E7BCD0896ADAFFF5CCA738E /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9F04D387EEF3A5D07ECD1F7E /* MockAppSettingsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAppSettingsHelper.swift; sourceTree = ""; }; + A79D46F44C7BC55B9B595AEF /* CameraPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreviewView.swift; sourceTree = ""; }; + ABCCADF0E7337301A472E75C /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + B8D39F3EE77B9DF5FFBA4CAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + BA969E606F5A3829BC9D4445 /* UIImage+Buffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Buffer.swift"; sourceTree = ""; }; + BCCE205F4CCF2D486967BA25 /* MockSimulatorCameraSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSimulatorCameraSession.swift; sourceTree = ""; }; + D750C5508AB271C099351E42 /* Torch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Torch.swift; sourceTree = ""; }; + D76A4D11F53602B77F9F5B2E /* CameraExifMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraExifMetadata.swift; sourceTree = ""; }; + DDD1039F321BAF147F162F7D /* StripeCameraCoreTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeCameraCoreTestUtils.h; sourceTree = ""; }; + E7FB32269B8E36C3CBDB370F /* StripeCameraCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeCameraCore.h; sourceTree = ""; }; + FBD8FEBAE0069BE88B75B0C5 /* CameraSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSession.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 267776DB1189FACBD4FA2AEA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F57E0BF10039F7055013066 /* XCTest.framework in Frameworks */, + E01A054861E5D82D18C7D224 /* StripeCameraCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9CC1AF0A3093D51C5085D503 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 10BCE429AA79F7F0D1A9F2D3 /* XCTest.framework in Frameworks */, + D2186D6E491660D0C2A9D577 /* StripeCameraCore.framework in Frameworks */, + 1EF9F2AB3E43769AB2BA6D04 /* StripeCameraCoreTestUtils.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E3A6C5C1A5D47CEFAFD1853F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AFBF50F47562CB34903064E /* StripeCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00F9BB709FA21D59419FA5AE /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + ABCCADF0E7337301A472E75C /* Project-Debug.xcconfig */, + 0F4174046507759A9668F4EE /* Project-Release.xcconfig */, + 44E9A4900C4B8D08A617488C /* StripeiOS Tests-Debug.xcconfig */, + 17E9A614AF4543DB62E7F5B4 /* StripeiOS Tests-Release.xcconfig */, + 83596C8ED3829B17A93490A6 /* StripeiOS-Debug.xcconfig */, + 4E0D85BEF8C269A46D5ECEAC /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 03256F1FDB473FE93A57A38A /* Source */ = { + isa = PBXGroup; + children = ( + 170C84B7FB58FAA15283903E /* Categories */, + 7E719691677D20AEAEF0C120 /* Coordinators */, + 72FEB5C092565B365BBC3906 /* Views */, + D76A4D11F53602B77F9F5B2E /* CameraExifMetadata.swift */, + ); + path = Source; + sourceTree = ""; + }; + 0FB9D3D4625E1AEA6EE1CC22 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7158E127DC7294CDABDEF5B8 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 170C84B7FB58FAA15283903E /* Categories */ = { + isa = PBXGroup; + children = ( + 3B4106DF455718394E209C00 /* CGRect+StripeCameraCore.swift */, + 0A0B9FDC8622F6CDF9F8A4B4 /* CVPixelBuffer+StripeCameraCore.swift */, + 7D9BDABE163D20DF20F2237D /* UIDeviceOrientation+StripeCameraCore.swift */, + BA969E606F5A3829BC9D4445 /* UIImage+Buffer.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 171BE48EE95E66F94D6CCC33 /* StripeCameraCoreTests */ = { + isa = PBXGroup; + children = ( + CC0771AB4883EEDAB5AA7D1C /* Unit */, + 5DFD03E78034CFB59E244199 /* Info.plist */, + ); + path = StripeCameraCoreTests; + sourceTree = ""; + }; + 18A8A17329DE3C8891103ECD /* Categories */ = { + isa = PBXGroup; + children = ( + 2B5B6BCFF035A9E214EA9EA0 /* CGRect_StripeCameraCoreTest.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 27E56A8242FD577334930F5B /* StripeCameraCoreTestUtils */ = { + isa = PBXGroup; + children = ( + BCB41D804D8A44F6DEEF4B8E /* Mocks */, + B8D39F3EE77B9DF5FFBA4CAE /* Info.plist */, + DDD1039F321BAF147F162F7D /* StripeCameraCoreTestUtils.h */, + ); + path = StripeCameraCoreTestUtils; + sourceTree = ""; + }; + 5CE9B5F8E73CBC18E22AF375 /* Project */ = { + isa = PBXGroup; + children = ( + 00F9BB709FA21D59419FA5AE /* BuildConfigurations */, + E2CD33A048BB3FD49B4D47FD /* StripeCameraCore */, + 171BE48EE95E66F94D6CCC33 /* StripeCameraCoreTests */, + 27E56A8242FD577334930F5B /* StripeCameraCoreTestUtils */, + ); + name = Project; + sourceTree = ""; + }; + 72FEB5C092565B365BBC3906 /* Views */ = { + isa = PBXGroup; + children = ( + A79D46F44C7BC55B9B595AEF /* CameraPreviewView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 7E719691677D20AEAEF0C120 /* Coordinators */ = { + isa = PBXGroup; + children = ( + 48DFFB1E2F90176D156E315B /* AppSettingsHelper.swift */, + 3D0F925920743B648B04C949 /* CameraPermissionsManager.swift */, + FBD8FEBAE0069BE88B75B0C5 /* CameraSession.swift */, + BCCE205F4CCF2D486967BA25 /* MockSimulatorCameraSession.swift */, + D750C5508AB271C099351E42 /* Torch.swift */, + ); + path = Coordinators; + sourceTree = ""; + }; + BCB41D804D8A44F6DEEF4B8E /* Mocks */ = { + isa = PBXGroup; + children = ( + 9F04D387EEF3A5D07ECD1F7E /* MockAppSettingsHelper.swift */, + 832A750C892D9D3FC54D9CF3 /* MockCameraPermissionsManager.swift */, + 8091DB42296B7EC745567AF7 /* MockTestCameraSession.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + CC0771AB4883EEDAB5AA7D1C /* Unit */ = { + isa = PBXGroup; + children = ( + 18A8A17329DE3C8891103ECD /* Categories */, + ); + path = Unit; + sourceTree = ""; + }; + E2B3561EEB5629A212FD2AB4 /* Products */ = { + isa = PBXGroup; + children = ( + 6DBC37790A9F68168A89095B /* StripeCameraCore.framework */, + 226FED2248EE3F96F217BEE9 /* StripeCameraCoreTests.xctest */, + 2720B7BD13954418A8AC52E5 /* StripeCameraCoreTestUtils.framework */, + 8E7BCD0896ADAFFF5CCA738E /* StripeCore.framework */, + ); + name = Products; + sourceTree = ""; + }; + E2CD33A048BB3FD49B4D47FD /* StripeCameraCore */ = { + isa = PBXGroup; + children = ( + 03256F1FDB473FE93A57A38A /* Source */, + 609D9ADB9E9898694A2A67FF /* Info.plist */, + E7FB32269B8E36C3CBDB370F /* StripeCameraCore.h */, + ); + path = StripeCameraCore; + sourceTree = ""; + }; + F72F2AFE9BE5E2CCBE7C6436 = { + isa = PBXGroup; + children = ( + 5CE9B5F8E73CBC18E22AF375 /* Project */, + 0FB9D3D4625E1AEA6EE1CC22 /* Frameworks */, + E2B3561EEB5629A212FD2AB4 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 21D2298D6D8328B46AB437BB /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 6D44C32189C0CB409C9E4712 /* StripeCameraCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8FED65A79644B7F1CB19619B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 28F23B4322876ED2BAC8C933 /* StripeCameraCoreTestUtils.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 6A948E338F0BF55DBFD83170 /* StripeCameraCoreTestUtils */ = { + isa = PBXNativeTarget; + buildConfigurationList = 12D1B5D975CF0E6EC4956438 /* Build configuration list for PBXNativeTarget "StripeCameraCoreTestUtils" */; + buildPhases = ( + 8FED65A79644B7F1CB19619B /* Headers */, + F2FB0D7ACB63AFA0A81A5FF6 /* Sources */, + 83C726705BB615A9940B98CC /* Resources */, + BCA0A45A41520FB95F4E8D10 /* Embed Frameworks */, + 267776DB1189FACBD4FA2AEA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D1513DE143A5DDFAB1AA4069 /* PBXTargetDependency */, + ); + name = StripeCameraCoreTestUtils; + productName = StripeCameraCoreTestUtils; + productReference = 2720B7BD13954418A8AC52E5 /* StripeCameraCoreTestUtils.framework */; + productType = "com.apple.product-type.framework"; + }; + B50782C89D54809DFFCF80D0 /* StripeCameraCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7056F053793C453C8750F440 /* Build configuration list for PBXNativeTarget "StripeCameraCore" */; + buildPhases = ( + 21D2298D6D8328B46AB437BB /* Headers */, + 62DE5865F3F7B8CB25F62395 /* Sources */, + 445990B4DBEECCD2FBA8B324 /* Resources */, + 2F2CDDE07BFFE88706C6A908 /* Embed Frameworks */, + E3A6C5C1A5D47CEFAFD1853F /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeCameraCore; + productName = StripeCameraCore; + productReference = 6DBC37790A9F68168A89095B /* StripeCameraCore.framework */; + productType = "com.apple.product-type.framework"; + }; + F3DED9AB60FDAB786A737384 /* StripeCameraCoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6BB26614346BD154B399D073 /* Build configuration list for PBXNativeTarget "StripeCameraCoreTests" */; + buildPhases = ( + 85B8B0A9CAD01C91AD19797A /* Sources */, + 98362E27FEF7FBD502400444 /* Resources */, + F97E2A90DA9F982FC293A4A9 /* Embed Frameworks */, + 9CC1AF0A3093D51C5085D503 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F102799B90F2BBF57EBDBBDC /* PBXTargetDependency */, + 24ECB0864CAE52D6ABF4503E /* PBXTargetDependency */, + ); + name = StripeCameraCoreTests; + productName = StripeCameraCoreTests; + productReference = 226FED2248EE3F96F217BEE9 /* StripeCameraCoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B93DF1E4460962AFB28CF144 /* Project object */ = { + isa = PBXProject; + attributes = { + TargetAttributes = { + }; + }; + buildConfigurationList = C1216BCDB94950EBFA541D74 /* Build configuration list for PBXProject "StripeCameraCore" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = F72F2AFE9BE5E2CCBE7C6436; + productRefGroup = E2B3561EEB5629A212FD2AB4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B50782C89D54809DFFCF80D0 /* StripeCameraCore */, + 6A948E338F0BF55DBFD83170 /* StripeCameraCoreTestUtils */, + F3DED9AB60FDAB786A737384 /* StripeCameraCoreTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 445990B4DBEECCD2FBA8B324 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 83C726705BB615A9940B98CC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 98362E27FEF7FBD502400444 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 62DE5865F3F7B8CB25F62395 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0485C4A62D9444E75877E170 /* CameraExifMetadata.swift in Sources */, + 48B09ECBED820F3097779208 /* CGRect+StripeCameraCore.swift in Sources */, + 2CEF8A27C8864A6198C69A5D /* CVPixelBuffer+StripeCameraCore.swift in Sources */, + F181EEBCE3D2CAD043DAF0F0 /* UIDeviceOrientation+StripeCameraCore.swift in Sources */, + D9687FAD52D1896F28309580 /* UIImage+Buffer.swift in Sources */, + 9FC42D068D1719AB28C3068F /* AppSettingsHelper.swift in Sources */, + 06FEEA450541E2D36D5A10FC /* CameraPermissionsManager.swift in Sources */, + A95255494DF27A7CAE9A70CE /* CameraSession.swift in Sources */, + 2A1AAD8E5A589E4E74B02A35 /* MockSimulatorCameraSession.swift in Sources */, + 0F02BB3F9F7936D7BB593226 /* Torch.swift in Sources */, + 9CE6EE572439AD5B22B77294 /* CameraPreviewView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 85B8B0A9CAD01C91AD19797A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AB24784F49EB99E7C36509BD /* CGRect_StripeCameraCoreTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F2FB0D7ACB63AFA0A81A5FF6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 88A1AF2B173B4055D8F44A8C /* MockAppSettingsHelper.swift in Sources */, + 064567ECD2B71E5EB7D3A201 /* MockCameraPermissionsManager.swift in Sources */, + 8F3073987F30B9B8DDF0D956 /* MockTestCameraSession.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 24ECB0864CAE52D6ABF4503E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCameraCoreTestUtils; + target = 6A948E338F0BF55DBFD83170 /* StripeCameraCoreTestUtils */; + targetProxy = 55BD94DFEA7738F26C23A545 /* PBXContainerItemProxy */; + }; + D1513DE143A5DDFAB1AA4069 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCameraCore; + target = B50782C89D54809DFFCF80D0 /* StripeCameraCore */; + targetProxy = 0321B7B71CC9BDDCF236B375 /* PBXContainerItemProxy */; + }; + F102799B90F2BBF57EBDBBDC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCameraCore; + target = B50782C89D54809DFFCF80D0 /* StripeCameraCore */; + targetProxy = BA0100EB31091CFA64BF12A6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1555A494851A82726CF41684 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 17E9A614AF4543DB62E7F5B4 /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCameraCoreTestUtils/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCameraCoreTestUtils; + PRODUCT_NAME = StripeCameraCoreTestUtils; + SDKROOT = iphoneos; + }; + name = Release; + }; + 8101CFE889F8AC21AF12FC99 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 83596C8ED3829B17A93490A6 /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeCameraCore/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-camera-core"; + PRODUCT_NAME = StripeCameraCore; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 85DD0786DE8D23550B2113B7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ABCCADF0E7337301A472E75C /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + A6D92FFDFE11DCBEC32190D4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 44E9A4900C4B8D08A617488C /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCameraCoreTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCameraCoreTests; + PRODUCT_NAME = StripeCameraCoreTests; + SDKROOT = iphoneos; + }; + name = Debug; + }; + B26CBFD3B42072A398AE6605 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 17E9A614AF4543DB62E7F5B4 /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCameraCoreTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCameraCoreTests; + PRODUCT_NAME = StripeCameraCoreTests; + SDKROOT = iphoneos; + }; + name = Release; + }; + E1A07E117DC9E8051D871578 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4E0D85BEF8C269A46D5ECEAC /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeCameraCore/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-camera-core"; + PRODUCT_NAME = StripeCameraCore; + SDKROOT = iphoneos; + }; + name = Release; + }; + F11D6A5494AA72875ECC53A4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0F4174046507759A9668F4EE /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + FD80773E1582BF61724D6EDE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 44E9A4900C4B8D08A617488C /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCameraCoreTestUtils/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCameraCoreTestUtils; + PRODUCT_NAME = StripeCameraCoreTestUtils; + SDKROOT = iphoneos; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 12D1B5D975CF0E6EC4956438 /* Build configuration list for PBXNativeTarget "StripeCameraCoreTestUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FD80773E1582BF61724D6EDE /* Debug */, + 1555A494851A82726CF41684 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6BB26614346BD154B399D073 /* Build configuration list for PBXNativeTarget "StripeCameraCoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A6D92FFDFE11DCBEC32190D4 /* Debug */, + B26CBFD3B42072A398AE6605 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7056F053793C453C8750F440 /* Build configuration list for PBXNativeTarget "StripeCameraCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8101CFE889F8AC21AF12FC99 /* Debug */, + E1A07E117DC9E8051D871578 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1216BCDB94950EBFA541D74 /* Build configuration list for PBXProject "StripeCameraCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 85DD0786DE8D23550B2113B7 /* Debug */, + F11D6A5494AA72875ECC53A4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = B93DF1E4460962AFB28CF144 /* Project object */; +} diff --git a/StripeCameraCore/StripeCameraCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeCameraCore/StripeCameraCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeCameraCore/StripeCameraCore.xcodeproj/xcshareddata/xcschemes/StripeCameraCore.xcscheme b/StripeCameraCore/StripeCameraCore.xcodeproj/xcshareddata/xcschemes/StripeCameraCore.xcscheme new file mode 100644 index 00000000..d616e1ac --- /dev/null +++ b/StripeCameraCore/StripeCameraCore.xcodeproj/xcshareddata/xcschemes/StripeCameraCore.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeCameraCore/StripeCameraCore/Info.plist b/StripeCameraCore/StripeCameraCore/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeCameraCore/StripeCameraCore/Source/CameraExifMetadata.swift b/StripeCameraCore/StripeCameraCore/Source/CameraExifMetadata.swift new file mode 100644 index 00000000..4860f94b --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/CameraExifMetadata.swift @@ -0,0 +1,46 @@ +// +// CameraExifMetadata.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 4/14/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import CoreMedia +import Foundation +import ImageIO + +/// A helper to extract properties from an EXIF metadata dictionary +@_spi(STP) public struct CameraExifMetadata: Equatable { + public let brightnessValue: Double? + public let focalLength: Double? + public let lensModel: String? +} + +extension CameraExifMetadata { + public init?( + exifDictionary: [CFString: Any]? + ) { + guard let exifDictionary = exifDictionary else { + return nil + } + + self.init( + brightnessValue: exifDictionary[kCGImagePropertyExifBrightnessValue] as? Double, + focalLength: exifDictionary[kCGImagePropertyExifFocalLength] as? Double, + lensModel: exifDictionary[kCGImagePropertyExifLensModel] as? String + ) + } + + public init?( + sampleBuffer: CMSampleBuffer + ) { + self.init( + exifDictionary: CMGetAttachment( + sampleBuffer, + key: kCGImagePropertyExifDictionary, + attachmentModeOut: nil + ) as? [CFString: Any] + ) + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Categories/CGRect+StripeCameraCore.swift b/StripeCameraCore/StripeCameraCore/Source/Categories/CGRect+StripeCameraCore.swift new file mode 100644 index 00000000..29c5661b --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Categories/CGRect+StripeCameraCore.swift @@ -0,0 +1,80 @@ +// +// CGRect+StripeCameraCore.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 12/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import CoreGraphics +import Foundation + +@_spi(STP) extension CGRect { + + /// Represents the bounds of a normalized coordinate system with range from (0,0) to (1,1) + public static let normalizedBounds = CGRect(x: 0, y: 0, width: 1, height: 1) + + /// - Returns: A `CGRect` that has its y-coordinates inverted between the + /// upper-left corner and lower-left corner. + /// + /// - Note: + /// This should only be used for rects that are using a normalized + /// coordinate system, meaning that the coordinate of the corner opposite + /// origin is (1,1) + public var invertedNormalizedCoordinates: CGRect { + return CGRect( + x: minX, + y: 1 - minY - height, + width: width, + height: height + ) + } + + /// Converts a rectangle that's using a normalized coordinate system from a + /// center-crop coordinate system to an un-cropped coordinate system + /// + /// Example, if the original size has a portrait aspect ratio, center-cropping + /// the rect will result in the square area: + /// ``` + /// +---------+ + /// | | + /// |---------| + /// | | + /// | | + /// | | + /// |---------| + /// | | + /// +---------+ + /// ``` + /// + /// This method converts the rect's coordinate relative to the center-cropped + /// area into coordinates relative to the original un-cropped area: + /// ``` + /// +---------+ + /// | | + /// +---------+ | | + /// | +--+ | | +--+ | + /// | | | | --> | | | | + /// | +--+ | | +--+ | + /// +---------+ | | + /// | | + /// +---------+ + /// ``` + /// + /// - Parameters: + /// - size: The original size of the un-cropped area. + public func convertFromNormalizedCenterCropSquare( + toOriginalSize originalSize: CGSize + ) -> CGRect { + let croppedWidth = min(originalSize.width, originalSize.height) + let scaleX = croppedWidth / originalSize.width + let scaleY = croppedWidth / originalSize.height + + return CGRect( + x: (minX - 0.5) * scaleX + 0.5, + y: (minY - 0.5) * scaleY + 0.5, + width: width * scaleX, + height: height * scaleY + ) + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Categories/CVPixelBuffer+StripeCameraCore.swift b/StripeCameraCore/StripeCameraCore/Source/Categories/CVPixelBuffer+StripeCameraCore.swift new file mode 100644 index 00000000..6d147652 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Categories/CVPixelBuffer+StripeCameraCore.swift @@ -0,0 +1,18 @@ +// +// CVPixelBuffer+StripeCameraCore.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 3/16/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import CoreVideo +import VideoToolbox + +@_spi(STP) extension CVPixelBuffer { + public func cgImage() -> CGImage? { + var cgImage: CGImage? + VTCreateCGImageFromCVPixelBuffer(self, options: nil, imageOut: &cgImage) + return cgImage + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Categories/UIDeviceOrientation+StripeCameraCore.swift b/StripeCameraCore/StripeCameraCore/Source/Categories/UIDeviceOrientation+StripeCameraCore.swift new file mode 100644 index 00000000..e9bdbdf5 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Categories/UIDeviceOrientation+StripeCameraCore.swift @@ -0,0 +1,26 @@ +// +// UIDeviceOrientation+StripeCameraCore.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 1/20/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import AVKit +import Foundation +import UIKit + +@_spi(STP) extension UIDeviceOrientation { + public var videoOrientation: AVCaptureVideoOrientation { + switch UIDevice.current.orientation { + case .portraitUpsideDown: + return .portraitUpsideDown + case .landscapeLeft: + return .landscapeRight + case .landscapeRight: + return .landscapeLeft + default: + return .portrait + } + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Categories/UIImage+Buffer.swift b/StripeCameraCore/StripeCameraCore/Source/Categories/UIImage+Buffer.swift new file mode 100644 index 00000000..9d13e713 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Categories/UIImage+Buffer.swift @@ -0,0 +1,118 @@ +// Ignoring file format becase of compiler directive which would force the whole file to be +// indented, including import statements. +// swift-format-ignore-file + +// Taken from https://gist.github.com/createwithswift/30a058c2745c8b09e64e7b073485e516 +// +// MIT License +// +// Copyright (c) 2021 Create with Swift +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#if targetEnvironment(simulator) + +import CoreMedia +import UIKit + +extension UIImage { + @_spi(STP) public func convertToPixelBuffer() -> CVPixelBuffer? { + + let attributes = + [ + kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, + kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue, + ] as CFDictionary + + var pixelBuffer: CVPixelBuffer? + + let status = CVPixelBufferCreate( + kCFAllocatorDefault, + Int(self.size.width), + Int(self.size.height), + kCVPixelFormatType_32ARGB, + attributes, + &pixelBuffer + ) + + guard status == kCVReturnSuccess else { + return nil + } + + CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) + + let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!) + let rgbColorSpace = CGColorSpaceCreateDeviceRGB() + + let context = CGContext( + data: pixelData, + width: Int(self.size.width), + height: Int(self.size.height), + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), + space: rgbColorSpace, + bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue + ) + + context?.translateBy(x: 0, y: self.size.height) + context?.scaleBy(x: 1.0, y: -1.0) + + UIGraphicsPushContext(context!) + self.draw(in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)) + UIGraphicsPopContext() + + CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0)) + + return pixelBuffer + } + + @_spi(STP) public func convertToSampleBuffer() -> CMSampleBuffer? { + guard let pixelBuffer = convertToPixelBuffer() else { + return nil + } + + var sampleBuffer: CMSampleBuffer? + var optionalVideoInfo: CMVideoFormatDescription? + var timingInfo: CMSampleTimingInfo = .invalid + + CMVideoFormatDescriptionCreateForImageBuffer( + allocator: nil, + imageBuffer: pixelBuffer, + formatDescriptionOut: &optionalVideoInfo + ) + guard let videoInfo = optionalVideoInfo else { + return nil + } + + CMSampleBufferCreateForImageBuffer( + allocator: kCFAllocatorDefault, + imageBuffer: pixelBuffer, + dataReady: true, + makeDataReadyCallback: nil, + refcon: nil, + formatDescription: videoInfo, + sampleTiming: &timingInfo, + sampleBufferOut: &sampleBuffer + ) + + return sampleBuffer + } +} + +#endif diff --git a/StripeCameraCore/StripeCameraCore/Source/Coordinators/AppSettingsHelper.swift b/StripeCameraCore/StripeCameraCore/Source/Coordinators/AppSettingsHelper.swift new file mode 100644 index 00000000..02b2ff78 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Coordinators/AppSettingsHelper.swift @@ -0,0 +1,43 @@ +// +// AppSettingsHelper.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 12/3/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +public protocol AppSettingsHelperProtocol { + var canOpenAppSettings: Bool { get } + func openAppSettings() +} + +/// Helper class that opens the app's settings screen in the Settings app. +@_spi(STP) public class AppSettingsHelper: AppSettingsHelperProtocol { + + public static let shared = AppSettingsHelper() + + private(set) lazy var appSettingsUrl: URL? = URL(string: UIApplication.openSettingsURLString) + + private init() { + // Use shared instance instead of init + } + + /// `true` if the system is able to open the app's settings screen. + public var canOpenAppSettings: Bool { + guard let settingsUrl = appSettingsUrl else { + return false + } + return UIApplication.shared.canOpenURL(settingsUrl) + } + + /// Opens the app's settings screen, if possible. + public func openAppSettings() { + guard let settingsUrl = appSettingsUrl else { + return + } + UIApplication.shared.open(settingsUrl) + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Coordinators/CameraPermissionsManager.swift b/StripeCameraCore/StripeCameraCore/Source/Coordinators/CameraPermissionsManager.swift new file mode 100644 index 00000000..36db983b --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Coordinators/CameraPermissionsManager.swift @@ -0,0 +1,85 @@ +// +// CameraPermissionsManager.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 12/3/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import AVKit +import Foundation + +@_spi(STP) public protocol CameraPermissionsManagerProtocol { + /// Completion block called when done requesting permissions. + /// + /// - Parameter granted: If camera permissions are granted for the app. + /// Value is `nil` if the authorization status cannot be determined. + typealias CompletionBlock = (_ granted: Bool?) -> Void + + var hasCameraAccess: Bool { get } + func requestCameraAccess( + completeOnQueue queue: DispatchQueue, + completion: @escaping CompletionBlock + ) +} + +/// Dependency-injectable class to assist with accessing and requesting camera authorization +@_spi(STP) public final class CameraPermissionsManager: CameraPermissionsManagerProtocol { + + public static let shared = CameraPermissionsManager() + + /// If this app currently has authorization to access the device's camera + public var hasCameraAccess: Bool { + return AVCaptureDevice.authorizationStatus(for: .video) == .authorized + } + + private init() { + // Use shared instance instead of init + } + + /// Requests camera permissions and calls completion block with result after retrieving them. + /// + /// - Parameters: + /// - completion: + /// - queue: DispatchQueue to complete the + /// + /// - Note: + /// If the user has already granted or denied camera permissions to the app, + /// this callback will respond immediately after `requestCameraAccess` is + /// called on the video feed and `showedPrompt` will be false. + /// + /// If the user has not yet granted or denied camera permissions to the app, + /// they will be prompted to do so. This callback will respond after the user + /// selects a response and `showedPrompt` will be true. + /// + public func requestCameraAccess( + completeOnQueue queue: DispatchQueue = .main, + completion: @escaping CompletionBlock + ) { + let wrappedCompletion: CompletionBlock = { granted in + queue.async { + completion(granted) + } + } + + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + wrappedCompletion(true) + + case .notDetermined: + AVCaptureDevice.requestAccess( + for: .video, + completionHandler: { granted in + wrappedCompletion(granted) + } + ) + + case .restricted, + .denied: + wrappedCompletion(false) + + default: + wrappedCompletion(nil) + } + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Coordinators/CameraSession.swift b/StripeCameraCore/StripeCameraCore/Source/Coordinators/CameraSession.swift new file mode 100644 index 00000000..55476fee --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Coordinators/CameraSession.swift @@ -0,0 +1,508 @@ +// +// CameraSession.swift +// StripeCameraCore +// +// Created by Jaime Park on 12/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import AVKit +@_spi(STP) import StripeCore + +@_spi(STP) @frozen public enum CameraSessionError: Error { + /// Can't find capture device to add + case captureDeviceNotFound + /// Session configuration has failed + case configurationFailed +} + +@_spi(STP) public protocol CameraSessionProtocol: AnyObject { + + var previewView: CameraPreviewView? { get set } + + func configureSession( + configuration: CameraSession.Configuration, + delegate: AVCaptureVideoDataOutputSampleBufferDelegate, + completeOn queue: DispatchQueue, + completion: @escaping (CameraSession.SetupResult) -> Void + ) + + func setVideoOrientation( + orientation: AVCaptureVideoOrientation + ) + + func toggleCamera( + to position: CameraSession.CameraPosition, + completeOn queue: DispatchQueue, + completion: @escaping (CameraSession.SetupResult) -> Void + ) + + func toggleTorch() + + func getCameraProperties() -> CameraSession.DeviceProperties? + + func startSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) + + func stopSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) +} + +@_spi(STP) public final class CameraSession: CameraSessionProtocol { + @frozen public enum SetupResult { + /// Session has successfully updated + case success + /// Session did not update due to an error + case failed(error: Error) + } + + public enum CameraPosition { + case front + case back + } + + public struct Configuration { + /// The initial position of camera: front or back + public let initialCameraPosition: CameraPosition + /// The initial video orientation of the camera session + public let initialOrientation: AVCaptureVideoOrientation + /// The capture device’s focus mode. + /// - Seealso: https://developer.apple.com/documentation/avfoundation/avcapturedevice/focusmode + public let focusMode: AVCaptureDevice.FocusMode? + /// The point of interest for focusing. + /// - Seealso: + /// https://developer.apple.com/documentation/avfoundation/avcapturedevice/focuspointofinterest + public let focusPointOfInterest: CGPoint? + /// A preset value of the quality of the capture session + public let sessionPreset: AVCaptureSession.Preset + /// Video settings for the video output + /// - Seealso: https://developer.apple.com/documentation/avfoundation/avcapturephotosettings/video_settings + public let outputSettings: [String: Any] + /// https://developer.apple.com/documentation/avfoundation/avcapturedevice/1624622-autofocusrangerestriction + public let autoFocusRangeRestriction: AVCaptureDevice.AutoFocusRangeRestriction + + /// - Parameters: + /// - initialCameraPosition: The initial position of camera: front or back + /// - initialOrientation: The initial video orientation of the camera session + /// - focusMode: The focus mode of the camera session + /// - focusPointOfInterest: The point of interest for focusing + /// - sessionPreset: A preset value of the quality of the capture session + /// - outputSettings: Video settings for the video output + public init( + initialCameraPosition: CameraPosition, + initialOrientation: AVCaptureVideoOrientation, + focusMode: AVCaptureDevice.FocusMode? = nil, + focusPointOfInterest: CGPoint? = nil, + sessionPreset: AVCaptureSession.Preset = .high, + outputSettings: [String: Any] = [:], + autoFocusRangeRestriction: AVCaptureDevice.AutoFocusRangeRestriction = .none + ) { + self.initialCameraPosition = initialCameraPosition + self.initialOrientation = initialOrientation + self.focusMode = focusMode + self.focusPointOfInterest = focusPointOfInterest + self.sessionPreset = sessionPreset + self.outputSettings = outputSettings + self.autoFocusRangeRestriction = autoFocusRangeRestriction + } + } + + public struct DeviceProperties: Equatable { + public let exposureDuration: CMTime + public let cameraDeviceType: AVCaptureDevice.DeviceType + public let isVirtualDevice: Bool? + public let lensPosition: Float + public let exposureISO: Float + public let isAdjustingFocus: Bool + } + + // MARK: - Properties + + public weak var previewView: CameraPreviewView? { + didSet { + guard oldValue !== previewView else { + return + } + + // Remove captureSession from previous view and add it to new one + oldValue?.setCaptureSession(nil, on: sessionQueue) + previewView?.setCaptureSession(session, on: sessionQueue) + } + } + + private let session: AVCaptureSession = AVCaptureSession() + private var captureConnection: AVCaptureConnection? + private let sessionQueue = DispatchQueue(label: "com.stripe.camera-session") + private var torchDevice: Torch? + private var setupResult: SetupResult? + + private var videoDeviceInput: AVCaptureDeviceInput? { + didSet { + if let videoDeviceInput = videoDeviceInput { + // Set torch with new capture input + self.torchDevice = Torch(device: videoDeviceInput.device) + } + } + } + + // MARK: - Public + + public init() { + // This is needed to expose init publicly + } + + /// Configures the camera session with the initial inputs and outputs. + /// + /// If the camera session has been configured already, then the configuration + /// is ignored and the previous setup result is passed to the completion block. + /// + /// - Parameters: + /// - configuration: Configuration settings for the session + /// - delegate: + /// - queue: DispatchQueue the completion block should be called on + /// - completion: A block executed when the session is done being configured + public func configureSession( + configuration: Configuration, + delegate: AVCaptureVideoDataOutputSampleBufferDelegate, + completeOn queue: DispatchQueue, + completion: @escaping (SetupResult) -> Void + ) { + sessionQueue.async { [weak self] in + guard let self = self else { return } + + // Check if already configured + if let setupResult = self.setupResult { + completion(setupResult) + return + } + + self.session.beginConfiguration() + self.session.sessionPreset = configuration.sessionPreset + self.session.commitConfiguration() + + self.configureSessionInput(with: configuration.initialCameraPosition).chained { + [weak self] _ -> Future in + guard let self = self else { + // If self has been deallocated before configuring output, return failure + let promise = Promise() + promise.reject(with: CameraSessionError.configurationFailed) + return promise + } + + return self.configureSessionOutput( + with: configuration.outputSettings, + orientation: configuration.initialOrientation, + focusMode: configuration.focusMode, + focusPointOfInterest: configuration.focusPointOfInterest, + autoFocusRangeRestriction: configuration.autoFocusRangeRestriction, + delegate: delegate + ) + }.observe(on: queue) { [weak self] result in + self?.setupResult = result.setupResult + completion(result.setupResult) + } + } + } + + /// Attempts to change the video orientation of both the session output + /// and the preview view layer. + /// + /// - Parameters: + /// - orientation: The desired video orientation + public func setVideoOrientation( + orientation: AVCaptureVideoOrientation + ) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.captureConnection?.videoOrientation = orientation + self.previewView?.videoPreviewLayer.connection?.videoOrientation = orientation + } + } + + /// Returns the properties from the camera device. + /// + /// - Note: This method can only be called on the camera session thread, + /// meaning it's only meant to be called from the output delegate's + /// `captureOutput` method. + public func getCameraProperties() -> CameraSession.DeviceProperties? { + dispatchPrecondition(condition: .onQueue(sessionQueue)) + + guard let device = videoDeviceInput?.device else { + return nil + } + + let isVirtualDevice = device.isVirtualDevice + + return .init( + exposureDuration: device.exposureDuration, + cameraDeviceType: device.deviceType, + isVirtualDevice: isVirtualDevice, + lensPosition: device.lensPosition, + exposureISO: device.iso, + isAdjustingFocus: device.isAdjustingFocus + ) + } + + /// Attempts to switch camera input to a new camera position. + /// - Parameters: + /// - position: The camera position to toggle to + /// - queue: DispatchQueue the completion block should be called on + /// - completion: A block executed when the camera has finished toggling + public func toggleCamera( + to position: CameraPosition, + completeOn queue: DispatchQueue, + completion: @escaping (SetupResult) -> Void + ) { + configureSessionInput(with: position).observe(on: queue) { result in + completion(result.setupResult) + } + } + + /// Attempts to toggle the torch on or off. + public func toggleTorch() { + self.torchDevice?.toggle() + } + + /// Starts the camera session, calling a completion block when the session has + /// been started. + /// + /// - Parameters: + /// - queue: The queue to call the completion block on + /// - completion: A block executed when the session has been started + public func startSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) { + sessionQueue.async { [weak self] in + defer { + queue.async { + completion() + } + } + + guard let self = self, + case .success = self.setupResult + else { + return + } + + self.session.startRunning() + } + } + + /// Stop the camera session + public func stopSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) { + sessionQueue.async { [weak self] in + defer { + queue.async { + completion() + } + } + + guard let self = self, + case .success = self.setupResult + else { + return + } + + self.session.stopRunning() + } + } +} + +// MARK: - Private + +extension CameraSession { + fileprivate func configureSessionInput( + with position: CameraPosition + ) -> Future { + let promise = Promise() + + sessionQueue.async { [weak self] in + guard let self = self else { return } + + self.session.beginConfiguration() + defer { + self.session.commitConfiguration() + } + + do { + // Remove inputs + self.session.inputs.forEach { + self.session.removeInput($0) + } + + let newVideoDeviceInput = try self.captureDeviceInput(position: position) + + // Add video input + guard self.session.canAddInput(newVideoDeviceInput) + else { + promise.reject(with: CameraSessionError.configurationFailed) + return + } + self.session.addInput(newVideoDeviceInput) + + // Keep reference to video device input + self.videoDeviceInput = newVideoDeviceInput + + promise.resolve(with: ()) + } catch { + promise.reject(with: error) + } + } + + return promise + } + + fileprivate func configureSessionOutput( + with videoSettings: [String: Any], + orientation: AVCaptureVideoOrientation, + focusMode: AVCaptureDevice.FocusMode?, + focusPointOfInterest: CGPoint?, + autoFocusRangeRestriction: AVCaptureDevice.AutoFocusRangeRestriction, + delegate: AVCaptureVideoDataOutputSampleBufferDelegate + ) -> Future { + let promise = Promise() + + sessionQueue.async { [weak self] in + guard let self = self else { return } + + self.session.beginConfiguration() + defer { + self.session.commitConfiguration() + } + + let videoOutput = AVCaptureVideoDataOutput() + videoOutput.videoSettings = videoSettings + videoOutput.alwaysDiscardsLateVideoFrames = true + videoOutput.setSampleBufferDelegate(delegate, queue: self.sessionQueue) + + guard self.session.canAddOutput(videoOutput) else { + promise.reject(with: CameraSessionError.configurationFailed) + return + } + + // Add output to session + self.session.addOutput(videoOutput) + + // Update output connection reference + self.captureConnection = videoOutput.connection(with: .video) + + // Update new output and previewLayer orientation + self.setVideoOrientation(orientation: orientation) + + // Set focus if needed + guard let focusMode = focusMode else { + promise.resolve(with: ()) + return + } + + promise.fulfill { [weak self] in + try self?.setFocusOnCurrentQueue( + focusMode: focusMode, + autoFocusRangeRestriction: autoFocusRangeRestriction, + focusPointOfInterest: focusPointOfInterest + ) + } + } + + return promise + } + + fileprivate func captureDeviceInput(position: CameraPosition) throws -> AVCaptureDeviceInput { + let captureDevices = AVCaptureDevice.DiscoverySession( + deviceTypes: position.captureDeviceTypes, + mediaType: .video, + position: position.captureDevicePosition + ) + + guard let captureDevice = captureDevices.devices.first else { + throw CameraSessionError.captureDeviceNotFound + } + + return try AVCaptureDeviceInput(device: captureDevice) + } + + fileprivate func setFocusOnCurrentQueue( + focusMode: AVCaptureDevice.FocusMode, + autoFocusRangeRestriction: AVCaptureDevice.AutoFocusRangeRestriction, + focusPointOfInterest: CGPoint? + ) throws { + dispatchPrecondition(condition: .onQueue(sessionQueue)) + + guard let device = videoDeviceInput?.device else { + return + } + + try device.lockForConfiguration() + if device.isFocusModeSupported(focusMode) { + device.focusMode = focusMode + + } + + if device.isAutoFocusRangeRestrictionSupported { + device.autoFocusRangeRestriction = autoFocusRangeRestriction + } + + if let focusPointOfInterest = focusPointOfInterest, + device.isFocusPointOfInterestSupported + { + if device.isSmoothAutoFocusSupported { + device.isSmoothAutoFocusEnabled = true + } + device.focusPointOfInterest = focusPointOfInterest + } + + if device.isLowLightBoostSupported { + device.automaticallyEnablesLowLightBoostWhenAvailable = true + } + device.unlockForConfiguration() + } +} + +// MARK: - CameraPosition + +extension CameraSession.CameraPosition { + /// Returns a list of camera devices, ordered by preferred device, for this + /// camera position. + var captureDeviceTypes: [AVCaptureDevice.DeviceType] { + switch self { + case .front: + return [.builtInTrueDepthCamera, .builtInWideAngleCamera] + + case .back: + return [.builtInTripleCamera, .builtInDualCamera, .builtInDualWideCamera, .builtInWideAngleCamera] + } + } + + var captureDevicePosition: AVCaptureDevice.Position { + switch self { + case .front: + return .front + case .back: + return .back + } + } +} + +// MARK: - Result + +/// Helper to convert a Result into SetupResult +extension Result where Success == Void, Failure == Error { + fileprivate var setupResult: CameraSession.SetupResult { + switch self { + case .success: + return .success + case .failure(let error): + return .failed(error: error) + } + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Coordinators/MockSimulatorCameraSession.swift b/StripeCameraCore/StripeCameraCore/Source/Coordinators/MockSimulatorCameraSession.swift new file mode 100644 index 00000000..ef0bdf74 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Coordinators/MockSimulatorCameraSession.swift @@ -0,0 +1,220 @@ +// +// MockSimulatorCameraSession.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 1/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +// Ignoring file formatt becase of compiler directive which would force the whole file to be +// indented, including import statements. +// swift-format-ignore-file + +#if targetEnvironment(simulator) + +import AVKit +import Foundation +@_spi(STP) import StripeCore + +/// Mocks a CameraSession on the simulator. +@_spi(STP) public final class MockSimulatorCameraSession: CameraSessionProtocol { + + enum Error: Swift.Error { + case sessionNotConfigured + case noImages + } + + static let mockSampleBufferTimeInterval: TimeInterval = 0.1 + + private let images: [UIImage] + private var nextImageToReturn: Int = 0 + private var currentImage: UIImage? + private let sessionQueue = DispatchQueue(label: "com.stripe.mock-simulator-camera-session") + private let videoOutput = AVCaptureVideoDataOutput() + private lazy var captureConnection = AVCaptureConnection( + inputPorts: [], + output: videoOutput + ) + private var mockSampleBufferTimer: Timer? + weak private var delegate: AVCaptureVideoDataOutputSampleBufferDelegate? + + // MARK: - Public + + private var isConfigured: Bool = false + private var cameraPosition: CameraSession.CameraPosition = .front + + public weak var previewView: CameraPreviewView? { + didSet { + setPreviewViewToCurrentImage() + } + } + + public init( + images: [UIImage] + ) { + self.images = images + } + + public func configureSession( + configuration: CameraSession.Configuration, + delegate: AVCaptureVideoDataOutputSampleBufferDelegate, + completeOn queue: DispatchQueue, + completion: @escaping (CameraSession.SetupResult) -> Void + ) { + sessionQueue.async { [weak self] in + let wrappedCompletion = { setupResult in + queue.async { + completion(setupResult) + } + } + + guard let self = self else { return } + self.delegate = delegate + + guard !self.images.isEmpty else { + self.isConfigured = false + wrappedCompletion(.failed(error: Error.noImages)) + return + } + + self.cameraPosition = configuration.initialCameraPosition + self.isConfigured = true + wrappedCompletion(.success) + } + } + + public func setVideoOrientation(orientation: AVCaptureVideoOrientation) { + // no-op + } + + public func toggleCamera( + to position: CameraSession.CameraPosition, + completeOn queue: DispatchQueue, + completion: @escaping (CameraSession.SetupResult) -> Void + ) { + sessionQueue.async { [weak self] in + guard let self = self else { return } + + let wrappedCompletion = { setupResult in + queue.async { + completion(setupResult) + } + } + + guard self.isConfigured else { + wrappedCompletion(.failed(error: Error.sessionNotConfigured)) + return + } + + self.cameraPosition = position + wrappedCompletion(.success) + } + } + + public func getCameraProperties() -> CameraSession.DeviceProperties? { + return .init( + exposureDuration: CMTime(value: 0, timescale: 1), + cameraDeviceType: .builtInDualCamera, + isVirtualDevice: nil, + lensPosition: 0, + exposureISO: 0, + isAdjustingFocus: false + ) + } + + public func toggleTorch() { + // no-op + } + + public func startSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) { + sessionQueue.async { [weak self] in + guard let self = self else { return } + + defer { + queue.async { + completion() + } + } + + if self.isConfigured && self.currentImage == nil { + self.currentImage = self.images.stp_boundSafeObject(at: self.nextImageToReturn) + self.setPreviewViewToCurrentImage() + } + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.mockSampleBufferTimer = Timer.scheduledTimer( + timeInterval: MockSimulatorCameraSession.mockSampleBufferTimeInterval, + target: self, + selector: #selector(self.mockSampleBufferDelegateCallback), + userInfo: nil, + repeats: true + ) + } + } + } + + public func stopSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) { + sessionQueue.async { [weak self] in + guard let self = self else { return } + + defer { + queue.async { + completion() + } + } + + self.mockSampleBufferTimer?.invalidate() + + if self.currentImage != nil { + self.nextImageToReturn += 1 + } + self.currentImage = nil + } + } +} + +extension MockSimulatorCameraSession { + @objc fileprivate func mockSampleBufferDelegateCallback() { + sessionQueue.async { [weak self] in + guard let self = self, + var image = self.currentImage + else { + return + } + + // Flip image horizontally if mocking front-facing camera + if self.cameraPosition == .front, + let cgImage = image.cgImage + { + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored) + } + + guard let sampleBuffer = image.convertToSampleBuffer() else { + return + } + + self.delegate?.captureOutput?( + self.videoOutput, + didOutput: sampleBuffer, + from: self.captureConnection + ) + } + } + + fileprivate func setPreviewViewToCurrentImage() { + DispatchQueue.main.async { [weak self, weak currentImage] in + guard let self = self else { return } + self.previewView?.layer.contents = currentImage?.cgImage + self.previewView?.layer.contentsGravity = .resizeAspectFill + } + } +} + +#endif diff --git a/StripeCameraCore/StripeCameraCore/Source/Coordinators/Torch.swift b/StripeCameraCore/StripeCameraCore/Source/Coordinators/Torch.swift new file mode 100644 index 00000000..caa5ef05 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Coordinators/Torch.swift @@ -0,0 +1,53 @@ +// +// Torch.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 12/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import AVFoundation +import Foundation + +struct Torch { + enum State { + case off + case on + } + let device: AVCaptureDevice? + var state: State + var lastStateChange: Date + var level: Float + + init( + device: AVCaptureDevice + ) { + self.state = .off + self.lastStateChange = Date() + if device.hasTorch { + self.device = device + if device.isTorchActive { + self.state = .on + } + } else { + self.device = nil + } + self.level = 1.0 + } + + mutating func toggle() { + self.state = self.state == .on ? .off : .on + do { + try self.device?.lockForConfiguration() + if self.state == .on { + try self.device?.setTorchModeOn(level: self.level) + } else { + self.device?.torchMode = .off + } + } catch { + // no-op + } + // Always unlock when we're done even if `setTorchModeOn` threw + self.device?.unlockForConfiguration() + } +} diff --git a/StripeCameraCore/StripeCameraCore/Source/Views/CameraPreviewView.swift b/StripeCameraCore/StripeCameraCore/Source/Views/CameraPreviewView.swift new file mode 100644 index 00000000..61a8ed97 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/Source/Views/CameraPreviewView.swift @@ -0,0 +1,81 @@ +// +// CameraPreviewView.swift +// StripeCameraCore +// +// Created by Mel Ludowise on 12/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import AVFoundation +import UIKit + +@_spi(STP) public class CameraPreviewView: UIView { + var videoPreviewLayer: AVCaptureVideoPreviewLayer { + return layer as! AVCaptureVideoPreviewLayer + } + + /// Camera session that configures the video feed for this view. + /// + /// - Note: + /// A session can only be used on one `PreviewView` at a time. Setting the same + /// session on a different `PreviewView` will remove it from the previous one. + public weak var session: CameraSessionProtocol? { + didSet { + guard oldValue !== session else { + return + } + oldValue?.previewView = nil + session?.previewView = self + } + } + + // MARK: Initialization + + public init() { + super.init(frame: .zero) + + videoPreviewLayer.videoGravity = .resizeAspectFill + } + + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } + + // MARK: UIView + + public override class var layerClass: AnyClass { + return AVCaptureVideoPreviewLayer.self + } + + // MARK: Internal + + /// Sets the video capture session in thread-safe way that doesn't block main + /// thread when configuring the session. + /// + /// - Parameters: + /// - captureSession: The capture session to set on this view + /// - cameraSessionQueue: The CameraSession's queue to configure the session + func setCaptureSession( + _ captureSession: AVCaptureSession?, + on cameraSessionQueue: DispatchQueue + ) { + // Get reference to videoPreviewLayer on main queue then dispatch to + // worker queue to set session so it doesn't block main. + + let mainWorkItem = DispatchWorkItem { [weak self] in + guard let self = self else { return } + cameraSessionQueue.async { + [weak captureSession, weak videoPreviewLayer = self.videoPreviewLayer] in + videoPreviewLayer?.session = captureSession + } + } + + if Thread.isMainThread { + mainWorkItem.perform() + } else { + DispatchQueue.main.async(execute: mainWorkItem) + } + } +} diff --git a/StripeCameraCore/StripeCameraCore/StripeCameraCore.h b/StripeCameraCore/StripeCameraCore/StripeCameraCore.h new file mode 100644 index 00000000..6cb45611 --- /dev/null +++ b/StripeCameraCore/StripeCameraCore/StripeCameraCore.h @@ -0,0 +1,18 @@ +// +// StripeCameraCore.h +// StripeCameraCore +// +// Created by Mel Ludowise on 11/15/21. +// + +#import + +//! Project version number for StripeCameraCore. +FOUNDATION_EXPORT double StripeCameraCoreVersionNumber; + +//! Project version string for StripeCameraCore. +FOUNDATION_EXPORT const unsigned char StripeCameraCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeCameraCore/StripeCameraCoreTestUtils/Info.plist b/StripeCameraCore/StripeCameraCoreTestUtils/Info.plist new file mode 100644 index 00000000..9bcb2444 --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTestUtils/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockAppSettingsHelper.swift b/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockAppSettingsHelper.swift new file mode 100644 index 00000000..608e1d1f --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockAppSettingsHelper.swift @@ -0,0 +1,21 @@ +// +// MockAppSettingsHelper.swift +// StripeCameraCoreTestUtils +// +// Created by Mel Ludowise on 12/3/21. +// + +import Foundation +@_spi(STP) import StripeCameraCore + +@_spi(STP) public class MockAppSettingsHelper: AppSettingsHelperProtocol { + + public var canOpenAppSettings = false + public private(set) var didOpenAppSettings = false + + public init() {} + + public func openAppSettings() { + didOpenAppSettings = true + } +} diff --git a/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockCameraPermissionsManager.swift b/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockCameraPermissionsManager.swift new file mode 100644 index 00000000..811451be --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockCameraPermissionsManager.swift @@ -0,0 +1,41 @@ +// +// MockCameraPermissionsManager.swift +// StripeCameraCoreTestUtils +// +// Created by Mel Ludowise on 12/3/21. +// + +import Foundation +@_spi(STP) import StripeCameraCore +import XCTest + +@_spi(STP) public class MockCameraPermissionsManager: CameraPermissionsManagerProtocol { + + public var hasCameraAccess = false + + public private(set) var didRequestCameraAccess = false + public let didCompleteExpectation = XCTestExpectation( + description: "MockCameraPermissionsManager completion did finish" + ) + + private var completion: CompletionBlock = { _ in } + + public init() {} + + public func requestCameraAccess( + completeOnQueue queue: DispatchQueue, + completion: @escaping CompletionBlock + ) { + self.completion = { granted in + queue.async { [weak self] in + completion(granted) + self?.didCompleteExpectation.fulfill() + } + } + didRequestCameraAccess = true + } + + public func respondToRequest(granted: Bool?) { + completion(granted) + } +} diff --git a/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockTestCameraSession.swift b/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockTestCameraSession.swift new file mode 100644 index 00000000..5ba9bea0 --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTestUtils/Mocks/MockTestCameraSession.swift @@ -0,0 +1,180 @@ +// +// MockTestCameraSession.swift +// StripeCameraCoreTestUtils +// +// Created by Mel Ludowise on 1/21/22. +// + +import AVKit +import Foundation +@_spi(STP)@testable import StripeCameraCore +import XCTest + +@_spi(STP) public final class MockTestCameraSession: CameraSessionProtocol { + + /// Mock image to display in previewView. Should be used for snapshot tests + public var mockImage: UIImage? { + didSet { + setPreviewViewToMockImage() + } + } + + public var previewView: CameraPreviewView? { + didSet { + setPreviewViewToMockImage() + } + } + + public var mockDeviceProperties = CameraSession.DeviceProperties( + exposureDuration: CMTime(), + cameraDeviceType: .builtInDualCamera, + isVirtualDevice: nil, + lensPosition: 0, + exposureISO: 0, + isAdjustingFocus: false + ) + + public init() { + // no-op + } + + // MARK: configureSession + + private var configureCompletion: ((CameraSession.SetupResult) -> Void)? + public private(set) var configureSessionCompletionExp = XCTestExpectation( + description: "configureSession completion block called" + ) + + public private(set) var sessionPreset: AVCaptureSession.Preset? + public private(set) var outputSettings: [String: Any]? + + public func configureSession( + configuration: CameraSession.Configuration, + delegate: AVCaptureVideoDataOutputSampleBufferDelegate, + completeOn queue: DispatchQueue, + completion: @escaping (CameraSession.SetupResult) -> Void + ) { + cameraPosition = configuration.initialCameraPosition + videoOrientation = configuration.initialOrientation + sessionPreset = configuration.sessionPreset + outputSettings = configuration.outputSettings + configureCompletion = { setupResult in + queue.async { [weak self] in + self?.configureSessionCompletionExp.fulfill() + completion(setupResult) + } + } + } + + public func respondToConfigureSession(setupResult: CameraSession.SetupResult) { + configureCompletion?(setupResult) + } + + // MARK: setVideoOrientation + + public private(set) var videoOrientation: AVCaptureVideoOrientation? + + public func setVideoOrientation(orientation: AVCaptureVideoOrientation) { + self.videoOrientation = orientation + } + + // MARK: toggleCamera + + private var toggleCameraCompletion: ((CameraSession.SetupResult) -> Void)? + public private(set) var cameraPosition: CameraSession.CameraPosition? + + public func toggleCamera( + to position: CameraSession.CameraPosition, + completeOn queue: DispatchQueue, + completion: @escaping (CameraSession.SetupResult) -> Void + ) { + toggleCameraCompletion = { setupResult in + queue.async { + completion(setupResult) + } + } + cameraPosition = position + } + + public func respondToToggleCamera(setupResult: CameraSession.SetupResult) { + toggleCameraCompletion?(setupResult) + } + + // MARK: toggleTorch + + public private(set) var didToggleTorch = false + + public func toggleTorch() { + didToggleTorch = true + } + + // MARK: getCameraProperties + + public func getCameraProperties() -> CameraSession.DeviceProperties? { + return mockDeviceProperties + } + + // MARK: startSession + + private var startSessionCompletion: (() -> Void)? + public private(set) var startSessionCompletionExp = XCTestExpectation( + description: "startSession completion block called" + ) + + public func startSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) { + startSessionCompletion = { + queue.async { [weak self] in + self?.startSessionCompletionExp.fulfill() + completion() + } + } + } + + public func respondToStartSession() { + startSessionCompletion?() + } + + // MARK: stopSession + + private var stopSessionCompletion: (() -> Void)? + public private(set) var stopSessionCompletionExp = XCTestExpectation( + description: "stopSession completion block called" + ) + + public func stopSession( + completeOn queue: DispatchQueue, + completion: @escaping () -> Void + ) { + stopSessionCompletion = { + queue.async { [weak self] in + self?.stopSessionCompletionExp.fulfill() + completion() + } + } + } + + public func respondToStopSession() { + stopSessionCompletion?() + } + + // MARK: previewView + + func setPreviewViewToMockImage() { + let block = { [weak self] in + guard let self = self else { return } + self.previewView?.layer.contents = self.mockImage?.cgImage + self.previewView?.layer.contentsGravity = .resizeAspectFill + } + + // Only dispatch to main async if necessary so this can run + // synchronously for snapshot tests. + if Thread.isMainThread { + block() + } else { + DispatchQueue.main.async(execute: block) + } + } +} diff --git a/StripeCameraCore/StripeCameraCoreTestUtils/StripeCameraCoreTestUtils.h b/StripeCameraCore/StripeCameraCoreTestUtils/StripeCameraCoreTestUtils.h new file mode 100644 index 00000000..1f3c16b0 --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTestUtils/StripeCameraCoreTestUtils.h @@ -0,0 +1,18 @@ +// +// StripeCameraCoreTestUtils.h +// StripeCameraCoreTestUtils +// +// Created by Mel Ludowise on 11/15/21. +// + +#import + +//! Project version number for StripeCameraCoreTestUtils. +FOUNDATION_EXPORT double StripeCameraCoreTestUtilsVersionNumber; + +//! Project version string for StripeCameraCoreTestUtils. +FOUNDATION_EXPORT const unsigned char StripeCameraCoreTestUtilsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeCameraCore/StripeCameraCoreTests/Info.plist b/StripeCameraCore/StripeCameraCoreTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripeCameraCore/StripeCameraCoreTests/Unit/Categories/CGRect_StripeCameraCoreTest.swift b/StripeCameraCore/StripeCameraCoreTests/Unit/Categories/CGRect_StripeCameraCoreTest.swift new file mode 100644 index 00000000..317135ec --- /dev/null +++ b/StripeCameraCore/StripeCameraCoreTests/Unit/Categories/CGRect_StripeCameraCoreTest.swift @@ -0,0 +1,68 @@ +// +// CGRect_StripeCameraCoreTest.swift +// StripeCameraCoreTests +// +// Created by Mel Ludowise on 12/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import CoreGraphics +@_spi(STP) import StripeCameraCore +import XCTest + +class CGRect_StripeCameraCoreTest: XCTestCase { + func testInvertedNormalizedCoordinates() { + let rect = CGRect(x: 0.1, y: 0.1, width: 0.5, height: 0.5) + let invertedRect = rect.invertedNormalizedCoordinates + + XCTAssertEqual(invertedRect.minX, 0.1) + XCTAssertEqual(invertedRect.minY, 0.4) + XCTAssertEqual(invertedRect.width, 0.5) + XCTAssertEqual(invertedRect.height, 0.5) + } + + func testConvertFromNormalizedCenterCropSquareLandscape() { + // Centered-square rect corresponds to (350,0) 900x900 + let originalSize = CGSize(width: 1600, height: 900) + + // Corresponds to actual coordinates of + // (350+900*0.25,900*0.25) 900*0.25 x 900*0.25 + // = (575,225) 225x225 + let normalizedSquareRect = CGRect(x: 0.25, y: 0.25, width: 0.25, height: 0.25) + + // Should have coordinates of + // (575/1600, 225/900) 225/1600 x 225/900 + // = (0.359375,0.25) 0.140625 x 0.25 + let convertedRect = normalizedSquareRect.convertFromNormalizedCenterCropSquare( + toOriginalSize: originalSize + ) + + XCTAssertEqual(convertedRect.minX, 0.359375) + XCTAssertEqual(convertedRect.minY, 0.25) + XCTAssertEqual(convertedRect.width, 0.140625) + XCTAssertEqual(convertedRect.height, 0.25) + } + + func testConvertFromNormalizedCenterCropSquarePortrait() { + // Centered-square rect corresponds to (0,350) 900x900 + let originalSize = CGSize(width: 900, height: 1600) + + // Corresponds to actual coordinates of + // (900*0.25,350+900*0.25) 900*0.25 x 900*0.25 + // = (225,575) 225x225 + let normalizedSquareRect = CGRect(x: 0.25, y: 0.25, width: 0.25, height: 0.25) + + // Should have coordinates of + // (225/900, 575/1600) 225/900 x 225/1600 + // = (0.25,0.359375) 0.25 x 0.140625 + let convertedRect = normalizedSquareRect.convertFromNormalizedCenterCropSquare( + toOriginalSize: originalSize + ) + + XCTAssertEqual(convertedRect.minX, 0.25) + XCTAssertEqual(convertedRect.minY, 0.359375) + XCTAssertEqual(convertedRect.width, 0.25) + XCTAssertEqual(convertedRect.height, 0.140625) + } + +} diff --git a/StripeCardScan/BuildConfigurations/StripeCardScan-Debug.xcconfig b/StripeCardScan/BuildConfigurations/StripeCardScan-Debug.xcconfig new file mode 100644 index 00000000..0f5765ca --- /dev/null +++ b/StripeCardScan/BuildConfigurations/StripeCardScan-Debug.xcconfig @@ -0,0 +1,7 @@ +// +// StripeCardScan-Debug.xcconfig +// + +#include "../../BuildConfigurations/StripeiOS-Debug.xcconfig" + +APPLICATION_EXTENSION_API_ONLY = NO diff --git a/StripeCardScan/BuildConfigurations/StripeCardScan-Release.xcconfig b/StripeCardScan/BuildConfigurations/StripeCardScan-Release.xcconfig new file mode 100644 index 00000000..e99bbccc --- /dev/null +++ b/StripeCardScan/BuildConfigurations/StripeCardScan-Release.xcconfig @@ -0,0 +1,7 @@ +// +// StripeCardScan-Release.xcconfig +// + +#include "../../BuildConfigurations/StripeiOS-Release.xcconfig" + +APPLICATION_EXTENSION_API_ONLY = NO diff --git a/StripeCardScan/README.md b/StripeCardScan/README.md new file mode 100644 index 00000000..3f270ff3 --- /dev/null +++ b/StripeCardScan/README.md @@ -0,0 +1,87 @@ +# Stripe CardScan iOS SDK + +This library provides support for the standalone Stripe CardScan product. + +## Overview + +This library provides a user interface through which users can scan payment cards and extract information from them. It uses the Stripe Publishable Key to authenticate with Stripe services. + +Note that this is a standalone SDK and, while compatible with, does not directly integrate with the [PaymentIntent](https://stripe.com/docs/api/payment_intents) API nor with [next_action](https://stripe.com/docs/api/errors#errors-payment_intent-next_action). + +This library can be used entirely outside of a Stripe integration and with other payment processing providers. + +## Requirements + +- iOS 13.0 or higher +- Xcode 15 or higher + +## Example + +See the [CardImageVerification Example](https://github.com/stripe/stripe-ios/tree/master/Example/CardImageVerification%20Example) directory for an example application that you can try for yourself! + +## Installation + +- Cocoapod + - `pod install StripeCardScan` +- SPM + - In Xcode, select File > Add Packages… and enter https://github.com/stripe/stripe-ios-spm as the repository URL. + - Select the latest version number from our releases page. + - Add the `StripeCardScan` product to the target of your app. + +## Integration +### Credit Card OCR +Add `CardScanSheet` in your view controller where you want to invoke the credit card scanning flow. + +1. Set up camera permissions + * The SDK uses the camera, so you'll need to add an description of camera usage to your Info.plist file: +![info.plist camera permissions](https://gblobscdn.gitbook.com/assets%2F-MAfqrnL3d-uke0sAFsI%2Fsync%2F573e3f05043e4d903189b5fb107d4b3565bdb11b.png?alt=media) +![camera permissions prompt](https://gblobscdn.gitbook.com/assets%2F-MAfqrnL3d-uke0sAFsI%2Fsync%2F0d7119d3cbe2f519e5e5c04b56fe43539e4435e1.png?alt=media) + + * Alternatively, you can add this permission directly to your Info.plist file: + ``` + NSCameraUsageDescriptionkey> + We need access to your camera to scan your cardstring> + ``` +2. Add `CardScanSheet` in your app where you want to invoke the scan flow + * Initialize `CardScanSheet` + * When it’s time to invoke the scan flow, display the sheet with `CardScanSheet.present()` + * When the verification flow is finished, the sheet will be dismissed and the completion block will be called with a [Result](https://stripe.dev/stripe-ios/) + +### Example Implementation +```swift + +import UIKit +import StripeCardScan + +class ViewController: UIViewController { + + @IBAction func cardScanSheetButtonPressed() { + let cardScanSheet = CardScanSheet() + + cardScanSheet.present(from: self) { [weak self] result in + switch result { + case .completed(let scannedCard): + /* + * The user scanned a card. The result of the scan are detailed + * in the `scannedCard` field of the result. + */ + print("scan success") + case .canceled: + /* + * The scan was canceled by the user. + */ + print("scan canceled") + case .failed(let error): + /* + * The scan failed. The displayable error is + * included in the `localizedDescription`. + */ + print("scan failed: \(error.localizedDescription)") + } + } + } +} +``` + +## Credit Card Verification +🚧 diff --git a/StripeCardScan/StripeCardScan.xcodeproj/project.pbxproj b/StripeCardScan/StripeCardScan.xcodeproj/project.pbxproj new file mode 100644 index 00000000..91e6292b --- /dev/null +++ b/StripeCardScan/StripeCardScan.xcodeproj/project.pbxproj @@ -0,0 +1,1263 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 040020B8558B0489DF99F8B5 /* DetectedAllBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7EE5FDCA3B075FE5BBAF42A /* DetectedAllBoxes.swift */; }; + 050B462804602A9F4DB58A9D /* Expiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F3DDAD14A9F2DEB98E236E /* Expiry.swift */; }; + 05188062E522359CE24912CF /* ScannedCardImageData+Verification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C3EE6315FB1507E40F10D85 /* ScannedCardImageData+Verification.swift */; }; + 06711423AB563634EADFCCC0 /* VerifyCardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F912323832888F88EA218BA9 /* VerifyCardViewController.swift */; }; + 0838FEA7071EDCE1B633F093 /* PredictionAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B452EE8B96AF1B0691E7D43 /* PredictionAPI.swift */; }; + 0A4DCE2C98659B92DDB29B9A /* ScanAnalyticsManager+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6563E5CE5CD0439D5DBF35D7 /* ScanAnalyticsManager+Tasks.swift */; }; + 0ADD10809FEABDCB59A8FA72 /* CardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236AE86CE7DCB0DFA3B7C978 /* CardType.swift */; }; + 0D062E99363099A5728F3DCD /* ScanAnalyticsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F6ECCDC721E4D46DAC4F48 /* ScanAnalyticsManagerTests.swift */; }; + 0D62200AA65D71F040037CC8 /* CardImageVerification_CardAdd_200.json in Resources */ = {isa = PBXBuildFile; fileRef = CF90067085243D998A1D6030 /* CardImageVerification_CardAdd_200.json */; }; + 0D8CA0E3EFF9694CAE4FB8F7 /* ScanConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9618F7B15B33EC5B339A94 /* ScanConfiguration.swift */; }; + 0EAA2314FEA8D05D24C498BD /* Torch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C827434739027B2DD43449EA /* Torch.swift */; }; + 10E840B00703A92CC487B324 /* CGRectExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7633166B4D9096FE995F08F0 /* CGRectExtension.swift */; }; + 12C11D2633C7819DF275CCC2 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = EA26D443783FE045B0D7888D /* OHHTTPStubsSwift */; }; + 164BF41B5C522B7338376BB2 /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938958FCB9B918AD6CDF0F4E /* BlurView.swift */; }; + 17E34C9EF882AEAE47BECAB4 /* ErrorCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BEC906101069A1E3C50FA51 /* ErrorCorrection.swift */; }; + 189DEBAF38F2FB88CCA39EA2 /* UxModel+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3FCD1B2283A2B02EBAEF4F /* UxModel+Utils.swift */; }; + 18B63245A933E292345C9410 /* UxAndOcrMainLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87448BAE438EF08C55AA4B29 /* UxAndOcrMainLoop.swift */; }; + 19EF4634631CFC121DE21D1B /* CardImageVerificationDetailsResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDAF8E33A3D7CD020AC904A /* CardImageVerificationDetailsResponseTest.swift */; }; + 19F7EC09C9B0ED11A4601E08 /* ScanStatsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61E754E520B4E21920D1DA /* ScanStatsPayload.swift */; }; + 1AD72764EA8BCB66A9D7B637 /* CreditCardOcrPrediction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEE8C18A3CECFF0751AB0D5 /* CreditCardOcrPrediction.swift */; }; + 1B10222A5121C9B2C3479FAB /* StripeCardScan.h in Headers */ = {isa = PBXBuildFile; fileRef = AB56DDBBF0E5BC10682C7D96 /* StripeCardScan.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1B71815D5B3EC8AC21E2A202 /* UIImage+pixelBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2D9886B0ED119E8D1FB6448 /* UIImage+pixelBuffer.swift */; }; + 1DE075A700592250D87DF558 /* ScannedCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76C70F9A1879ED8B41639945 /* ScannedCard.swift */; }; + 20DCEB955D9E0660EAB3ABEB /* InterfaceOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77D0D8C2909455B76E246468 /* InterfaceOrientation.swift */; }; + 20DF2E9D5A543360DF3A4DDA /* DetectedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53D407C0AC8D44ECC0C3C52 /* DetectedBox.swift */; }; + 213A91107E76434972A1F848 /* SSDOcr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F3E329C37141862BD4A4DE7 /* SSDOcr.swift */; }; + 25FAF64D4FE93BF38E807ECA /* VerifyFramesAPIBindingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4301B281F5E0A5BACBBD68CC /* VerifyFramesAPIBindingsTests.swift */; }; + 2A9ECCF086685AD607D82492 /* AppleOcr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E24CF248F312DDE025D662A /* AppleOcr.swift */; }; + 2D042FD2E8A0C8236596CE54 /* CardScanFraudData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D41E45A285FEF48E9528997 /* CardScanFraudData.swift */; }; + 2F10596629118185A3A8F011 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = C96A49C871FDB73B36A91670 /* OHHTTPStubs */; }; + 2F3FA5E8CCBF7F77106A8268 /* EndToEndTestDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3ECBF071E7507C9D41F232 /* EndToEndTestDataSource.swift */; }; + 30E3E90F9C8E3D3E8FCA869E /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC12B6E0657203AAB765468C /* PreviewView.swift */; }; + 313F5F7D2B0BE5BE00BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F7C2B0BE5BE00BD98A9 /* Docs.docc */; }; + 35F2BB10395405DB6C1E31B5 /* SSDOcrOutputExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89DA7FF10BC3A7B2D102FB /* SSDOcrOutputExtensions.swift */; }; + 36525A0F774E7FA055D8B525 /* CardBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEDE71D7051DDC98698C4057 /* CardBase.swift */; }; + 3736756FBB875060C86E2777 /* CardScanSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BC8B1C28C3F6C8330164E0 /* CardScanSheet.swift */; }; + 3F5B9466E9FC13CA29218750 /* ImageHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091163ED5E37922332F502B1 /* ImageHelpers.swift */; }; + 3FCC183583090FD0246D9336 /* ScanBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A790FC6FAEC3F88FB2C26E27 /* ScanBaseViewController.swift */; }; + 41366AB64512BB7E4248A904 /* OcrDDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC0B91384C63CBFB790C8EE6 /* OcrDDUtils.swift */; }; + 41F2E4475B9B19350C31F255 /* PaymentCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67C8AB382B81DE27C8614F6D /* PaymentCard.swift */; }; + 420FF119A7147335D802AB14 /* CardImageVerificationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F09D21BAC44C461B1AD7CB /* CardImageVerificationSheet.swift */; }; + 43AA83BFF8DEFC09D406669F /* CardVerifyStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B3242DC2FCDF20D51D23AE1 /* CardVerifyStateMachine.swift */; }; + 449DD2A5D1F3FF94524D3CD6 /* STPAPIClient+CardImageVerificationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC4F647EB512813078B9A6F /* STPAPIClient+CardImageVerificationTest.swift */; }; + 45C8D17FED02529B7FC4E8C3 /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6418F87D5168A2A08D65A482 /* STPLocalizedString.swift */; }; + 47DCE237223D40B1EB183704 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA2733CEC85F8E427CA99CB /* AppState.swift */; }; + 48580A7E92CC8EBFD2FD4C91 /* DetectedAllOcrBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1441A7A39C36C2A634A882F5 /* DetectedAllOcrBoxes.swift */; }; + 48E4577B073DD9485BC62902 /* ImageCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA128D69F3C32A4EF67CF0 /* ImageCompressionTests.swift */; }; + 499BDA59FD973003028A867B /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; productRef = F3D550995F9421BED1BE23A2 /* iOSSnapshotTestCase */; }; + 49E1959C680AD68D1D345D6B /* DeviceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDB03A9253E8FAD07B739438 /* DeviceUtils.swift */; }; + 4ACBD9754F304CE4F70CAE43 /* MainLoopStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D550382D6320388A5BA75A1 /* MainLoopStateMachine.swift */; }; + 4B01B9F4FB647908AA5276E2 /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17FA90EE37CF1E6A49532491 /* StripeCoreTestUtils.framework */; }; + 4D1016654AFB3D9EE26092B7 /* PredictionUtilOcr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 795E739553651C8AC40E6AC1 /* PredictionUtilOcr.swift */; }; + 52519CA3928967768049164E /* CardImageVerification_CardSet_200.json in Resources */ = {isa = PBXBuildFile; fileRef = BDD0C14472FA163BD395DBD3 /* CardImageVerification_CardSet_200.json */; }; + 5382B471868AA7D95BBD2B3A /* SSDOcrDetect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEB912151CB564AE21D073C /* SSDOcrDetect.swift */; }; + 54469A1A5D77BAD54061BEA1 /* ScanStatsPayload+Tasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1CD398A6AFB6747D095C59 /* ScanStatsPayload+Tasks.swift */; }; + 5488DA1817A0C9F780EF69B2 /* CardVerifyFraudData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63FA7635BAFDC7DD54B1A731 /* CardVerifyFraudData.swift */; }; + 59889C85D6D25B3222776B28 /* synthetic_test_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4D9820BC2FFB68C808FB9507 /* synthetic_test_image.jpg */; }; + 5B1749A19231B20E2A081EB9 /* OcrObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC83A169A82484B9F60E452 /* OcrObject.swift */; }; + 60AF52B9EFFF12EBABE4D221 /* CreditCardOcrImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B28BBCB062B6AE5A3B7CBC /* CreditCardOcrImplementation.swift */; }; + 65C1EB5447CD5DF162CDEC2B /* Bouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA16EAC68E04D4FDE67DB807 /* Bouncer.swift */; }; + 6E9908C612ECD2E0AEA3DFF8 /* AtomicPropertyWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9B548B9626366BB0F110873 /* AtomicPropertyWrapper.swift */; }; + 70B5FF75EF5DC8FFB23B95F7 /* UxModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C038EFADF52B2477FC934EE0 /* UxModelTests.swift */; }; + 74330F51A91DC3A617F7AE42 /* SimpleScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E1DA01CB00CBDE36735EDD /* SimpleScanViewController.swift */; }; + 77443628E02F3AEFF112314D /* CardScanMockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 746AAB9327BDE6791F9393B0 /* CardScanMockData.swift */; }; + 77BB4BE6E5E03626936453C6 /* STPAPIClient+CardImageVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E54DFA3C47D94639297588 /* STPAPIClient+CardImageVerification.swift */; }; + 79A96C88970E4E61BAC47412 /* ScanStatsPayloadAPIBindingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9ADC4CC0B4ADFE0A5427E /* ScanStatsPayloadAPIBindingsTests.swift */; }; + 7B0CF214778E17FEC721602E /* PredictionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBE2CADB8D8E98D6EEE10C0 /* PredictionResult.swift */; }; + 7C71166DACDB829F8CBC383D /* NMS.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD9FF5C9DD64BE92DB0927D /* NMS.swift */; }; + 7CED1B42C94A49D6BF98C9E4 /* VideoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = D42510E09D32547455CDDBEF /* VideoFeed.swift */; }; + 7D9C9C2A26EF11F0C5D67D7C /* CGrect+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD87F9971C25C4B99138318F /* CGrect+utils.swift */; }; + 8463F2DB039C481D26A24BAA /* PostDetectionAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9F1B20AEB433DE3CE0042C /* PostDetectionAlgorithm.swift */; }; + 84975E58102A5D8C62C6C4E5 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A2DB0837C11D8829C027CF /* String+Localized.swift */; }; + 858CDA751309CA04202B6203 /* MachineLearningResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF17FD18E52CB3508AD895E3 /* MachineLearningResult.swift */; }; + 86635536450EE7D583B8BD59 /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A1E35C6DF336F34B6C7AEA /* StripeCore+Import.swift */; }; + 8CF112F4889C96617504A931 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6C27E58C1953533C68D43361 /* Localizable.strings */; }; + 8E4B117272F5B17A1E6AD609 /* UxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF173627F545C88CB1551B0 /* UxModel.swift */; }; + 8FC373E15C1169677F563901 /* OcrPriorsGen.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED780E8B4C14EE39F853B3A1 /* OcrPriorsGen.swift */; }; + 9188179F13E5EA15E0419580 /* ScanStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A49708CF5D37C7CB267732 /* ScanStats.swift */; }; + 93D4785D8B91F12B6DDDE203 /* UxAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F85031ADAE5928D977465C2 /* UxAnalyzer.swift */; }; + 9FA5A312032FC54B35BB604E /* SoftNMS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A93BFDEE4072A2A9C30397 /* SoftNMS.swift */; }; + A060350B93E3369463AE2898 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E889051D7CD76D4FB9084A1 /* StripeCore.framework */; }; + A7014CF97250D97BC683FAC8 /* CreditCardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8735A24B634F3B9060CADE45 /* CreditCardUtils.swift */; }; + A7F92FB7213E66A7D15A1BE3 /* SSDOcr.mlmodelc in Resources */ = {isa = PBXBuildFile; fileRef = E73DC7DD9C3E3395DF3F5A29 /* SSDOcr.mlmodelc */; }; + A8D0A57687A7CF9F389D7BDB /* ScanAnalyticsManager+Managers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A337FE5C3BE4FAB001E5520 /* ScanAnalyticsManager+Managers.swift */; }; + AB21982DC43977754F237756 /* VerificationFramesData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24C2345660A95FBF90BF4850 /* VerificationFramesData.swift */; }; + ACC3C1A295E43983F67F858E /* FrameData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C926149A434F4F3BC961D9 /* FrameData.swift */; }; + AE144927F21D5DF9A8C0EF79 /* ScannedCardDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15541EEEE784EECDD3E7399D /* ScannedCardDetails.swift */; }; + AFA334F5007A4C141A96FC2E /* VerifyFrames.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FAA99A64BCD00A336A5E01 /* VerifyFrames.swift */; }; + AFB2482D559BCC08E96C3615 /* CardImageVerificationIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5004FC0706B2660B4C6EA0 /* CardImageVerificationIntent.swift */; }; + B00954CF5F2A15B72CB94FA5 /* VerifyCardAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B38020E8E33CA893CBA6F3 /* VerifyCardAddViewController.swift */; }; + B2DF7862E5F80ACD8DEE9317 /* UxModel.mlmodelc in Resources */ = {isa = PBXBuildFile; fileRef = F4721524B6285C507681CC4D /* UxModel.mlmodelc */; }; + B47F583CE0F239881877E5D3 /* ZoomedInCGImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10A26712A38A91726C314329 /* ZoomedInCGImage.swift */; }; + B66697826FEA9FCE81DDD6F4 /* ActiveStateComputation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBC1F86A03CF9AAC6FE5825 /* ActiveStateComputation.swift */; }; + BDEA39DE5D3775AD2A4BFB6C /* CardImageVerificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24DEA029F94A359B7A571BB /* CardImageVerificationControllerTests.swift */; }; + BEE2BFA103DE985D057A0F9D /* ScanStatsPayload+Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028DA95BD9A0DA8AD69162CB /* ScanStatsPayload+Common.swift */; }; + BFFA3E8CE79BFFE47530FAF6 /* DetectedSSDBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2EF6799FCE0817D9E3A536B /* DetectedSSDBox.swift */; }; + C2C41F5E568BF767C8232E74 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14E5A51A77928A2700E23321 /* XCTest.framework */; }; + C3B9A4A30443A38C2D17BE8E /* CardNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311A5A7EF789248E35BB1FEC /* CardNetwork.swift */; }; + C57BCF07EDF419D36ED4E690 /* ScanEventsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1FBA1B4676CC5E9F785D51 /* ScanEventsProtocol.swift */; }; + C5DBC36A41FB70C5DCCC97C7 /* SimpleScanViewController+Verify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34F806DF921888A9657E2928 /* SimpleScanViewController+Verify.swift */; }; + C633115B02A0CE24AC55A747 /* OcrDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD9AF72B522A1E535A4EC81 /* OcrDD.swift */; }; + C7259DA76E6AB9BF53735D19 /* Array+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA457DF27DC45455B8E1345 /* Array+utils.swift */; }; + C7A941539DBCFA790DCDB0B0 /* NonNameWords.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96A85E323CFDA8BA35BC293 /* NonNameWords.swift */; }; + C8E2E98B7ED108804E0A0E24 /* SSDCreditCardOcr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B00395C6A2B14A6D56735B9 /* SSDCreditCardOcr.swift */; }; + CBA4FE649A3B21DAC3A61E5B /* CancellationReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C171A1336B7FB0C321D194 /* CancellationReason.swift */; }; + CC07F702B9EC043ACB0AC1E5 /* ScannedCardImageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAE45D8AC7BBA5DC433A9383 /* ScannedCardImageData.swift */; }; + CCB7722FDB8E6E68E90F3D12 /* CreditCardOcrPrediction+expiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E72DD18B9FCB80ABEDE2689 /* CreditCardOcrPrediction+expiry.swift */; }; + CED42084C5FC46C17E357AD5 /* ScanAnalyticsManager+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97CA4D8286B196A49B8D535 /* ScanAnalyticsManager+Helpers.swift */; }; + D2030CA1B3B7DAF3229189AD /* Image+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE9C295E982F92DE2418AE1 /* Image+utils.swift */; }; + D27E02429E5DC8B28DFE8945 /* CardImageVerificationDetailsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 256455DAEDCDDB1AEAF6A278 /* CardImageVerificationDetailsResponse.swift */; }; + D42EEFBB72CD0AB00C6B4728 /* String+Sha256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 732D3AB4CE25CC5C26A352B1 /* String+Sha256.swift */; }; + D6F0E1C93997BB36AF0608C0 /* StringResourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A72927DA8A946FDD03843DE /* StringResourceTests.swift */; }; + DB74787AF4EEF0E506DD0E1C /* Data+Sha256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63662689AD097C859B8759B1 /* Data+Sha256.swift */; }; + DBE3EE5DAEEEA44D6C7ECD4E /* SSDOcr+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505DF34D76A56FEC2B8453F9 /* SSDOcr+Utils.swift */; }; + DDBCE05A3794129A9A04D511 /* AppleCreditCardOcr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDF39435211B456B7579739 /* AppleCreditCardOcr.swift */; }; + DF352D12199B55B659A11795 /* DetectedSSDOcrBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D58BEEC9D88537E32260CBA /* DetectedSSDOcrBox.swift */; }; + E43FE03D43CB0D7BAA73745A /* StripeCardScan.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD6884D0B3347BBF2E61B1D6 /* StripeCardScan.framework */; }; + E4EC278027AF802C39C74C48 /* CardImageVerificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B313BA373EF6B567BF40A240 /* CardImageVerificationController.swift */; }; + E70A7E38A7D858031F900649 /* CardScanSheetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF4C0BF8E55D53B004FABCA /* CardScanSheetError.swift */; }; + E90CC9715EC99F6B7FAC98FA /* CardImageVerificationSheetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 545E03D5167516D28C8A9F08 /* CardImageVerificationSheetConfiguration.swift */; }; + EA0F179DF887D94E19138A5E /* AsyncModelLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF53C194DDC96160505D54D5 /* AsyncModelLoading.swift */; }; + EA2DBA78722CD65ED599190D /* CardScanMisc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3AD0FDF25BE452AE4ED34 /* CardScanMisc.swift */; }; + EB061FA550AFFE2AB2E348DF /* ScanAnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69E2630C0FC7C9DDD551FD67 /* ScanAnalyticsManager.swift */; }; + EE1CB7D3FA2B44DC17E6FF4C /* OcrMainLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E38717F47ED06C115E058DE /* OcrMainLoop.swift */; }; + EF96103F82491640651C49F6 /* AppInfoUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 362A27BB81840A22104C9A49 /* AppInfoUtils.swift */; }; + F0ED2BFE143DD07337D389E5 /* CornerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E012270975B6DC09A8199F /* CornerView.swift */; }; + F16FAC158C6B0A7687547B4B /* FadeInAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3CAFFFE55918BB4939868B6 /* FadeInAnimation.swift */; }; + F4E4941F5AA2EC2C2CC4F7FB /* StripeCardScanBundleLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06F97A4CE5104309B720D603 /* StripeCardScanBundleLocator.swift */; }; + F9F19A6F2245863FEA485335 /* CreditCardOcrResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92803C69CE8253D73A25F /* CreditCardOcrResult.swift */; }; + FE55286899CFB0E34356D4D6 /* StrictModeFramesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6D4DB87B26D439686392AE /* StrictModeFramesTest.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + F7C4E731844D6B46A4FBCACD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A6BF50E5B02355004AC6020 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 03CF79975A56288F02F20E52; + remoteInfo = StripeCardScan; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 20B092D23CA44561EF997447 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 451E97BA25D9CE29EDAB7618 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 00A93BFDEE4072A2A9C30397 /* SoftNMS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftNMS.swift; sourceTree = ""; }; + 0197A1615C3FA86907CB6186 /* StripeCardScan-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeCardScan-Debug.xcconfig"; sourceTree = ""; }; + 028DA95BD9A0DA8AD69162CB /* ScanStatsPayload+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScanStatsPayload+Common.swift"; sourceTree = ""; }; + 03E2C12C0D172DD7DAE33399 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 048ED08A6959593F896B3AC8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 05B28BBCB062B6AE5A3B7CBC /* CreditCardOcrImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardOcrImplementation.swift; sourceTree = ""; }; + 06F97A4CE5104309B720D603 /* StripeCardScanBundleLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeCardScanBundleLocator.swift; sourceTree = ""; }; + 091163ED5E37922332F502B1 /* ImageHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHelpers.swift; sourceTree = ""; }; + 0B00395C6A2B14A6D56735B9 /* SSDCreditCardOcr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDCreditCardOcr.swift; sourceTree = ""; }; + 0BB35800FB840EFC76240DC7 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 0C7BAE904C7E1D7A9FDF35F1 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + 0D1FBA1B4676CC5E9F785D51 /* ScanEventsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanEventsProtocol.swift; sourceTree = ""; }; + 0DC83A169A82484B9F60E452 /* OcrObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OcrObject.swift; sourceTree = ""; }; + 10311DC6AC19B7A73A8F930F /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 10A26712A38A91726C314329 /* ZoomedInCGImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomedInCGImage.swift; sourceTree = ""; }; + 11D3465E527ABADBD2485A93 /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + 12F09D21BAC44C461B1AD7CB /* CardImageVerificationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationSheet.swift; sourceTree = ""; }; + 1441A7A39C36C2A634A882F5 /* DetectedAllOcrBoxes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedAllOcrBoxes.swift; sourceTree = ""; }; + 14795A45279E8F328AB54920 /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 14BC8B1C28C3F6C8330164E0 /* CardScanSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardScanSheet.swift; sourceTree = ""; }; + 14E5A51A77928A2700E23321 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 15541EEEE784EECDD3E7399D /* ScannedCardDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedCardDetails.swift; sourceTree = ""; }; + 17BCBB8B34EBA904BB245CBD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 17FA90EE37CF1E6A49532491 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1A337FE5C3BE4FAB001E5520 /* ScanAnalyticsManager+Managers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScanAnalyticsManager+Managers.swift"; sourceTree = ""; }; + 1C94EE6DB0E3114A38DD9CFE /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; + 1FD9AF72B522A1E535A4EC81 /* OcrDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OcrDD.swift; sourceTree = ""; }; + 236AE86CE7DCB0DFA3B7C978 /* CardType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardType.swift; sourceTree = ""; }; + 24C2345660A95FBF90BF4850 /* VerificationFramesData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFramesData.swift; sourceTree = ""; }; + 256455DAEDCDDB1AEAF6A278 /* CardImageVerificationDetailsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationDetailsResponse.swift; sourceTree = ""; }; + 2667E0E0C7DC6CDAF03F8B40 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + 27F4BF66273070C2054BBAC0 /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + 27F6ECCDC721E4D46DAC4F48 /* ScanAnalyticsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanAnalyticsManagerTests.swift; sourceTree = ""; }; + 2D61E754E520B4E21920D1DA /* ScanStatsPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanStatsPayload.swift; sourceTree = ""; }; + 2E889051D7CD76D4FB9084A1 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2F3E329C37141862BD4A4DE7 /* SSDOcr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDOcr.swift; sourceTree = ""; }; + 2F9447632965B9BCE1E595E4 /* StripeCardScanTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeCardScanTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 311A5A7EF789248E35BB1FEC /* CardNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardNetwork.swift; sourceTree = ""; }; + 313F5F7C2B0BE5BE00BD98A9 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; + 34F806DF921888A9657E2928 /* SimpleScanViewController+Verify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SimpleScanViewController+Verify.swift"; sourceTree = ""; }; + 362A27BB81840A22104C9A49 /* AppInfoUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoUtils.swift; sourceTree = ""; }; + 3938F9E3F0F267045D3FDDEB /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + 3A9C2B8A3B96E194CED6841C /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 3BC4F647EB512813078B9A6F /* STPAPIClient+CardImageVerificationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+CardImageVerificationTest.swift"; sourceTree = ""; }; + 3DEE8C18A3CECFF0751AB0D5 /* CreditCardOcrPrediction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardOcrPrediction.swift; sourceTree = ""; }; + 3E24CF248F312DDE025D662A /* AppleOcr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleOcr.swift; sourceTree = ""; }; + 42454BA950282D9ADB9C6557 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 4301B281F5E0A5BACBBD68CC /* VerifyFramesAPIBindingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyFramesAPIBindingsTests.swift; sourceTree = ""; }; + 45C9A1A26405DEAD8E11F13D /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 4A1CD398A6AFB6747D095C59 /* ScanStatsPayload+Tasks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScanStatsPayload+Tasks.swift"; sourceTree = ""; }; + 4B452EE8B96AF1B0691E7D43 /* PredictionAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredictionAPI.swift; sourceTree = ""; }; + 4B89D81A4099CC71A3FBC133 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 4BBE2CADB8D8E98D6EEE10C0 /* PredictionResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredictionResult.swift; sourceTree = ""; }; + 4CA3AD0FDF25BE452AE4ED34 /* CardScanMisc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardScanMisc.swift; sourceTree = ""; }; + 4D9820BC2FFB68C808FB9507 /* synthetic_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = synthetic_test_image.jpg; sourceTree = ""; }; + 4DA27FFBD426C871C5BD254E /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + 4F85031ADAE5928D977465C2 /* UxAnalyzer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UxAnalyzer.swift; sourceTree = ""; }; + 505DF34D76A56FEC2B8453F9 /* SSDOcr+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SSDOcr+Utils.swift"; sourceTree = ""; }; + 53C171A1336B7FB0C321D194 /* CancellationReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellationReason.swift; sourceTree = ""; }; + 545E03D5167516D28C8A9F08 /* CardImageVerificationSheetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationSheetConfiguration.swift; sourceTree = ""; }; + 58A5F2CEEAAB78AA425A5737 /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + 5969433D88B5BD3B7F56B2BD /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + 5A6D5C8E51C23370E889F75F /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + 5A72927DA8A946FDD03843DE /* StringResourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringResourceTests.swift; sourceTree = ""; }; + 5CF9ADC4CC0B4ADFE0A5427E /* ScanStatsPayloadAPIBindingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanStatsPayloadAPIBindingsTests.swift; sourceTree = ""; }; + 5D58BEEC9D88537E32260CBA /* DetectedSSDOcrBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedSSDOcrBox.swift; sourceTree = ""; }; + 5E72DD18B9FCB80ABEDE2689 /* CreditCardOcrPrediction+expiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreditCardOcrPrediction+expiry.swift"; sourceTree = ""; }; + 6186A0AAB96E236DEF0B5B79 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + 63662689AD097C859B8759B1 /* Data+Sha256.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Sha256.swift"; sourceTree = ""; }; + 63FA7635BAFDC7DD54B1A731 /* CardVerifyFraudData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardVerifyFraudData.swift; sourceTree = ""; }; + 6418F87D5168A2A08D65A482 /* STPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = ""; }; + 64A1E35C6DF336F34B6C7AEA /* StripeCore+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeCore+Import.swift"; sourceTree = ""; }; + 6563E5CE5CD0439D5DBF35D7 /* ScanAnalyticsManager+Tasks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScanAnalyticsManager+Tasks.swift"; sourceTree = ""; }; + 66A49708CF5D37C7CB267732 /* ScanStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanStats.swift; sourceTree = ""; }; + 67C8AB382B81DE27C8614F6D /* PaymentCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentCard.swift; sourceTree = ""; }; + 69A2DB0837C11D8829C027CF /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + 69B38020E8E33CA893CBA6F3 /* VerifyCardAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyCardAddViewController.swift; sourceTree = ""; }; + 69E2630C0FC7C9DDD551FD67 /* ScanAnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanAnalyticsManager.swift; sourceTree = ""; }; + 6BEC906101069A1E3C50FA51 /* ErrorCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorCorrection.swift; sourceTree = ""; }; + 6C3EE6315FB1507E40F10D85 /* ScannedCardImageData+Verification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScannedCardImageData+Verification.swift"; sourceTree = ""; }; + 6EBC1F86A03CF9AAC6FE5825 /* ActiveStateComputation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveStateComputation.swift; sourceTree = ""; }; + 6F41FBE3D90B5F6518E42051 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + 732D3AB4CE25CC5C26A352B1 /* String+Sha256.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Sha256.swift"; sourceTree = ""; }; + 746AAB9327BDE6791F9393B0 /* CardScanMockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardScanMockData.swift; sourceTree = ""; }; + 7633166B4D9096FE995F08F0 /* CGRectExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtension.swift; sourceTree = ""; }; + 76C70F9A1879ED8B41639945 /* ScannedCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedCard.swift; sourceTree = ""; }; + 77D0D8C2909455B76E246468 /* InterfaceOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceOrientation.swift; sourceTree = ""; }; + 795E739553651C8AC40E6AC1 /* PredictionUtilOcr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredictionUtilOcr.swift; sourceTree = ""; }; + 79A802EA46840CD45CBECEEA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7D41E45A285FEF48E9528997 /* CardScanFraudData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardScanFraudData.swift; sourceTree = ""; }; + 7D550382D6320388A5BA75A1 /* MainLoopStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoopStateMachine.swift; sourceTree = ""; }; + 7DBB233BD36CBAE2700A4511 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + 7DDAF8E33A3D7CD020AC904A /* CardImageVerificationDetailsResponseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationDetailsResponseTest.swift; sourceTree = ""; }; + 7E38717F47ED06C115E058DE /* OcrMainLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OcrMainLoop.swift; sourceTree = ""; }; + 8094FE7CCD3CDA8B914EC534 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 84E54DFA3C47D94639297588 /* STPAPIClient+CardImageVerification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+CardImageVerification.swift"; sourceTree = ""; }; + 8735A24B634F3B9060CADE45 /* CreditCardUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardUtils.swift; sourceTree = ""; }; + 87448BAE438EF08C55AA4B29 /* UxAndOcrMainLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UxAndOcrMainLoop.swift; sourceTree = ""; }; + 88F89EC392AFBE060336B63F /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + 89BBC06EC107C8BDEF79BB3D /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 8AEB912151CB564AE21D073C /* SSDOcrDetect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDOcrDetect.swift; sourceTree = ""; }; + 8C227CA761586744976C952D /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + 8CA457DF27DC45455B8E1345 /* Array+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+utils.swift"; sourceTree = ""; }; + 8EDF39435211B456B7579739 /* AppleCreditCardOcr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleCreditCardOcr.swift; sourceTree = ""; }; + 8FB602C9E909E31BAE6703D6 /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 938958FCB9B918AD6CDF0F4E /* BlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = ""; }; + 9593D6788C4DBF554735E2B6 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + 96F3DDAD14A9F2DEB98E236E /* Expiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expiry.swift; sourceTree = ""; }; + 9A256BCB7822DD94572DDA07 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 9B3242DC2FCDF20D51D23AE1 /* CardVerifyStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardVerifyStateMachine.swift; sourceTree = ""; }; + 9C3890F6A5A4FF68F95746DC /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + 9E4CF9A676A86169070DD54D /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + A2EF6799FCE0817D9E3A536B /* DetectedSSDBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedSSDBox.swift; sourceTree = ""; }; + A3C926149A434F4F3BC961D9 /* FrameData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameData.swift; sourceTree = ""; }; + A612DDE1110EA970BF69B33D /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + A67EDE2BCCC081DEDD684AAC /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + A6E1DA01CB00CBDE36735EDD /* SimpleScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleScanViewController.swift; sourceTree = ""; }; + A790FC6FAEC3F88FB2C26E27 /* ScanBaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanBaseViewController.swift; sourceTree = ""; }; + A7EE5FDCA3B075FE5BBAF42A /* DetectedAllBoxes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedAllBoxes.swift; sourceTree = ""; }; + AB56DDBBF0E5BC10682C7D96 /* StripeCardScan.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeCardScan.h; sourceTree = ""; }; + AB64890F80D91D9D7B92197D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + ABB38869FD175D7BD6BB3890 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + AC9F1B20AEB433DE3CE0042C /* PostDetectionAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostDetectionAlgorithm.swift; sourceTree = ""; }; + ACD9FF5C9DD64BE92DB0927D /* NMS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NMS.swift; sourceTree = ""; }; + ADCB34B81919043FA35E8BC3 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + AEE9C295E982F92DE2418AE1 /* Image+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Image+utils.swift"; sourceTree = ""; }; + AF17FD18E52CB3508AD895E3 /* MachineLearningResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MachineLearningResult.swift; sourceTree = ""; }; + B0A7946C3A1E943FB72F3FB7 /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + B2D9886B0ED119E8D1FB6448 /* UIImage+pixelBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+pixelBuffer.swift"; sourceTree = ""; }; + B313BA373EF6B567BF40A240 /* CardImageVerificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationController.swift; sourceTree = ""; }; + B3CAFFFE55918BB4939868B6 /* FadeInAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeInAnimation.swift; sourceTree = ""; }; + B53D407C0AC8D44ECC0C3C52 /* DetectedBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectedBox.swift; sourceTree = ""; }; + B97CA4D8286B196A49B8D535 /* ScanAnalyticsManager+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ScanAnalyticsManager+Helpers.swift"; sourceTree = ""; }; + BAE45D8AC7BBA5DC433A9383 /* ScannedCardImageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedCardImageData.swift; sourceTree = ""; }; + BC0B91384C63CBFB790C8EE6 /* OcrDDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OcrDDUtils.swift; sourceTree = ""; }; + BD87F9971C25C4B99138318F /* CGrect+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGrect+utils.swift"; sourceTree = ""; }; + BDD0C14472FA163BD395DBD3 /* CardImageVerification_CardSet_200.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CardImageVerification_CardSet_200.json; sourceTree = ""; }; + BEDE71D7051DDC98698C4057 /* CardBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardBase.swift; sourceTree = ""; }; + BEEE30227F7D34546F0FBB58 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + C038EFADF52B2477FC934EE0 /* UxModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UxModelTests.swift; sourceTree = ""; }; + C264A676DA41344551BA6661 /* StripeCardScan-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeCardScan-Release.xcconfig"; sourceTree = ""; }; + C3E012270975B6DC09A8199F /* CornerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerView.swift; sourceTree = ""; }; + C827434739027B2DD43449EA /* Torch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Torch.swift; sourceTree = ""; }; + CA16EAC68E04D4FDE67DB807 /* Bouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bouncer.swift; sourceTree = ""; }; + CB48DDCA458A62DCE342F517 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + CD5004FC0706B2660B4C6EA0 /* CardImageVerificationIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationIntent.swift; sourceTree = ""; }; + CD6D4DB87B26D439686392AE /* StrictModeFramesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrictModeFramesTest.swift; sourceTree = ""; }; + CDB03A9253E8FAD07B739438 /* DeviceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUtils.swift; sourceTree = ""; }; + CF3FCD1B2283A2B02EBAEF4F /* UxModel+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UxModel+Utils.swift"; sourceTree = ""; }; + CF90067085243D998A1D6030 /* CardImageVerification_CardAdd_200.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CardImageVerification_CardAdd_200.json; sourceTree = ""; }; + D0AA128D69F3C32A4EF67CF0 /* ImageCompressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompressionTests.swift; sourceTree = ""; }; + D42510E09D32547455CDDBEF /* VideoFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoFeed.swift; sourceTree = ""; }; + D532C440ADCD017AD326299B /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + D9B548B9626366BB0F110873 /* AtomicPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicPropertyWrapper.swift; sourceTree = ""; }; + DA3ECBF071E7507C9D41F232 /* EndToEndTestDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndToEndTestDataSource.swift; sourceTree = ""; }; + DC12B6E0657203AAB765468C /* PreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = ""; }; + DCA2733CEC85F8E427CA99CB /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + DCF4C0BF8E55D53B004FABCA /* CardScanSheetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardScanSheetError.swift; sourceTree = ""; }; + DD6884D0B3347BBF2E61B1D6 /* StripeCardScan.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCardScan.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DDF173627F545C88CB1551B0 /* UxModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UxModel.swift; sourceTree = ""; }; + E4FAA99A64BCD00A336A5E01 /* VerifyFrames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyFrames.swift; sourceTree = ""; }; + E73DC7DD9C3E3395DF3F5A29 /* SSDOcr.mlmodelc */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SSDOcr.mlmodelc; sourceTree = ""; }; + EA89DA7FF10BC3A7B2D102FB /* SSDOcrOutputExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSDOcrOutputExtensions.swift; sourceTree = ""; }; + ED780E8B4C14EE39F853B3A1 /* OcrPriorsGen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OcrPriorsGen.swift; sourceTree = ""; }; + EDE92803C69CE8253D73A25F /* CreditCardOcrResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardOcrResult.swift; sourceTree = ""; }; + EF53C194DDC96160505D54D5 /* AsyncModelLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncModelLoading.swift; sourceTree = ""; }; + F1FCC855CCB0816414A4BEC1 /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + F24DEA029F94A359B7A571BB /* CardImageVerificationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardImageVerificationControllerTests.swift; sourceTree = ""; }; + F3AF32D7DF7EE1420D16291D /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + F4721524B6285C507681CC4D /* UxModel.mlmodelc */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = UxModel.mlmodelc; sourceTree = ""; }; + F87D884128F85E7F16BCBA9B /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + F912323832888F88EA218BA9 /* VerifyCardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyCardViewController.swift; sourceTree = ""; }; + F96A85E323CFDA8BA35BC293 /* NonNameWords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonNameWords.swift; sourceTree = ""; }; + FD40E90452A83FFBBF65C8D8 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + FF9618F7B15B33EC5B339A94 /* ScanConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanConfiguration.swift; sourceTree = ""; }; + FFE89E65E767D03B92383C76 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1639B69C00B8C21CEBF08A55 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C2C41F5E568BF767C8232E74 /* XCTest.framework in Frameworks */, + E43FE03D43CB0D7BAA73745A /* StripeCardScan.framework in Frameworks */, + 4B01B9F4FB647908AA5276E2 /* StripeCoreTestUtils.framework in Frameworks */, + 499BDA59FD973003028A867B /* iOSSnapshotTestCase in Frameworks */, + 2F10596629118185A3A8F011 /* OHHTTPStubs in Frameworks */, + 12C11D2633C7819DF275CCC2 /* OHHTTPStubsSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 29BDDDCECDEC71451BAAB324 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A060350B93E3369463AE2898 /* StripeCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 01BEE24ED60FE104004E8F2C /* CompiledModels */ = { + isa = PBXGroup; + children = ( + E73DC7DD9C3E3395DF3F5A29 /* SSDOcr.mlmodelc */, + F4721524B6285C507681CC4D /* UxModel.mlmodelc */, + ); + path = CompiledModels; + sourceTree = ""; + }; + 0DFCF5271EC90615420288B3 /* Api */ = { + isa = PBXGroup; + children = ( + C8B14FA3DC29B84F382E6D1F /* Models */, + 256455DAEDCDDB1AEAF6A278 /* CardImageVerificationDetailsResponse.swift */, + 84E54DFA3C47D94639297588 /* STPAPIClient+CardImageVerification.swift */, + ); + path = Api; + sourceTree = ""; + }; + 0E245ED03746B2F8EE73B5F9 /* StripeCardScanTests */ = { + isa = PBXGroup; + children = ( + C8CC6DB5A60043E8A373BC6F /* Helpers */, + 40C830CD6973E7AD722C2213 /* Mock Data */, + 3172AE79E1A187D904BD1022 /* Resources */, + A35971ECF566C2769A608A06 /* Unit */, + 048ED08A6959593F896B3AC8 /* Info.plist */, + ); + path = StripeCardScanTests; + sourceTree = ""; + }; + 10507D99D0E81A181A25B4BC /* MLModels */ = { + isa = PBXGroup; + children = ( + EF53C194DDC96160505D54D5 /* AsyncModelLoading.swift */, + 2F3E329C37141862BD4A4DE7 /* SSDOcr.swift */, + 505DF34D76A56FEC2B8453F9 /* SSDOcr+Utils.swift */, + DDF173627F545C88CB1551B0 /* UxModel.swift */, + CF3FCD1B2283A2B02EBAEF4F /* UxModel+Utils.swift */, + ); + path = MLModels; + sourceTree = ""; + }; + 105B9420C58B9406F7939324 = { + isa = PBXGroup; + children = ( + 36C8C7335EC9F37DE4446270 /* Project */, + D275033F1B615793C2F8B8E0 /* Frameworks */, + 72B7637B9160BBA39556BA0A /* Products */, + ); + sourceTree = ""; + }; + 20CA55CAA02DC2833AD1C426 /* ML Models */ = { + isa = PBXGroup; + children = ( + C038EFADF52B2477FC934EE0 /* UxModelTests.swift */, + ); + path = "ML Models"; + sourceTree = ""; + }; + 25575C2DBB0C8AAA658EC26E /* JSON */ = { + isa = PBXGroup; + children = ( + CF90067085243D998A1D6030 /* CardImageVerification_CardAdd_200.json */, + BDD0C14472FA163BD395DBD3 /* CardImageVerification_CardSet_200.json */, + ); + path = JSON; + sourceTree = ""; + }; + 3172AE79E1A187D904BD1022 /* Resources */ = { + isa = PBXGroup; + children = ( + 4D9820BC2FFB68C808FB9507 /* synthetic_test_image.jpg */, + ); + path = Resources; + sourceTree = ""; + }; + 3228566B727B9D25D13A3BAB /* Resources */ = { + isa = PBXGroup; + children = ( + 01BEE24ED60FE104004E8F2C /* CompiledModels */, + 6A6547B8AC5A170A0092DCF5 /* Localizations */, + ); + path = Resources; + sourceTree = ""; + }; + 324D2CF5354B3283BC0C49ED /* CardVerify */ = { + isa = PBXGroup; + children = ( + 0DFCF5271EC90615420288B3 /* Api */, + BC0070EA31577F5573D7B787 /* Card Image Verification */, + 69D6875F84C12C2E18B96B01 /* Helpers */, + CA16EAC68E04D4FDE67DB807 /* Bouncer.swift */, + BEDE71D7051DDC98698C4057 /* CardBase.swift */, + 7D41E45A285FEF48E9528997 /* CardScanFraudData.swift */, + 4CA3AD0FDF25BE452AE4ED34 /* CardScanMisc.swift */, + 63FA7635BAFDC7DD54B1A731 /* CardVerifyFraudData.swift */, + 9B3242DC2FCDF20D51D23AE1 /* CardVerifyStateMachine.swift */, + B3CAFFFE55918BB4939868B6 /* FadeInAnimation.swift */, + A3C926149A434F4F3BC961D9 /* FrameData.swift */, + 67C8AB382B81DE27C8614F6D /* PaymentCard.swift */, + 34F806DF921888A9657E2928 /* SimpleScanViewController+Verify.swift */, + 06F97A4CE5104309B720D603 /* StripeCardScanBundleLocator.swift */, + 4F85031ADAE5928D977465C2 /* UxAnalyzer.swift */, + 87448BAE438EF08C55AA4B29 /* UxAndOcrMainLoop.swift */, + 69B38020E8E33CA893CBA6F3 /* VerifyCardAddViewController.swift */, + F912323832888F88EA218BA9 /* VerifyCardViewController.swift */, + 10A26712A38A91726C314329 /* ZoomedInCGImage.swift */, + ); + path = CardVerify; + sourceTree = ""; + }; + 36C8C7335EC9F37DE4446270 /* Project */ = { + isa = PBXGroup; + children = ( + 79C01535652A2F89EB22D9A9 /* BuildConfigurations */, + 6C3738C5D8A82E9A3C5AFC85 /* BuildConfigurations */, + DF93066F033C46A7B100FC82 /* StripeCardScan */, + 0E245ED03746B2F8EE73B5F9 /* StripeCardScanTests */, + ); + name = Project; + sourceTree = ""; + }; + 3C728AAC6B6FF615A064C757 /* CardUtils */ = { + isa = PBXGroup; + children = ( + 311A5A7EF789248E35BB1FEC /* CardNetwork.swift */, + 236AE86CE7DCB0DFA3B7C978 /* CardType.swift */, + 8735A24B634F3B9060CADE45 /* CreditCardUtils.swift */, + 96F3DDAD14A9F2DEB98E236E /* Expiry.swift */, + ); + path = CardUtils; + sourceTree = ""; + }; + 40C830CD6973E7AD722C2213 /* Mock Data */ = { + isa = PBXGroup; + children = ( + 25575C2DBB0C8AAA658EC26E /* JSON */, + ); + path = "Mock Data"; + sourceTree = ""; + }; + 620D5B89096A8B2B2202E6EB /* CreditCardOcr */ = { + isa = PBXGroup; + children = ( + 8EDF39435211B456B7579739 /* AppleCreditCardOcr.swift */, + 05B28BBCB062B6AE5A3B7CBC /* CreditCardOcrImplementation.swift */, + 3DEE8C18A3CECFF0751AB0D5 /* CreditCardOcrPrediction.swift */, + EDE92803C69CE8253D73A25F /* CreditCardOcrResult.swift */, + 6BEC906101069A1E3C50FA51 /* ErrorCorrection.swift */, + AF17FD18E52CB3508AD895E3 /* MachineLearningResult.swift */, + 7D550382D6320388A5BA75A1 /* MainLoopStateMachine.swift */, + F96A85E323CFDA8BA35BC293 /* NonNameWords.swift */, + 7E38717F47ED06C115E058DE /* OcrMainLoop.swift */, + 0DC83A169A82484B9F60E452 /* OcrObject.swift */, + 0B00395C6A2B14A6D56735B9 /* SSDCreditCardOcr.swift */, + ); + path = CreditCardOcr; + sourceTree = ""; + }; + 66F259BA8091A6C5D28C4963 /* MLRuntime */ = { + isa = PBXGroup; + children = ( + 6EBC1F86A03CF9AAC6FE5825 /* ActiveStateComputation.swift */, + DCA2733CEC85F8E427CA99CB /* AppState.swift */, + A7EE5FDCA3B075FE5BBAF42A /* DetectedAllBoxes.swift */, + 1441A7A39C36C2A634A882F5 /* DetectedAllOcrBoxes.swift */, + B53D407C0AC8D44ECC0C3C52 /* DetectedBox.swift */, + A2EF6799FCE0817D9E3A536B /* DetectedSSDBox.swift */, + 5D58BEEC9D88537E32260CBA /* DetectedSSDOcrBox.swift */, + ACD9FF5C9DD64BE92DB0927D /* NMS.swift */, + 1FD9AF72B522A1E535A4EC81 /* OcrDD.swift */, + BC0B91384C63CBFB790C8EE6 /* OcrDDUtils.swift */, + ED780E8B4C14EE39F853B3A1 /* OcrPriorsGen.swift */, + AC9F1B20AEB433DE3CE0042C /* PostDetectionAlgorithm.swift */, + 4B452EE8B96AF1B0691E7D43 /* PredictionAPI.swift */, + 4BBE2CADB8D8E98D6EEE10C0 /* PredictionResult.swift */, + 795E739553651C8AC40E6AC1 /* PredictionUtilOcr.swift */, + 00A93BFDEE4072A2A9C30397 /* SoftNMS.swift */, + 8AEB912151CB564AE21D073C /* SSDOcrDetect.swift */, + EA89DA7FF10BC3A7B2D102FB /* SSDOcrOutputExtensions.swift */, + ); + path = MLRuntime; + sourceTree = ""; + }; + 69D6875F84C12C2E18B96B01 /* Helpers */ = { + isa = PBXGroup; + children = ( + DA3ECBF071E7507C9D41F232 /* EndToEndTestDataSource.swift */, + 6418F87D5168A2A08D65A482 /* STPLocalizedString.swift */, + 69A2DB0837C11D8829C027CF /* String+Localized.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 6A6547B8AC5A170A0092DCF5 /* Localizations */ = { + isa = PBXGroup; + children = ( + 6C27E58C1953533C68D43361 /* Localizable.strings */, + ); + path = Localizations; + sourceTree = ""; + }; + 6C3738C5D8A82E9A3C5AFC85 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 14795A45279E8F328AB54920 /* Project-Debug.xcconfig */, + 7DBB233BD36CBAE2700A4511 /* Project-Release.xcconfig */, + F1FCC855CCB0816414A4BEC1 /* StripeiOS Tests-Debug.xcconfig */, + A67EDE2BCCC081DEDD684AAC /* StripeiOS Tests-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 72B7637B9160BBA39556BA0A /* Products */ = { + isa = PBXGroup; + children = ( + DD6884D0B3347BBF2E61B1D6 /* StripeCardScan.framework */, + 2F9447632965B9BCE1E595E4 /* StripeCardScanTests.xctest */, + 2E889051D7CD76D4FB9084A1 /* StripeCore.framework */, + 17FA90EE37CF1E6A49532491 /* StripeCoreTestUtils.framework */, + ); + name = Products; + sourceTree = ""; + }; + 79C01535652A2F89EB22D9A9 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 0197A1615C3FA86907CB6186 /* StripeCardScan-Debug.xcconfig */, + C264A676DA41344551BA6661 /* StripeCardScan-Release.xcconfig */, + ); + path = BuildConfigurations; + sourceTree = ""; + }; + 8AA53DD0D15C5BC1040EE334 /* Scan Analytics */ = { + isa = PBXGroup; + children = ( + 2D61E754E520B4E21920D1DA /* ScanStatsPayload.swift */, + 028DA95BD9A0DA8AD69162CB /* ScanStatsPayload+Common.swift */, + 4A1CD398A6AFB6747D095C59 /* ScanStatsPayload+Tasks.swift */, + ); + path = "Scan Analytics"; + sourceTree = ""; + }; + 9AE51241602B3092F45811E8 /* Extensions */ = { + isa = PBXGroup; + children = ( + 8CA457DF27DC45455B8E1345 /* Array+utils.swift */, + BD87F9971C25C4B99138318F /* CGrect+utils.swift */, + 7633166B4D9096FE995F08F0 /* CGRectExtension.swift */, + 5E72DD18B9FCB80ABEDE2689 /* CreditCardOcrPrediction+expiry.swift */, + AEE9C295E982F92DE2418AE1 /* Image+utils.swift */, + B2D9886B0ED119E8D1FB6448 /* UIImage+pixelBuffer.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 9FDF572663A7F311B8506E13 /* CardScan */ = { + isa = PBXGroup; + children = ( + A1C38CDC3470D8E7A9F39671 /* AppleOcr */, + 3C728AAC6B6FF615A064C757 /* CardUtils */, + 620D5B89096A8B2B2202E6EB /* CreditCardOcr */, + 9AE51241602B3092F45811E8 /* Extensions */, + 10507D99D0E81A181A25B4BC /* MLModels */, + 66F259BA8091A6C5D28C4963 /* MLRuntime */, + E2C9F4FF2603FAF1BD4567B4 /* UI */, + E86A9678BB840BD3EA2AEB94 /* Utils */, + ); + path = CardScan; + sourceTree = ""; + }; + A1C38CDC3470D8E7A9F39671 /* AppleOcr */ = { + isa = PBXGroup; + children = ( + 3E24CF248F312DDE025D662A /* AppleOcr.swift */, + ); + path = AppleOcr; + sourceTree = ""; + }; + A35971ECF566C2769A608A06 /* Unit */ = { + isa = PBXGroup; + children = ( + D6C5CA3B4336E2024BFB5037 /* API Bindings */, + 20CA55CAA02DC2833AD1C426 /* ML Models */, + F24DEA029F94A359B7A571BB /* CardImageVerificationControllerTests.swift */, + 7DDAF8E33A3D7CD020AC904A /* CardImageVerificationDetailsResponseTest.swift */, + D0AA128D69F3C32A4EF67CF0 /* ImageCompressionTests.swift */, + 27F6ECCDC721E4D46DAC4F48 /* ScanAnalyticsManagerTests.swift */, + CD6D4DB87B26D439686392AE /* StrictModeFramesTest.swift */, + 5A72927DA8A946FDD03843DE /* StringResourceTests.swift */, + ); + path = Unit; + sourceTree = ""; + }; + BC0070EA31577F5573D7B787 /* Card Image Verification */ = { + isa = PBXGroup; + children = ( + 53C171A1336B7FB0C321D194 /* CancellationReason.swift */, + B313BA373EF6B567BF40A240 /* CardImageVerificationController.swift */, + CD5004FC0706B2660B4C6EA0 /* CardImageVerificationIntent.swift */, + 12F09D21BAC44C461B1AD7CB /* CardImageVerificationSheet.swift */, + 545E03D5167516D28C8A9F08 /* CardImageVerificationSheetConfiguration.swift */, + DCF4C0BF8E55D53B004FABCA /* CardScanSheetError.swift */, + 69E2630C0FC7C9DDD551FD67 /* ScanAnalyticsManager.swift */, + B97CA4D8286B196A49B8D535 /* ScanAnalyticsManager+Helpers.swift */, + 1A337FE5C3BE4FAB001E5520 /* ScanAnalyticsManager+Managers.swift */, + 6563E5CE5CD0439D5DBF35D7 /* ScanAnalyticsManager+Tasks.swift */, + 76C70F9A1879ED8B41639945 /* ScannedCard.swift */, + BAE45D8AC7BBA5DC433A9383 /* ScannedCardImageData.swift */, + 6C3EE6315FB1507E40F10D85 /* ScannedCardImageData+Verification.swift */, + 64A1E35C6DF336F34B6C7AEA /* StripeCore+Import.swift */, + ); + path = "Card Image Verification"; + sourceTree = ""; + }; + C8B14FA3DC29B84F382E6D1F /* Models */ = { + isa = PBXGroup; + children = ( + 8AA53DD0D15C5BC1040EE334 /* Scan Analytics */, + 24C2345660A95FBF90BF4850 /* VerificationFramesData.swift */, + E4FAA99A64BCD00A336A5E01 /* VerifyFrames.swift */, + ); + path = Models; + sourceTree = ""; + }; + C8CC6DB5A60043E8A373BC6F /* Helpers */ = { + isa = PBXGroup; + children = ( + 746AAB9327BDE6791F9393B0 /* CardScanMockData.swift */, + 63662689AD097C859B8759B1 /* Data+Sha256.swift */, + 091163ED5E37922332F502B1 /* ImageHelpers.swift */, + 15541EEEE784EECDD3E7399D /* ScannedCardDetails.swift */, + 732D3AB4CE25CC5C26A352B1 /* String+Sha256.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + D275033F1B615793C2F8B8E0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 14E5A51A77928A2700E23321 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D6C5CA3B4336E2024BFB5037 /* API Bindings */ = { + isa = PBXGroup; + children = ( + 5CF9ADC4CC0B4ADFE0A5427E /* ScanStatsPayloadAPIBindingsTests.swift */, + 3BC4F647EB512813078B9A6F /* STPAPIClient+CardImageVerificationTest.swift */, + 4301B281F5E0A5BACBBD68CC /* VerifyFramesAPIBindingsTests.swift */, + ); + path = "API Bindings"; + sourceTree = ""; + }; + DF93066F033C46A7B100FC82 /* StripeCardScan */ = { + isa = PBXGroup; + children = ( + 313F5F7C2B0BE5BE00BD98A9 /* Docs.docc */, + 3228566B727B9D25D13A3BAB /* Resources */, + F28CFA0B72751113E838303F /* Source */, + 17BCBB8B34EBA904BB245CBD /* Info.plist */, + AB56DDBBF0E5BC10682C7D96 /* StripeCardScan.h */, + ); + path = StripeCardScan; + sourceTree = ""; + }; + E2C9F4FF2603FAF1BD4567B4 /* UI */ = { + isa = PBXGroup; + children = ( + 938958FCB9B918AD6CDF0F4E /* BlurView.swift */, + 14BC8B1C28C3F6C8330164E0 /* CardScanSheet.swift */, + C3E012270975B6DC09A8199F /* CornerView.swift */, + 77D0D8C2909455B76E246468 /* InterfaceOrientation.swift */, + DC12B6E0657203AAB765468C /* PreviewView.swift */, + A790FC6FAEC3F88FB2C26E27 /* ScanBaseViewController.swift */, + FF9618F7B15B33EC5B339A94 /* ScanConfiguration.swift */, + 0D1FBA1B4676CC5E9F785D51 /* ScanEventsProtocol.swift */, + 66A49708CF5D37C7CB267732 /* ScanStats.swift */, + A6E1DA01CB00CBDE36735EDD /* SimpleScanViewController.swift */, + C827434739027B2DD43449EA /* Torch.swift */, + D42510E09D32547455CDDBEF /* VideoFeed.swift */, + ); + path = UI; + sourceTree = ""; + }; + E86A9678BB840BD3EA2AEB94 /* Utils */ = { + isa = PBXGroup; + children = ( + 362A27BB81840A22104C9A49 /* AppInfoUtils.swift */, + D9B548B9626366BB0F110873 /* AtomicPropertyWrapper.swift */, + CDB03A9253E8FAD07B739438 /* DeviceUtils.swift */, + ); + path = Utils; + sourceTree = ""; + }; + F28CFA0B72751113E838303F /* Source */ = { + isa = PBXGroup; + children = ( + 9FDF572663A7F311B8506E13 /* CardScan */, + 324D2CF5354B3283BC0C49ED /* CardVerify */, + ); + path = Source; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + B4E70071212998150E28FF9A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 1B10222A5121C9B2C3479FAB /* StripeCardScan.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 03CF79975A56288F02F20E52 /* StripeCardScan */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3942B170A6D222D5B5128339 /* Build configuration list for PBXNativeTarget "StripeCardScan" */; + buildPhases = ( + B4E70071212998150E28FF9A /* Headers */, + 44F5D31B0E7A7BFCB9425841 /* Sources */, + 7FD545F62DB7C0787E9CD12D /* Resources */, + 451E97BA25D9CE29EDAB7618 /* Embed Frameworks */, + 29BDDDCECDEC71451BAAB324 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeCardScan; + productName = StripeCardScan; + productReference = DD6884D0B3347BBF2E61B1D6 /* StripeCardScan.framework */; + productType = "com.apple.product-type.framework"; + }; + DCC6FFCFBE0B51B33F4CBB26 /* StripeCardScanTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 89AE00D6B3B382DD3D3A3C70 /* Build configuration list for PBXNativeTarget "StripeCardScanTests" */; + buildPhases = ( + 201682B08469C4AC551E9BF8 /* Sources */, + 54119F2010F36CB483CA88CC /* Resources */, + 20B092D23CA44561EF997447 /* Embed Frameworks */, + 1639B69C00B8C21CEBF08A55 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 82DAA855443FCAA25F64C5C0 /* PBXTargetDependency */, + ); + name = StripeCardScanTests; + packageProductDependencies = ( + F3D550995F9421BED1BE23A2 /* iOSSnapshotTestCase */, + C96A49C871FDB73B36A91670 /* OHHTTPStubs */, + EA26D443783FE045B0D7888D /* OHHTTPStubsSwift */, + ); + productName = StripeCardScanTests; + productReference = 2F9447632965B9BCE1E595E4 /* StripeCardScanTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9A6BF50E5B02355004AC6020 /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = CBB5D42795303F47D9D5F2F5 /* Build configuration list for PBXProject "StripeCardScan" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = 105B9420C58B9406F7939324; + packageReferences = ( + 3B10FCF0EE5D8ADC4672F64E /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + 1F624C14E7802E9748883013 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + ); + productRefGroup = 72B7637B9160BBA39556BA0A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 03CF79975A56288F02F20E52 /* StripeCardScan */, + DCC6FFCFBE0B51B33F4CBB26 /* StripeCardScanTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 54119F2010F36CB483CA88CC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D62200AA65D71F040037CC8 /* CardImageVerification_CardAdd_200.json in Resources */, + 52519CA3928967768049164E /* CardImageVerification_CardSet_200.json in Resources */, + 59889C85D6D25B3222776B28 /* synthetic_test_image.jpg in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7FD545F62DB7C0787E9CD12D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A7F92FB7213E66A7D15A1BE3 /* SSDOcr.mlmodelc in Resources */, + B2DF7862E5F80ACD8DEE9317 /* UxModel.mlmodelc in Resources */, + 8CF112F4889C96617504A931 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 201682B08469C4AC551E9BF8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 77443628E02F3AEFF112314D /* CardScanMockData.swift in Sources */, + DB74787AF4EEF0E506DD0E1C /* Data+Sha256.swift in Sources */, + 3F5B9466E9FC13CA29218750 /* ImageHelpers.swift in Sources */, + AE144927F21D5DF9A8C0EF79 /* ScannedCardDetails.swift in Sources */, + D42EEFBB72CD0AB00C6B4728 /* String+Sha256.swift in Sources */, + 449DD2A5D1F3FF94524D3CD6 /* STPAPIClient+CardImageVerificationTest.swift in Sources */, + 79A96C88970E4E61BAC47412 /* ScanStatsPayloadAPIBindingsTests.swift in Sources */, + 25FAF64D4FE93BF38E807ECA /* VerifyFramesAPIBindingsTests.swift in Sources */, + BDEA39DE5D3775AD2A4BFB6C /* CardImageVerificationControllerTests.swift in Sources */, + 19EF4634631CFC121DE21D1B /* CardImageVerificationDetailsResponseTest.swift in Sources */, + 48E4577B073DD9485BC62902 /* ImageCompressionTests.swift in Sources */, + 70B5FF75EF5DC8FFB23B95F7 /* UxModelTests.swift in Sources */, + 0D062E99363099A5728F3DCD /* ScanAnalyticsManagerTests.swift in Sources */, + FE55286899CFB0E34356D4D6 /* StrictModeFramesTest.swift in Sources */, + D6F0E1C93997BB36AF0608C0 /* StringResourceTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 44F5D31B0E7A7BFCB9425841 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A9ECCF086685AD607D82492 /* AppleOcr.swift in Sources */, + C3B9A4A30443A38C2D17BE8E /* CardNetwork.swift in Sources */, + 0ADD10809FEABDCB59A8FA72 /* CardType.swift in Sources */, + A7014CF97250D97BC683FAC8 /* CreditCardUtils.swift in Sources */, + 050B462804602A9F4DB58A9D /* Expiry.swift in Sources */, + DDBCE05A3794129A9A04D511 /* AppleCreditCardOcr.swift in Sources */, + 60AF52B9EFFF12EBABE4D221 /* CreditCardOcrImplementation.swift in Sources */, + 1AD72764EA8BCB66A9D7B637 /* CreditCardOcrPrediction.swift in Sources */, + F9F19A6F2245863FEA485335 /* CreditCardOcrResult.swift in Sources */, + 17E34C9EF882AEAE47BECAB4 /* ErrorCorrection.swift in Sources */, + 858CDA751309CA04202B6203 /* MachineLearningResult.swift in Sources */, + 4ACBD9754F304CE4F70CAE43 /* MainLoopStateMachine.swift in Sources */, + C7A941539DBCFA790DCDB0B0 /* NonNameWords.swift in Sources */, + EE1CB7D3FA2B44DC17E6FF4C /* OcrMainLoop.swift in Sources */, + 5B1749A19231B20E2A081EB9 /* OcrObject.swift in Sources */, + C8E2E98B7ED108804E0A0E24 /* SSDCreditCardOcr.swift in Sources */, + C7259DA76E6AB9BF53735D19 /* Array+utils.swift in Sources */, + 10E840B00703A92CC487B324 /* CGRectExtension.swift in Sources */, + 7D9C9C2A26EF11F0C5D67D7C /* CGrect+utils.swift in Sources */, + CCB7722FDB8E6E68E90F3D12 /* CreditCardOcrPrediction+expiry.swift in Sources */, + D2030CA1B3B7DAF3229189AD /* Image+utils.swift in Sources */, + 1B71815D5B3EC8AC21E2A202 /* UIImage+pixelBuffer.swift in Sources */, + EA0F179DF887D94E19138A5E /* AsyncModelLoading.swift in Sources */, + DBE3EE5DAEEEA44D6C7ECD4E /* SSDOcr+Utils.swift in Sources */, + 213A91107E76434972A1F848 /* SSDOcr.swift in Sources */, + 189DEBAF38F2FB88CCA39EA2 /* UxModel+Utils.swift in Sources */, + 8E4B117272F5B17A1E6AD609 /* UxModel.swift in Sources */, + B66697826FEA9FCE81DDD6F4 /* ActiveStateComputation.swift in Sources */, + 47DCE237223D40B1EB183704 /* AppState.swift in Sources */, + 040020B8558B0489DF99F8B5 /* DetectedAllBoxes.swift in Sources */, + 48580A7E92CC8EBFD2FD4C91 /* DetectedAllOcrBoxes.swift in Sources */, + 20DF2E9D5A543360DF3A4DDA /* DetectedBox.swift in Sources */, + BFFA3E8CE79BFFE47530FAF6 /* DetectedSSDBox.swift in Sources */, + DF352D12199B55B659A11795 /* DetectedSSDOcrBox.swift in Sources */, + 7C71166DACDB829F8CBC383D /* NMS.swift in Sources */, + C633115B02A0CE24AC55A747 /* OcrDD.swift in Sources */, + 41366AB64512BB7E4248A904 /* OcrDDUtils.swift in Sources */, + 8FC373E15C1169677F563901 /* OcrPriorsGen.swift in Sources */, + 8463F2DB039C481D26A24BAA /* PostDetectionAlgorithm.swift in Sources */, + 0838FEA7071EDCE1B633F093 /* PredictionAPI.swift in Sources */, + 7B0CF214778E17FEC721602E /* PredictionResult.swift in Sources */, + 4D1016654AFB3D9EE26092B7 /* PredictionUtilOcr.swift in Sources */, + 5382B471868AA7D95BBD2B3A /* SSDOcrDetect.swift in Sources */, + 35F2BB10395405DB6C1E31B5 /* SSDOcrOutputExtensions.swift in Sources */, + 9FA5A312032FC54B35BB604E /* SoftNMS.swift in Sources */, + 164BF41B5C522B7338376BB2 /* BlurView.swift in Sources */, + 3736756FBB875060C86E2777 /* CardScanSheet.swift in Sources */, + F0ED2BFE143DD07337D389E5 /* CornerView.swift in Sources */, + 20DCEB955D9E0660EAB3ABEB /* InterfaceOrientation.swift in Sources */, + 30E3E90F9C8E3D3E8FCA869E /* PreviewView.swift in Sources */, + 3FCC183583090FD0246D9336 /* ScanBaseViewController.swift in Sources */, + 0D8CA0E3EFF9694CAE4FB8F7 /* ScanConfiguration.swift in Sources */, + C57BCF07EDF419D36ED4E690 /* ScanEventsProtocol.swift in Sources */, + 9188179F13E5EA15E0419580 /* ScanStats.swift in Sources */, + 74330F51A91DC3A617F7AE42 /* SimpleScanViewController.swift in Sources */, + 0EAA2314FEA8D05D24C498BD /* Torch.swift in Sources */, + 7CED1B42C94A49D6BF98C9E4 /* VideoFeed.swift in Sources */, + EF96103F82491640651C49F6 /* AppInfoUtils.swift in Sources */, + 6E9908C612ECD2E0AEA3DFF8 /* AtomicPropertyWrapper.swift in Sources */, + 49E1959C680AD68D1D345D6B /* DeviceUtils.swift in Sources */, + D27E02429E5DC8B28DFE8945 /* CardImageVerificationDetailsResponse.swift in Sources */, + BEE2BFA103DE985D057A0F9D /* ScanStatsPayload+Common.swift in Sources */, + 54469A1A5D77BAD54061BEA1 /* ScanStatsPayload+Tasks.swift in Sources */, + 19F7EC09C9B0ED11A4601E08 /* ScanStatsPayload.swift in Sources */, + AB21982DC43977754F237756 /* VerificationFramesData.swift in Sources */, + AFA334F5007A4C141A96FC2E /* VerifyFrames.swift in Sources */, + 77BB4BE6E5E03626936453C6 /* STPAPIClient+CardImageVerification.swift in Sources */, + 65C1EB5447CD5DF162CDEC2B /* Bouncer.swift in Sources */, + CBA4FE649A3B21DAC3A61E5B /* CancellationReason.swift in Sources */, + E4EC278027AF802C39C74C48 /* CardImageVerificationController.swift in Sources */, + AFB2482D559BCC08E96C3615 /* CardImageVerificationIntent.swift in Sources */, + 420FF119A7147335D802AB14 /* CardImageVerificationSheet.swift in Sources */, + E90CC9715EC99F6B7FAC98FA /* CardImageVerificationSheetConfiguration.swift in Sources */, + E70A7E38A7D858031F900649 /* CardScanSheetError.swift in Sources */, + CED42084C5FC46C17E357AD5 /* ScanAnalyticsManager+Helpers.swift in Sources */, + A8D0A57687A7CF9F389D7BDB /* ScanAnalyticsManager+Managers.swift in Sources */, + 0A4DCE2C98659B92DDB29B9A /* ScanAnalyticsManager+Tasks.swift in Sources */, + EB061FA550AFFE2AB2E348DF /* ScanAnalyticsManager.swift in Sources */, + 1DE075A700592250D87DF558 /* ScannedCard.swift in Sources */, + 05188062E522359CE24912CF /* ScannedCardImageData+Verification.swift in Sources */, + CC07F702B9EC043ACB0AC1E5 /* ScannedCardImageData.swift in Sources */, + 86635536450EE7D583B8BD59 /* StripeCore+Import.swift in Sources */, + 36525A0F774E7FA055D8B525 /* CardBase.swift in Sources */, + 2D042FD2E8A0C8236596CE54 /* CardScanFraudData.swift in Sources */, + EA2DBA78722CD65ED599190D /* CardScanMisc.swift in Sources */, + 5488DA1817A0C9F780EF69B2 /* CardVerifyFraudData.swift in Sources */, + 43AA83BFF8DEFC09D406669F /* CardVerifyStateMachine.swift in Sources */, + F16FAC158C6B0A7687547B4B /* FadeInAnimation.swift in Sources */, + ACC3C1A295E43983F67F858E /* FrameData.swift in Sources */, + 2F3FA5E8CCBF7F77106A8268 /* EndToEndTestDataSource.swift in Sources */, + 313F5F7D2B0BE5BE00BD98A9 /* Docs.docc in Sources */, + 45C8D17FED02529B7FC4E8C3 /* STPLocalizedString.swift in Sources */, + 84975E58102A5D8C62C6C4E5 /* String+Localized.swift in Sources */, + 41F2E4475B9B19350C31F255 /* PaymentCard.swift in Sources */, + C5DBC36A41FB70C5DCCC97C7 /* SimpleScanViewController+Verify.swift in Sources */, + F4E4941F5AA2EC2C2CC4F7FB /* StripeCardScanBundleLocator.swift in Sources */, + 93D4785D8B91F12B6DDDE203 /* UxAnalyzer.swift in Sources */, + 18B63245A933E292345C9410 /* UxAndOcrMainLoop.swift in Sources */, + B00954CF5F2A15B72CB94FA5 /* VerifyCardAddViewController.swift in Sources */, + 06711423AB563634EADFCCC0 /* VerifyCardViewController.swift in Sources */, + B47F583CE0F239881877E5D3 /* ZoomedInCGImage.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 82DAA855443FCAA25F64C5C0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCardScan; + target = 03CF79975A56288F02F20E52 /* StripeCardScan */; + targetProxy = F7C4E731844D6B46A4FBCACD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 6C27E58C1953533C68D43361 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + B0A7946C3A1E943FB72F3FB7 /* bg-BG */, + 8FB602C9E909E31BAE6703D6 /* ca-ES */, + 27F4BF66273070C2054BBAC0 /* cs-CZ */, + 4B89D81A4099CC71A3FBC133 /* da */, + FFE89E65E767D03B92383C76 /* de */, + D532C440ADCD017AD326299B /* el-GR */, + BEEE30227F7D34546F0FBB58 /* en */, + 6186A0AAB96E236DEF0B5B79 /* en-GB */, + 79A802EA46840CD45CBECEEA /* es */, + 5A6D5C8E51C23370E889F75F /* es-419 */, + CB48DDCA458A62DCE342F517 /* et-EE */, + 03E2C12C0D172DD7DAE33399 /* fi */, + 6F41FBE3D90B5F6518E42051 /* fil */, + 3A9C2B8A3B96E194CED6841C /* fr */, + F3AF32D7DF7EE1420D16291D /* fr-CA */, + 9C3890F6A5A4FF68F95746DC /* hr */, + 0C7BAE904C7E1D7A9FDF35F1 /* hu */, + 10311DC6AC19B7A73A8F930F /* id */, + ABB38869FD175D7BD6BB3890 /* it */, + 8094FE7CCD3CDA8B914EC534 /* ja */, + F87D884128F85E7F16BCBA9B /* ko */, + 58A5F2CEEAAB78AA425A5737 /* lt-LT */, + FD40E90452A83FFBBF65C8D8 /* lv-LV */, + 11D3465E527ABADBD2485A93 /* ms-MY */, + 9593D6788C4DBF554735E2B6 /* mt */, + 9A256BCB7822DD94572DDA07 /* nb */, + AB64890F80D91D9D7B92197D /* nl */, + 8C227CA761586744976C952D /* nn-NO */, + 1C94EE6DB0E3114A38DD9CFE /* pl-PL */, + 45C9A1A26405DEAD8E11F13D /* pt-BR */, + 2667E0E0C7DC6CDAF03F8B40 /* pt-PT */, + 9E4CF9A676A86169070DD54D /* ro-RO */, + 42454BA950282D9ADB9C6557 /* ru */, + 5969433D88B5BD3B7F56B2BD /* sk-SK */, + 88F89EC392AFBE060336B63F /* sl-SI */, + 89BBC06EC107C8BDEF79BB3D /* sv */, + 3938F9E3F0F267045D3FDDEB /* tr */, + 0BB35800FB840EFC76240DC7 /* vi */, + ADCB34B81919043FA35E8BC3 /* zh-Hans */, + A612DDE1110EA970BF69B33D /* zh-Hant */, + 4DA27FFBD426C871C5BD254E /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 5EA7D712D38589E2EE95AB3A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0197A1615C3FA86907CB6186 /* StripeCardScan-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeCardScan/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-card-scan"; + PRODUCT_NAME = StripeCardScan; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 7F230CA2FE5A1AE4D1DC64D7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7DBB233BD36CBAE2700A4511 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + DCF5B81249E77660231925A7 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F1FCC855CCB0816414A4BEC1 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCardScanTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCardScanTests; + PRODUCT_NAME = StripeCardScanTests; + SDKROOT = iphoneos; + }; + name = Debug; + }; + EDDF50C51DE9D6BEF2ACB9E5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C264A676DA41344551BA6661 /* StripeCardScan-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeCardScan/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-card-scan"; + PRODUCT_NAME = StripeCardScan; + SDKROOT = iphoneos; + }; + name = Release; + }; + EE63655B6AEB11CB8934D19B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A67EDE2BCCC081DEDD684AAC /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCardScanTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCardScanTests; + PRODUCT_NAME = StripeCardScanTests; + SDKROOT = iphoneos; + }; + name = Release; + }; + FBC92FF6082EA51664498AE4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 14795A45279E8F328AB54920 /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3942B170A6D222D5B5128339 /* Build configuration list for PBXNativeTarget "StripeCardScan" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EA7D712D38589E2EE95AB3A /* Debug */, + EDDF50C51DE9D6BEF2ACB9E5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 89AE00D6B3B382DD3D3A3C70 /* Build configuration list for PBXNativeTarget "StripeCardScanTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DCF5B81249E77660231925A7 /* Debug */, + EE63655B6AEB11CB8934D19B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CBB5D42795303F47D9D5F2F5 /* Build configuration list for PBXProject "StripeCardScan" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FBC92FF6082EA51664498AE4 /* Debug */, + 7F230CA2FE5A1AE4D1DC64D7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 1F624C14E7802E9748883013 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; + 3B10FCF0EE5D8ADC4672F64E /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davidme-stripe/OHHTTPStubs"; + requirement = { + branch = "stripe-mock"; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C96A49C871FDB73B36A91670 /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; + EA26D443783FE045B0D7888D /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; + F3D550995F9421BED1BE23A2 /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 9A6BF50E5B02355004AC6020 /* Project object */; +} diff --git a/StripeCardScan/StripeCardScan.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeCardScan/StripeCardScan.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeCardScan/StripeCardScan.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeCardScan/StripeCardScan.xcodeproj/xcshareddata/xcschemes/StripeCardScan.xcscheme b/StripeCardScan/StripeCardScan.xcodeproj/xcshareddata/xcschemes/StripeCardScan.xcscheme new file mode 100644 index 00000000..b81b60a2 --- /dev/null +++ b/StripeCardScan/StripeCardScan.xcodeproj/xcshareddata/xcschemes/StripeCardScan.xcscheme @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeCardScan/StripeCardScan/Docs.docc/StripeCardScan.md b/StripeCardScan/StripeCardScan/Docs.docc/StripeCardScan.md new file mode 100644 index 00000000..48a428af --- /dev/null +++ b/StripeCardScan/StripeCardScan/Docs.docc/StripeCardScan.md @@ -0,0 +1,3 @@ +# ``StripeCardScan`` + +Placeholder \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Info.plist b/StripeCardScan/StripeCardScan/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeCardScan/StripeCardScan/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/analytics/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/analytics/coremldata.bin new file mode 100644 index 00000000..77dbb917 Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/analytics/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/coremldata.bin new file mode 100644 index 00000000..b6893365 Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/metadata.json b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/metadata.json new file mode 100644 index 00000000..1673fa50 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/metadata.json @@ -0,0 +1,92 @@ +[ + { + "shortDescription" : "", + "metadataOutputVersion" : "3.0", + "outputSchema" : [ + { + "hasShapeFlexibility" : "0", + "isOptional" : "0", + "dataType" : "Float32", + "formattedType" : "MultiArray (Float32)", + "shortDescription" : "MultiArray of shape (1, 1, 1, 3420, 10). The first and second dimensions correspond to sequence and batch size, respectively", + "shape" : "[]", + "name" : "scores", + "type" : "MultiArray" + }, + { + "hasShapeFlexibility" : "0", + "isOptional" : "0", + "dataType" : "Float32", + "formattedType" : "MultiArray (Float32)", + "shortDescription" : "MultiArray of shape (1, 1, 1, 3420, 4). The first and second dimensions correspond to sequence and batch size, respectively", + "shape" : "[]", + "name" : "boxes", + "type" : "MultiArray" + }, + { + "hasShapeFlexibility" : "0", + "isOptional" : "0", + "dataType" : "Float32", + "formattedType" : "MultiArray (Float32)", + "shortDescription" : "MultiArray of shape (1, 1, 1, 3420, 1). The first and second dimensions correspond to sequence and batch size, respectively", + "shape" : "[]", + "name" : "filter", + "type" : "MultiArray" + } + ], + "version" : "", + "modelParameters" : [ + + ], + "author" : "", + "specificationVersion" : 2, + "license" : "", + "isUpdatable" : "0", + "availability" : { + "macOS" : "10.13.2", + "tvOS" : "11.2", + "watchOS" : "4.2", + "iOS" : "11.2", + "macCatalyst" : "11.2" + }, + "modelType" : { + "name" : "MLModelType_neuralNetwork" + }, + "inputSchema" : [ + { + "height" : "375", + "colorspace" : "RGB", + "isOptional" : "0", + "width" : "600", + "isColor" : "1", + "formattedType" : "Image (Color 600 × 375)", + "hasSizeFlexibility" : "0", + "type" : "Image", + "shortDescription" : "", + "name" : "0" + } + ], + "userDefinedMetadata" : { + + }, + "generatedClassName" : "SSDOcr", + "neuralNetworkLayerTypeHistogram" : { + "UnaryExp" : 1, + "Concat" : 2, + "ActivationSigmoidHard" : 2, + "Convolution" : 64, + "UnaryInverse" : 1, + "Flatten" : 2, + "ReduceSum" : 2, + "Reshape" : 5, + "Permute" : 7, + "Add" : 10, + "BatchNorm" : 60, + "Multiply" : 1, + "ActivationLinear" : 8, + "ActivationReLU" : 41, + "Slice" : 1 + }, + "method" : "predict" + } +] \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.net b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.net new file mode 100644 index 00000000..c673fefa --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.net @@ -0,0 +1,2489 @@ +{ + "transform_params" : { + "0" : { + "bias_a" : 0, + "bias_g" : -0.99221789836883545, + "bias_r" : -0.99221789836883545, + "bias_b" : -0.99221789836883545, + "center_mean" : 0, + "is_network_bgr" : 0, + "scale" : 0.0077821011655032635 + } + }, + "properties" : { + + }, + "analyses" : { + + }, + "format_version" : 200, + "storage" : "model.espresso.weights", + "layers" : [ + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "377", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "379", + "K" : 3, + "blob_biases" : 1, + "stride_x" : 2, + "name" : "377", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 1, + "stride_y" : 2, + "has_biases" : 1, + "C" : 16, + "bottom" : "0", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 3 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "380", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "382", + "K" : 16, + "blob_biases" : 5, + "name" : "380", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 16, + "pad_t" : 1, + "has_biases" : 1, + "C" : 16, + "bottom" : "379", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 7 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "383", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "384", + "K" : 16, + "blob_biases" : 9, + "name" : "383", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 8, + "bottom" : "382", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 11 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "385", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "387", + "K" : 8, + "blob_biases" : 13, + "name" : "385", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "384", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 15 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "388", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "390", + "K" : 48, + "blob_biases" : 17, + "stride_x" : 2, + "name" : "388", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 48, + "pad_t" : 1, + "stride_y" : 2, + "has_biases" : 1, + "C" : 48, + "bottom" : "387", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 19 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "391", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "392", + "K" : 48, + "blob_biases" : 21, + "name" : "391", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 12, + "bottom" : "390", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 23 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "393", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "395", + "K" : 12, + "blob_biases" : 25, + "name" : "393", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 72, + "bottom" : "392", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 27 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "396", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "398", + "K" : 72, + "blob_biases" : 29, + "name" : "396", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 72, + "pad_t" : 1, + "has_biases" : 1, + "C" : 72, + "bottom" : "395", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 31 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "399", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "400", + "K" : 72, + "blob_biases" : 33, + "name" : "399", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 12, + "bottom" : "398", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 35 + }, + { + "bottom" : "392,400", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "401", + "top" : "401", + "type" : "elementwise", + "name" : "401", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "402", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "404", + "K" : 12, + "blob_biases" : 37, + "name" : "402", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 72, + "bottom" : "401", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 39 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "405", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "407", + "K" : 72, + "blob_biases" : 41, + "stride_x" : 2, + "name" : "405", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 72, + "pad_t" : 1, + "stride_y" : 2, + "has_biases" : 1, + "C" : 72, + "bottom" : "404", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 43 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "408", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "409", + "K" : 72, + "blob_biases" : 45, + "name" : "408", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "407", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 47 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "410", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "412", + "K" : 16, + "blob_biases" : 49, + "name" : "410", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "409", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 51 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "413", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "415", + "K" : 96, + "blob_biases" : 53, + "name" : "413", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 96, + "pad_t" : 1, + "has_biases" : 1, + "C" : 96, + "bottom" : "412", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 55 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "416", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "417", + "K" : 96, + "blob_biases" : 57, + "name" : "416", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "415", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 59 + }, + { + "bottom" : "409,417", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "418", + "top" : "418", + "type" : "elementwise", + "name" : "418", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "419", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "421", + "K" : 16, + "blob_biases" : 61, + "name" : "419", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "418", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 63 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "422", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "424", + "K" : 96, + "blob_biases" : 65, + "name" : "422", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 96, + "pad_t" : 1, + "has_biases" : 1, + "C" : 96, + "bottom" : "421", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 67 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "425", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "426", + "K" : 96, + "blob_biases" : 69, + "name" : "425", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "424", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 71 + }, + { + "bottom" : "418,426", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "427", + "top" : "427", + "type" : "elementwise", + "name" : "427", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "428", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "430", + "K" : 16, + "blob_biases" : 73, + "name" : "428", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "427", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 75 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "431", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "433", + "K" : 96, + "blob_biases" : 77, + "stride_x" : 2, + "name" : "431", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 96, + "pad_t" : 1, + "stride_y" : 2, + "has_biases" : 1, + "C" : 96, + "bottom" : "430", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 79 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "434", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "435", + "K" : 96, + "blob_biases" : 81, + "name" : "434", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "433", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 83 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "436", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "438", + "K" : 32, + "blob_biases" : 85, + "name" : "436", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "435", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 87 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "439", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "441", + "K" : 192, + "blob_biases" : 89, + "name" : "439", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 1, + "has_biases" : 1, + "C" : 192, + "bottom" : "438", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 91 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "442", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "443", + "K" : 192, + "blob_biases" : 93, + "name" : "442", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "441", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 95 + }, + { + "bottom" : "435,443", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "444", + "top" : "444", + "type" : "elementwise", + "name" : "444", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "445", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "447", + "K" : 32, + "blob_biases" : 97, + "name" : "445", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "444", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 99 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "448", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "450", + "K" : 192, + "blob_biases" : 101, + "name" : "448", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 1, + "has_biases" : 1, + "C" : 192, + "bottom" : "447", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 103 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "451", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "452", + "K" : 192, + "blob_biases" : 105, + "name" : "451", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "450", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 107 + }, + { + "bottom" : "444,452", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "453", + "top" : "453", + "type" : "elementwise", + "name" : "453", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "454", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "456", + "K" : 32, + "blob_biases" : 109, + "name" : "454", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "453", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 111 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "457", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "459", + "K" : 192, + "blob_biases" : 113, + "name" : "457", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 1, + "has_biases" : 1, + "C" : 192, + "bottom" : "456", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 115 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "460", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "461", + "K" : 192, + "blob_biases" : 117, + "name" : "460", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "459", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 119 + }, + { + "bottom" : "453,461", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "462", + "top" : "462", + "type" : "elementwise", + "name" : "462", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "463", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "465", + "K" : 32, + "blob_biases" : 121, + "name" : "463", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "462", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 123 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "466", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "468", + "K" : 192, + "blob_biases" : 125, + "name" : "466", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 1, + "has_biases" : 1, + "C" : 192, + "bottom" : "465", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 127 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "469", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "470", + "K" : 192, + "blob_biases" : 129, + "name" : "469", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "468", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 131 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "471", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "473", + "K" : 48, + "blob_biases" : 133, + "name" : "471", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "470", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 135 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "474", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "476", + "K" : 288, + "blob_biases" : 137, + "name" : "474", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "has_biases" : 1, + "C" : 288, + "bottom" : "473", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 139 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "477", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "478", + "K" : 288, + "blob_biases" : 141, + "name" : "477", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "476", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 143 + }, + { + "bottom" : "470,478", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "479", + "top" : "479", + "type" : "elementwise", + "name" : "479", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "480", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "482", + "K" : 48, + "blob_biases" : 145, + "name" : "480", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "479", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 147 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "483", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "485", + "K" : 288, + "blob_biases" : 149, + "name" : "483", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "has_biases" : 1, + "C" : 288, + "bottom" : "482", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 151 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "486", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "487", + "K" : 288, + "blob_biases" : 153, + "name" : "486", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "485", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 155 + }, + { + "bottom" : "479,487", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "488", + "top" : "488", + "type" : "elementwise", + "name" : "488", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "489", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "491", + "K" : 48, + "blob_biases" : 157, + "name" : "489", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "488", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 159 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "492", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "494", + "K" : 288, + "blob_biases" : 161, + "stride_x" : 2, + "name" : "492", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "stride_y" : 2, + "has_biases" : 1, + "C" : 288, + "bottom" : "491", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 163 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "495", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "496", + "K" : 288, + "blob_biases" : 165, + "name" : "495", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 80, + "bottom" : "494", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 167 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "497", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "499", + "K" : 288, + "blob_biases" : 169, + "name" : "497", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "has_biases" : 1, + "C" : 288, + "bottom" : "491", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 171 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "500", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "502", + "K" : 288, + "blob_biases" : 173, + "name" : "500", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "has_biases" : 1, + "C" : 288, + "bottom" : "499", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 175 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "503", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "505", + "K" : 288, + "blob_biases" : 177, + "name" : "503", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "has_biases" : 1, + "C" : 288, + "bottom" : "502", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 179 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "506", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "506", + "K" : 288, + "blob_biases" : 181, + "name" : "506", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 33, + "bottom" : "505", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 183 + }, + { + "axis_h" : 0, + "axis_w" : 2, + "bottom" : "506", + "axis_k" : 1, + "axis_n" : 3, + "axis_seq" : 4, + "weights" : { + + }, + "debug_info" : "507", + "top" : "507", + "type" : "transpose", + "name" : "507" + }, + { + "name" : "509", + "weights" : { + + }, + "dst_w" : 1, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : 1, + "mode" : 0, + "bottom" : "507", + "debug_info" : "509", + "hint_fallback_from_metal" : 1, + "dst_seq" : -1, + "dst_k" : 11, + "top" : "509" + }, + { + "pad_r" : 1, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "510", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "511", + "K" : 288, + "blob_biases" : 185, + "name" : "510", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 1, + "has_biases" : 1, + "C" : 288, + "bottom" : "491", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 187 + }, + { + "alpha" : 0.1666666716337204, + "bottom" : "511", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "512_scale_0_1", + "top" : "511_scale_0_1", + "type" : "activation", + "name" : "512_scale_0_1", + "beta" : 0 + }, + { + "bottom" : "511_scale_0_1", + "weights" : { + + }, + "mode" : 7, + "debug_info" : "512_clip_0_1", + "top" : "511_clip_0_1", + "type" : "activation", + "name" : "512_clip_0_1", + "beta" : 0 + }, + { + "alpha" : 6, + "bottom" : "511_clip_0_1", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "512", + "top" : "512", + "type" : "activation", + "name" : "512", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "513", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "513", + "K" : 288, + "blob_biases" : 189, + "name" : "513", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 12, + "bottom" : "512", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 191 + }, + { + "axis_h" : 0, + "axis_w" : 2, + "bottom" : "513", + "axis_k" : 1, + "axis_n" : 3, + "axis_seq" : 4, + "weights" : { + + }, + "debug_info" : "514", + "top" : "514", + "type" : "transpose", + "name" : "514" + }, + { + "name" : "524", + "weights" : { + + }, + "dst_w" : 4, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : -1, + "mode" : 0, + "bottom" : "514", + "debug_info" : "524", + "dst_seq" : 1, + "dst_k" : 1, + "top" : "524" + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "525", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "527", + "K" : 80, + "blob_biases" : 193, + "name" : "525", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "496", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 195 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "528", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "530", + "K" : 480, + "blob_biases" : 197, + "name" : "528", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 480, + "pad_t" : 1, + "has_biases" : 1, + "C" : 480, + "bottom" : "527", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 199 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "531", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "532", + "K" : 480, + "blob_biases" : 201, + "name" : "531", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 80, + "bottom" : "530", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 203 + }, + { + "bottom" : "496,532", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "533", + "top" : "533", + "type" : "elementwise", + "name" : "533", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "534", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "536", + "K" : 80, + "blob_biases" : 205, + "name" : "534", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "533", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 207 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "537", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "539", + "K" : 480, + "blob_biases" : 209, + "name" : "537", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 480, + "pad_t" : 1, + "has_biases" : 1, + "C" : 480, + "bottom" : "536", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 211 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "540", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "541", + "K" : 480, + "blob_biases" : 213, + "name" : "540", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 80, + "bottom" : "539", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 215 + }, + { + "bottom" : "533,541", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "542", + "top" : "542", + "type" : "elementwise", + "name" : "542", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "543", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "545", + "K" : 80, + "blob_biases" : 217, + "name" : "543", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "542", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 219 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "546", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "548", + "K" : 480, + "blob_biases" : 221, + "name" : "546", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 480, + "pad_t" : 1, + "has_biases" : 1, + "C" : 480, + "bottom" : "545", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 223 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "549", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "550", + "K" : 480, + "blob_biases" : 225, + "name" : "549", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 160, + "bottom" : "548", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 227 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "551", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "553", + "K" : 160, + "blob_biases" : 229, + "name" : "551", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 1280, + "bottom" : "550", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 231 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "554", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "556", + "K" : 1280, + "blob_biases" : 233, + "name" : "554", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1280, + "pad_t" : 1, + "has_biases" : 1, + "C" : 1280, + "bottom" : "553", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 235 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "557", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "559", + "K" : 1280, + "blob_biases" : 237, + "name" : "557", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1280, + "pad_t" : 1, + "has_biases" : 1, + "C" : 1280, + "bottom" : "556", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 239 + }, + { + "pad_r" : 1, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "560", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "562", + "K" : 1280, + "blob_biases" : 241, + "name" : "560", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1280, + "pad_t" : 1, + "has_biases" : 1, + "C" : 1280, + "bottom" : "559", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 243 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "563", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "563", + "K" : 1280, + "blob_biases" : 245, + "name" : "563", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 33, + "bottom" : "562", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 247 + }, + { + "axis_h" : 0, + "axis_w" : 2, + "bottom" : "563", + "axis_k" : 1, + "axis_n" : 3, + "axis_seq" : 4, + "weights" : { + + }, + "debug_info" : "564", + "top" : "564", + "type" : "transpose", + "name" : "564" + }, + { + "name" : "566", + "weights" : { + + }, + "dst_w" : 1, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : 1, + "mode" : 0, + "bottom" : "564", + "debug_info" : "566", + "dst_seq" : -1, + "dst_k" : 11, + "top" : "566" + }, + { + "pad_r" : 1, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "567", + "pad_fill_mode" : 0, + "pad_b" : 1, + "pad_l" : 1, + "top" : "568", + "K" : 1280, + "blob_biases" : 249, + "name" : "567", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1280, + "pad_t" : 1, + "has_biases" : 1, + "C" : 1280, + "bottom" : "553", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 251 + }, + { + "alpha" : 0.1666666716337204, + "bottom" : "568", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "569_scale_0_1", + "top" : "568_scale_0_1", + "type" : "activation", + "name" : "569_scale_0_1", + "beta" : 0 + }, + { + "bottom" : "568_scale_0_1", + "weights" : { + + }, + "mode" : 7, + "debug_info" : "569_clip_0_1", + "top" : "568_clip_0_1", + "type" : "activation", + "name" : "569_clip_0_1", + "beta" : 0 + }, + { + "alpha" : 6, + "bottom" : "568_clip_0_1", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "569", + "top" : "569", + "type" : "activation", + "name" : "569", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "570", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "570", + "K" : 1280, + "blob_biases" : 253, + "name" : "570", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 12, + "bottom" : "569", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 0, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 255 + }, + { + "axis_h" : 0, + "axis_w" : 2, + "bottom" : "570", + "axis_k" : 1, + "axis_n" : 3, + "axis_seq" : 4, + "weights" : { + + }, + "debug_info" : "571", + "top" : "571", + "type" : "transpose", + "name" : "571" + }, + { + "name" : "581", + "weights" : { + + }, + "dst_w" : 4, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : -1, + "mode" : 0, + "bottom" : "571", + "debug_info" : "581", + "dst_seq" : 1, + "dst_k" : 1, + "top" : "581" + }, + { + "bottom" : "509,566", + "weights" : { + + }, + "simple_concat" : 1, + "hint_fallback_from_metal" : 1, + "debug_info" : "582", + "top" : "582", + "type" : "sequence_concat", + "name" : "582" + }, + { + "axis_h" : 2, + "axis_w" : 0, + "bottom" : "524", + "axis_k" : 1, + "axis_n" : 3, + "axis_seq" : 4, + "weights" : { + + }, + "debug_info" : "boxes_input_transpose0", + "top" : "boxes_524_transpose", + "type" : "transpose", + "name" : "boxes_input_transpose0" + }, + { + "axis_h" : 2, + "axis_w" : 0, + "bottom" : "581", + "axis_k" : 1, + "axis_n" : 3, + "axis_seq" : 4, + "weights" : { + + }, + "debug_info" : "boxes_input_transpose1", + "top" : "boxes_581_transpose", + "type" : "transpose", + "name" : "boxes_input_transpose1" + }, + { + "weights" : { + + }, + "debug_info" : "boxes", + "top" : "boxes_transpose", + "type" : "concat", + "name" : "boxes", + "bottom" : "boxes_524_transpose,boxes_581_transpose" + }, + { + "axis_seq" : 4, + "name" : "boxes_output_transpose0", + "axis_n" : 3, + "axis_h" : 2, + "type" : "transpose", + "attributes" : { + "is_output" : 1 + }, + "bottom" : "boxes_transpose", + "axis_w" : 0, + "axis_k" : 1, + "debug_info" : "boxes_output_transpose0", + "weights" : { + + }, + "top" : "boxes" + }, + { + "bottom" : "582", + "alpha" : 1, + "operation" : 27, + "weights" : { + + }, + "hint_fallback_from_metal" : 1, + "fused_relu" : 0, + "debug_info" : "584", + "top" : "584", + "type" : "elementwise", + "name" : "584", + "beta" : 0 + }, + { + "bottom" : "584", + "axis_mode" : 4, + "weights" : { + + }, + "mode" : 0, + "hint_fallback_from_metal" : 1, + "debug_info" : "585", + "use_version" : 1, + "top" : "585", + "type" : "reduce", + "name" : "585" + }, + { + "bottom" : "585", + "weights" : { + + }, + "mode" : 0, + "nd_axis" : 0, + "debug_info" : "587", + "top" : "587", + "type" : "flatten", + "name" : "587" + }, + { + "bottom" : "584", + "weights" : { + + }, + "mode" : 6, + "hint_fallback_from_metal" : 1, + "debug_info" : "588", + "top" : "588", + "type" : "activation", + "name" : "588", + "beta" : 0 + }, + { + "bottom" : "588", + "weights" : { + + }, + "mode" : 6, + "hint_fallback_from_metal" : 1, + "debug_info" : "589", + "top" : "589", + "type" : "activation", + "name" : "589", + "beta" : 0 + }, + { + "bottom" : "587", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "590", + "top" : "590", + "type" : "activation", + "name" : "590", + "beta" : 0 + }, + { + "bottom" : "590", + "alpha" : 1, + "operation" : 10, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "591_inverse", + "top" : "590_inverse", + "type" : "elementwise", + "name" : "591_inverse", + "beta" : 0 + }, + { + "bottom" : "589,590_inverse", + "alpha" : 1, + "operation" : 1, + "weights" : { + + }, + "hint_fallback_from_metal" : 1, + "fused_relu" : 0, + "debug_info" : "591", + "top" : "591", + "type" : "elementwise", + "name" : "591", + "beta" : 0 + }, + { + "bottom" : "591", + "weights" : { + + }, + "mode" : 6, + "hint_fallback_from_metal" : 1, + "debug_info" : "592", + "top" : "592", + "type" : "activation", + "name" : "592", + "beta" : 0 + }, + { + "bottom" : "592", + "end" : 11, + "start" : 1, + "weights" : { + + }, + "hint_fallback_from_metal" : 1, + "debug_info" : "593", + "axis" : 2, + "top" : "593", + "type" : "slice", + "name" : "593" + }, + { + "bottom" : "593", + "axis_mode" : 4, + "weights" : { + + }, + "mode" : 0, + "hint_fallback_from_metal" : 1, + "debug_info" : "594", + "use_version" : 1, + "top" : "594", + "type" : "reduce", + "name" : "594" + }, + { + "dst_seq" : 1, + "weights" : { + + }, + "dst_w" : 10, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : -1, + "mode" : 0, + "attributes" : { + "is_output" : 1 + }, + "bottom" : "593", + "debug_info" : "scores", + "hint_fallback_from_metal" : 1, + "dst_k" : 1, + "name" : "scores", + "top" : "scores" + }, + { + "bottom" : "594", + "weights" : { + + }, + "mode" : 0, + "nd_axis" : 0, + "debug_info" : "filter", + "top" : "filter", + "type" : "flatten", + "name" : "filter", + "attributes" : { + "is_output" : 1 + } + } + ] +} \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.shape b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.shape new file mode 100644 index 00000000..9e6d92c3 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.shape @@ -0,0 +1,661 @@ +{ + "layer_shapes" : { + "584" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "407" : { + "k" : 72, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "430" : { + "k" : 96, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "569" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "415" : { + "k" : 96, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "502" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "592" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "511_scale_0_1" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "585" : { + "k" : 1, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "593" : { + "k" : 10, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "424" : { + "k" : 96, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "499" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "511" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "409" : { + "k" : 16, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "594" : { + "k" : 1, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "417" : { + "k" : 16, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "filter" : { + "k" : 1, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "512" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "587" : { + "k" : 1, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "433" : { + "k" : 96, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "418" : { + "k" : 16, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "441" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "505" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "426" : { + "k" : 16, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "513" : { + "k" : 12, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "588" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "568_scale_0_1" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "506" : { + "k" : 33, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "427" : { + "k" : 16, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "450" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "514" : { + "k" : 24, + "w" : 12, + "n" : 1, + "h" : 38 + }, + "435" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "568_clip_0_1" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "589" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "443" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "507" : { + "k" : 24, + "w" : 33, + "n" : 1, + "h" : 38 + }, + "530" : { + "k" : 480, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "590_inverse" : { + "k" : 1, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "444" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "452" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "524" : { + "k" : 1, + "w" : 4, + "n" : 1, + "h" : 2736 + }, + "509" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 2736, + "h" : 1 + }, + "532" : { + "k" : 80, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "453" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "438" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "461" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "382" : { + "k" : 16, + "w" : 300, + "n" : 1, + "h" : 188 + }, + "533" : { + "k" : 80, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "390" : { + "k" : 48, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "541" : { + "k" : 80, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "462" : { + "k" : 32, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "470" : { + "k" : 48, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "447" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "542" : { + "k" : 80, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "527" : { + "k" : 480, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "550" : { + "k" : 160, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "384" : { + "k" : 8, + "w" : 300, + "n" : 1, + "h" : 188 + }, + "392" : { + "k" : 12, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "456" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "511_clip_0_1" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "536" : { + "k" : 480, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "465" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "473" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "545" : { + "k" : 480, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "379" : { + "k" : 16, + "w" : 300, + "n" : 1, + "h" : 188 + }, + "553" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "387" : { + "k" : 48, + "w" : 300, + "n" : 1, + "h" : 188 + }, + "395" : { + "k" : 72, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "459" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "482" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "400" : { + "k" : 12, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "539" : { + "k" : 480, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "562" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "570" : { + "k" : 12, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "boxes_transpose" : { + "k" : 3420, + "w" : 4, + "n" : 1, + "h" : 1 + }, + "468" : { + "k" : 192, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "491" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "401" : { + "k" : 12, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "476" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "563" : { + "k" : 33, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "548" : { + "k" : 480, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "571" : { + "k" : 12, + "w" : 12, + "n" : 1, + "h" : 19 + }, + "556" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "564" : { + "k" : 12, + "w" : 33, + "n" : 1, + "h" : 19 + }, + "398" : { + "k" : 72, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "0" : { + "k" : 3, + "w" : 600, + "n" : 1, + "h" : 375 + }, + "485" : { + "k" : 288, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "boxes" : { + "k" : 1, + "w" : 4, + "n" : 1, + "h" : 3420 + }, + "boxes_524_transpose" : { + "k" : 2736, + "w" : 4, + "n" : 1, + "h" : 1 + }, + "478" : { + "k" : 48, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "scores" : { + "k" : 1, + "w" : 10, + "n" : 1, + "h" : 3420 + }, + "boxes_581_transpose" : { + "k" : 684, + "w" : 4, + "n" : 1, + "h" : 1 + }, + "494" : { + "k" : 288, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "581" : { + "k" : 1, + "w" : 4, + "n" : 1, + "h" : 684 + }, + "404" : { + "k" : 72, + "w" : 150, + "n" : 1, + "h" : 94 + }, + "479" : { + "k" : 48, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "566" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 684, + "h" : 1 + }, + "412" : { + "k" : 96, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "487" : { + "k" : 48, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "559" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "582" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "590" : { + "k" : 1, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + }, + "488" : { + "k" : 48, + "w" : 38, + "n" : 1, + "h" : 24 + }, + "421" : { + "k" : 96, + "w" : 75, + "n" : 1, + "h" : 47 + }, + "496" : { + "k" : 80, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "568" : { + "k" : 1280, + "w" : 19, + "n" : 1, + "h" : 12 + }, + "591" : { + "k" : 11, + "w" : 1, + "n" : 1, + "seq" : 3420, + "h" : 1 + } + } +} \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.weights b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.weights new file mode 100644 index 00000000..727f6b6e Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model.espresso.weights differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model/coremldata.bin new file mode 100644 index 00000000..ce9aa8bf Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/model/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/neural_network_optionals/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/neural_network_optionals/coremldata.bin new file mode 100644 index 00000000..6459c295 Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/SSDOcr.mlmodelc/neural_network_optionals/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/analytics/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/analytics/coremldata.bin new file mode 100644 index 00000000..a859710c Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/analytics/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/coremldata.bin new file mode 100644 index 00000000..5f4878e5 Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/metadata.json b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/metadata.json new file mode 100644 index 00000000..9a994a9b --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/metadata.json @@ -0,0 +1,66 @@ +[ + { + "shortDescription" : "", + "metadataOutputVersion" : "3.0", + "outputSchema" : [ + { + "hasShapeFlexibility" : "0", + "isOptional" : "0", + "dataType" : "Double", + "formattedType" : "MultiArray (Double 3)", + "shortDescription" : "", + "shape" : "[3]", + "name" : "output1", + "type" : "MultiArray" + } + ], + "version" : "", + "modelParameters" : [ + + ], + "author" : "", + "specificationVersion" : 2, + "license" : "", + "isUpdatable" : "0", + "availability" : { + "macOS" : "10.13.2", + "tvOS" : "11.2", + "watchOS" : "4.2", + "iOS" : "11.2", + "macCatalyst" : "11.2" + }, + "modelType" : { + "name" : "MLModelType_neuralNetwork" + }, + "inputSchema" : [ + { + "height" : "224", + "colorspace" : "RGB", + "isOptional" : "0", + "width" : "224", + "isColor" : "1", + "formattedType" : "Image (Color 224 × 224)", + "hasSizeFlexibility" : "0", + "type" : "Image", + "shortDescription" : "", + "name" : "input1" + } + ], + "userDefinedMetadata" : { + "coremltoolsVersion" : "3.3" + }, + "generatedClassName" : "UxModel", + "neuralNetworkLayerTypeHistogram" : { + "ActivationLinear" : 72, + "ActivationReLU" : 36, + "Softmax" : 1, + "Add" : 10, + "PoolingAverage" : 1, + "UnaryThreshold" : 36, + "BatchNorm" : 53, + "Convolution" : 54, + "Reshape" : 2 + }, + "method" : "predict" + } +] \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.net b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.net new file mode 100644 index 00000000..c289e4f7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.net @@ -0,0 +1,3252 @@ +{ + "transform_params" : { + "input1" : { + "bias_a" : 0, + "bias_g" : 0, + "bias_r" : 0, + "bias_b" : 0, + "center_mean" : 0, + "is_network_bgr" : 0, + "scale" : 0.0039215688593685627 + } + }, + "properties" : { + + }, + "analyses" : { + + }, + "format_version" : 200, + "storage" : "model.espresso.weights", + "layers" : [ + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_1", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_1_output_relu", + "K" : 3, + "blob_biases" : 1, + "stride_x" : 2, + "name" : "conv2d_1", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "stride_y" : 2, + "has_biases" : 1, + "C" : 16, + "bottom" : "input1", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 3 + }, + { + "alpha" : -1, + "bottom" : "activation_1_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_1__neg__", + "top" : "activation_1_output_relu_neg", + "type" : "activation", + "name" : "activation_1__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_1_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_1__clip__", + "top" : "activation_1_output_relu_clip", + "type" : "elementwise", + "name" : "activation_1__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_1_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_1_neg2", + "top" : "activation_1_output", + "type" : "activation", + "name" : "activation_1_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_2", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_2_output_relu", + "K" : 16, + "blob_biases" : 5, + "name" : "conv2d_2", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "activation_1_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 7 + }, + { + "alpha" : -1, + "bottom" : "activation_2_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_2__neg__", + "top" : "activation_2_output_relu_neg", + "type" : "activation", + "name" : "activation_2__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_2_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_2__clip__", + "top" : "activation_2_output_relu_clip", + "type" : "elementwise", + "name" : "activation_2__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_2_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_2_neg2", + "top" : "activation_2_output", + "type" : "activation", + "name" : "activation_2_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_1", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_3_output_relu", + "K" : 16, + "blob_biases" : 9, + "name" : "depthwise_conv2d_1", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 16, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "activation_2_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 11 + }, + { + "alpha" : -1, + "bottom" : "activation_3_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_3__neg__", + "top" : "activation_3_output_relu_neg", + "type" : "activation", + "name" : "activation_3__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_3_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_3__clip__", + "top" : "activation_3_output_relu_clip", + "type" : "elementwise", + "name" : "activation_3__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_3_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_3_neg2", + "top" : "activation_3_output", + "type" : "activation", + "name" : "activation_3_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_3", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_4_output", + "K" : 16, + "blob_biases" : 13, + "name" : "conv2d_3", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 8, + "bottom" : "activation_3_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 15 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_4", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_4_output_relu", + "K" : 8, + "blob_biases" : 17, + "name" : "conv2d_4", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "batch_normalization_4_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 19 + }, + { + "alpha" : -1, + "bottom" : "activation_4_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_4__neg__", + "top" : "activation_4_output_relu_neg", + "type" : "activation", + "name" : "activation_4__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_4_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_4__clip__", + "top" : "activation_4_output_relu_clip", + "type" : "elementwise", + "name" : "activation_4__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_4_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_4_neg2", + "top" : "activation_4_output", + "type" : "activation", + "name" : "activation_4_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_2", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_5_output_relu", + "K" : 48, + "blob_biases" : 21, + "stride_x" : 2, + "name" : "depthwise_conv2d_2", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 48, + "pad_t" : 0, + "stride_y" : 2, + "has_biases" : 1, + "C" : 48, + "bottom" : "activation_4_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 23 + }, + { + "alpha" : -1, + "bottom" : "activation_5_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_5__neg__", + "top" : "activation_5_output_relu_neg", + "type" : "activation", + "name" : "activation_5__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_5_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_5__clip__", + "top" : "activation_5_output_relu_clip", + "type" : "elementwise", + "name" : "activation_5__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_5_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_5_neg2", + "top" : "activation_5_output", + "type" : "activation", + "name" : "activation_5_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_5", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_7_output", + "K" : 48, + "blob_biases" : 25, + "name" : "conv2d_5", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 12, + "bottom" : "activation_5_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 27 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_6", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_6_output_relu", + "K" : 12, + "blob_biases" : 29, + "name" : "conv2d_6", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 72, + "bottom" : "batch_normalization_7_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 31 + }, + { + "alpha" : -1, + "bottom" : "activation_6_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_6__neg__", + "top" : "activation_6_output_relu_neg", + "type" : "activation", + "name" : "activation_6__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_6_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_6__clip__", + "top" : "activation_6_output_relu_clip", + "type" : "elementwise", + "name" : "activation_6__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_6_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_6_neg2", + "top" : "activation_6_output", + "type" : "activation", + "name" : "activation_6_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_3", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_7_output_relu", + "K" : 72, + "blob_biases" : 33, + "name" : "depthwise_conv2d_3", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 72, + "pad_t" : 0, + "has_biases" : 1, + "C" : 72, + "bottom" : "activation_6_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 35 + }, + { + "alpha" : -1, + "bottom" : "activation_7_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_7__neg__", + "top" : "activation_7_output_relu_neg", + "type" : "activation", + "name" : "activation_7__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_7_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_7__clip__", + "top" : "activation_7_output_relu_clip", + "type" : "elementwise", + "name" : "activation_7__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_7_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_7_neg2", + "top" : "activation_7_output", + "type" : "activation", + "name" : "activation_7_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_7", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_10_output", + "K" : 72, + "blob_biases" : 37, + "name" : "conv2d_7", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 12, + "bottom" : "activation_7_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 39 + }, + { + "bottom" : "batch_normalization_10_output,batch_normalization_7_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_1", + "top" : "add_1_output", + "type" : "elementwise", + "name" : "add_1", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_8", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_8_output_relu", + "K" : 12, + "blob_biases" : 41, + "name" : "conv2d_8", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 72, + "bottom" : "add_1_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 43 + }, + { + "alpha" : -1, + "bottom" : "activation_8_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_8__neg__", + "top" : "activation_8_output_relu_neg", + "type" : "activation", + "name" : "activation_8__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_8_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_8__clip__", + "top" : "activation_8_output_relu_clip", + "type" : "elementwise", + "name" : "activation_8__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_8_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_8_neg2", + "top" : "activation_8_output", + "type" : "activation", + "name" : "activation_8_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_4", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_9_output_relu", + "K" : 72, + "blob_biases" : 45, + "stride_x" : 2, + "name" : "depthwise_conv2d_4", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 72, + "pad_t" : 0, + "stride_y" : 2, + "has_biases" : 1, + "C" : 72, + "bottom" : "activation_8_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 47 + }, + { + "alpha" : -1, + "bottom" : "activation_9_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_9__neg__", + "top" : "activation_9_output_relu_neg", + "type" : "activation", + "name" : "activation_9__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_9_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_9__clip__", + "top" : "activation_9_output_relu_clip", + "type" : "elementwise", + "name" : "activation_9__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_9_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_9_neg2", + "top" : "activation_9_output", + "type" : "activation", + "name" : "activation_9_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_9", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_13_output", + "K" : 72, + "blob_biases" : 49, + "name" : "conv2d_9", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "activation_9_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 51 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_10", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_10_output_relu", + "K" : 16, + "blob_biases" : 53, + "name" : "conv2d_10", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "batch_normalization_13_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 55 + }, + { + "alpha" : -1, + "bottom" : "activation_10_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_10__neg__", + "top" : "activation_10_output_relu_neg", + "type" : "activation", + "name" : "activation_10__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_10_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_10__clip__", + "top" : "activation_10_output_relu_clip", + "type" : "elementwise", + "name" : "activation_10__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_10_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_10_neg2", + "top" : "activation_10_output", + "type" : "activation", + "name" : "activation_10_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_5", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_11_output_relu", + "K" : 96, + "blob_biases" : 57, + "name" : "depthwise_conv2d_5", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 96, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "activation_10_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 59 + }, + { + "alpha" : -1, + "bottom" : "activation_11_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_11__neg__", + "top" : "activation_11_output_relu_neg", + "type" : "activation", + "name" : "activation_11__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_11_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_11__clip__", + "top" : "activation_11_output_relu_clip", + "type" : "elementwise", + "name" : "activation_11__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_11_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_11_neg2", + "top" : "activation_11_output", + "type" : "activation", + "name" : "activation_11_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_11", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_16_output", + "K" : 96, + "blob_biases" : 61, + "name" : "conv2d_11", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "activation_11_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 63 + }, + { + "bottom" : "batch_normalization_16_output,batch_normalization_13_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_2", + "top" : "add_2_output", + "type" : "elementwise", + "name" : "add_2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_12", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_12_output_relu", + "K" : 16, + "blob_biases" : 65, + "name" : "conv2d_12", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "add_2_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 67 + }, + { + "alpha" : -1, + "bottom" : "activation_12_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_12__neg__", + "top" : "activation_12_output_relu_neg", + "type" : "activation", + "name" : "activation_12__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_12_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_12__clip__", + "top" : "activation_12_output_relu_clip", + "type" : "elementwise", + "name" : "activation_12__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_12_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_12_neg2", + "top" : "activation_12_output", + "type" : "activation", + "name" : "activation_12_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_6", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_13_output_relu", + "K" : 96, + "blob_biases" : 69, + "name" : "depthwise_conv2d_6", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 96, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "activation_12_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 71 + }, + { + "alpha" : -1, + "bottom" : "activation_13_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_13__neg__", + "top" : "activation_13_output_relu_neg", + "type" : "activation", + "name" : "activation_13__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_13_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_13__clip__", + "top" : "activation_13_output_relu_clip", + "type" : "elementwise", + "name" : "activation_13__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_13_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_13_neg2", + "top" : "activation_13_output", + "type" : "activation", + "name" : "activation_13_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_13", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_19_output", + "K" : 96, + "blob_biases" : 73, + "name" : "conv2d_13", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 16, + "bottom" : "activation_13_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 75 + }, + { + "bottom" : "batch_normalization_19_output,add_2_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_3", + "top" : "add_3_output", + "type" : "elementwise", + "name" : "add_3", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_14", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_14_output_relu", + "K" : 16, + "blob_biases" : 77, + "name" : "conv2d_14", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 96, + "bottom" : "add_3_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 79 + }, + { + "alpha" : -1, + "bottom" : "activation_14_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_14__neg__", + "top" : "activation_14_output_relu_neg", + "type" : "activation", + "name" : "activation_14__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_14_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_14__clip__", + "top" : "activation_14_output_relu_clip", + "type" : "elementwise", + "name" : "activation_14__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_14_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_14_neg2", + "top" : "activation_14_output", + "type" : "activation", + "name" : "activation_14_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_7", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_15_output_relu", + "K" : 96, + "blob_biases" : 81, + "stride_x" : 2, + "name" : "depthwise_conv2d_7", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 96, + "pad_t" : 0, + "stride_y" : 2, + "has_biases" : 1, + "C" : 96, + "bottom" : "activation_14_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 83 + }, + { + "alpha" : -1, + "bottom" : "activation_15_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_15__neg__", + "top" : "activation_15_output_relu_neg", + "type" : "activation", + "name" : "activation_15__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_15_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_15__clip__", + "top" : "activation_15_output_relu_clip", + "type" : "elementwise", + "name" : "activation_15__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_15_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_15_neg2", + "top" : "activation_15_output", + "type" : "activation", + "name" : "activation_15_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_15", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_22_output", + "K" : 96, + "blob_biases" : 85, + "name" : "conv2d_15", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "activation_15_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 87 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_16", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_16_output_relu", + "K" : 32, + "blob_biases" : 89, + "name" : "conv2d_16", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "batch_normalization_22_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 91 + }, + { + "alpha" : -1, + "bottom" : "activation_16_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_16__neg__", + "top" : "activation_16_output_relu_neg", + "type" : "activation", + "name" : "activation_16__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_16_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_16__clip__", + "top" : "activation_16_output_relu_clip", + "type" : "elementwise", + "name" : "activation_16__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_16_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_16_neg2", + "top" : "activation_16_output", + "type" : "activation", + "name" : "activation_16_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_8", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_17_output_relu", + "K" : 192, + "blob_biases" : 93, + "name" : "depthwise_conv2d_8", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "activation_16_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 95 + }, + { + "alpha" : -1, + "bottom" : "activation_17_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_17__neg__", + "top" : "activation_17_output_relu_neg", + "type" : "activation", + "name" : "activation_17__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_17_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_17__clip__", + "top" : "activation_17_output_relu_clip", + "type" : "elementwise", + "name" : "activation_17__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_17_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_17_neg2", + "top" : "activation_17_output", + "type" : "activation", + "name" : "activation_17_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_17", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_25_output", + "K" : 192, + "blob_biases" : 97, + "name" : "conv2d_17", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "activation_17_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 99 + }, + { + "bottom" : "batch_normalization_25_output,batch_normalization_22_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_4", + "top" : "add_4_output", + "type" : "elementwise", + "name" : "add_4", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_18", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_18_output_relu", + "K" : 32, + "blob_biases" : 101, + "name" : "conv2d_18", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "add_4_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 103 + }, + { + "alpha" : -1, + "bottom" : "activation_18_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_18__neg__", + "top" : "activation_18_output_relu_neg", + "type" : "activation", + "name" : "activation_18__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_18_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_18__clip__", + "top" : "activation_18_output_relu_clip", + "type" : "elementwise", + "name" : "activation_18__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_18_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_18_neg2", + "top" : "activation_18_output", + "type" : "activation", + "name" : "activation_18_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_9", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_19_output_relu", + "K" : 192, + "blob_biases" : 105, + "name" : "depthwise_conv2d_9", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "activation_18_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 107 + }, + { + "alpha" : -1, + "bottom" : "activation_19_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_19__neg__", + "top" : "activation_19_output_relu_neg", + "type" : "activation", + "name" : "activation_19__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_19_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_19__clip__", + "top" : "activation_19_output_relu_clip", + "type" : "elementwise", + "name" : "activation_19__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_19_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_19_neg2", + "top" : "activation_19_output", + "type" : "activation", + "name" : "activation_19_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_19", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_28_output", + "K" : 192, + "blob_biases" : 109, + "name" : "conv2d_19", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "activation_19_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 111 + }, + { + "bottom" : "batch_normalization_28_output,add_4_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_5", + "top" : "add_5_output", + "type" : "elementwise", + "name" : "add_5", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_20", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_20_output_relu", + "K" : 32, + "blob_biases" : 113, + "name" : "conv2d_20", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "add_5_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 115 + }, + { + "alpha" : -1, + "bottom" : "activation_20_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_20__neg__", + "top" : "activation_20_output_relu_neg", + "type" : "activation", + "name" : "activation_20__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_20_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_20__clip__", + "top" : "activation_20_output_relu_clip", + "type" : "elementwise", + "name" : "activation_20__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_20_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_20_neg2", + "top" : "activation_20_output", + "type" : "activation", + "name" : "activation_20_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_10", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_21_output_relu", + "K" : 192, + "blob_biases" : 117, + "name" : "depthwise_conv2d_10", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "activation_20_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 119 + }, + { + "alpha" : -1, + "bottom" : "activation_21_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_21__neg__", + "top" : "activation_21_output_relu_neg", + "type" : "activation", + "name" : "activation_21__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_21_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_21__clip__", + "top" : "activation_21_output_relu_clip", + "type" : "elementwise", + "name" : "activation_21__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_21_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_21_neg2", + "top" : "activation_21_output", + "type" : "activation", + "name" : "activation_21_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_21", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_31_output", + "K" : 192, + "blob_biases" : 121, + "name" : "conv2d_21", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 32, + "bottom" : "activation_21_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 123 + }, + { + "bottom" : "batch_normalization_31_output,add_5_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_6", + "top" : "add_6_output", + "type" : "elementwise", + "name" : "add_6", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_22", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_22_output_relu", + "K" : 32, + "blob_biases" : 125, + "name" : "conv2d_22", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "add_6_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 127 + }, + { + "alpha" : -1, + "bottom" : "activation_22_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_22__neg__", + "top" : "activation_22_output_relu_neg", + "type" : "activation", + "name" : "activation_22__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_22_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_22__clip__", + "top" : "activation_22_output_relu_clip", + "type" : "elementwise", + "name" : "activation_22__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_22_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_22_neg2", + "top" : "activation_22_output", + "type" : "activation", + "name" : "activation_22_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_11", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_23_output_relu", + "K" : 192, + "blob_biases" : 129, + "name" : "depthwise_conv2d_11", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 192, + "pad_t" : 0, + "has_biases" : 1, + "C" : 192, + "bottom" : "activation_22_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 131 + }, + { + "alpha" : -1, + "bottom" : "activation_23_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_23__neg__", + "top" : "activation_23_output_relu_neg", + "type" : "activation", + "name" : "activation_23__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_23_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_23__clip__", + "top" : "activation_23_output_relu_clip", + "type" : "elementwise", + "name" : "activation_23__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_23_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_23_neg2", + "top" : "activation_23_output", + "type" : "activation", + "name" : "activation_23_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_23", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_34_output", + "K" : 192, + "blob_biases" : 133, + "name" : "conv2d_23", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "activation_23_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 135 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_24", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_24_output_relu", + "K" : 48, + "blob_biases" : 137, + "name" : "conv2d_24", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "batch_normalization_34_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 139 + }, + { + "alpha" : -1, + "bottom" : "activation_24_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_24__neg__", + "top" : "activation_24_output_relu_neg", + "type" : "activation", + "name" : "activation_24__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_24_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_24__clip__", + "top" : "activation_24_output_relu_clip", + "type" : "elementwise", + "name" : "activation_24__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_24_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_24_neg2", + "top" : "activation_24_output", + "type" : "activation", + "name" : "activation_24_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_12", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_25_output_relu", + "K" : 288, + "blob_biases" : 141, + "name" : "depthwise_conv2d_12", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "activation_24_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 143 + }, + { + "alpha" : -1, + "bottom" : "activation_25_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_25__neg__", + "top" : "activation_25_output_relu_neg", + "type" : "activation", + "name" : "activation_25__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_25_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_25__clip__", + "top" : "activation_25_output_relu_clip", + "type" : "elementwise", + "name" : "activation_25__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_25_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_25_neg2", + "top" : "activation_25_output", + "type" : "activation", + "name" : "activation_25_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_25", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_37_output", + "K" : 288, + "blob_biases" : 145, + "name" : "conv2d_25", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "activation_25_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 147 + }, + { + "bottom" : "batch_normalization_37_output,batch_normalization_34_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_7", + "top" : "add_7_output", + "type" : "elementwise", + "name" : "add_7", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_26", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_26_output_relu", + "K" : 48, + "blob_biases" : 149, + "name" : "conv2d_26", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "add_7_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 151 + }, + { + "alpha" : -1, + "bottom" : "activation_26_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_26__neg__", + "top" : "activation_26_output_relu_neg", + "type" : "activation", + "name" : "activation_26__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_26_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_26__clip__", + "top" : "activation_26_output_relu_clip", + "type" : "elementwise", + "name" : "activation_26__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_26_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_26_neg2", + "top" : "activation_26_output", + "type" : "activation", + "name" : "activation_26_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_13", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_27_output_relu", + "K" : 288, + "blob_biases" : 153, + "name" : "depthwise_conv2d_13", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "activation_26_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 155 + }, + { + "alpha" : -1, + "bottom" : "activation_27_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_27__neg__", + "top" : "activation_27_output_relu_neg", + "type" : "activation", + "name" : "activation_27__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_27_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_27__clip__", + "top" : "activation_27_output_relu_clip", + "type" : "elementwise", + "name" : "activation_27__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_27_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_27_neg2", + "top" : "activation_27_output", + "type" : "activation", + "name" : "activation_27_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_27", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_40_output", + "K" : 288, + "blob_biases" : 157, + "name" : "conv2d_27", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 48, + "bottom" : "activation_27_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 159 + }, + { + "bottom" : "batch_normalization_40_output,add_7_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_8", + "top" : "add_8_output", + "type" : "elementwise", + "name" : "add_8", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_28", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_28_output_relu", + "K" : 48, + "blob_biases" : 161, + "name" : "conv2d_28", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 288, + "bottom" : "add_8_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 163 + }, + { + "alpha" : -1, + "bottom" : "activation_28_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_28__neg__", + "top" : "activation_28_output_relu_neg", + "type" : "activation", + "name" : "activation_28__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_28_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_28__clip__", + "top" : "activation_28_output_relu_clip", + "type" : "elementwise", + "name" : "activation_28__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_28_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_28_neg2", + "top" : "activation_28_output", + "type" : "activation", + "name" : "activation_28_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_14", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_29_output_relu", + "K" : 288, + "blob_biases" : 165, + "stride_x" : 2, + "name" : "depthwise_conv2d_14", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 288, + "pad_t" : 0, + "stride_y" : 2, + "has_biases" : 1, + "C" : 288, + "bottom" : "activation_28_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 167 + }, + { + "alpha" : -1, + "bottom" : "activation_29_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_29__neg__", + "top" : "activation_29_output_relu_neg", + "type" : "activation", + "name" : "activation_29__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_29_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_29__clip__", + "top" : "activation_29_output_relu_clip", + "type" : "elementwise", + "name" : "activation_29__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_29_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_29_neg2", + "top" : "activation_29_output", + "type" : "activation", + "name" : "activation_29_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_29", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_43_output", + "K" : 288, + "blob_biases" : 169, + "name" : "conv2d_29", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 80, + "bottom" : "activation_29_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 171 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_30", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_30_output_relu", + "K" : 80, + "blob_biases" : 173, + "name" : "conv2d_30", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "batch_normalization_43_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 175 + }, + { + "alpha" : -1, + "bottom" : "activation_30_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_30__neg__", + "top" : "activation_30_output_relu_neg", + "type" : "activation", + "name" : "activation_30__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_30_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_30__clip__", + "top" : "activation_30_output_relu_clip", + "type" : "elementwise", + "name" : "activation_30__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_30_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_30_neg2", + "top" : "activation_30_output", + "type" : "activation", + "name" : "activation_30_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_15", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_31_output_relu", + "K" : 480, + "blob_biases" : 177, + "name" : "depthwise_conv2d_15", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 480, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "activation_30_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 179 + }, + { + "alpha" : -1, + "bottom" : "activation_31_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_31__neg__", + "top" : "activation_31_output_relu_neg", + "type" : "activation", + "name" : "activation_31__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_31_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_31__clip__", + "top" : "activation_31_output_relu_clip", + "type" : "elementwise", + "name" : "activation_31__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_31_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_31_neg2", + "top" : "activation_31_output", + "type" : "activation", + "name" : "activation_31_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_31", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_46_output", + "K" : 480, + "blob_biases" : 181, + "name" : "conv2d_31", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 80, + "bottom" : "activation_31_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 183 + }, + { + "bottom" : "batch_normalization_46_output,batch_normalization_43_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_9", + "top" : "add_9_output", + "type" : "elementwise", + "name" : "add_9", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_32", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_32_output_relu", + "K" : 80, + "blob_biases" : 185, + "name" : "conv2d_32", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "add_9_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 187 + }, + { + "alpha" : -1, + "bottom" : "activation_32_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_32__neg__", + "top" : "activation_32_output_relu_neg", + "type" : "activation", + "name" : "activation_32__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_32_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_32__clip__", + "top" : "activation_32_output_relu_clip", + "type" : "elementwise", + "name" : "activation_32__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_32_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_32_neg2", + "top" : "activation_32_output", + "type" : "activation", + "name" : "activation_32_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_16", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_33_output_relu", + "K" : 480, + "blob_biases" : 189, + "name" : "depthwise_conv2d_16", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 480, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "activation_32_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 191 + }, + { + "alpha" : -1, + "bottom" : "activation_33_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_33__neg__", + "top" : "activation_33_output_relu_neg", + "type" : "activation", + "name" : "activation_33__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_33_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_33__clip__", + "top" : "activation_33_output_relu_clip", + "type" : "elementwise", + "name" : "activation_33__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_33_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_33_neg2", + "top" : "activation_33_output", + "type" : "activation", + "name" : "activation_33_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_33", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_49_output", + "K" : 480, + "blob_biases" : 193, + "name" : "conv2d_33", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 80, + "bottom" : "activation_33_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 195 + }, + { + "bottom" : "batch_normalization_49_output,add_9_output", + "alpha" : 1, + "operation" : 0, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "add_10", + "top" : "add_10_output", + "type" : "elementwise", + "name" : "add_10", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_34", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_34_output_relu", + "K" : 80, + "blob_biases" : 197, + "name" : "conv2d_34", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "add_10_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 199 + }, + { + "alpha" : -1, + "bottom" : "activation_34_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_34__neg__", + "top" : "activation_34_output_relu_neg", + "type" : "activation", + "name" : "activation_34__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_34_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_34__clip__", + "top" : "activation_34_output_relu_clip", + "type" : "elementwise", + "name" : "activation_34__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_34_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_34_neg2", + "top" : "activation_34_output", + "type" : "activation", + "name" : "activation_34_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "depthwise_conv2d_17", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_35_output_relu", + "K" : 480, + "blob_biases" : 201, + "name" : "depthwise_conv2d_17", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 480, + "pad_t" : 0, + "has_biases" : 1, + "C" : 480, + "bottom" : "activation_34_output", + "weights" : { + + }, + "Nx" : 3, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 3, + "n_parallel" : 1, + "blob_weights_f16" : 203 + }, + { + "alpha" : -1, + "bottom" : "activation_35_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_35__neg__", + "top" : "activation_35_output_relu_neg", + "type" : "activation", + "name" : "activation_35__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_35_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_35__clip__", + "top" : "activation_35_output_relu_clip", + "type" : "elementwise", + "name" : "activation_35__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_35_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_35_neg2", + "top" : "activation_35_output", + "type" : "activation", + "name" : "activation_35_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_35", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "batch_normalization_52_output", + "K" : 480, + "blob_biases" : 205, + "name" : "conv2d_35", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 160, + "bottom" : "activation_35_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 207 + }, + { + "pad_r" : 0, + "fused_relu" : 1, + "fused_tanh" : 0, + "debug_info" : "conv2d_36", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "activation_36_output_relu", + "K" : 160, + "blob_biases" : 209, + "name" : "conv2d_36", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 1280, + "bottom" : "batch_normalization_52_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 211 + }, + { + "alpha" : -1, + "bottom" : "activation_36_output_relu", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_36__neg__", + "top" : "activation_36_output_relu_neg", + "type" : "activation", + "name" : "activation_36__neg__", + "beta" : 0 + }, + { + "bottom" : "activation_36_output_relu_neg", + "alpha" : -6, + "operation" : 25, + "weights" : { + + }, + "fused_relu" : 0, + "debug_info" : "activation_36__clip__", + "top" : "activation_36_output_relu_clip", + "type" : "elementwise", + "name" : "activation_36__clip__", + "beta" : 0 + }, + { + "alpha" : -1, + "bottom" : "activation_36_output_relu_clip", + "weights" : { + + }, + "mode" : 6, + "debug_info" : "activation_36_neg2", + "top" : "activation_36_output", + "type" : "activation", + "name" : "activation_36_neg2", + "beta" : 0 + }, + { + "pad_r" : 0, + "debug_info" : "global_average_pooling2d_1", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "size_x" : 7, + "is_global" : 1, + "top" : "global_average_pooling2d_1_output", + "top_shape_style" : 0, + "stride_x" : 1, + "avg_or_max" : 0, + "average_count_exclude_padding" : 1, + "type" : "pool", + "name" : "global_average_pooling2d_1", + "pad_t" : 0, + "stride_y" : 1, + "bottom" : "activation_36_output", + "weights" : { + + }, + "pad_mode" : 2, + "size_y" : 7, + "pad_value" : 0 + }, + { + "name" : "reshape_1", + "weights" : { + + }, + "dst_w" : 1, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : 1, + "mode" : 1, + "bottom" : "global_average_pooling2d_1_output", + "debug_info" : "reshape_1", + "dst_seq" : 1, + "dst_k" : 1280, + "top" : "reshape_1_output" + }, + { + "pad_r" : 0, + "fused_relu" : 0, + "fused_tanh" : 0, + "debug_info" : "conv2d_38", + "pad_fill_mode" : 0, + "pad_b" : 0, + "pad_l" : 0, + "top" : "conv2d_38_output", + "K" : 1280, + "blob_biases" : 213, + "name" : "conv2d_38", + "has_batch_norm" : 0, + "type" : "convolution", + "n_groups" : 1, + "pad_t" : 0, + "has_biases" : 1, + "C" : 3, + "bottom" : "reshape_1_output", + "weights" : { + + }, + "Nx" : 1, + "pad_mode" : 1, + "pad_value" : 0, + "Ny" : 1, + "n_parallel" : 1, + "blob_weights_f16" : 215 + }, + { + "bottom" : "conv2d_38_output", + "weights" : { + + }, + "debug_info" : "softmax", + "top" : "softmax_output", + "C" : 2, + "type" : "softmax", + "name" : "softmax" + }, + { + "name" : "reshape_3", + "weights" : { + + }, + "dst_w" : 1, + "version" : 1, + "dst_n" : 0, + "type" : "reshape", + "dst_h" : 1, + "mode" : 1, + "attributes" : { + "is_output" : 1 + }, + "bottom" : "softmax_output", + "debug_info" : "reshape_3", + "dst_k" : 3, + "dst_seq" : 1, + "top" : "output1" + } + ] +} \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.shape b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.shape new file mode 100644 index 00000000..ad7ffbb8 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.shape @@ -0,0 +1,1066 @@ +{ + "layer_shapes" : { + "activation_32_output_relu_neg" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_3_output_relu_clip" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_24_output_relu" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_13_output_relu_neg" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_34_output" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_32_output_relu_clip" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_2_output_relu_neg" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_18_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "input1" : { + "k" : 3, + "w" : 224, + "n" : 1, + "h" : 224 + }, + "activation_27_output_relu_clip" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "reshape_1_output" : { + "k" : 1280, + "w" : 1, + "n" : 1, + "h" : 1 + }, + "batch_normalization_46_output" : { + "k" : 80, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_3_output" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_33_output" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_31_output_relu_neg" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_11_output_relu" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_27_output_relu" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_8_output_relu" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_23_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_18_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_32_output" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_17_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "add_2_output" : { + "k" : 16, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_20_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_31_output" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_8_output_relu_neg" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_14_output_relu_clip" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_6_output_relu" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_14_output_relu" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "batch_normalization_4_output" : { + "k" : 8, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_35_output_relu_neg" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "batch_normalization_43_output" : { + "k" : 80, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_16_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_30_output" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_26_output_relu" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_34_output" : { + "k" : 48, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_52_output" : { + "k" : 160, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_34_output_relu_clip" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_10_output_relu_clip" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_29_output_relu_clip" : { + "k" : 288, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_9_output" : { + "k" : 72, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_4_output_relu" : { + "k" : 48, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_24_output" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_8_output_relu_clip" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_3_output_relu_neg" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_34_output_relu_neg" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_30_output_relu_clip" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_25_output_relu_clip" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_13_output_relu" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_23_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_29_output_relu" : { + "k" : 288, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "add_6_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "output1" : { + "k" : 3, + "w" : 1, + "n" : 1, + "h" : 1 + }, + "activation_23_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_5_output_relu_clip" : { + "k" : 48, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_22_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_40_output" : { + "k" : 48, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_31_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_21_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_16_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_9_output_relu_neg" : { + "k" : 72, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_16_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_8_output" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_19_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_21_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_22_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_28_output_relu" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_2_output_relu_clip" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_12_output_relu_clip" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "add_4_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_15_output" : { + "k" : 96, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_20_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "global_average_pooling2d_1_output" : { + "k" : 1280, + "w" : 1, + "n" : 1, + "h" : 1 + }, + "activation_4_output_relu_neg" : { + "k" : 48, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_19_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_31_output_relu" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_14_output" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "batch_normalization_7_output" : { + "k" : 12, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "batch_normalization_37_output" : { + "k" : 48, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_21_output_relu_neg" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_28_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_19_output" : { + "k" : 16, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_15_output_relu" : { + "k" : 96, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_5_output" : { + "k" : 48, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_13_output" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_36_output_relu_neg" : { + "k" : 1280, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_10_output_relu_neg" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_34_output_relu" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_12_output" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_5_output_relu" : { + "k" : 48, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_18_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_30_output_relu" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_25_output_relu_neg" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "add_8_output" : { + "k" : 48, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_11_output" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_20_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_25_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "batch_normalization_16_output" : { + "k" : 16, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_2_output" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_21_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_4_output" : { + "k" : 48, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_3_output_relu" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_10_output" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_36_output" : { + "k" : 1280, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_7_output_relu_clip" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_33_output_relu" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_24_output_relu_neg" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_5_output_relu_neg" : { + "k" : 48, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_35_output_relu_clip" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_11_output_relu_clip" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_17_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_35_output" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "add_5_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_1_output_relu" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_31_output_relu_clip" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_4_output_relu_clip" : { + "k" : 48, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_26_output_relu_clip" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_29_output" : { + "k" : 288, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_20_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_36_output_relu" : { + "k" : 1280, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "batch_normalization_22_output" : { + "k" : 32, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_1_output" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "batch_normalization_13_output" : { + "k" : 16, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_28_output_relu_neg" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "conv2d_38_output" : { + "k" : 3, + "w" : 1, + "n" : 1, + "h" : 1 + }, + "activation_32_output_relu" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_28_output" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_22_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_17_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_12_output_relu_neg" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_1_output_relu_clip" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_27_output" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "add_3_output" : { + "k" : 16, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_9_output_relu" : { + "k" : 72, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_23_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "softmax_output" : { + "k" : 3, + "w" : 1, + "n" : 1, + "h" : 1 + }, + "activation_27_output_relu_neg" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_13_output_relu_clip" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_35_output_relu" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_30_output_relu_neg" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_26_output" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_6_output_relu_neg" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "add_9_output" : { + "k" : 80, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_11_output_relu_neg" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "batch_normalization_10_output" : { + "k" : 12, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_33_output_relu_clip" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_7_output_relu" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_10_output_relu" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_28_output_relu_clip" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_25_output" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_26_output_relu_neg" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_7_output" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_22_output_relu" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_19_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "add_1_output" : { + "k" : 12, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_1_output_relu_neg" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_9_output_relu_clip" : { + "k" : 72, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_24_output_relu_clip" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_19_output_relu_clip" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_15_output_relu_neg" : { + "k" : 96, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_18_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "add_7_output" : { + "k" : 48, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_25_output_relu" : { + "k" : 288, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_2_output_relu" : { + "k" : 16, + "w" : 112, + "n" : 1, + "h" : 112 + }, + "activation_17_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_15_output_relu_clip" : { + "k" : 96, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_33_output_relu_neg" : { + "k" : 480, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "batch_normalization_49_output" : { + "k" : 80, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_6_output_relu_clip" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_14_output_relu_neg" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_6_output" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "activation_16_output" : { + "k" : 192, + "w" : 14, + "n" : 1, + "h" : 14 + }, + "activation_7_output_relu_neg" : { + "k" : 72, + "w" : 56, + "n" : 1, + "h" : 56 + }, + "add_10_output" : { + "k" : 80, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_12_output_relu" : { + "k" : 96, + "w" : 28, + "n" : 1, + "h" : 28 + }, + "activation_29_output_relu_neg" : { + "k" : 288, + "w" : 7, + "n" : 1, + "h" : 7 + }, + "activation_36_output_relu_clip" : { + "k" : 1280, + "w" : 7, + "n" : 1, + "h" : 7 + } + } +} \ No newline at end of file diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.weights b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.weights new file mode 100644 index 00000000..d58a125e Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model.espresso.weights differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model/coremldata.bin new file mode 100644 index 00000000..ee515f75 Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/model/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/neural_network_optionals/coremldata.bin b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/neural_network_optionals/coremldata.bin new file mode 100644 index 00000000..6459c295 Binary files /dev/null and b/StripeCardScan/StripeCardScan/Resources/CompiledModels/UxModel.mlmodelc/neural_network_optionals/coremldata.bin differ diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/bg-BG.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..377a1d6f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/bg-BG.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Картата не съвпада"; + +"Enable camera access" = "Активиране на достъпа до камера"; + +"Enter card details manually" = "Ръчно въвеждане на данните на картата"; + +"To scan your card you'll need to update your phone settings" = "За да сканирате Вашата карта, трябва да актуализирате настройките на телефона си."; + +"Torch" = "Фенерче"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/ca-ES.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..65a9acbf --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/ca-ES.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "La targeta no coincideix"; + +"Enable camera access" = "Habilita l'accés a la càmera"; + +"Enter card details manually" = "Introdueix manualment les dades de la targeta"; + +"To scan your card you'll need to update your phone settings" = "Per escanejar la teva targeta, caldrà que actualitzis la configuració del telèfon"; + +"Torch" = "Llanterna"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/cs-CZ.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..5345a1e9 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Karta neodpovídá"; + +"Enable camera access" = "Povolit přístup k fotoaparátu"; + +"Enter card details manually" = "Zadejte podrobnosti o kartě ručně"; + +"To scan your card you'll need to update your phone settings" = "K naskenování karty budete muset aktualizovat nastavení telefonu"; + +"Torch" = "Baterka"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/da.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/da.lproj/Localizable.strings new file mode 100644 index 00000000..800855f7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/da.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kortet matcher ikke"; + +"Enable camera access" = "Aktiver kameraadgang"; + +"Enter card details manually" = "Indtast kortoplysninger manuelt"; + +"To scan your card you'll need to update your phone settings" = "Du skal opdatere dine telefonindstillinger for at scanne dit kort"; + +"Torch" = "Lommelygte"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/de.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/de.lproj/Localizable.strings new file mode 100644 index 00000000..e1216b09 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/de.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Karten stimmen nicht überein"; + +"Enable camera access" = "Kamerazugriff erlauben"; + +"Enter card details manually" = "Kartenangaben manuell eingeben"; + +"To scan your card you'll need to update your phone settings" = "Um die Karte scannen zu können, müssen Sie Ihre Telefoneinstellungen aktualisieren."; + +"Torch" = "Taschenlampe"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/el-GR.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..f388ead3 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/el-GR.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Η κάρτα δεν αντιστοιχεί"; + +"Enable camera access" = "Ενεργοποιήστε την πρόσβαση στην κάμερα"; + +"Enter card details manually" = "Εισαγάγετε τα στοιχεία της κάρτας με μη αυτόματο τρόπο"; + +"To scan your card you'll need to update your phone settings" = "Για να σαρώσετε την κάρτα σας, θα πρέπει να ενημερώσετε τις ρυθμίσεις τηλεφώνου σας"; + +"Torch" = "Φακός"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/en-GB.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..23581679 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/en-GB.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Card doesn't match"; + +"Enable camera access" = "Enable camera access"; + +"Enter card details manually" = "Enter card details manually"; + +"To scan your card you'll need to update your phone settings" = "To scan your card, you'll need to update your phone settings"; + +"Torch" = "Torch"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/en.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..3dfd72af --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/en.lproj/Localizable.strings @@ -0,0 +1,15 @@ +/* Label of the error message when the scanned card mismatches the card on file */ +"Card doesn't match" = "Card doesn't match"; + +/* Label for button to take customer to camera settings */ +"Enable camera access" = "Enable camera access"; + +/* Label for button to enter card details manually instead of scanning */ +"Enter card details manually" = "Enter card details manually"; + +/* Label to explain that they need to update phone settings to scan */ +"To scan your card you'll need to update your phone settings" = "To scan your card you'll need to update your phone settings"; + +/* Label for the button that toggles the camera's torch */ +"Torch" = "Torch"; + diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/es-419.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..d7d58f2f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/es-419.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "La tarjeta no coincide."; + +"Enable camera access" = "Habilitar acceso a la cámara"; + +"Enter card details manually" = "Ingresar los datos de la tarjeta manualmente"; + +"To scan your card you'll need to update your phone settings" = "Para escanear la tarjeta, tendrás que actualizar la configuración de tu teléfono."; + +"Torch" = "Linterna"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/es.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/es.lproj/Localizable.strings new file mode 100644 index 00000000..9f10c359 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/es.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "La tarjeta no coincide"; + +"Enable camera access" = "Permitir acceso a la cámara"; + +"Enter card details manually" = "Introducir manualmente los datos de la tarjeta"; + +"To scan your card you'll need to update your phone settings" = "Para escanear la tarjeta, tienes que actualizar los ajustes de tu teléfono"; + +"Torch" = "Linterna"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/et-EE.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..1e48c27b --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/et-EE.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kaardid ei ole vastavuses"; + +"Enable camera access" = "Luba juurdepääs kaamerale"; + +"Enter card details manually" = "Sisesta kaardi andmed käsitsi"; + +"To scan your card you'll need to update your phone settings" = "Kaardi skannimiseks tuleb uuendada telefoni seadeid"; + +"Torch" = "Valgusti"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/fi.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/fi.lproj/Localizable.strings new file mode 100644 index 00000000..38349b28 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/fi.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kortit eivät vastaa toisiaan"; + +"Enable camera access" = "Ota kameraan pääsy käyttöön"; + +"Enter card details manually" = "Syötä kortin tiedot manuaalisesti"; + +"To scan your card you'll need to update your phone settings" = "Päivitä puhelimesi asetukset, jotta voit skannata korttisi"; + +"Torch" = "Taskulamppu"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/fil.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/fil.lproj/Localizable.strings new file mode 100644 index 00000000..132cd1cc --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/fil.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Hindi tugma ang card"; + +"Enable camera access" = "Paganahin ang access sa kamera"; + +"Enter card details manually" = "Manwal na ilagay ang mga detalye ng card"; + +"To scan your card you'll need to update your phone settings" = "Para ma-scan ang iyong card, kakailanganin mong i-update ang mga setting ng iyong telepono"; + +"Torch" = "Flashlight"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/fr-CA.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..4b00078f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/fr-CA.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "La carte ne correspond pas"; + +"Enable camera access" = "Autoriser l'accès à l'appareil photo"; + +"Enter card details manually" = "Saisir les informations de carte manuellement"; + +"To scan your card you'll need to update your phone settings" = "Pour scanner votre carte, vous devez mettre à jour les paramètres de votre téléphone."; + +"Torch" = "Lampe de poche"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/fr.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/fr.lproj/Localizable.strings new file mode 100644 index 00000000..f76d2fec --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/fr.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "La carte bancaire ne correspond pas"; + +"Enable camera access" = "Autoriser l'accès à l'appareil photo"; + +"Enter card details manually" = "Saisir les informations de carte bancaire manuellement"; + +"To scan your card you'll need to update your phone settings" = "Pour numériser votre carte bancaire, vous devrez mettre à jour les paramètres de votre téléphone."; + +"Torch" = "Torche"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/hr.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/hr.lproj/Localizable.strings new file mode 100644 index 00000000..4248ef8f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/hr.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Dokument se ne podudara"; + +"Enable camera access" = "Omogući pristup kameri"; + +"Enter card details manually" = "Ručno unesi podatke"; + +"To scan your card you'll need to update your phone settings" = "Morate ažurirati postavke telefona prije skeniranja"; + +"Torch" = "Svjetiljka"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/hu.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/hu.lproj/Localizable.strings new file mode 100644 index 00000000..ffc4d7ab --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/hu.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Nem egyező kártya"; + +"Enable camera access" = "Kamerához való hozzáférés engedélyezése"; + +"Enter card details manually" = "Kártyaadatok megadása kézzel"; + +"To scan your card you'll need to update your phone settings" = "A kártya beolvasásához frissítse a telefonja beállításait"; + +"Torch" = "Vaku"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/id.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/id.lproj/Localizable.strings new file mode 100644 index 00000000..01a337da --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/id.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kartu tidak cocok"; + +"Enable camera access" = "Aktifkan akses kamera"; + +"Enter card details manually" = "Masukkan detail kartu secara manual"; + +"To scan your card you'll need to update your phone settings" = "Untuk memindai kartu, Anda harus memperbarui pengaturan telepon Anda"; + +"Torch" = "Senter"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/it.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/it.lproj/Localizable.strings new file mode 100644 index 00000000..bce6ba4f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/it.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Mancata corrispondenza carta"; + +"Enable camera access" = "Abilita fotocamera"; + +"Enter card details manually" = "Inserisci manualmente dati carta"; + +"To scan your card you'll need to update your phone settings" = "Per scansionare la carta, aggiorna le impostazioni del telefono."; + +"Torch" = "Torcia"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/ja.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/ja.lproj/Localizable.strings new file mode 100644 index 00000000..83b63935 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/ja.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "カードが一致しません"; + +"Enable camera access" = "カメラへのアクセスを有効にする"; + +"Enter card details manually" = "カード詳細を手動で入力する"; + +"To scan your card you'll need to update your phone settings" = "カードをスキャンするには、スマートフォンの設定を更新する必要があります"; + +"Torch" = "ライト"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/ko.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/ko.lproj/Localizable.strings new file mode 100644 index 00000000..5747f86a --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/ko.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "카드 불일치"; + +"Enable camera access" = "카메라 액세스 사용"; + +"Enter card details manually" = "카드 세부사항 직접 입력"; + +"To scan your card you'll need to update your phone settings" = "카드를 스캔하려면 휴대폰 설정을 업데이트해야 합니다."; + +"Torch" = "손전등"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/lt-LT.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..49cd9470 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/lt-LT.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kortelė neatitinka"; + +"Enable camera access" = "Įjungti prieigą prie fotoaparato"; + +"Enter card details manually" = "Įveskite kortelės duomenis rankiniu būdu"; + +"To scan your card you'll need to update your phone settings" = "Jei norite nuskaityti kortelę, turite atnaujinti telefono nustatymus"; + +"Torch" = "Žibintuvėlis"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/lv-LV.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..fd62b950 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/lv-LV.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Karte nesakrīt"; + +"Enable camera access" = "Iespējot kameras piekļuvi"; + +"Enter card details manually" = "Manuāli ievadiet kartes informāciju"; + +"To scan your card you'll need to update your phone settings" = "Lai noskenētu karti, jums vajadzēs atjaunināt tālruņa iestatījumus"; + +"Torch" = "Lukturis"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/ms-MY.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..7f5e1a85 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/ms-MY.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kad tidak sepadan"; + +"Enable camera access" = "Aktifkan akses kamera"; + +"Enter card details manually" = "Masukkan butiran kad secara manual"; + +"To scan your card you'll need to update your phone settings" = "Untuk mengimbas kad anda, anda perlu mengemaskinikan tetapan telefon anda"; + +"Torch" = "Lampu suluh"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/mt.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/mt.lproj/Localizable.strings new file mode 100644 index 00000000..cfb41d55 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/mt.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Il-karta ma taqbilx"; + +"Enable camera access" = "Agħti aċċess għall-kamera"; + +"Enter card details manually" = "Daħħal id-dettalji tal-karta int stess"; + +"To scan your card you'll need to update your phone settings" = "Jeħtieġlek taġġorna s-settings tal-mowbajl tiegħek biex tkun tista' tiskennja l-karta tiegħek"; + +"Torch" = "Torċ"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/nb.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/nb.lproj/Localizable.strings new file mode 100644 index 00000000..4fc539e7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/nb.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kortet samsvarer ikke"; + +"Enable camera access" = "Aktiver kameratilgang"; + +"Enter card details manually" = "Angi kortopplysninger manuelt"; + +"To scan your card you'll need to update your phone settings" = "Oppdater telefoninnstillingene dine for å skanne kortet"; + +"Torch" = "Lommelykt"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/nl.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/nl.lproj/Localizable.strings new file mode 100644 index 00000000..f5562952 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/nl.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Betaalkaart komt niet overeen."; + +"Enable camera access" = "Toegang tot camera inschakelen"; + +"Enter card details manually" = "Gegevens van betaalkaart handmatig invoeren"; + +"To scan your card you'll need to update your phone settings" = "Werk de instellingen van je telefoon bij om je kaart te scannen."; + +"Torch" = "Zaklamp"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/nn-NO.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..04e96708 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/nn-NO.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kort samsvarer ikkje"; + +"Enable camera access" = "Aktiver kameratilgang"; + +"Enter card details manually" = "Skriv inn kortdetaljar manuelt"; + +"To scan your card you'll need to update your phone settings" = "For å skanne kortet må du oppdatere innstillingane på telefonen"; + +"Torch" = "Lommelykt"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/pl-PL.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..129eefe0 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/pl-PL.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Karta nie pasuje"; + +"Enable camera access" = "Włącz dostęp do kamery"; + +"Enter card details manually" = "Wprowadź dane karty ręcznie"; + +"To scan your card you'll need to update your phone settings" = "Aby zeskanować kartę, musisz zaktualizować ustawienia telefonu"; + +"Torch" = "Latarka"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/pt-BR.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..03613eb1 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/pt-BR.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "O cartão não corresponde ao registro"; + +"Enable camera access" = "Habilitar acesso à câmera"; + +"Enter card details manually" = "Inserir manualmente os dados do cartão"; + +"To scan your card you'll need to update your phone settings" = "Para ler seu cartão, atualize as configurações do celular"; + +"Torch" = "Lanterna"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/pt-PT.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..82c2c3d8 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/pt-PT.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "O cartão não corresponde"; + +"Enable camera access" = "Ativar acesso à câmara"; + +"Enter card details manually" = "Introduzir detalhes do cartão manualmente"; + +"To scan your card you'll need to update your phone settings" = "Para analisar o seu cartão, precisa de atualizar as definições do telemóvel"; + +"Torch" = "Lanterna"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/ro-RO.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..e4da7316 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/ro-RO.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Cardul nu se potrivește"; + +"Enable camera access" = "Activați accesul la cameră"; + +"Enter card details manually" = "Introduceți manual informațiile cardului"; + +"To scan your card you'll need to update your phone settings" = "Pentru a vă scana cardul, trebuie să actualizați setările telefonului"; + +"Torch" = "Lanternă"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/ru.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/ru.lproj/Localizable.strings new file mode 100644 index 00000000..366c7c17 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/ru.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Карта не соответствует"; + +"Enable camera access" = "Разрешить доступ к камере"; + +"Enter card details manually" = "Ввести реквизиты карты вручную"; + +"To scan your card you'll need to update your phone settings" = "Чтобы сканировать карту, нужно изменить настройки телефона."; + +"Torch" = "Фонарик"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/sk-SK.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..c7aaeec9 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/sk-SK.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Karta sa nezhoduje"; + +"Enable camera access" = "Povoliť prístup k fotoaparátu"; + +"Enter card details manually" = "Zadať údaje o karte manuálne"; + +"To scan your card you'll need to update your phone settings" = "Ak chcete naskenovať kartu, budete musieť aktualizovať nastavenia telefónu"; + +"Torch" = "Svetlo"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/sl-SI.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..53a576f3 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/sl-SI.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kartica se ne ujema"; + +"Enable camera access" = "Omogoči dostop do kamere"; + +"Enter card details manually" = "Ročni vnos podatkov o kartici"; + +"To scan your card you'll need to update your phone settings" = "Če želite optično prebrati kartico, morate posodobiti nastavitve telefona."; + +"Torch" = "Bliskavica"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/sv.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/sv.lproj/Localizable.strings new file mode 100644 index 00000000..6bb31868 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/sv.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kortet överensstämmer inte"; + +"Enable camera access" = "Aktivera kameraåtkomst"; + +"Enter card details manually" = "Ange kortinformationen manuellt"; + +"To scan your card you'll need to update your phone settings" = "För att skanna ditt kort behöver du uppdatera dina telefoninställningar"; + +"Torch" = "Ficklampa"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/tr.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/tr.lproj/Localizable.strings new file mode 100644 index 00000000..6d95a0e2 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/tr.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Kart eşleşmiyor"; + +"Enable camera access" = "Kamera erişimine izin ver"; + +"Enter card details manually" = "Kart bilgilerini manuel olarak girin"; + +"To scan your card you'll need to update your phone settings" = "Kartınızı taramak için telefon ayarlarınızı güncellemeniz gerekiyor"; + +"Torch" = "El feneri"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/vi.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/vi.lproj/Localizable.strings new file mode 100644 index 00000000..02d7c5b5 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/vi.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "Thẻ không khớp"; + +"Enable camera access" = "Bật truy cập máy ảnh"; + +"Enter card details manually" = "Nhập thông tin thẻ theo cách thủ công"; + +"To scan your card you'll need to update your phone settings" = "Để quét thẻ, quý vị cần cập nhật cài đặt điện thoại"; + +"Torch" = "Đèn pin"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/zh-HK.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..7bdb471f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/zh-HK.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "卡不匹配"; + +"Enable camera access" = "啟用相機存取"; + +"Enter card details manually" = "手動輸入銀行卡詳情"; + +"To scan your card you'll need to update your phone settings" = "要掃描銀行卡,您需要更新您的手機設置"; + +"Torch" = "手電筒"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..46b3feb4 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "卡不匹配"; + +"Enable camera access" = "启用摄像头访问"; + +"Enter card details manually" = "手动输入银行卡详情"; + +"To scan your card you'll need to update your phone settings" = "要扫描银行卡,您需要更新您的手机设置"; + +"Torch" = "手电筒"; diff --git a/StripeCardScan/StripeCardScan/Resources/Localizations/zh-Hant.lproj/Localizable.strings b/StripeCardScan/StripeCardScan/Resources/Localizations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..05aa848a --- /dev/null +++ b/StripeCardScan/StripeCardScan/Resources/Localizations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,9 @@ +"Card doesn't match" = "卡不匹配"; + +"Enable camera access" = "啟用相機存取"; + +"Enter card details manually" = "手動輸入金融卡詳情"; + +"To scan your card you'll need to update your phone settings" = "要掃描金融卡,您需要更新您的手機設定"; + +"Torch" = "手電筒"; diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/AppleOcr/AppleOcr.swift b/StripeCardScan/StripeCardScan/Source/CardScan/AppleOcr/AppleOcr.swift new file mode 100644 index 00000000..19947a46 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/AppleOcr/AppleOcr.swift @@ -0,0 +1,79 @@ +import UIKit +import Vision + +struct AppleOcr { + static func configure() { + // warm up the model eventually + } + + static func convertToImageRect(boundingBox: VNRectangleObservation, imageSize: CGSize) -> CGRect + { + let topLeft = VNImagePointForNormalizedPoint( + boundingBox.topLeft, + Int(imageSize.width), + Int(imageSize.height) + ) + let bottomRight = VNImagePointForNormalizedPoint( + boundingBox.bottomRight, + Int(imageSize.width), + Int(imageSize.height) + ) + // flip it for top left (0,0) image coordinates + return CGRect( + x: topLeft.x, + y: imageSize.height - topLeft.y, + width: abs(bottomRight.x - topLeft.x), + height: abs(topLeft.y - bottomRight.y) + ) + + } + + static func performOcr(image: CGImage, completion: @escaping ([OcrObject]) -> Void) { + let textRequest = VNRecognizeTextRequest { request, _ in + let imageSize = CGSize(width: image.width, height: image.height) + + guard let results = request.results as? [VNRecognizedTextObservation], !results.isEmpty + else { + completion([]) + return + } + let outputObjects: [OcrObject] = results.compactMap { result in + guard let candidate = result.topCandidates(1).first, + let box = try? candidate.boundingBox( + for: candidate.string.startIndex.. Void) { + DispatchQueue.global(qos: .userInitiated).async { + performOcr(image: image) { complete($0) } + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CardNetwork.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CardNetwork.swift new file mode 100644 index 00000000..11775fa4 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CardNetwork.swift @@ -0,0 +1,34 @@ +// +// CardNetwork.swift +// CardScan +// +// Created by Jaime Park on 1/31/20. +// + +import Foundation + +enum CardNetwork: Int { + case VISA + case MASTERCARD + case AMEX + case DISCOVER + case UNIONPAY + case JCB + case DINERSCLUB + case REGIONAL + case UNKNOWN + + func toString() -> String { + switch self { + case .VISA: return "Visa" + case .MASTERCARD: return "MasterCard" + case .AMEX: return "Amex" + case .DISCOVER: return "Discover" + case .UNIONPAY: return "Union Pay" + case .JCB: return "Jcb" + case .DINERSCLUB: return "Diners Club" + case .REGIONAL: return "Regional" + case .UNKNOWN: return "Unknown" + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CardType.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CardType.swift new file mode 100644 index 00000000..a028d388 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CardType.swift @@ -0,0 +1,33 @@ +// +// CardType.swift +// CardScan +// +// Created by Adam Wushensky on 8/27/20. +// + +import Foundation + +enum CardType: Int { + case CREDIT + case DEBIT + case PREPAID + case UNKNOWN + + func toString() -> String { + switch self { + case .CREDIT: return "Credit" + case .DEBIT: return "Debit" + case .PREPAID: return "Prepaid" + case .UNKNOWN: return "Unknown" + } + } + + static func fromString(_ str: String) -> CardType { + switch str.lowercased() { + case "credit": return .CREDIT + case "debit": return .DEBIT + case "prepaid": return .PREPAID + default: return .UNKNOWN + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CreditCardUtils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CreditCardUtils.swift new file mode 100644 index 00000000..f7cba144 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/CreditCardUtils.swift @@ -0,0 +1,390 @@ +import Foundation + +struct CreditCardUtils { + static let maxCvvLength = 3 + static let maxCvvLengthAmex = 4 + + static let maxPanLength = 16 + static let maxPanLengthAmericanExpress = 15 + static let maxPanLengthDinersClub = 14 + + private static let prefixesAmericanExpress = ["34", "37"] + private static let prefixesDinersClub = [ + "300", "301", "302", "303", "304", "305", "309", "36", "38", "39", + ] + private static let prefixesDiscover = ["6011", "64", "65"] + private static let prefixesJcb = ["35"] + private static let prefixesMastercard = [ + "2221", "2222", "2223", "2224", "2225", "2226", + "2227", "2228", "2229", "223", "224", "225", "226", + "227", "228", "229", "23", "24", "25", "26", "270", + "271", "2720", "50", "51", "52", "53", "54", "55", + "67", + ] + private static let prefixesUnionPay = ["62"] + private static let prefixesVisa = ["4"] + + static var prefixesRegional: [String] = [] + + private static var cardTypeMap: [(ClosedRange, CardType)]? + + /// Adds the BINs implemented by the MIR network in Russia as regional cards + static func addMirSupport() { + prefixesRegional += ["2200", "2201", "2202", "2203", "2204"] + } + + /// Checks if the card number is valid. + /// - Parameter cardNumber: The card number as a string . + /// - Returns: `true` if valid, `false` otherwise + static func isValidNumber(cardNumber: String) -> Bool { + return isValidLuhnNumber(cardNumber: cardNumber) && isValidLength(cardNumber: cardNumber) + } + + /// Checks if the card's cvv is valid + /// - Parameters: + /// - cvv: The cvv as a string + /// - network: The card's bank network + /// - Returns: `true` if valid, `false ` otherwise + static func isValidCvv(cvv: String, network: CardNetwork) -> Bool { + let cvv = cvv.trimmingCharacters(in: .whitespacesAndNewlines) + return (network == CardNetwork.AMEX && cvv.count == maxCvvLengthAmex) + || (cvv.count == maxCvvLength) + } + + /// Checks if the card's expiration date is valid + /// - Parameters: + /// - expMonth: The expiration month as a string + /// - expYear: The expiration year as a string + /// - Returns: `true` is both expiration month and year are valid, `false` otherwise + static func isValidDate(expMonth: String, expYear: String) -> Bool { + guard let expirationMonth = Int(expMonth.trimmingCharacters(in: .whitespacesAndNewlines)) + else { + return false + } + + guard let expirationYear = Int(expYear.trimmingCharacters(in: .whitespacesAndNewlines)) + else { + return false + } + + if !isValidMonth(expMonth: expirationMonth) { + return false + } else if !isValidYear(expYear: expirationYear) { + return false + } else { + return !hasMonthPassed(expMonth: expirationMonth, expYear: expirationYear) + } + } + + /// Checks if the card's expiration month is valid + /// - Parameter expMonth: The expiration month as an integer + /// - Returns: `true` if valid, `false` otherwise + static func isValidMonth(expMonth: Int) -> Bool { + return 1...12 ~= expMonth + } + + /// Checks if the card's expiration year is valid + /// - Parameter expYear: The expiration year as an integer + /// - Returns: `true` if valid, `false` otherwise + static func isValidYear(expYear: Int) -> Bool { + return !hasYearPassed(expYear: expYear) + } + + /// Checks if the card's expiration month has passed + /// - Parameters: + /// - expMonth: The expiration month as an integer + /// - expYear: The expiration year as an integer + /// - Returns: `true` if expiration month has passed current time, `false` otherwise + static func hasMonthPassed(expMonth: Int, expYear: Int) -> Bool { + let currentMonth = getCurrentMonth() + let currentYear = getCurrentYear() + + if hasYearPassed(expYear: expYear) { + return true + } else { + return normalizeYear(expYear: expYear) == currentYear && expMonth < currentMonth + } + } + + /// Checks if the card's expiration year has passed + /// - Parameter expYear: The expiration year as an integer + /// - Returns: `true` if expiration year has passed current time, `false` otherwise + static func hasYearPassed(expYear: Int) -> Bool { + let currentYear = getCurrentYear() + guard let expirationYear = normalizeYear(expYear: expYear) else { + return false + } + return expirationYear < currentYear + } + + /// Returns expiration year in four digits. If expiration year is two digits, it appends the current century to the beginning of the year + /// - Parameter expYear: The expiration year as an integer + /// - Returns: An `Int` of the four digit year, `nil` otherwise + static func normalizeYear(expYear: Int) -> Int? { + let currentYear = getCurrentYear() + var expirationYear: Int + + if 0...99 ~= expYear { + let currentYearToString = String(currentYear) + let currentYearPrefix = currentYearToString.prefix(2) + guard let concatExpYear = Int("\(currentYearPrefix)\(expYear)") else { + return nil + } + expirationYear = concatExpYear + } else { + expirationYear = expYear + } + + return expirationYear + } + + static func getCurrentYear() -> Int { + let date = Date() + let now = Calendar.current + let currentYear = now.component(.year, from: date) + return currentYear + } + + static func getCurrentMonth() -> Int { + let date = Date() + let now = Calendar.current + // The start of the month begins from 1 + let currentMonth = now.component(.month, from: date) + return currentMonth + } + + // https://en.wikipedia.org/wiki/Luhn_algorithm + // assume 16 digits are for MC and Visa (start with 4, 5) and 15 is for Amex + // which starts with 3 + /// Checks if the card number passes the Luhn's algorithm + /// - Parameter cardNumber: The card number as a string + /// - Returns: `true` if the card number is a valid Luhn number, `false` otherwise + static func isValidLuhnNumber(cardNumber: String) -> Bool { + if cardNumber.isEmpty || !isValidBin(cardNumber: cardNumber) { + return false + } + + var sum = 0 + let reversedCharacters = cardNumber.reversed().map { String($0) } + for (idx, element) in reversedCharacters.enumerated() { + guard let digit = Int(element) else { return false } + switch ((idx % 2 == 1), digit) { + case (true, 9): sum += 9 + case (true, 0...8): sum += (digit * 2) % 9 + default: sum += digit + } + } + return sum % 10 == 0 + } + + /// Checks if the card number contains a valid bin + /// - Parameter cardNumber: The card number as a string + /// - Returns: `true` if the card number contains a valid bin, `false` otherwise + static func isValidBin(cardNumber: String) -> Bool { + determineCardNetwork(cardNumber: cardNumber) != CardNetwork.UNKNOWN + } + + static func isValidLength(cardNumber: String) -> Bool { + return isValidLength( + cardNumber: cardNumber, + network: determineCardNetwork(cardNumber: cardNumber) + ) + } + + /// Checks if the inputted card number has a valid length in accordance with the card's bank network + /// - Parameters: + /// - cardNumber: The card number as a string + /// - network: The card's bank network + /// - Returns: `true` is card number is a valid length, `false` otherwise + static func isValidLength(cardNumber: String, network: CardNetwork) -> Bool { + let cardNumber = cardNumber.trimmingCharacters(in: .whitespacesAndNewlines) + let cardNumberLength = cardNumber.count + + if cardNumber.isEmpty || network == CardNetwork.UNKNOWN { + return false + } + + switch network { + case CardNetwork.AMEX: + return cardNumberLength == maxPanLengthAmericanExpress + case CardNetwork.DINERSCLUB: + return cardNumberLength == maxPanLengthDinersClub + default: + return cardNumberLength == maxPanLength + } + } + + /// Returns the card's issuer / bank network based on the card number + /// - Parameter cardNumber: The card number as a string + /// - Returns: The card's bank network as a CardNetwork enum + static func determineCardNetwork(cardNumber: String) -> CardNetwork { + let cardNumber = cardNumber.trimmingCharacters(in: .whitespacesAndNewlines) + + if cardNumber.isEmpty { + return CardNetwork.UNKNOWN + } + + switch true { + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesAmericanExpress): + return CardNetwork.AMEX + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesDiscover): + return CardNetwork.DISCOVER + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesJcb): + return CardNetwork.JCB + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesDinersClub): + return CardNetwork.DINERSCLUB + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesVisa): + return CardNetwork.VISA + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesMastercard): + return CardNetwork.MASTERCARD + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesUnionPay): + return CardNetwork.UNIONPAY + case hasAnyPrefix(cardNumber: cardNumber, prefixes: prefixesRegional): + return CardNetwork.REGIONAL + default: + return CardNetwork.UNKNOWN + } + } + + /// Returns the card's type (debit, credit, preiad, unknown) based on the card number + /// - Parameter cardNumber: The card number as a string + /// - Returns: The card's type as a CardType enum + static func determineCardType(cardNumber: String) -> CardType { + guard let iin = Int(cardNumber.prefix(6)) else { + return .UNKNOWN + } + + let cardTypes: [(ClosedRange, CardType)] + if let cardTypeMap = self.cardTypeMap { + cardTypes = cardTypeMap + } else { + guard + let filePath = StripeCardScanBundleLocator.resourcesBundle.path( + forResource: "card_types", + ofType: "txt" + ) + else { + // unable to find the file + return .UNKNOWN + } + + guard let contents = try? String(contentsOfFile: filePath) else { + // unable to read the contents of the file + return .UNKNOWN + } + + cardTypes = contents.components(separatedBy: "\n").compactMap { + let items = $0.components(separatedBy: ",") + guard items.count == 3 else { + return nil + } + + let cardType = CardType.fromString(items[2]) + guard let startIin = Int(items[0]), let endIin = Int(items[1]) else { + return nil + } + guard cardType != .UNKNOWN else { + return nil + } + + return (startIin...endIin, cardType) + } + + guard !cardTypes.isEmpty else { + return .UNKNOWN + } + + self.cardTypeMap = cardTypes + } + + return cardTypes.first { $0.0.contains(iin) }?.1 ?? .UNKNOWN + } + + /// Determines whether a card number belongs to any bank network according to their bin + /// - Parameters: + /// - cardNumber: The card number as a string + /// - prefixes: The set of bin prefixes used with certain bank networks + /// - Returns: `true` if card number belongs to a bank network, `false` otherwise + static func hasAnyPrefix(cardNumber: String, prefixes: [String]) -> Bool { + return prefixes.filter { cardNumber.hasPrefix($0) }.count > 0 + } + + // TODO: Will be replaced with `formatCardNumber` in future version + static func format(number: String) -> String { + return formatCardNumber(cardNumber: number) + } + + /// Returns the card number formatted for display + /// - Parameter cardNumber: The card number as a string + /// - Returns: The card number formatted + static func formatCardNumber(cardNumber: String) -> String { + if cardNumber.count == maxPanLength { + return format16(cardNumber: cardNumber) + } else if cardNumber.count == maxPanLengthAmericanExpress { + return format15(cardNumber: cardNumber) + } else { + return cardNumber + } + } + + /// Returns the card's expiration date formatted for display + /// - Parameters: + /// - expMonth: The expiration month as a string + /// - expYear: The expiration year as a string + /// - Returns: The card's expiration date formatted as MM/YY + static func formatExpirationDate(expMonth: String, expYear: String) -> String { + var month = expMonth + let year = "\(expYear.suffix(2))" + + if expMonth.count == 1 { + month = "0\(expMonth)" + } + + return "\(month)/\(year)" + } + + static func format15(cardNumber: String) -> String { + var displayNumber = "" + for (idx, char) in cardNumber.enumerated() { + if idx == 4 || idx == 10 { + displayNumber += " " + } + displayNumber += String(char) + } + return displayNumber + } + + static func format16(cardNumber: String) -> String { + var displayNumber = "" + for (idx, char) in cardNumber.enumerated() { + if (idx % 4) == 0 && idx != 0 { + displayNumber += " " + } + displayNumber += String(char) + } + return displayNumber + } +} + +// TODO: Added extension to make older network changes available, will remove in future version +extension CreditCardUtils { + static func isVisa(number: String) -> Bool { + return determineCardNetwork(cardNumber: number) == CardNetwork.VISA + } + + static func isAmex(number: String) -> Bool { + return determineCardNetwork(cardNumber: number) == CardNetwork.AMEX + } + + static func isDiscover(number: String) -> Bool { + return determineCardNetwork(cardNumber: number) == CardNetwork.DISCOVER + } + + static func isMastercard(number: String) -> Bool { + return determineCardNetwork(cardNumber: number) == CardNetwork.MASTERCARD + } + + static func isUnionPay(number: String) -> Bool { + return determineCardNetwork(cardNumber: number) == CardNetwork.UNIONPAY + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/Expiry.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/Expiry.swift new file mode 100644 index 00000000..64230d17 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CardUtils/Expiry.swift @@ -0,0 +1,20 @@ +import Foundation + +struct Expiry: Hashable { + let string: String + let month: UInt + let year: UInt + + static func == (lhs: Expiry, rhs: Expiry) -> Bool { + return lhs.string == rhs.string + } + + func hash(into hasher: inout Hasher) { + self.string.hash(into: &hasher) + } + + func display() -> String { + let twoDigitYear = self.year % 100 + return String(format: "%02d/%02d", self.month, twoDigitYear) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/AppleCreditCardOcr.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/AppleCreditCardOcr.swift new file mode 100644 index 00000000..4ebd1f07 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/AppleCreditCardOcr.swift @@ -0,0 +1,96 @@ +// +// AppleCreditCardOcr.swift +// ocr-playground-ios +// +// Created by Sam King on 3/20/20. +// Copyright © 2020 Sam King. All rights reserved. +// +import UIKit + +class AppleCreditCardOcr: CreditCardOcrImplementation { + override func recognizeCard( + in fullImage: CGImage, + roiRectangle: CGRect + ) -> CreditCardOcrPrediction { + guard let (image, roiForOcr) = fullImage.croppedImageForSsd(roiRectangle: roiRectangle) + else { + return CreditCardOcrPrediction.emptyPrediction(cgImage: fullImage) + } + + var pan: String? + var expiryMonth: String? + var expiryYear: String? + let semaphore = DispatchSemaphore(value: 0) + let startTime = Date() + var name: String? + var nameBox: CGRect? + var numberBox: CGRect? + var expiryBox: CGRect? + var nameCandidates: [OcrObject] = [] + AppleOcr.recognizeText(in: image) { results in + for result in results { + let predictedPan = CreditCardOcrPrediction.pan(result.text) + let expiry = CreditCardOcrPrediction.likelyExpiry(result.text) + if let (month, year) = expiry { + if CreditCardUtils.isValidDate(expMonth: month, expYear: year) { + if expiryMonth == nil { + expiryBox = result.rect + expiryMonth = month + } + if expiryYear == nil { expiryYear = year } + } + } + if pan == nil && predictedPan != nil { + pan = predictedPan + numberBox = result.rect + } + + let predictedName = AppleCreditCardOcr.likelyName(result.text) + if predictedName != nil { + nameCandidates.append(result) + } + } + + let minY = numberBox.map({ $0.minY - $0.height }) ?? expiryBox?.minY + let names = nameCandidates.filter { name in + let isInExpectedLocation = minY.map({ name.rect.minY >= ($0 - 5.0) }) ?? false + return name.confidence >= 0.5 && isInExpectedLocation + } + + // just pick the first one for now + if let nameResult = names.first { + name = AppleCreditCardOcr.likelyName(nameResult.text) + nameBox = nameResult.rect + } + + semaphore.signal() + } + semaphore.wait() + let duration = -startTime.timeIntervalSinceNow + self.computationTime += duration + self.frames += 1 + + return CreditCardOcrPrediction( + image: image, + ocrCroppingRectangle: roiForOcr, + number: pan, + expiryMonth: expiryMonth, + expiryYear: expiryYear, + name: name, + computationTime: duration, + numberBoxes: numberBox.map { [$0] }, + expiryBoxes: expiryBox.map { [$0] }, + nameBoxes: nameBox.map { [$0] } + ) + } + + static func likelyName(_ text: String) -> String? { + let words = text.split(separator: " ").map { String($0) } + let validWords = words.filter { + !NameWords.nonNameWordMatch($0) && NameWords.onlyLettersAndSpaces($0) + } + let validWordCount = validWords.count >= 2 + + return validWordCount ? validWords.joined(separator: " ") : nil + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrImplementation.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrImplementation.swift new file mode 100644 index 00000000..b63b4c8d --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrImplementation.swift @@ -0,0 +1,42 @@ +// +// CreditCardOcr.swift +// ocr-playground-ios +// +// Created by Sam King on 3/19/20. +// Copyright © 2020 Sam King. All rights reserved. +// +import UIKit + +/// Base class for any OCR prediction systems. All implementations must override `recognizeCard` and update the `frames` +/// and `computationTime` member variables + +@_spi(STP) public class CreditCardOcrImplementation { + let dispatchQueue: ActiveStateComputation + var frames = 0 + var computationTime = 0.0 + let startTime = Date() + + var framesPerSecond: Double { + return Double(frames) / -startTime.timeIntervalSinceNow + } + + var mlFramesPerSecond: Double { + return Double(frames) / computationTime + } + + init( + dispatchQueueLabel: String + ) { + self.dispatchQueue = ActiveStateComputation(label: dispatchQueueLabel) + } + + init( + dispatchQueue: ActiveStateComputation + ) { + self.dispatchQueue = dispatchQueue + } + + func recognizeCard(in fullImage: CGImage, roiRectangle: CGRect) -> CreditCardOcrPrediction { + preconditionFailure("This method must be overridden") + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrPrediction.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrPrediction.swift new file mode 100644 index 00000000..aa36135f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrPrediction.swift @@ -0,0 +1,191 @@ +// +// CreditCardOcrPrediction.swift +// ocr-playground-ios +// +// Created by Sam King on 3/19/20. +// Copyright © 2020 Sam King. All rights reserved. +// + +import CoreGraphics +import Foundation + +struct UxFrameConfidenceValues { + let hasOcr: Bool + let uxPan: Double + let uxNoPan: Double + let uxNoCard: Double + + func toArray() -> [Double] { + return [hasOcr ? 1.0 : 0.0, uxPan, uxNoPan, uxNoCard] + } +} + +enum CenteredCardState { + case numberSide + case nonNumberSide + case noCard + + func hasCard() -> Bool { + return self == .numberSide || self == .nonNumberSide + } +} + +struct CreditCardOcrPrediction { + let image: CGImage + let ocrCroppingRectangle: CGRect + let number: String? + let expiryMonth: String? + let expiryYear: String? + let name: String? + let computationTime: Double + let numberBoxes: [CGRect]? + let expiryBoxes: [CGRect]? + let nameBoxes: [CGRect]? + + // this is only used by Card Verify and the Liveness check and filled in by the UxModel + var centeredCardState: CenteredCardState? + var uxFrameConfidenceValues: UxFrameConfidenceValues? + + init( + image: CGImage, + ocrCroppingRectangle: CGRect, + number: String?, + expiryMonth: String?, + expiryYear: String?, + name: String?, + computationTime: Double, + numberBoxes: [CGRect]?, + expiryBoxes: [CGRect]?, + nameBoxes: [CGRect]?, + centeredCardState: CenteredCardState? = nil, + uxFrameConfidenceValues: UxFrameConfidenceValues? = nil + ) { + + self.image = image + self.ocrCroppingRectangle = ocrCroppingRectangle + self.number = number + self.expiryMonth = expiryMonth + self.expiryYear = expiryYear + self.name = name + self.computationTime = computationTime + self.numberBoxes = numberBoxes + self.expiryBoxes = expiryBoxes + self.nameBoxes = nameBoxes + self.centeredCardState = centeredCardState + self.uxFrameConfidenceValues = uxFrameConfidenceValues + } + + func with(uxPrediction: UxModelOutput) -> CreditCardOcrPrediction { + let uxFrameConfidenceValues: UxFrameConfidenceValues? = { + if let (hasPan, hasNoPan, hasNoCard) = uxPrediction.confidenceValues() { + let hasOcr = self.number != nil + return UxFrameConfidenceValues( + hasOcr: hasOcr, + uxPan: hasPan, + uxNoPan: hasNoPan, + uxNoCard: hasNoCard + ) + } + return nil + }() + + return CreditCardOcrPrediction( + image: self.image, + ocrCroppingRectangle: self.ocrCroppingRectangle, + number: self.number, + expiryMonth: self.expiryMonth, + expiryYear: self.expiryYear, + name: self.name, + computationTime: self.computationTime, + numberBoxes: self.numberBoxes, + expiryBoxes: self.expiryBoxes, + nameBoxes: self.nameBoxes, + centeredCardState: uxPrediction.cardCenteredState(), + uxFrameConfidenceValues: uxFrameConfidenceValues + ) + } + + static func emptyPrediction(cgImage: CGImage) -> CreditCardOcrPrediction { + CreditCardOcrPrediction( + image: cgImage, + ocrCroppingRectangle: CGRect(), + number: nil, + expiryMonth: nil, + expiryYear: nil, + name: nil, + computationTime: 0.0, + numberBoxes: nil, + expiryBoxes: nil, + nameBoxes: nil + ) + } + + var expiryForDisplay: String? { + guard let month = expiryMonth, let year = expiryYear else { return nil } + return "\(month)/\(year)" + } + + var expiryAsUInt: (UInt, UInt)? { + guard let month = expiryMonth.flatMap({ UInt($0) }) else { return nil } + guard let year = expiryYear.flatMap({ UInt($0) }) else { return nil } + + return (month, year) + } + + var numberBox: CGRect? { + let xmin = numberBoxes?.map { $0.minX }.min() ?? 0.0 + let xmax = numberBoxes?.map { $0.maxX }.max() ?? 0.0 + let ymin = numberBoxes?.map { $0.minY }.min() ?? 0.0 + let ymax = numberBoxes?.map { $0.maxY }.max() ?? 0.0 + return CGRect(x: xmin, y: ymin, width: (xmax - xmin), height: (ymax - ymin)) + } + + var expiryBox: CGRect? { + return expiryBoxes.flatMap { $0.first } + } + + var numberBoxesInFullImageFrame: [CGRect]? { + guard let boxes = numberBoxes else { return nil } + let cropOrigin = ocrCroppingRectangle.origin + return boxes.map { + CGRect( + x: $0.origin.x + cropOrigin.x, + y: $0.origin.y + cropOrigin.y, + width: $0.size.width, + height: $0.size.height + ) + } + } + + static func likelyExpiry(_ string: String) -> (String, String)? { + guard let regex = try? NSRegularExpression(pattern: "^.*(0[1-9]|1[0-2])[./]([1-2][0-9])$") + else { + return nil + } + + let result = regex.matches(in: string, range: NSRange(string.startIndex..., in: string)) + + if result.count == 0 { + return nil + } + + guard let nsrange1 = result.first?.range(at: 1), + let range1 = Range(nsrange1, in: string) + else { return nil } + guard let nsrange2 = result.first?.range(at: 2), + let range2 = Range(nsrange2, in: string) + else { return nil } + + return (String(string[range1]), String(string[range2])) + } + + static func pan(_ text: String) -> String? { + let digitsAndSpace = text.reduce(true) { $0 && (($1 >= "0" && $1 <= "9") || $1 == " ") } + let number = text.compactMap { $0 >= "0" && $0 <= "9" ? $0 : nil }.map { String($0) } + .joined() + + guard digitsAndSpace else { return nil } + guard CreditCardUtils.isValidNumber(cardNumber: number) else { return nil } + return number + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrResult.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrResult.swift new file mode 100644 index 00000000..ce2efb00 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/CreditCardOcrResult.swift @@ -0,0 +1,62 @@ +// +// CreditCardOcrResult.swift +// ocr-playground-ios +// +// Created by Sam King on 3/20/20. +// Copyright © 2020 Sam King. All rights reserved. +// + +import Foundation + +class CreditCardOcrResult: MachineLearningResult { + let mostRecentPrediction: CreditCardOcrPrediction + let number: String + let expiry: String? + let name: String? + let state: MainLoopState + + // this is only used by Card Verify and the Liveness check and filled in by the UxModel + var hasCenteredCard: CenteredCardState? + + init( + mostRecentPrediction: CreditCardOcrPrediction, + number: String, + expiry: String?, + name: String?, + state: MainLoopState, + duration: Double, + frames: Int + ) { + self.mostRecentPrediction = mostRecentPrediction + self.number = number + self.expiry = expiry + self.name = name + self.state = state + super.init(duration: duration, frames: frames) + } + + var expiryMonth: String? { + return expiry.flatMap { $0.split(separator: "/").first.map { String($0) } } + } + var expiryYear: String? { + return expiry.flatMap { $0.split(separator: "/").last.map { String($0) } } + } + + static func finishedWithNonNumberSideCard( + prediction: CreditCardOcrPrediction, + duration: Double, + frames: Int + ) -> CreditCardOcrResult { + let result = CreditCardOcrResult( + mostRecentPrediction: prediction, + number: "", + expiry: nil, + name: nil, + state: .finished, + duration: duration, + frames: frames + ) + result.hasCenteredCard = .nonNumberSide + return result + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/ErrorCorrection.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/ErrorCorrection.swift new file mode 100644 index 00000000..f530cd94 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/ErrorCorrection.swift @@ -0,0 +1,95 @@ +import Foundation + +class ErrorCorrection { + let stateMachine: MainLoopStateMachine + var frames = 0 + var numbers: [String: Int] = [:] + var expiries: [String: Int] = [:] + var names: [String: Int] = [:] + let startTime = Date() + var mostRecentPrediction: CreditCardOcrPrediction? + + var framesPerSecond: Double { + return Double(frames) / -startTime.timeIntervalSinceNow + } + + init( + stateMachine: MainLoopStateMachine + ) { + self.stateMachine = stateMachine + } + + var number: String? { + return self.numbers.sorted { $0.1 > $1.1 }.map { $0.0 }.first + } + + func result() -> CreditCardOcrResult? { + guard stateMachine.loopState() != .initial else { return nil } + let predictedNumber = self.numbers.sorted { $0.1 > $1.1 }.map { $0.0 }.first + let predictedExpiry = self.expiries.sorted { $0.1 > $1.1 }.map { $0.0 }.first + let predictedName = self.names.sorted { $0.1 > $1.1 }.map { $0.0 }.first + guard let prediction = self.mostRecentPrediction else { return nil } + + guard let number = predictedNumber else { + // TODO(stk): this is a hack to deal with the case where we are finished + // but don't have a OCR (e.g., non-number side for liveness) + if stateMachine.loopState() == .finished { + return CreditCardOcrResult.finishedWithNonNumberSideCard( + prediction: prediction, + duration: -startTime.timeIntervalSinceNow, + frames: frames + ) + } + + if stateMachine.loopState() == .ocrIncorrect, let number = prediction.number { + return CreditCardOcrResult( + mostRecentPrediction: prediction, + number: number, + expiry: prediction.expiryForDisplay, + name: prediction.name, + state: stateMachine.loopState(), + duration: -startTime.timeIntervalSinceNow, + frames: frames + ) + } + + return nil + } + + return CreditCardOcrResult( + mostRecentPrediction: prediction, + number: number, + expiry: predictedExpiry, + name: predictedName, + state: stateMachine.loopState(), + duration: -startTime.timeIntervalSinceNow, + frames: frames + ) + } + + func add(prediction: CreditCardOcrPrediction) -> CreditCardOcrResult? { + self.frames += 1 + + let newState = stateMachine.event(prediction: prediction) + + if newState != .ocrIncorrect { + if let pan = prediction.number { + self.numbers[pan] = (self.numbers[pan] ?? 0) + 1 + } + if let expiry = prediction.expiryForDisplay { + self.expiries[expiry] = (self.expiries[expiry] ?? 0) + 1 + } + for name in prediction.name?.split(separator: "\n").map({ String($0) }) ?? [] { + self.names[name] = (self.names[name] ?? 0) + 1 + } + } + + self.mostRecentPrediction = prediction + + return result() + } + + func reset() -> ErrorCorrection { + return ErrorCorrection(stateMachine: stateMachine.reset()) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/MachineLearningResult.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/MachineLearningResult.swift new file mode 100644 index 00000000..a65b9543 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/MachineLearningResult.swift @@ -0,0 +1,24 @@ +// +// MachineLearningResult.swift +// CardScan +// +// Created by Sam King on 4/30/20. +// + +import Foundation + +class MachineLearningResult { + let duration: Double + let frames: Int + var framePerSecond: Double { + return Double(frames) / duration + } + + init( + duration: Double, + frames: Int + ) { + self.duration = duration + self.frames = frames + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/MainLoopStateMachine.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/MainLoopStateMachine.swift new file mode 100644 index 00000000..eab6c5b9 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/MainLoopStateMachine.swift @@ -0,0 +1,119 @@ +// +// MainLoopStateMachine.swift +// CardScan +// +// Created by Sam King on 8/5/20. +// + +import Foundation + +enum MainLoopState: Equatable { + case initial + case ocrOnly + case cardOnly + case ocrAndCard + case ocrIncorrect + case ocrDelayForCard + case ocrForceFlash + case finished + case nameAndExpiry +} + +protocol MainLoopStateMachine { + func loopState() -> MainLoopState + func event(prediction: CreditCardOcrPrediction) -> MainLoopState + func reset() -> MainLoopStateMachine +} + +// Note: This class is _not_ thread safe, it relies on syncrhonization +// from the `OcrMainLoop` +class OcrMainLoopStateMachine: NSObject, MainLoopStateMachine { + var state: MainLoopState = .initial + var startTimeForCurrentState = Date() + let errorCorrectionDurationSeconds = 2.0 + + override init() {} + + func loopState() -> MainLoopState { + return state + } + + func event(prediction: CreditCardOcrPrediction) -> MainLoopState { + let newState = transition(prediction: prediction) + if let newState = newState { + startTimeForCurrentState = Date() + state = newState + } + + return newState ?? state + } + + func transition(prediction: CreditCardOcrPrediction) -> MainLoopState? { + let timeInCurrentStateSeconds = -startTimeForCurrentState.timeIntervalSinceNow + let frameHasOcr = prediction.number != nil + + switch (state, timeInCurrentStateSeconds, frameHasOcr) { + case (.initial, _, true): + return .ocrOnly + case (.ocrOnly, errorCorrectionDurationSeconds..., _): + return .finished + default: + // no state transitions + return nil + } + } + + func reset() -> MainLoopStateMachine { + return OcrMainLoopStateMachine() + } +} + +class OcrAccurateMainLoopStateMachine: NSObject, MainLoopStateMachine { + var state: MainLoopState = .initial + var startTimeForCurrentState = Date() + var hasExpiryPrediction = false + + let minimumErrorCorrection = 2.0 + var maximumErrorCorrection = 4.0 + + func loopState() -> MainLoopState { + return state + } + + override init() {} + + init( + maxErrorCorrection: Double + ) { + self.maximumErrorCorrection = maxErrorCorrection + } + + func event(prediction: CreditCardOcrPrediction) -> MainLoopState { + let newState = transition(prediction: prediction) + if let newState = newState { + startTimeForCurrentState = Date() + state = newState + } + return newState ?? state + } + + func transition(prediction: CreditCardOcrPrediction) -> MainLoopState? { + let timeInCurrentStateSeconds = -startTimeForCurrentState.timeIntervalSinceNow + let frameHasOcr = prediction.number != nil + hasExpiryPrediction = hasExpiryPrediction || prediction.expiryForDisplay != nil + switch (state, timeInCurrentStateSeconds, frameHasOcr, hasExpiryPrediction) { + case (.initial, _, true, _): + return .ocrOnly + case (.ocrOnly, minimumErrorCorrection..., _, true): + return .finished + case (.ocrOnly, maximumErrorCorrection..., _, false): + return .finished + default: + // no state transitions + return nil + } + } + func reset() -> MainLoopStateMachine { + return OcrAccurateMainLoopStateMachine(maxErrorCorrection: maximumErrorCorrection) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/NonNameWords.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/NonNameWords.swift new file mode 100644 index 00000000..3e222a12 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/NonNameWords.swift @@ -0,0 +1,43 @@ +// +// NonNameWords.swift +// ocr-playground-ios +// +// Created by Sam King on 3/23/20. +// Copyright © 2020 Sam King. All rights reserved. +// + +import Foundation + +struct NameWords { + static let blacklist: Set = [ + "customer", "debit", "visa", "mastercard", "navy", "american", "express", "thru", "good", + "authorized", "signature", "wells", "navy", "credit", "federal", + "union", "bank", "valid", "validfrom", "validthru", "llc", "business", "netspend", + "goodthru", "chase", "fargo", "hsbc", "usaa", "chaseo", "commerce", + "last", "of", "lastdayof", "check", "card", "inc", "first", "member", "since", + "american", "express", "republic", "bmo", "capital", "one", "capitalone", "platinum", + "expiry", "date", "expiration", "cash", "back", "td", "access", "international", "interac", + "nterac", "entreprise", "business", "md", "enterprise", "fifth", "third", "fifththird", + "world", "rewards", "citi", "member", "cardmember", "cardholder", "valued", "since", + "membersince", "cardmembersince", "cardholdersince", "freedom", "quicksilver", "penfed", + "use", "this", "card", "is", "subject", "to", "the", "inc", "not", "transferable", "gto", + "mgy", "sign", + ] + + static func nonNameWordMatch(_ text: String) -> Bool { + let lowerCase = text.lowercased() + return blacklist.contains(lowerCase) + } + + static func onlyLettersAndSpaces(_ text: String) -> Bool { + let lettersAndSpace = text.reduce(true) { acc, value in + let capitalLetter = value >= "A" && value <= "Z" + // for now we're only going to accept upper case names + // let lowerCaseLetter = value >= "a" && value <= "z" + let space = value == " " + return acc && (capitalLetter || space) + } + + return lettersAndSpace + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/OcrMainLoop.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/OcrMainLoop.swift new file mode 100644 index 00000000..e18f2d16 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/OcrMainLoop.swift @@ -0,0 +1,311 @@ +/// This is the main loop for OCR. It runs one of our OCR systems in paralell with the Apple OCR system and +/// combines results. From a high level this implements a standard producer-consumer where the main +/// system will push images and ROI rectangles into the main loop and two Analyzers, or OCR systems +/// will consume the images. +/// +/// The producer, which pushes images will keep N (2 currently) images in the queue and when a new image +/// comes in it will remove old images leaving the N most recent images. That way we can try to get more +/// diversity in images by virtue of maximizing the time in between images that it reads. +/// +/// The consumers pull images from the queue and run the full OCR algorithm, including expiry extraction and +/// full error correction on the combined results. +/// +/// In terms of iOS abstractions, we make heavy use of dispatch queues. We have a single `mutexQueue` +/// that we use to mutate our shared state. This queue is a serial queue and our method for synchronizing +/// access. One thing to be careful with is we use `sync` in places to access our `mutexQueue`. This +/// method can lead to deadlock if you aren't careful. +/// +/// # Correcness criteria +/// We make heavy use of dispatch queues for paralellism, so it's important to be disciplined about how +/// we access shared state +/// +/// ## Shared state +/// All shared state updates need to happeon on the `mutexQueue` except for `machineLearningQueue`, +/// which we set at the constructor and access it read only. +/// +/// ## Delegate invocation +/// All invocations of delegate methods need to happen on the main queue, and for each prediction there +/// are one or more methods that may get called in order: +/// - `prediction` this happens on all predictions +/// - if the scan predicts a number, then `showCardDetails` happens with the current overall predicted number, expiry, and name +/// - if the scan is complete, then `complete` includes the final result +/// +/// To finalize results, we clear out the `mainLoopDelegate` after it's done +/// +/// It's important that we not update `scanStats` after complete is called or call any futher delegate functions, although +/// more predictions might come through after the fact +/// +/// We also expose `shouldUsePrediction` that delegates can implement to discard a prediction, but note that the `prediction` +/// method still fires even when this returns false. Note: `shouldUsePrediction` is called from the `mutexQueue` so handlers +/// don't need to synchronize but they may need to handle any computation that needs to happen on the main loop appropriately. +/// +/// ## userCancelled +/// One aspect to be careful with when someone invokes the `userCancelled` method is that there could be a race with OCR and it +/// could complete OCR in parallel with this call. The net result we want is if a caller calls this method we don't subsequenty fire any of +/// the `OcrMainLoopDelegate` methods and we want to make sure that `scanStats.success` is always `false` to correctly +/// denote that this scan failed. +/// +/// To handle this correctly we: +/// - use the `userDidCancel` variable here and in any of our blocks that run on the main dispatch queue. Since this call should +/// come from the main dispatch queue, those calls, where we invoke the callback methods, will run after this one and we prevent firing +/// their delegate methods. +/// - the logic to set `scanStats.success` will come on the `muxtexQueue`, but could execute either before or after this block runs. +/// - If it's before then this block will overwrite the `success` results with the unsuccessful result here. If the +/// - If it runs after, there is a check and it sets `scanStats.success` iff it isn't already set +/// - we use `sync` on the `muxtexQueue` to make sure that when this method returns any subsequent calls to `scanStats` are +/// always `success = false` +/// +/// ## backgrounding +/// We track when the app is in the active state and stop accepting images when it's inactive. When it becomes active we reset +/// the `errorCorrection` state before re-enabling computation. +/// +/// This backgrounding logic is less about correctness and more about making sure that the SDK doesn't send the caller predictions +/// at unexpected times by making sure that the app is active if and when it sends notifications of success. + +import UIKit + +protocol OcrMainLoopDelegate: AnyObject { + func complete(creditCardOcrResult: CreditCardOcrResult) + func prediction( + prediction: CreditCardOcrPrediction, + imageData: ScannedCardImageData, + state: MainLoopState + ) + func showCardDetails(number: String?, expiry: String?, name: String?) + func showCardDetailsWithFlash(number: String?, expiry: String?, name: String?) + func showWrongCard(number: String?, expiry: String?, name: String?) + func showNoCard() + func shouldUsePrediction( + errorCorrectedNumber: String?, + prediction: CreditCardOcrPrediction + ) -> Bool +} + +protocol MachineLearningLoop: AnyObject { + func push(imageData: ScannedCardImageData) +} + +class OcrMainLoop: MachineLearningLoop { + enum AnalyzerType { + case apple + case ssd + } + + var scanStats = ScanStats() + + weak var mainLoopDelegate: OcrMainLoopDelegate? + var errorCorrection = ErrorCorrection(stateMachine: OcrMainLoopStateMachine()) + var imageQueue: [ScannedCardImageData] = [] + var imageQueueSize = 2 + var analyzerQueue: [CreditCardOcrImplementation] = [] + let mutexQueue = DispatchQueue(label: "OcrMainLoopMutex") + var inBackground = false + var machineLearningQueues: [DispatchQueue] = [] + var userDidCancel = false + + init( + analyzers: [AnalyzerType] = [.ssd, .apple] + ) { + var ocrImplementations: [CreditCardOcrImplementation] = [] + for analyzer in analyzers { + let queueLabel = "\(analyzer) OCR ML" + switch analyzer { + case .ssd: + ocrImplementations.append(SSDCreditCardOcr(dispatchQueueLabel: queueLabel)) + case .apple: + ocrImplementations.append(AppleCreditCardOcr(dispatchQueueLabel: queueLabel)) + } + } + setupMl(ocrImplementations: ocrImplementations) + } + + /// Note: you must call this function in your constructor + func setupMl(ocrImplementations: [CreditCardOcrImplementation]) { + scanStats.model = "ssd+apple" + for ocrImplementation in ocrImplementations { + analyzerQueue.append(ocrImplementation) + } + registerAppNotifications() + } + + func reset() { + mutexQueue.async { + self.errorCorrection = self.errorCorrection.reset() + } + } + + static func warmUp() { + // TODO(stk): Implement this later + } + + // see the Correctness Criteria note in the comments above for why this is correct + // Make sure you call this from the main dispatch queue + func userCancelled() { + userDidCancel = true + mutexQueue.sync { [weak self] in + guard let self = self else { return } + self.scanStats.userCanceled = userDidCancel + if self.scanStats.success == nil { + self.scanStats.success = false + self.scanStats.endTime = Date() + self.mainLoopDelegate = nil + } + } + } + + func push(imageData: ScannedCardImageData) { + mutexQueue.sync { + guard !inBackground else { return } + // only keep the latest images + imageQueue.insert(imageData, at: 0) + while imageQueue.count > imageQueueSize { + _ = imageQueue.popLast() + } + + // if we have any analyzers waiting, fire them off now + guard let ocr = analyzerQueue.popLast() else { return } + analyzer(ocr: ocr) + } + } + + func postAnalyzerToQueueAndRun(ocr: CreditCardOcrImplementation) { + mutexQueue.async { [weak self] in + guard let self = self else { return } + self.analyzerQueue.insert(ocr, at: 0) + // only kick off the next analyzer if there is an image in the queue + if self.imageQueue.count > 0 { + guard let ocr = self.analyzerQueue.popLast() else { return } + self.analyzer(ocr: ocr) + } + } + } + + func analyzer(ocr: CreditCardOcrImplementation) { + ocr.dispatchQueue.async { [weak self] in + var scannedCardImageData: ScannedCardImageData? + + // grab an image and roi from the image queue. If the image queue is empty then add ourselves + // back to the analyzer queue + self?.mutexQueue.sync { + guard !(self?.inBackground ?? false) else { + self?.analyzerQueue.insert(ocr, at: 0) + return + } + guard let imageDataFromQueue = self?.imageQueue.popLast() else { + self?.analyzerQueue.insert(ocr, at: 0) + return + } + scannedCardImageData = imageDataFromQueue + } + + guard let imageData = scannedCardImageData else { return } + + // run our ML model, add ourselves back to the analyzer queue unless we have a result + // and the result is finished + let prediction = ocr.recognizeCard( + in: imageData.previewLayerImage, + roiRectangle: imageData.previewLayerViewfinderRect + ) + self?.mutexQueue.async { + guard let self = self else { return } + self.scanStats.scans += 1 + let delegate = self.mainLoopDelegate + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + guard !self.userDidCancel else { return } + delegate?.prediction( + prediction: prediction, + imageData: imageData, + state: self.errorCorrection.stateMachine.loopState() + ) + } + guard let result = self.combine(prediction: prediction), result.state == .finished + else { + self.postAnalyzerToQueueAndRun(ocr: ocr) + return + } + } + } + } + + func combine(prediction: CreditCardOcrPrediction) -> CreditCardOcrResult? { + guard + mainLoopDelegate?.shouldUsePrediction( + errorCorrectedNumber: errorCorrection.number, + prediction: prediction + ) ?? true + else { return nil } + guard let result = errorCorrection.add(prediction: prediction) else { return nil } + let delegate = mainLoopDelegate + if result.state == .finished && scanStats.success == nil { + scanStats.success = true + scanStats.endTime = Date() + mainLoopDelegate = nil + } + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + guard !self.userDidCancel else { return } + switch result.state { + case MainLoopState.initial, MainLoopState.cardOnly: + delegate?.showNoCard() + case MainLoopState.ocrIncorrect: + delegate?.showWrongCard( + number: result.number, + expiry: result.expiry, + name: result.name + ) + case MainLoopState.ocrOnly, MainLoopState.ocrAndCard, MainLoopState.ocrDelayForCard: + delegate?.showCardDetails( + number: result.number, + expiry: result.expiry, + name: result.name + ) + case .ocrForceFlash: + delegate?.showCardDetailsWithFlash( + number: result.number, + expiry: result.expiry, + name: result.name + ) + case MainLoopState.finished: + delegate?.complete(creditCardOcrResult: result) + case MainLoopState.nameAndExpiry: + break + } + } + return result + } + + // MARK: - backrounding logic + @objc func willResignActive() { + // make sure that no new images get pushed to our image buffer + // and we clear out the image buffer + mutexQueue.sync { + self.inBackground = true + self.imageQueue = [] + } + } + + @objc func didBecomeActive() { + mutexQueue.sync { + self.inBackground = false + self.errorCorrection = self.errorCorrection.reset() + } + } + + func registerAppNotifications() { + // We don't need to unregister these functions because the system will clean + // them up for us + NotificationCenter.default.addObserver( + self, + selector: #selector(self.willResignActive), + name: UIApplication.willResignActiveNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(self.didBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/OcrObject.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/OcrObject.swift new file mode 100644 index 00000000..9313dc62 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/OcrObject.swift @@ -0,0 +1,32 @@ +import UIKit + +struct OcrObject { + let rect: CGRect + let text: String + let confidence: Float + let imageSize: CGSize + + init( + text: String, + conf: Float, + textBox: CGRect, + imageSize: CGSize + ) { + self.text = text + self.confidence = conf + self.rect = textBox + self.imageSize = imageSize + } + + func toDict() -> [String: Any] { + return [ + "x_min": self.rect.minX / self.imageSize.width, + "y_min": self.rect.minY / self.imageSize.height, + "height": self.rect.height / self.imageSize.height, + "width": self.rect.width / self.imageSize.width, + "text": self.text, + "confidence": self.confidence, + ] + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/SSDCreditCardOcr.swift b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/SSDCreditCardOcr.swift new file mode 100644 index 00000000..1f712e90 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/CreditCardOcr/SSDCreditCardOcr.swift @@ -0,0 +1,50 @@ +// +// SSDCreditCardOcr.swift +// CardScan +// +// Created by xaen on 5/15/20. +// +import UIKit + +class SSDCreditCardOcr: CreditCardOcrImplementation { + let ocr: OcrDD + + override init( + dispatchQueueLabel: String + ) { + ocr = OcrDD() + super.init(dispatchQueueLabel: dispatchQueueLabel) + } + + override func recognizeCard( + in fullImage: CGImage, + roiRectangle: CGRect + ) -> CreditCardOcrPrediction { + + guard + let (image, ocrRoiRectangle) = fullImage.croppedImageForSsd(roiRectangle: roiRectangle) + else { + return CreditCardOcrPrediction.emptyPrediction(cgImage: fullImage) + } + + let startTime = Date() + let number = ocr.perform(croppedCardImage: image) + let duration = -startTime.timeIntervalSinceNow + let numberBoxes = ocr.lastDetectedBoxes + + self.computationTime += duration + self.frames += 1 + return CreditCardOcrPrediction( + image: image, + ocrCroppingRectangle: ocrRoiRectangle, + number: number, + expiryMonth: nil, + expiryYear: nil, + name: nil, + computationTime: duration, + numberBoxes: numberBoxes, + expiryBoxes: nil, + nameBoxes: nil + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/Array+utils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/Array+utils.swift new file mode 100644 index 00000000..93572b0a --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/Array+utils.swift @@ -0,0 +1,16 @@ +// +// Array+utils.swift +// CardScan +// +// Created by Jaime Park on 6/11/21. +// + +import Foundation + +extension Array { + func chunked(into size: Int) -> [[Element]] { + return stride(from: 0, to: count, by: size).map { + Array(self[$0.. Float { + let areaCurrent = self.width * self.height + if areaCurrent <= 0 { + return 0 + } + + let areaNext = nextBox.width * nextBox.height + if areaNext <= 0 { + return 0 + } + + let intersectionMinX = max(self.minX, nextBox.minX) + let intersectionMinY = max(self.minY, nextBox.minY) + let intersectionMaxX = min(self.maxX, nextBox.maxX) + let intersectionMaxY = min(self.maxY, nextBox.maxY) + let intersectionArea = + max(intersectionMaxY - intersectionMinY, 0) + * max(intersectionMaxX - intersectionMinX, 0) + return Float(intersectionArea / (areaCurrent + areaNext - intersectionArea)) + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/CGrect+utils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/CGrect+utils.swift new file mode 100644 index 00000000..7609d957 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/CGrect+utils.swift @@ -0,0 +1,18 @@ +// +// CGrect+utils.swift +// CardScan +// +// Created by Jaime Park on 6/11/21. +// + +import CoreGraphics + +extension CGRect { + func centerY() -> CGFloat { + return (minY / 2 + maxY / 2) + } + + func centerX() -> CGFloat { + return (minX / 2 + maxX / 2) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/CreditCardOcrPrediction+expiry.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/CreditCardOcrPrediction+expiry.swift new file mode 100644 index 00000000..2c0de5df --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/CreditCardOcrPrediction+expiry.swift @@ -0,0 +1,14 @@ +import Foundation + +extension CreditCardOcrPrediction { + func expiryObject() -> Expiry? { + if let month = self.expiryMonth.flatMap({ UInt($0) }), + let year = self.expiryYear.flatMap({ UInt($0) }), + let expiryString = self.expiryForDisplay + { + return Expiry(string: expiryString, month: month, year: year) + } else { + return nil + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/Image+utils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/Image+utils.swift new file mode 100644 index 00000000..bef9101d --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/Image+utils.swift @@ -0,0 +1,270 @@ +// +// Image+utils.swift +// StripeCardScan +// +// Created by Sam King on 11/08/21. +// + +import CoreGraphics +import UIKit +import VideoToolbox + +extension CGSize { + func scaledAndCentered(centerIn: CGRect) -> CGRect { + let targetWidth = centerIn.width + let targetHeight = centerIn.height + + let scale = min(targetWidth / self.width, targetHeight / self.height) + + let scaledWidth = self.width * scale + let scaledHeight = self.height * scale + + let x = (targetWidth - scaledWidth) / 2.0 + let y = (targetHeight - scaledHeight) / 2.0 + + return CGRect(x: x, y: y, width: scaledWidth, height: scaledHeight) + } +} + +extension UIImage { + static func grayImage(size: CGSize) -> UIImage? { + UIGraphicsBeginImageContext(size) + UIColor.gray.setFill() + UIRectFill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } +} + +extension CGImage { + + func extendedEdges(targetSize: CGSize) -> CGImage? { + var result: CGImage? + + let size = CGSize(width: self.width, height: self.height) + let targetRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height) + let centeredRect = size.scaledAndCentered(centerIn: targetRect) + + if let context = CGContext( + data: nil, + width: Int(targetRect.width), + height: Int(targetRect.height), + bitsPerComponent: self.bitsPerComponent, + bytesPerRow: self.bytesPerRow, + space: self.colorSpace!, + bitmapInfo: self.bitmapInfo.rawValue + ) { + + context.setFillColor(UIColor.white.cgColor) + context.fill([targetRect]) + + context.draw(self, in: centeredRect) + + result = context.makeImage() + } + + return result + } + + // Crop a full image + func croppedImageForSsd(roiRectangle: CGRect) -> (CGImage, CGRect)? { + + // add 10% to our ROI rectangle + let centerX = roiRectangle.origin.x + roiRectangle.size.width * 0.5 + let centerY = roiRectangle.origin.y + roiRectangle.size.height * 0.5 + + let width = + (roiRectangle.size.width * 1.1) < roiRectangle.size.width + ? (roiRectangle.size.width * 1.1) : roiRectangle.size.width + let height = 375.0 * width / 600.0 + let x = centerX - width * 0.5 + let y = centerY - height * 0.5 + + let ssdRoiRectangle = CGRect(x: x, y: y, width: width, height: height) + + if let image = self.cropping(to: ssdRoiRectangle) { + return (image, ssdRoiRectangle) + } else if let image = self.cropping(to: roiRectangle) { + // fall back if the crop was out of bounds + return (image, roiRectangle) + } + + return nil + } + + // crop a full image + func squareImageForUxModel(roiRectangle: CGRect) -> CGImage? { + // add 10% to our ROI rectangle and make it square centered at the ROI rectangle + let deltaX = roiRectangle.size.width * 0.1 + let deltaY = roiRectangle.size.width + deltaX - roiRectangle.height + + let roiPlusBuffer = CGRect( + x: roiRectangle.origin.x - deltaX * 0.5, + y: roiRectangle.origin.y - deltaY * 0.5, + width: roiRectangle.size.width + deltaX, + height: roiRectangle.size.height + deltaY + ) + + // if the expanded roi rectangle is too big, fall back to the tight roi rectangle + return self.cropping(to: roiPlusBuffer) ?? self.cropping(to: roiRectangle) + } + + // This cropping is used by the object detector + func squareCardImage(roiRectangle: CGRect) -> CGImage? { + let width = CGFloat(self.width) + let height = width + let centerY = (roiRectangle.maxY + roiRectangle.minY) * 0.5 + let cropRectangle = CGRect( + x: 0.0, + y: centerY - height * 0.5, + width: width, + height: height + ) + return self.cropping(to: cropRectangle) + } + + func drawBoundingBoxesOnImage(boxes: [(UIColor, CGRect)]) -> UIImage? { + let image = UIImage(cgImage: self) + let imageSize = image.size + let scale: CGFloat = 0 + UIGraphicsBeginImageContextWithOptions(imageSize, false, scale) + + image.draw(at: CGPoint(x: 0, y: 0)) + + UIGraphicsGetCurrentContext()?.setLineWidth(3.0) + + for (color, box) in boxes { + color.setStroke() + UIRectFrame(box) + } + + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } + + func drawGrayToFillFullScreen(croppedImage: CGImage, targetSize: CGSize) -> CGImage? { + let image = UIImage(cgImage: croppedImage) + + UIGraphicsBeginImageContext(targetSize) + // Make whole image grey + UIColor.gray.setFill() + UIRectFill(CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height)) + // Put in image in the center + image.draw( + in: CGRect( + x: 0.0, + y: (CGFloat(targetSize.height) - CGFloat(croppedImage.height)) / 2.0, + width: CGFloat(croppedImage.width), + height: CGFloat(croppedImage.height) + ) + ) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return newImage?.cgImage + } + + func toFullScreenAndRoi( + previewViewFrame: CGRect, + regionOfInterestLabelFrame: CGRect + ) -> (CGImage, CGRect)? { + let imageCenterX = CGFloat(self.width) / 2.0 + let imageCenterY = CGFloat(self.height) / 2.0 + + let imageAspectRatio = CGFloat(self.height) / CGFloat(self.width) + let previewViewAspectRatio = previewViewFrame.height / previewViewFrame.width + + let pointsToPixel: CGFloat = + imageAspectRatio > previewViewAspectRatio + ? CGFloat(self.width) / previewViewFrame.width + : CGFloat(self.height) / previewViewFrame.height + + let cropRatio = CGFloat(16.0) / CGFloat(9.0) + var fullScreenCropHeight: CGFloat = CGFloat(self.height) + var fullScreenCropWidth: CGFloat = CGFloat(self.width) + + let previewViewHeight = previewViewFrame.height * pointsToPixel + let previewViewWidth = previewViewFrame.width * pointsToPixel + + // Get ratio to convert points to pixels + let fullScreenImage: CGImage? = { + // if image is already 16:9, no need to crop to match crop ratio + /// TODO(jaimepark): make sure this works + if abs(cropRatio - imageAspectRatio) < 0.0001 { + return self + } + + // imageAspectRatio not being 16:9 implies image being in landscape + // get width to first not cut out any card information + fullScreenCropWidth = previewViewFrame.width * pointsToPixel + fullScreenCropHeight = fullScreenCropWidth * (16.0 / 9.0) + let imageHeight = CGFloat(self.height) + + // If 16:9 crop height is larger than the image height itself (i.e. custom formsheet size height is much shorter than the width), crop the image with full height and add grey boxes + if fullScreenCropHeight > imageHeight { + guard + let croppedImage = self.cropping( + to: CGRect( + x: imageCenterX - fullScreenCropWidth / 2.0, + y: imageCenterY - imageHeight / 2.0, + width: fullScreenCropWidth, + height: imageHeight + ) + ) + else { return nil } + return self.drawGrayToFillFullScreen( + croppedImage: croppedImage, + targetSize: CGSize(width: fullScreenCropWidth, height: fullScreenCropHeight) + ) + } + + return self.cropping( + to: CGRect( + x: imageCenterX - fullScreenCropWidth / 2.0, + y: imageCenterY - fullScreenCropHeight / 2.0, + width: fullScreenCropWidth, + height: fullScreenCropHeight + ) + ) + }() + + let roiRect: CGRect? = { + let roiWidth = regionOfInterestLabelFrame.width * pointsToPixel + let roiHeight = regionOfInterestLabelFrame.height * pointsToPixel + + var roiCenterX = roiWidth / 2.0 + regionOfInterestLabelFrame.origin.x * pointsToPixel + var roiCenterY = roiHeight / 2.0 + regionOfInterestLabelFrame.origin.y * pointsToPixel + + if fullScreenCropHeight > previewViewHeight { + roiCenterY += (fullScreenCropHeight - previewViewHeight) / 2.0 + } + if fullScreenCropWidth > previewViewWidth { + roiCenterX += (fullScreenCropWidth - previewViewWidth) / 2.0 + } + + return CGRect( + x: roiCenterX - roiWidth / 2.0, + y: roiCenterY - roiHeight / 2.0, + width: roiWidth, + height: roiHeight + ) + }() + + guard let regionOfInterestRect = roiRect, let fullScreenCgImage = fullScreenImage else { + return nil + } + return (fullScreenCgImage, regionOfInterestRect) + } +} + +extension CVPixelBuffer { + func cgImage() -> CGImage? { + var cgImage: CGImage? + VTCreateCGImageFromCVPixelBuffer(self, options: nil, imageOut: &cgImage) + + return cgImage + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/UIImage+pixelBuffer.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/UIImage+pixelBuffer.swift new file mode 100644 index 00000000..959c22d0 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Extensions/UIImage+pixelBuffer.swift @@ -0,0 +1,248 @@ +// Copyright (c) 2017 M.I. Hollemans +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// https://github.com/hollance/CoreMLHelpers + +import UIKit +import VideoToolbox + +extension UIImage { + /// Resizes the image to width x height and converts it to an RGB CVPixelBuffer. + func pixelBuffer(width: Int, height: Int) -> CVPixelBuffer? { + return pixelBuffer( + width: width, + height: height, + pixelFormatType: kCVPixelFormatType_32ARGB, + colorSpace: CGColorSpaceCreateDeviceRGB(), + alphaInfo: .noneSkipFirst + ) + } + + /// Resizes the image to width x height and converts it to a grayscale CVPixelBuffer. + func pixelBufferGray(width: Int, height: Int) -> CVPixelBuffer? { + return pixelBuffer( + width: width, + height: height, + pixelFormatType: kCVPixelFormatType_OneComponent8, + colorSpace: CGColorSpaceCreateDeviceGray(), + alphaInfo: .none + ) + } + + /// Convert to pixel buffer without resizing + func pixelBufferGray() -> CVPixelBuffer? { + return pixelBufferGray(width: Int(self.size.width), height: Int(self.size.height)) + } + + func pixelBuffer( + width: Int, + height: Int, + pixelFormatType: OSType, + colorSpace: CGColorSpace, + alphaInfo: CGImageAlphaInfo + ) -> CVPixelBuffer? { + var maybePixelBuffer: CVPixelBuffer? + let attrs = [ + kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, + kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue, + ] + let status = CVPixelBufferCreate( + kCFAllocatorDefault, + width, + height, + pixelFormatType, + attrs as CFDictionary, + &maybePixelBuffer + ) + + guard status == kCVReturnSuccess, let pixelBuffer = maybePixelBuffer else { + return nil + } + + CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) + let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer) + + guard + let context = CGContext( + data: pixelData, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), + space: colorSpace, + bitmapInfo: alphaInfo.rawValue + ) + else { + return nil + } + + UIGraphicsPushContext(context) + context.translateBy(x: 0, y: CGFloat(height)) + context.scaleBy(x: 1, y: -1) + self.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) + UIGraphicsPopContext() + + CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) + return pixelBuffer + } + + func areCornerPixelsBlack() -> Bool { + let pixelBuffer = self.pixelBufferGray()! + var result = true + + CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) + let width = CVPixelBufferGetWidth(pixelBuffer) + let height = CVPixelBufferGetHeight(pixelBuffer) + let pixels = CVPixelBufferGetBaseAddress(pixelBuffer) + let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) + // let pixelBufferIndex = x + y * bytesPerRow + var pixelValue = pixels?.load(fromByteOffset: 0, as: UInt8.self) ?? 0 + result = result && pixelValue == 0 + pixelValue = pixels?.load(fromByteOffset: (width - 1), as: UInt8.self) ?? 0 + result = result && pixelValue == 0 + pixelValue = pixels?.load(fromByteOffset: ((height - 1) * bytesPerRow), as: UInt8.self) ?? 0 + result = result && pixelValue == 0 + pixelValue = + pixels?.load(fromByteOffset: ((width - 1) + (height - 1) * bytesPerRow), as: UInt8.self) + ?? 0 + result = result && pixelValue == 0 + CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly) + + return result + } +} + +extension UIImage { + /// Creates a new UIImage from a CVPixelBuffer. + /// NOTE: This only works for RGB pixel buffers, not for grayscale. + convenience init?( + pixelBuffer: CVPixelBuffer + ) { + var cgImage: CGImage? + VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage) + + if let cgImage = cgImage { + self.init(cgImage: cgImage) + } else { + return nil + } + } + + /// Creates a new UIImage from a CVPixelBuffer, using Core Image. + convenience init?( + pixelBuffer: CVPixelBuffer, + context: CIContext + ) { + let ciImage = CIImage(cvPixelBuffer: pixelBuffer) + let rect = CGRect( + x: 0, + y: 0, + width: CVPixelBufferGetWidth(pixelBuffer), + height: CVPixelBufferGetHeight(pixelBuffer) + ) + if let cgImage = context.createCGImage(ciImage, from: rect) { + self.init(cgImage: cgImage) + } else { + return nil + } + } +} + +extension UIImage { + /// Creates a new UIImage from an array of RGBA bytes. + @nonobjc class func fromByteArrayRGBA( + _ bytes: [UInt8], + width: Int, + height: Int, + scale: CGFloat = 0, + orientation: UIImage.Orientation = .up + ) -> UIImage? { + return fromByteArray( + bytes, + width: width, + height: height, + scale: scale, + orientation: orientation, + bytesPerRow: width * 4, + colorSpace: CGColorSpaceCreateDeviceRGB(), + alphaInfo: .premultipliedLast + ) + } + + /// Creates a new UIImage from an array of grayscale bytes. + @nonobjc class func fromByteArrayGray( + _ bytes: [UInt8], + width: Int, + height: Int, + scale: CGFloat = 0, + orientation: UIImage.Orientation = .up + ) -> UIImage? { + return fromByteArray( + bytes, + width: width, + height: height, + scale: scale, + orientation: orientation, + bytesPerRow: width, + colorSpace: CGColorSpaceCreateDeviceGray(), + alphaInfo: .none + ) + } + + @nonobjc class func fromByteArray( + _ bytes: [UInt8], + width: Int, + height: Int, + scale: CGFloat, + orientation: UIImage.Orientation, + bytesPerRow: Int, + colorSpace: CGColorSpace, + alphaInfo: CGImageAlphaInfo + ) -> UIImage? { + var image: UIImage? + bytes.withUnsafeBytes { ptr in + if let context = CGContext( + data: UnsafeMutableRawPointer(mutating: ptr.baseAddress!), + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: bytesPerRow, + space: colorSpace, + bitmapInfo: alphaInfo.rawValue + ), + let cgImage = context.makeImage() + { + image = UIImage(cgImage: cgImage, scale: scale, orientation: orientation) + } + } + return image + } +} + +extension UIImage { + static func blankGrayImage(width: Int, height: Int) -> UIImage? { + UIGraphicsBeginImageContext(CGSize(width: width, height: height)) + UIColor.gray.setFill() + UIRectFill(CGRect(x: 0, y: 0, width: width, height: height)) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return newImage + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/AsyncModelLoading.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/AsyncModelLoading.swift new file mode 100644 index 00000000..5547d14f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/AsyncModelLoading.swift @@ -0,0 +1,66 @@ +// +// AsyncModelLoading.swift +// StripeCardScan +// +// Created by Scott Grant on 8/29/22. +// + +import CoreML + +protocol MLModelClassType { + static var urlOfModelInThisBundle: URL { get } +} + +protocol AsyncMLModelLoading { + associatedtype ModelClassType + + static func createModelClass(using model: MLModel) -> ModelClassType + static func asyncLoad( + contentsOf modelURL: URL, + configuration: MLModelConfiguration, + completionHandler handler: @escaping (Swift.Result) -> Void + ) +} + +extension AsyncMLModelLoading where ModelClassType: MLModelClassType { + static func asyncLoad( + contentsOf modelURL: URL = ModelClassType.urlOfModelInThisBundle, + configuration: MLModelConfiguration = MLModelConfiguration(), + completionHandler handler: @escaping (Swift.Result) -> Void + ) { + let deliverResult: (MLModel?, Error?) -> Void = { (model, error) in + if let error = error { + handler(.failure(error)) + } else if let model = model { + handler(.success(Self.createModelClass(using: model))) + } else { + fatalError( + "SPI failure: -[MLModel loadContentsOfURL:configuration::completionHandler:] vends nil for both model and error." + ) + } + } + + if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { + MLModel.__loadContents( + of: modelURL, + configuration: configuration, + completionHandler: deliverResult + ) + } else { + DispatchQueue.global(qos: .userInitiated).async { + var model: MLModel? + var error: Error? + + let result = Swift.Result { try MLModel(contentsOf: modelURL) } + switch result { + case .success(let m): + model = m + case .failure(let e): + error = e + } + + deliverResult(model, error) + } + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/SSDOcr+Utils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/SSDOcr+Utils.swift new file mode 100644 index 00000000..b1628c25 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/SSDOcr+Utils.swift @@ -0,0 +1,19 @@ +// +// SSDOcr+Utils.swift +// StripeCardScan +// +// Created by Scott Grant on 7/7/22. +// + +import CoreML + +extension SSDOcr: MLModelClassType { +} + +extension SSDOcr: AsyncMLModelLoading { + typealias ModelClassType = SSDOcr + + static func createModelClass(using model: MLModel) -> SSDOcr { + return SSDOcr(model: model) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/SSDOcr.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/SSDOcr.swift new file mode 100644 index 00000000..95ad81c7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/SSDOcr.swift @@ -0,0 +1,320 @@ +// +// SSDOcr.swift +// +// This file was automatically generated and should not be edited. +// + +import CoreML + +/// Model Prediction Input Type +@available(macOS 10.13.2, iOS 11.2, tvOS 11.2, watchOS 4.2, *) +class SSDOcrInput: MLFeatureProvider { + + /// 0 as color (kCVPixelFormatType_32BGRA) image buffer, 600 pixels wide by 375 pixels high + var _0: CVPixelBuffer + + var featureNames: Set { + return ["0"] + } + + func featureValue(for featureName: String) -> MLFeatureValue? { + if featureName == "0" { + return MLFeatureValue(pixelBuffer: _0) + } + return nil + } + + init( + _0: CVPixelBuffer + ) { + self._0 = _0 + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + convenience init( + _0With _0: CGImage + ) throws { + let ___0 = try MLFeatureValue( + cgImage: _0, + pixelsWide: 600, + pixelsHigh: 375, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + self.init(_0: ___0) + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + convenience init( + _0At _0: URL + ) throws { + let ___0 = try MLFeatureValue( + imageAt: _0, + pixelsWide: 600, + pixelsHigh: 375, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + self.init(_0: ___0) + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + func set_0(with _0: CGImage) throws { + self._0 = try MLFeatureValue( + cgImage: _0, + pixelsWide: 600, + pixelsHigh: 375, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + func set_0(with _0: URL) throws { + self._0 = try MLFeatureValue( + imageAt: _0, + pixelsWide: 600, + pixelsHigh: 375, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + } +} + +/// Model Prediction Output Type +@available(macOS 10.13.2, iOS 11.2, tvOS 11.2, watchOS 4.2, *) +class SSDOcrOutput: MLFeatureProvider { + + /// Source provided by CoreML + + private let provider: MLFeatureProvider + + /// MultiArray of shape (1, 1, 1, 3420, 10). The first and second dimensions correspond to sequence and batch size, respectively as multidimensional array of floats + lazy var scores: MLMultiArray = { + [unowned self] in return self.provider.featureValue(for: "scores")!.multiArrayValue + }()! + + /// MultiArray of shape (1, 1, 1, 3420, 4). The first and second dimensions correspond to sequence and batch size, respectively as multidimensional array of floats + lazy var boxes: MLMultiArray = { + [unowned self] in return self.provider.featureValue(for: "boxes")!.multiArrayValue + }()! + + /// MultiArray of shape (1, 1, 1, 3420, 1). The first and second dimensions correspond to sequence and batch size, respectively as multidimensional array of floats + lazy var filter: MLMultiArray = { + [unowned self] in return self.provider.featureValue(for: "filter")!.multiArrayValue + }()! + + var featureNames: Set { + return self.provider.featureNames + } + + func featureValue(for featureName: String) -> MLFeatureValue? { + return self.provider.featureValue(for: featureName) + } + + init( + scores: MLMultiArray, + boxes: MLMultiArray, + filter: MLMultiArray + ) { + self.provider = try! MLDictionaryFeatureProvider(dictionary: [ + "scores": MLFeatureValue(multiArray: scores), + "boxes": MLFeatureValue(multiArray: boxes), + "filter": MLFeatureValue(multiArray: filter), + ]) + } + + init( + features: MLFeatureProvider + ) { + self.provider = features + } +} + +/// Class for model loading and prediction +@available(macOS 10.13.2, iOS 11.2, tvOS 11.2, watchOS 4.2, *) +@_spi(STP) public class SSDOcr { + let model: MLModel + + /// URL of model assuming it was installed in the same bundle as this class + class var urlOfModelInThisBundle: URL { + let bundle = Bundle(for: self) + return bundle.url(forResource: "SSDOcr", withExtension: "mlmodelc")! + } + + /// Construct SSDOcr instance with an existing MLModel object. + /// + /// Usually the application does not use this initializer unless it makes a subclass of SSDOcr. + /// Such application may want to use `MLModel(contentsOfURL:configuration:)` and `SSDOcr.urlOfModelInThisBundle` to create a MLModel object to pass-in. + /// + /// - parameters: + /// - model: MLModel object + init( + model: MLModel + ) { + self.model = model + } + + /// Construct SSDOcr instance by automatically loading the model from the app's bundle. + @available( + *, + deprecated, + message: "Use init(configuration:) instead and handle errors appropriately." + ) + convenience init() { + try! self.init(contentsOf: type(of: self).urlOfModelInThisBundle) + } + + /// Construct a model with configuration + /// + /// - parameters: + /// - configuration: the desired model configuration + /// + /// - throws: an NSError object that describes the problem + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *) + convenience init( + configuration: MLModelConfiguration + ) throws { + try self.init( + contentsOf: type(of: self).urlOfModelInThisBundle, + configuration: configuration + ) + } + + /// Construct SSDOcr instance with explicit path to mlmodelc file + /// - parameters: + /// - modelURL: the file url of the model + /// + /// - throws: an NSError object that describes the problem + convenience init( + contentsOf modelURL: URL + ) throws { + try self.init(model: MLModel(contentsOf: modelURL)) + } + + /// Construct a model with URL of the .mlmodelc directory and configuration + /// + /// - parameters: + /// - modelURL: the file url of the model + /// - configuration: the desired model configuration + /// + /// - throws: an NSError object that describes the problem + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *) + convenience init( + contentsOf modelURL: URL, + configuration: MLModelConfiguration + ) throws { + try self.init(model: MLModel(contentsOf: modelURL, configuration: configuration)) + } + + /// Construct SSDOcr instance asynchronously with optional configuration. + /// + /// Model loading may take time when the model content is not immediately available (e.g. encrypted model). Use this factory method especially when the caller is on the main thread. + /// + /// - parameters: + /// - configuration: the desired model configuration + /// - handler: the completion handler to be called when the model loading completes successfully or unsuccessfully + @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) + class func load( + configuration: MLModelConfiguration = MLModelConfiguration(), + completionHandler handler: @escaping (Swift.Result) -> Void + ) { + return self.load( + contentsOf: self.urlOfModelInThisBundle, + configuration: configuration, + completionHandler: handler + ) + } + + /// Construct SSDOcr instance asynchronously with URL of the .mlmodelc directory with optional configuration. + /// + /// Model loading may take time when the model content is not immediately available (e.g. encrypted model). Use this factory method especially when the caller is on the main thread. + /// + /// - parameters: + /// - modelURL: the URL to the model + /// - configuration: the desired model configuration + /// - handler: the completion handler to be called when the model loading completes successfully or unsuccessfully + @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) + class func load( + contentsOf modelURL: URL, + configuration: MLModelConfiguration = MLModelConfiguration(), + completionHandler handler: @escaping (Swift.Result) -> Void + ) { + MLModel.__loadContents(of: modelURL, configuration: configuration) { (model, error) in + if let error = error { + handler(.failure(error)) + } else if let model = model { + handler(.success(SSDOcr(model: model))) + } else { + fatalError( + "SPI failure: -[MLModel loadContentsOfURL:configuration::completionHandler:] vends nil for both model and error." + ) + } + } + } + + /// Make a prediction using the structured interface + /// + /// - parameters: + /// - input: the input to the prediction as SSDOcrInput + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as SSDOcrOutput + func prediction(input: SSDOcrInput) throws -> SSDOcrOutput { + return try self.prediction(input: input, options: MLPredictionOptions()) + } + + /// Make a prediction using the structured interface + /// + /// - parameters: + /// - input: the input to the prediction as SSDOcrInput + /// - options: prediction options + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as SSDOcrOutput + func prediction(input: SSDOcrInput, options: MLPredictionOptions) throws -> SSDOcrOutput { + let outFeatures = try model.prediction(from: input, options: options) + return SSDOcrOutput(features: outFeatures) + } + + /// Make a prediction using the convenience interface + /// + /// - parameters: + /// - _0 as color (kCVPixelFormatType_32BGRA) image buffer, 600 pixels wide by 375 pixels high + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as SSDOcrOutput + func prediction(_0: CVPixelBuffer) throws -> SSDOcrOutput { + let input_ = SSDOcrInput(_0: _0) + return try self.prediction(input: input_) + } + + /// Make a batch prediction using the structured interface + /// + /// - parameters: + /// - inputs: the inputs to the prediction as [SSDOcrInput] + /// - options: prediction options + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as [SSDOcrOutput] + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *) + func predictions( + inputs: [SSDOcrInput], + options: MLPredictionOptions = MLPredictionOptions() + ) throws -> [SSDOcrOutput] { + let batchIn = MLArrayBatchProvider(array: inputs) + let batchOut = try model.predictions(from: batchIn, options: options) + var results: [SSDOcrOutput] = [] + results.reserveCapacity(inputs.count) + for i in 0.. UxModel { + return UxModel(model: model) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/UxModel.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/UxModel.swift new file mode 100644 index 00000000..12ec2425 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLModels/UxModel.swift @@ -0,0 +1,306 @@ +// +// UxModel.swift +// +// This file was automatically generated and should not be edited. +// + +import CoreML + +/// Model Prediction Input Type +@available(macOS 10.13.2, iOS 11.2, tvOS 11.2, watchOS 4.2, *) +class UxModelInput: MLFeatureProvider { + + /// input1 as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high + var input1: CVPixelBuffer + + var featureNames: Set { + return ["input1"] + } + + func featureValue(for featureName: String) -> MLFeatureValue? { + if featureName == "input1" { + return MLFeatureValue(pixelBuffer: input1) + } + return nil + } + + init( + input1: CVPixelBuffer + ) { + self.input1 = input1 + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + convenience init( + input1With input1: CGImage + ) throws { + let __input1 = try MLFeatureValue( + cgImage: input1, + pixelsWide: 224, + pixelsHigh: 224, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + self.init(input1: __input1) + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + convenience init( + input1At input1: URL + ) throws { + let __input1 = try MLFeatureValue( + imageAt: input1, + pixelsWide: 224, + pixelsHigh: 224, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + self.init(input1: __input1) + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + func setInput1(with input1: CGImage) throws { + self.input1 = try MLFeatureValue( + cgImage: input1, + pixelsWide: 224, + pixelsHigh: 224, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + } + + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + func setInput1(with input1: URL) throws { + self.input1 = try MLFeatureValue( + imageAt: input1, + pixelsWide: 224, + pixelsHigh: 224, + pixelFormatType: kCVPixelFormatType_32ARGB, + options: nil + ).imageBufferValue! + } +} + +/// Model Prediction Output Type +@available(macOS 10.13.2, iOS 11.2, tvOS 11.2, watchOS 4.2, *) +class UxModelOutput: MLFeatureProvider { + + /// Source provided by CoreML + + private let provider: MLFeatureProvider + + /// output1 as 3 element vector of doubles + lazy var output1: MLMultiArray = { + [unowned self] in return self.provider.featureValue(for: "output1")!.multiArrayValue + }()! + + var featureNames: Set { + return self.provider.featureNames + } + + func featureValue(for featureName: String) -> MLFeatureValue? { + return self.provider.featureValue(for: featureName) + } + + init( + output1: MLMultiArray + ) { + self.provider = try! MLDictionaryFeatureProvider(dictionary: [ + "output1": MLFeatureValue(multiArray: output1) + ]) + } + + init( + features: MLFeatureProvider + ) { + self.provider = features + } +} + +/// Class for model loading and prediction +@available(macOS 10.13.2, iOS 11.2, tvOS 11.2, watchOS 4.2, *) +@_spi(STP) public class UxModel { + let model: MLModel + + /// URL of model assuming it was installed in the same bundle as this class + class var urlOfModelInThisBundle: URL { + let bundle = Bundle(for: self) + return bundle.url(forResource: "UxModel", withExtension: "mlmodelc")! + } + + /// Construct UxModel instance with an existing MLModel object. + /// + /// Usually the application does not use this initializer unless it makes a subclass of UxModel. + /// Such application may want to use `MLModel(contentsOfURL:configuration:)` and `UxModel.urlOfModelInThisBundle` to create a MLModel object to pass-in. + /// + /// - parameters: + /// - model: MLModel object + init( + model: MLModel + ) { + self.model = model + } + + /// Construct UxModel instance by automatically loading the model from the app's bundle. + @available( + *, + deprecated, + message: "Use init(configuration:) instead and handle errors appropriately." + ) + convenience init() { + try! self.init(contentsOf: type(of: self).urlOfModelInThisBundle) + } + + /// Construct a model with configuration + /// + /// - parameters: + /// - configuration: the desired model configuration + /// + /// - throws: an NSError object that describes the problem + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *) + convenience init( + configuration: MLModelConfiguration + ) throws { + try self.init( + contentsOf: type(of: self).urlOfModelInThisBundle, + configuration: configuration + ) + } + + /// Construct UxModel instance with explicit path to mlmodelc file + /// - parameters: + /// - modelURL: the file url of the model + /// + /// - throws: an NSError object that describes the problem + convenience init( + contentsOf modelURL: URL + ) throws { + try self.init(model: MLModel(contentsOf: modelURL)) + } + + /// Construct a model with URL of the .mlmodelc directory and configuration + /// + /// - parameters: + /// - modelURL: the file url of the model + /// - configuration: the desired model configuration + /// + /// - throws: an NSError object that describes the problem + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *) + convenience init( + contentsOf modelURL: URL, + configuration: MLModelConfiguration + ) throws { + try self.init(model: MLModel(contentsOf: modelURL, configuration: configuration)) + } + + /// Construct UxModel instance asynchronously with optional configuration. + /// + /// Model loading may take time when the model content is not immediately available (e.g. encrypted model). Use this factory method especially when the caller is on the main thread. + /// + /// - parameters: + /// - configuration: the desired model configuration + /// - handler: the completion handler to be called when the model loading completes successfully or unsuccessfully + @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) + class func load( + configuration: MLModelConfiguration = MLModelConfiguration(), + completionHandler handler: @escaping (Swift.Result) -> Void + ) { + return self.load( + contentsOf: self.urlOfModelInThisBundle, + configuration: configuration, + completionHandler: handler + ) + } + + /// Construct UxModel instance asynchronously with URL of the .mlmodelc directory with optional configuration. + /// + /// Model loading may take time when the model content is not immediately available (e.g. encrypted model). Use this factory method especially when the caller is on the main thread. + /// + /// - parameters: + /// - modelURL: the URL to the model + /// - configuration: the desired model configuration + /// - handler: the completion handler to be called when the model loading completes successfully or unsuccessfully + @available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) + class func load( + contentsOf modelURL: URL, + configuration: MLModelConfiguration = MLModelConfiguration(), + completionHandler handler: @escaping (Swift.Result) -> Void + ) { + MLModel.__loadContents(of: modelURL, configuration: configuration) { (model, error) in + if let error = error { + handler(.failure(error)) + } else if let model = model { + handler(.success(UxModel(model: model))) + } else { + fatalError( + "SPI failure: -[MLModel loadContentsOfURL:configuration::completionHandler:] vends nil for both model and error." + ) + } + } + } + + /// Make a prediction using the structured interface + /// + /// - parameters: + /// - input: the input to the prediction as UxModelInput + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as UxModelOutput + func prediction(input: UxModelInput) throws -> UxModelOutput { + return try self.prediction(input: input, options: MLPredictionOptions()) + } + + /// Make a prediction using the structured interface + /// + /// - parameters: + /// - input: the input to the prediction as UxModelInput + /// - options: prediction options + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as UxModelOutput + func prediction(input: UxModelInput, options: MLPredictionOptions) throws -> UxModelOutput { + let outFeatures = try model.prediction(from: input, options: options) + return UxModelOutput(features: outFeatures) + } + + /// Make a prediction using the convenience interface + /// + /// - parameters: + /// - input1 as color (kCVPixelFormatType_32BGRA) image buffer, 224 pixels wide by 224 pixels high + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as UxModelOutput + func prediction(input1: CVPixelBuffer) throws -> UxModelOutput { + let input_ = UxModelInput(input1: input1) + return try self.prediction(input: input_) + } + + /// Make a batch prediction using the structured interface + /// + /// - parameters: + /// - inputs: the inputs to the prediction as [UxModelInput] + /// - options: prediction options + /// + /// - throws: an NSError object that describes the problem + /// + /// - returns: the result of the prediction as [UxModelOutput] + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *) + func predictions( + inputs: [UxModelInput], + options: MLPredictionOptions = MLPredictionOptions() + ) throws -> [UxModelOutput] { + let batchIn = MLArrayBatchProvider(array: inputs) + let batchOut = try model.predictions(from: batchIn, options: options) + var results: [UxModelOutput] = [] + results.reserveCapacity(inputs.count) + for i in 0.. Void] = [] + var isActive = false + + init( + label: String + ) { + self.queue = DispatchQueue(label: "ActiveStateComputation \(label)") + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.isActive = UIApplication.shared.applicationState == .active + + // We don't need to unregister these functions because the system will clean + // them up for us + NotificationCenter.default.addObserver( + self, + selector: #selector(self.willResignActive), + name: UIApplication.willResignActiveNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(self.didBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + } + } + + func async(execute work: @escaping () -> Void) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + let state = UIApplication.shared.applicationState + guard state == .active, self.isActive else { + self.pendingComputations.append(work) + return + } + + self.queue.async { work() } + } + } + + @objc func willResignActive() { + assert(UIApplication.shared.applicationState == .active) + assert(Thread.isMainThread) + isActive = false + queue.sync {} + } + + @objc func didBecomeActive() { + assert(UIApplication.shared.applicationState == .active) + assert(Thread.isMainThread) + isActive = true + for work in pendingComputations { + queue.async { work() } + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/AppState.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/AppState.swift new file mode 100644 index 00000000..3ceff4cc --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/AppState.swift @@ -0,0 +1,22 @@ +import Foundation + +struct AppState { + + static let lock = DispatchSemaphore(value: 1) + static private var isInBackground = false + + static var inBackground: Bool { + get { + lock.wait() + let background = isInBackground + lock.signal() + return background + } + + set(value) { + lock.wait() + isInBackground = value + lock.signal() + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedAllBoxes.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedAllBoxes.swift new file mode 100644 index 00000000..66310e76 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedAllBoxes.swift @@ -0,0 +1,18 @@ +// +// DetectedAllBoxes.swift +// CardScan +// +// Created by Zain on 8/15/19. +// +/// Data structure used to store all the detected boxes per frame or scan + +struct DetectedAllBoxes { + var allBoxes: [DetectedSSDBox] = [] + + init() {} + + func toArray() -> [[String: Any]] { + let frameArray = self.allBoxes.map { $0.toDict() } + return frameArray + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedAllOcrBoxes.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedAllOcrBoxes.swift new file mode 100644 index 00000000..88e8eece --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedAllOcrBoxes.swift @@ -0,0 +1,23 @@ +// +// DetectedAllOcrBoxes.swift +// CardScan +// +// Created by xaen on 3/22/20. +// +import CoreGraphics +import Foundation + +struct DetectedAllOcrBoxes { + var allBoxes: [DetectedSSDOcrBox] = [] + + init() {} + + func toArray() -> [[String: Any]] { + let frameArray = self.allBoxes.map { $0.toDict() } + return frameArray + } + + func getBoundingBoxesOfDigits() -> [CGRect] { + return self.allBoxes.map { $0.rect } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedBox.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedBox.swift new file mode 100644 index 00000000..36863411 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedBox.swift @@ -0,0 +1,62 @@ +import CoreGraphics +import Foundation + +/// Data structure to keep track of each box that the model detects. +/// +/// Note: the rect member is in the image's coordinate system. + +struct DetectedBox { + let rect: CGRect + let row: Int + let col: Int + let confidence: Double + let numRows: Int + let numCols: Int + let boxSize: CGSize + let cardSize: CGSize + let imageSize: CGSize + + init( + row: Int, + col: Int, + confidence: Double, + numRows: Int, + numCols: Int, + boxSize: CGSize, + cardSize: CGSize, + imageSize: CGSize + ) { + + // Resize the box to transform it from the model's coordinates into + // the image's coordinates + let w = boxSize.width * imageSize.width / cardSize.width + let h = boxSize.height * imageSize.height / cardSize.height + let x = (imageSize.width - w) / CGFloat(numCols - 1) * CGFloat(col) + let y = (imageSize.height - h) / CGFloat(numRows - 1) * CGFloat(row) + self.rect = CGRect(x: x, y: y, width: w, height: h) + self.row = row + self.col = col + self.confidence = confidence + self.numRows = numRows + self.numCols = numCols + self.boxSize = boxSize + self.cardSize = cardSize + self.imageSize = imageSize + } + + func move(row: Int, col: Int) -> DetectedBox? { + if row < 0 || row >= self.numRows || col < 0 || col >= self.numCols { + return nil + } + return DetectedBox( + row: row, + col: col, + confidence: self.confidence, + numRows: self.numRows, + numCols: self.numCols, + boxSize: self.boxSize, + cardSize: self.cardSize, + imageSize: self.imageSize + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedSSDBox.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedSSDBox.swift new file mode 100644 index 00000000..fffa3891 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedSSDBox.swift @@ -0,0 +1,51 @@ +// +// DetectedSSDBox.swift +// CardScan +// +// Created by Zain on 8/7/19. +// +import CoreGraphics +import Foundation + +struct DetectedSSDBox { + let rect: CGRect + let label: Int + let confidence: Float + let imgSize: CGSize + + init( + category: Int, + conf: Float, + XMin: Double, + YMin: Double, + XMax: Double, + YMax: Double, + imageSize: CGSize + ) { + + let XMin_ = XMin * Double(imageSize.width) + let XMax_ = XMax * Double(imageSize.width) + let YMin_ = YMin * Double(imageSize.height) + let YMax_ = YMax * Double(imageSize.height) + + self.label = category + self.confidence = conf + self.rect = CGRect(x: XMin_, y: YMin_, width: XMax_ - XMin_, height: YMax_ - YMin_) + self.imgSize = imageSize + } + + func toDict() -> [String: Any] { + // The model ouputs labels that are off by 1 + // compared to the previous versions and this line + // serves to retain the consistency. This label + // correction should be removed in the future. + return [ + "x_min": self.rect.minX / self.imgSize.width, + "y_min": self.rect.minY / self.imgSize.height, + "height": self.rect.height / self.imgSize.height, + "width": self.rect.width / self.imgSize.width, + "label": self.label - 1, + "confidence": self.confidence, + ] + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedSSDOcrBox.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedSSDOcrBox.swift new file mode 100644 index 00000000..4d2031f6 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/DetectedSSDOcrBox.swift @@ -0,0 +1,41 @@ +// +// DetectedSSDOcrBox.swift +// CardScan +// +// Created by xaen on 3/22/20. +// +import CoreGraphics +import Foundation + +struct DetectedSSDOcrBox { + let rect: CGRect + let label: Int + let confidence: Float + let imgSize: CGSize + + init( + category: Int, + conf: Float, + XMin: Double, + YMin: Double, + XMax: Double, + YMax: Double, + imageSize: CGSize + ) { + + let XMin_ = XMin * Double(imageSize.width) + let XMax_ = XMax * Double(imageSize.width) + let YMin_ = YMin * Double(imageSize.height) + let YMax_ = YMax * Double(imageSize.height) + + self.label = category + self.confidence = conf + self.rect = CGRect(x: XMin_, y: YMin_, width: XMax_ - XMin_, height: YMax_ - YMin_) + self.imgSize = imageSize + } + + func toDict() -> [String: Any] { + + return ["": ""] + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/NMS.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/NMS.swift new file mode 100644 index 00000000..4f5bc044 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/NMS.swift @@ -0,0 +1,75 @@ +// +// NMS.swift +// CardScan +// +// Created by Zain on 8/6/19. +// +import CoreGraphics +import Foundation +import os.log + +struct NMS { + static func hardNMS( + subsetBoxes: [[Float]], + probs: [Float], + iouThreshold: Float, + topK: Int, + candidateSize: Int + ) -> [Int] { + /// * I highly recommend checkout SOFT NMS Implementation of Facebook Detectron Framework + /// * + /// * Args: + /// * subsetBoxes (N, 4): boxes in corner-form and probabilities. + /// * iouThreshold: intersection over union threshold. + /// * topK: keep topK results. If k <= 0, keep all the results. + /// * candidateSize: only consider the candidates with the highest scores. + /// * + /// * Returns: + /// * pickedIndices: a list of indexes of the kept boxes + + let sorted = probs.enumerated().sorted(by: { $0.element > $1.element }) + var indices = sorted.map { $0.offset } + var current: Int = 0 + var currentBox = [Float]() + var pickedIndices = [Int]() + + if indices.count > 200 { + // TODO Fix This + indices = Array(indices[0..<200]) + os_log("Exceptional Situation more than 200 candiates found", type: .error) + } + + while indices.count > 0 { + current = indices.remove(at: 0) + pickedIndices.append(current) + + if topK > 0 && topK == pickedIndices.count { + break + } + currentBox = subsetBoxes[current] + + let currentBoxRect = CGRect( + x: Double(currentBox[0]), + y: Double(currentBox[1]), + width: Double(currentBox[2] - currentBox[0]), + height: Double(currentBox[3] - currentBox[1]) + ) + + indices.removeAll(where: { + currentBoxRect.iou( + nextBox: CGRect( + x: Double(subsetBoxes[$0][0]), + y: Double(subsetBoxes[$0][1]), + width: Double(subsetBoxes[$0][2] - subsetBoxes[$0][0]), + height: Double(subsetBoxes[$0][3] - subsetBoxes[$0][1]) + ) + ) >= iouThreshold + }) + + } + + return pickedIndices + + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/OcrDD.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/OcrDD.swift new file mode 100644 index 00000000..96bccd9f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/OcrDD.swift @@ -0,0 +1,27 @@ +// +// OcrDD.swift +// CardScan +// +// Created by xaen on 4/14/20. +// +import CoreGraphics +import Foundation +import UIKit + +class OcrDD { + var lastDetectedBoxes: [CGRect] = [] + var ssdOcr = SSDOcrDetect() + init() {} + + static func configure() { + let ssdOcr = SSDOcrDetect() + ssdOcr.warmUp() + } + + func perform(croppedCardImage: CGImage) -> String? { + let number = ssdOcr.predict(image: UIImage(cgImage: croppedCardImage)) + self.lastDetectedBoxes = ssdOcr.lastDetectedBoxes + return number + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/OcrDDUtils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/OcrDDUtils.swift new file mode 100644 index 00000000..8eaa7fcb --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/OcrDDUtils.swift @@ -0,0 +1,177 @@ +// +// OcrDDUtils.swift +// CardScan +// +// Created by xaen on 6/17/20. +// + +import UIKit + +struct OcrDDUtils { + static let offsetQuickRead: Float = 2.0 + static let falsePositiveTolerance: Float = 1.2 + static let minimumCardDigits = 12 + static let numOfQuickReadDigits = 16 + static let numOfQuickReadDigitsPerGroup = 4 + + static func isQuickRead(allBoxes: DetectedAllOcrBoxes) -> Bool { + + if (allBoxes.allBoxes.isEmpty) || (allBoxes.allBoxes.count != numOfQuickReadDigits) { + return false + } + + var boxCenters = [Float]() + var boxHeights = [Float]() + var aggregateDeviation: Float = 0 + + for idx in 0.. offsetQuickRead * medianHeight { + let quickReadGroups = allBoxes.allBoxes + .sorted(by: { return $0.rect.centerY() < $1.rect.centerY() }) + .chunked(into: 4) + .map { $0.sorted(by: { return $0.rect.centerX() < $1.rect.centerX() }) } + + guard let quickReadGroupFirstRowFirstDigit = quickReadGroups[0].first, + let quickReadGroupSecondRowFirstDigit = quickReadGroups[1].first, + let quickReadGroupFirstRowLastDigit = quickReadGroups[0].last, + let quickReadGroupSecondRowLastDigit = quickReadGroups[1].last + else { + return false + } + + if quickReadGroupSecondRowFirstDigit.rect.centerX() + < quickReadGroupFirstRowLastDigit.rect.centerX() + && quickReadGroupSecondRowLastDigit.rect.centerX() + > quickReadGroupFirstRowFirstDigit.rect.centerX() + { + return true + } + } + + return false + } + + static func processQuickRead(allBoxes: DetectedAllOcrBoxes) -> (String, [CGRect])? { + + if allBoxes.allBoxes.count != numOfQuickReadDigits { + return nil + } + + var _cardNumber: String = "" + var boxes: [CGRect] = [] + let sortedBoxes = allBoxes.allBoxes.sorted(by: { (left, right) -> Bool in + let leftAverageY = (left.rect.minY / 2 + left.rect.maxY / 2) + let rightAverageY = (right.rect.minY / 2 + right.rect.maxY / 2) + return leftAverageY < rightAverageY + }) + + var start = 0 + var end = numOfQuickReadDigitsPerGroup - 1 // since indices start with 0 + for _ in 0.. (String, [CGRect])? { + + if boxes.indices.contains(start) && boxes.indices.contains(end) { + var _groupNumber: String = "" + let groupSlice = boxes[start...end] + let group = Array(groupSlice) + let sortedGroup = group.sorted(by: { $0.rect.minX < $1.rect.minX }) + var sortedBoxes: [CGRect] = [] + + for idx in 0.. (String, [CGRect])? { + + if (allBoxes.allBoxes.isEmpty) || (allBoxes.allBoxes.count < minimumCardDigits) { + return nil + } + + var leftCordinates = [Float]() + var topCordinates = [Float]() + var bottomCordinates = [Float]() + var sortedBoxes = [CGRect]() + + for idx in 0.. [CGRect] { + + let imageHeight = 375 + let imageWidth = 600 + + let scaleHeight = Float(imageHeight) / Float(shrinkageHeight) + let scaleWidth = Float(imageWidth) / Float(shrinkageWidth) + + var boxes = [CGRect]() + var xCenter: Float + var yCenter: Float + var size: Float + var ratioOne: Float + var h: Float + var w: Float + + for j in 0.. [CGRect] { + + let priorsOne = OcrPriorsGen.genPriors( + featureMapSizeHeight: OcrPriorsGen.featureMapSizeBigHeight, + featureMapSizeWidth: OcrPriorsGen.featureMapSizeBigWidth, + shrinkageHeight: OcrPriorsGen.shrinkageSmallHeight, + shrinkageWidth: OcrPriorsGen.shrinkageSmallWidth, + boxSizeMin: OcrPriorsGen.boxSizeSmallLayerOne, + boxSizeMax: OcrPriorsGen.boxSizeBigLayerOne, + aspectRatioOne: OcrPriorsGen.aspectRatioOne, + noOfPriors: OcrPriorsGen.noOfPriorsPerLocation + ) + + let priorsTwo = OcrPriorsGen.genPriors( + featureMapSizeHeight: OcrPriorsGen.featureMapSizeSmallHeight, + featureMapSizeWidth: OcrPriorsGen.featureMapSizeSmallWidth, + shrinkageHeight: OcrPriorsGen.shrinkageBigHeight, + shrinkageWidth: OcrPriorsGen.shrinkageBigWidth, + boxSizeMin: OcrPriorsGen.boxSizeBigLayerOne, + boxSizeMax: OcrPriorsGen.boxSizeBigLayerTwo, + aspectRatioOne: OcrPriorsGen.aspectRatioOne, + noOfPriors: OcrPriorsGen.noOfPriorsPerLocation + ) + + let priorsCombined = priorsOne + priorsTwo + + return priorsCombined + + } +} + +extension Float { + func clamp(minimum: Float = 0.0, maximum: Float = 1.0) -> Float { + return max(minimum, min(maximum, self)) + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PostDetectionAlgorithm.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PostDetectionAlgorithm.swift new file mode 100644 index 00000000..d4c41401 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PostDetectionAlgorithm.swift @@ -0,0 +1,221 @@ +import Foundation + +/// Organize the boxes to find possible numbers. +/// +/// After running detection, the post processing algorithm will try to find +/// sequences of boxes that are plausible card numbers. The basic techniques +/// that it uses are non-maximum suppression and depth first search on box +/// sequences to find likely numbers. There are also a number of heuristics +/// for filtering out unlikely sequences. +struct PostDetectionAlgorithm { + let kNumberWordCount = 4 + let kAmexWordCount = 5 + let kMaxBoxesToDetect = 20 + let kDeltaRowForCombine = 2 + let kDeltaColForCombine = 2 + let kDeltaRowForHorizontalNumbers = 1 + let kDeltaColForVerticalNumbers = 1 + + let sortedBoxes: [DetectedBox] + let numRows: Int + let numCols: Int + + init( + boxes: [DetectedBox] + ) { + self.sortedBoxes = boxes.sorted { $0.confidence > $1.confidence }.prefix(kMaxBoxesToDetect) + .map { $0 } + + // it's ok if this doesn't match the card row/col counts because we only + // use this for our internal algorithms. I prefer doing this as it make + // proving array bounds easier since everything is local and as long as + // we only access arrays using row/col from our boxes then we'll always + // be in bounds + self.numRows = (self.sortedBoxes.map { $0.row }.max() ?? 0) + 1 + self.numCols = (self.sortedBoxes.map { $0.col }.max() ?? 0) + 1 + } + + /// Finds traditional numbers that are horizontal on a 16 digit card. + func horizontalNumbers() -> [[DetectedBox]] { + let boxes = self.combineCloseBoxes( + deltaRow: kDeltaRowForCombine, + deltaCol: kDeltaColForCombine + ) + let lines = self.findHorizontalNumbers(words: boxes, numberOfBoxes: kNumberWordCount) + + // boxes should be roughly evenly spaced, reject any that aren't + return lines.filter { line in + let deltas = zip(line, line.dropFirst()).map { box, nextBox in nextBox.col - box.col } + let maxDelta = deltas.max() ?? 0 + let minDelta = deltas.min() ?? 0 + + return (maxDelta - minDelta) <= 2 + } + } + + /// Used for Visa quick read where the digits are in groups of four but organized veritcally + func verticalNumbers() -> [[DetectedBox]] { + let boxes = self.combineCloseBoxes( + deltaRow: kDeltaRowForCombine, + deltaCol: kDeltaColForCombine + ) + let lines = self.findVerticalNumbers(words: boxes, numberOfBoxes: kNumberWordCount) + + // boxes should be roughly evenly spaced, reject any that aren't + return lines.filter { line in + let deltas = zip(line, line.dropFirst()).map { box, nextBox in nextBox.row - box.row } + let maxDelta = deltas.max() ?? 0 + let minDelta = deltas.min() ?? 0 + + return (maxDelta - minDelta) <= 2 + } + } + + /// Finds 15 digit horizontal Amex card numbers. + /// + /// Amex has groups of 4 6 5 numbers and our detection algorithm detects clusters of four + /// digits, but we did design it to detect the groups of four within the clusters of 5 and 6. + /// Thus, our goal with Amex is to find enough boxes of 4 to cover all of the amex digits. + func amexNumbers() -> [[DetectedBox]] { + let boxes = self.combineCloseBoxes(deltaRow: kDeltaRowForCombine, deltaCol: 1) + let lines = self.findHorizontalNumbers(words: boxes, numberOfBoxes: kAmexWordCount) + + return lines.filter { line in + let colDeltas = zip(line, line.dropFirst()).map { box, nextBox in nextBox.col - box.col + } + + // we have roughly evenly spaced clusters. A single box of four, a cluster of 6 and then + // a cluster of 5. We try to recognize the first and last few digits of the 5 and 6 + // cluster, and the 5 and 6 cluster are roughly evenly spaced but the boxes within + // are close + let evenColDeltas = colDeltas.enumerated().filter { $0.0 % 2 == 0 }.map { $0.1 } + let oddColDeltas = colDeltas.enumerated().filter { $0.0 % 2 == 1 }.map { $0.1 } + let evenOddDeltas = zip(evenColDeltas, oddColDeltas).map { even, odd in + Double(even) / Double(odd) + } + + return evenOddDeltas.reduce(true) { $0 && $1 >= 2.0 } + } + } + + /// Combine close boxes favoring high confidence boxes. + func combineCloseBoxes(deltaRow: Int, deltaCol: Int) -> [DetectedBox] { + var cardGrid: [[Bool]] = Array( + repeating: Array(repeating: false, count: self.numCols), + count: self.numRows + ) + + for box in self.sortedBoxes { + cardGrid[box.row][box.col] = true + } + + // since the boxes are sorted by confidence, go through them in order to + // result in only high confidence boxes winning. There are corner cases + // where this will leave extra boxes, but that's ok because we don't + // need to be perfect here + for box in self.sortedBoxes { + if cardGrid[box.row][box.col] == false { + continue + } + for row in (box.row - deltaRow)...(box.row + deltaRow) { + for col in (box.col - deltaCol)...(box.col + deltaCol) { + if row >= 0 && row < numRows && col >= 0 && col < numCols { + cardGrid[row][col] = false + } + } + } + // add this box back + cardGrid[box.row][box.col] = true + } + + return self.sortedBoxes.filter { cardGrid[$0.row][$0.col] } + } + + /// Find all boxes that form a sequence of four boxes. + /// + /// Does a depth first search on all boxes to find all boxes that form + /// a line with four boxes. The predicate dictates which boxes are added + /// so we have a separate prediate for horizontal vs vertical numbers. + func findNumbers( + currentLine: [DetectedBox], + words: [DetectedBox], + predicate: ((DetectedBox, DetectedBox) -> Bool), + numberOfBoxes: Int, + lines: inout [[DetectedBox]] + ) { + + if currentLine.count == numberOfBoxes { + lines.append(currentLine) + return + } + + if words.count == 0 { + return + } + + guard let currentWord = currentLine.last else { + return + } + + for (idx, word) in words.enumerated() { + if predicate(currentWord, word) { + findNumbers( + currentLine: (currentLine + [word]), + words: words.dropFirst(idx + 1).map { $0 }, + predicate: predicate, + numberOfBoxes: numberOfBoxes, + lines: &lines + ) + } + } + } + + func verticalAddBoxPredicate(_ currentWord: DetectedBox, _ nextWord: DetectedBox) -> Bool { + let deltaCol = kDeltaColForVerticalNumbers + return nextWord.row > currentWord.row && nextWord.col >= (currentWord.col - deltaCol) + && nextWord.col <= (currentWord.col + deltaCol) + } + + func horizontalAddBoxPredicate(_ currentWord: DetectedBox, _ nextWord: DetectedBox) -> Bool { + let deltaRow = kDeltaRowForHorizontalNumbers + return nextWord.col > currentWord.col && nextWord.row >= (currentWord.row - deltaRow) + && nextWord.row <= (currentWord.row + deltaRow) + } + + // Note: this is simple but inefficient. Since we're dealing with small + // lists (eg 20 items) it should be fine + func findHorizontalNumbers(words: [DetectedBox], numberOfBoxes: Int) -> [[DetectedBox]] { + let sortedWords = words.sorted { $0.col < $1.col } + var lines: [[DetectedBox]] = [[]] + + for (idx, word) in sortedWords.enumerated() { + findNumbers( + currentLine: [word], + words: sortedWords.dropFirst(idx + 1).map { $0 }, + predicate: horizontalAddBoxPredicate, + numberOfBoxes: numberOfBoxes, + lines: &lines + ) + } + + return lines + } + + func findVerticalNumbers(words: [DetectedBox], numberOfBoxes: Int) -> [[DetectedBox]] { + let sortedWords = words.sorted { $0.row < $1.row } + var lines: [[DetectedBox]] = [[]] + + for (idx, word) in sortedWords.enumerated() { + findNumbers( + currentLine: [word], + words: sortedWords.dropFirst(idx + 1).map { $0 }, + predicate: verticalAddBoxPredicate, + numberOfBoxes: numberOfBoxes, + lines: &lines + ) + } + + return lines + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionAPI.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionAPI.swift new file mode 100644 index 00000000..3c23eb9d --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionAPI.swift @@ -0,0 +1,78 @@ +// +// PredictionAPI.swift +// CardScan +// +// Created by Zain on 8/6/19. +// + +import Foundation + +struct Result { + var pickedBoxProbs: [Float] + var pickedLabels: [Int] + var pickedBoxes: [[Float]] + + init() { + pickedBoxProbs = [Float]() + pickedLabels = [Int]() + pickedBoxes = [[Float]]() + } +} +struct PredictionAPI { + + /// * A utitliy struct that applies non-max supression to each class + /// * picks out the remaining boxes, the class probabilities for classes + /// * that are kept and composes all the information in one place to be returned as + /// * an object. + func predictionAPI( + scores: [[Float]], + boxes: [[Float]], + probThreshold: Float, + iouThreshold: Float, + candidateSize: Int, + topK: Int + ) -> Result { + var pickedBoxes: [[Float]] = [[Float]]() + var pickedLabels: [Int] = [Int]() + var pickedBoxProbs: [Float] = [Float]() + + for classIndex in 1.. probThreshold { + probs.append(scores[rowIndex][classIndex]) + subsetBoxes.append(boxes[rowIndex]) + } + } + + if probs.count == 0 { + continue + } + + indicies = NMS.hardNMS( + subsetBoxes: subsetBoxes, + probs: probs, + iouThreshold: iouThreshold, + topK: topK, + candidateSize: candidateSize + ) + + for idx in indicies { + pickedBoxProbs.append(probs[idx]) + pickedBoxes.append(subsetBoxes[idx]) + pickedLabels.append(classIndex) + } + } + var result: Result = Result() + result.pickedBoxProbs = pickedBoxProbs + result.pickedLabels = pickedLabels + result.pickedBoxes = pickedBoxes + + return result + + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionResult.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionResult.swift new file mode 100644 index 00000000..69c28089 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionResult.swift @@ -0,0 +1,84 @@ +// +// PredictionResult.swift +// CardScan +// +// Created by Sam King on 11/16/18. +// +import CoreGraphics +import Foundation +import UIKit + +// +// The PredictionResult includes images of the bin and the last four. The OCR model returns clusters of 4 digits for +// the number so we use only the first 4 for the bin and the full last 4 as a single image +// +struct PredictionResult { + let cardWidth: CGFloat + let cardHeight: CGFloat + let numberBoxes: [CGRect] + let number: String + let cvvBoxes: [CGRect] + + func bin() -> String { + return String(number.prefix(6)) + } + + func last4() -> String { + return String(number.suffix(4)) + } + + static func translateBox( + from modelSize: CGSize, + to imageSize: CGSize, + for box: CGRect + ) -> CGRect { + let boxes = translateBoxes(from: modelSize, to: imageSize, for: [box]) + return boxes.first! + } + + static func translateBoxes( + from modelSize: CGSize, + to imageSize: CGSize, + for boxes: [CGRect] + ) -> [CGRect] { + let scaleX = imageSize.width / modelSize.width + let scaleY = imageSize.height / modelSize.height + + return boxes.map { + CGRect( + x: $0.origin.x * scaleX, + y: $0.origin.y * scaleY, + width: $0.size.width * scaleX, + height: $0.size.height * scaleY + ) + } + } + + func translateNumber(to originalImage: CGImage) -> [CGRect] { + let scaleX = CGFloat(originalImage.width) / self.cardWidth + let scaleY = CGFloat(originalImage.height) / self.cardHeight + + return self.numberBoxes.map { + CGRect( + x: $0.origin.x * scaleX, + y: $0.origin.y * scaleY, + width: $0.size.width * scaleX, + height: $0.size.height * scaleY + ) + } + } + + func extractImagePng(from image: CGImage, for box: CGRect) -> String? { + let uiImage = image.cropping(to: box).map { UIImage(cgImage: $0) } + return uiImage.flatMap { $0.pngData()?.base64EncodedString() } + } + + func resizeImage(image: UIImage, to size: CGSize) -> UIImage? { + UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height)) + image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionUtilOcr.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionUtilOcr.swift new file mode 100644 index 00000000..661392aa --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/PredictionUtilOcr.swift @@ -0,0 +1,68 @@ +// +// PredictionUtilOcr.swift +// CardScan +// +// Created by xaen on 6/4/20. +// + +import Foundation + +struct PredictionUtilOcr { + + /// * A utitliy struct that applies non-max supression to each class + /// * picks out the remaining boxes, the class probabilities for classes + /// * that are kept and composes all the information in one place to be returned as + /// * an object. + func predictionUtil( + scores: [[Float]], + boxes: [[Float]], + probThreshold: Float, + iouThreshold: Float, + candidateSize: Int, + topK: Int + ) -> Result { + var pickedBoxes = [[Float]]() + var pickedLabels = [Int]() + var pickedBoxProbs = [Float]() + + for classIndex in 0.. probThreshold { + probs.append(scores[rowIndex][classIndex]) + subsetBoxes.append(boxes[rowIndex]) + } + } + + if probs.count == 0 { + continue + } + + let (_pickedBoxes, _pickedScores) = SoftNMS.softNMS( + subsetBoxes: subsetBoxes, + probs: probs, + probThreshold: probThreshold, + sigma: SSDOcrDetect.sigma, + topK: topK, + candidateSize: candidateSize + ) + + for idx in 0..<_pickedScores.count { + pickedBoxProbs.append(_pickedScores[idx]) + pickedBoxes.append(_pickedBoxes[idx]) + pickedLabels.append((classIndex + 1) % 10) + } + + } + var result: Result = Result() + result.pickedBoxProbs = pickedBoxProbs + result.pickedLabels = pickedLabels + result.pickedBoxes = pickedBoxes + + return result + + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SSDOcrDetect.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SSDOcrDetect.swift new file mode 100644 index 00000000..3650af65 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SSDOcrDetect.swift @@ -0,0 +1,207 @@ +// +// SSDOcrDetect.swift +// CardScan +// +// Created by xaen on 3/21/20. +// + +import CoreGraphics +import Foundation +import UIKit + +/// Documentation for SSD OCR + +@_spi(STP) public class SSDOcrDetect { + @AtomicProperty var ssdOcrModel: SSDOcr? + static var priors: [CGRect]? + + static var ssdOcrResource = "SSDOcr" + static let ssdOcrExtension = "mlmodelc" + + // SSD Model parameters + static let sigma: Float = 0.5 + let ssdOcrImageWidth = 600 + let ssdOcrImageHeight = 375 + let probThreshold: Float = 0.45 + let filterThreshold: Float = 0.39 + let iouThreshold: Float = 0.5 + let centerVariance: Float = 0.1 + let sizeVariance: Float = 0.2 + let candidateSize = 200 + let topK = 20 + + // Statistics about last prediction + var lastDetectedBoxes: [CGRect] = [] + static var hasPrintedInitError = false + + func warmUp() { + SSDOcrDetect.initializeModels() + UIGraphicsBeginImageContext( + CGSize( + width: ssdOcrImageWidth, + height: ssdOcrImageHeight + ) + ) + UIColor.white.setFill() + UIRectFill( + CGRect( + x: 0, + y: 0, + width: ssdOcrImageWidth, + height: ssdOcrImageHeight + ) + ) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + guard let ssdOcrModel = ssdOcrModel else { + return + } + if let pixelBuffer = newImage?.pixelBuffer( + width: ssdOcrImageWidth, + height: ssdOcrImageHeight + ) { + let input = SSDOcrInput(_0: pixelBuffer) + _ = try? ssdOcrModel.prediction(input: input) + } + } + + @_spi(STP) public static func loadModelFromBundle() -> SSDOcr? { + guard + let ssdOcrUrl = StripeCardScanBundleLocator.resourcesBundle.url( + forResource: SSDOcrDetect.ssdOcrResource, + withExtension: SSDOcrDetect.ssdOcrExtension + ) + else { + return nil + } + + return try? SSDOcr(contentsOf: ssdOcrUrl) + } + + init() { + if SSDOcrDetect.priors == nil { + SSDOcrDetect.priors = OcrPriorsGen.combinePriors() + } + + loadModel() + } + + static func initializeModels() { + if SSDOcrDetect.priors == nil { + SSDOcrDetect.priors = OcrPriorsGen.combinePriors() + } + } + + private func loadModel() { + guard + let ssdOcrUrl = StripeCardScanBundleLocator.resourcesBundle.url( + forResource: SSDOcrDetect.ssdOcrResource, + withExtension: SSDOcrDetect.ssdOcrExtension + ) + else { + return + } + + SSDOcr.asyncLoad(contentsOf: ssdOcrUrl) { [weak self] result in + switch result { + case .success(let model): + self?.ssdOcrModel = model + case .failure(let error): + assertionFailure("Error loading model: \(error.localizedDescription)") + } + } + } + + func detectOcrObjects(prediction: SSDOcrOutput, image: UIImage) -> String? { + var DetectedOcrBoxes = DetectedAllOcrBoxes() + + var (scores, boxes, filterArray) = prediction.getScores(filterThreshold: filterThreshold) + let regularBoxes = prediction.convertLocationsToBoxes( + locations: boxes, + priors: SSDOcrDetect.priors ?? OcrPriorsGen.combinePriors(), + centerVariance: centerVariance, + sizeVariance: sizeVariance + ) + let cornerFormBoxes = prediction.centerFormToCornerForm(regularBoxes: regularBoxes) + + (scores, boxes) = prediction.filterScoresAndBoxes( + scores: scores, + boxes: cornerFormBoxes, + filterArray: filterArray, + filterThreshold: filterThreshold + ) + + if scores.isEmpty || boxes.isEmpty { + return nil + } + + let result: Result = PredictionUtilOcr().predictionUtil( + scores: scores, + boxes: boxes, + probThreshold: probThreshold, + iouThreshold: iouThreshold, + candidateSize: candidateSize, + topK: topK + ) + + for idx in 0.. String? { + + SSDOcrDetect.initializeModels() + guard + let pixelBuffer = image.pixelBuffer( + width: ssdOcrImageWidth, + height: ssdOcrImageHeight + ) + else { + return nil + + } + + guard let ocrDetectModel = ssdOcrModel else { + return nil + } + + let input = SSDOcrInput(_0: pixelBuffer) + + guard let prediction = try? ocrDetectModel.prediction(input: input) else { + return nil + } + return self.detectOcrObjects(prediction: prediction, image: image) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SSDOcrOutputExtensions.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SSDOcrOutputExtensions.swift new file mode 100644 index 00000000..b634dfd9 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SSDOcrOutputExtensions.swift @@ -0,0 +1,176 @@ +// +// SSDOcrOutputExtensions.swift +// CardScan +// +// Created by xaen on 3/22/20. +// + +import Accelerate +import Foundation + +extension SSDOcrOutput { + + func getScores(filterThreshold: Float) -> ([[Float]], [[Float]], [Float]) { + let pointerScores = UnsafeMutablePointer(OpaquePointer(self.scores.dataPointer)) + let pointerBoxes = UnsafeMutablePointer(OpaquePointer(self.boxes.dataPointer)) + let pointerFilter = UnsafeMutablePointer(OpaquePointer(self.filter.dataPointer)) + + let numOfRowsScores = self.scores.shape[3].intValue + let numOfColsScores = self.scores.shape[4].intValue + + var scoresTest = [[Float]]( + repeating: [Float]( + repeating: 0.0, + count: numOfColsScores + ), + count: numOfRowsScores + ) + + let numOfRowsBoxes = self.boxes.shape[3].intValue + let numOfColsBoxes = self.boxes.shape[4].intValue + + var boxesTest = [[Float]]( + repeating: [Float]( + repeating: 0.0, + count: numOfColsBoxes + ), + count: numOfRowsBoxes + ) + + var filterArray = [Float]( + repeating: 0.0, + count: numOfRowsScores + ) + + for idx3 in 0.. filterThreshold { + + for idx in countScores.. [[Float]] { + let pointer = UnsafeMutablePointer(OpaquePointer(self.boxes.dataPointer)) + let numOfRows = self.boxes.shape[3].intValue + let numOfCols = self.boxes.shape[4].intValue + + var boxesTest = [[Float]]( + repeating: [Float]( + repeating: 0.0, + count: numOfCols + ), + count: numOfRows + ) + + for idx in 0.. [[Float]] { + + var resultArray: [[Float]] = Array.init() + var elementArray: [Float] = Array.init() + var elementCount: Int = 0 + for firstArray in nums { + for val in firstArray { + elementArray.append(val) + if elementArray.count >= c { + resultArray.append(elementArray) + elementArray.removeAll() + } + elementCount = elementCount + 1 + } + } + if elementCount != r * c { + resultArray = nums + } + return resultArray + } + + func convertLocationsToBoxes( + locations: [[Float]], + priors: [CGRect], + centerVariance: Float, + sizeVariance: Float + ) -> [[Float]] { + + /// SSD into boxes in the form of (center_x, center_y, h, w) + var boxes = [[Float]]() + + for i in 0.. [[Float]] { + + /// * corner form XMin, YMin, XMax, YMax + var cornerFormBoxes = regularBoxes + for i in 0.. ([[Float]], [[Float]]) { + + var prunnedScores = [[Float]]() + var prunnedBoxes = [[Float]]() + + for i in 0.. filterThreshold { + prunnedScores.append(scores[i]) + prunnedBoxes.append(boxes[i]) + } + } + return (prunnedScores, prunnedBoxes) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SoftNMS.swift b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SoftNMS.swift new file mode 100644 index 00000000..5393b47f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/MLRuntime/SoftNMS.swift @@ -0,0 +1,80 @@ +// +// SoftNMS.swift +// CardScan +// +// Created by xaen on 4/3/20. +// + +import Accelerate +import Foundation + +struct SoftNMS { + static func softNMS( + subsetBoxes: [[Float]], + probs: [Float], + probThreshold: Float, + sigma: Float, + topK: Int, + candidateSize: Int + ) -> ([[Float]], [Float]) { + + var pickedBoxes = [[Float]]() + var pickedScores = [Float]() + + var subsetBoxes = subsetBoxes + var probs = probs + + while subsetBoxes.count > 0 { + var maxElement: Float = 0.0 + var vdspIndex: vDSP_Length = 0 + vDSP_maxvi(probs, 1, &maxElement, &vdspIndex, vDSP_Length(probs.count)) + let maxIdx = Int(vdspIndex) + + let currentBox = subsetBoxes[maxIdx] + pickedBoxes.append(subsetBoxes[maxIdx]) + pickedScores.append(maxElement) + + if subsetBoxes.count == 1 { + break + } + + // Take the last box and replace the max box with the last box + subsetBoxes.remove(at: maxIdx) + probs.remove(at: maxIdx) + + var ious = [Float](repeating: 0.0, count: subsetBoxes.count) + let currentBoxRect = CGRect( + x: Double(currentBox[0]), + y: Double(currentBox[1]), + width: Double(currentBox[2] - currentBox[0]), + height: Double(currentBox[3] - currentBox[1]) + ) + + for i in 0.. probThreshold { + probsPrunned.append(probs[i]) + subsetBoxesPrunned.append(subsetBoxes[i]) + } + } + probs = probsPrunned + subsetBoxes = subsetBoxesPrunned + } + + return (pickedBoxes, pickedScores) + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/BlurView.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/BlurView.swift new file mode 100644 index 00000000..9efdf2af --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/BlurView.swift @@ -0,0 +1,40 @@ +// +// BlurView.swift +// CardScan +// +// Created by Jaime Park on 8/15/19. +// + +import UIKit + +class BlurView: UIView { + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } + + override init( + frame: CGRect + ) { + super.init(frame: frame) + } + + func maskToRoi(roi: UIView) { + let maskLayer = CAShapeLayer() + let path = CGMutablePath() + let roiCornerRadius = roi.layer.cornerRadius + let roiFrame = roi.layer.frame + let roundedRectpath = UIBezierPath.init( + roundedRect: roiFrame, + cornerRadius: roiCornerRadius + ).cgPath + + path.addRect(self.layer.bounds) + path.addPath(roundedRectpath) + maskLayer.path = path + maskLayer.fillRule = .evenOdd + self.layer.mask = maskLayer + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/CardScanSheet.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/CardScanSheet.swift new file mode 100644 index 00000000..620b674c --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/CardScanSheet.swift @@ -0,0 +1,86 @@ +// +// CardScanSheet.swift +// StripeCardScan +// +// Created by Scott Grant on 6/3/22. +// + +import Foundation +import UIKit + +/// The result of an attempt to scan a card +@frozen public enum CardScanSheetResult { + /// The customer completed the scan + case completed(card: ScannedCard) + + /// The customer canceled the scan + case canceled + + /// The attempt failed. + /// - Parameter error: The error encountered by the customer. You can display its `localizedDescription` to the customer. + case failed(error: Error) +} + +/// A drop-in class that presents a sheet for a customer to scan their card +public class CardScanSheet { + + public init() {} + + /// Presents a sheet for a customer to scan their card + /// - Parameter presentingViewController: The view controller to present a card scan sheet + /// - Parameter completion: Called with the result of the scan after the card scan sheet is dismissed + public func present( + from presentingViewController: UIViewController, + completion: @escaping (CardScanSheetResult) -> Void, + animated: Bool = true + ) { + // Guard against basic user error + guard presentingViewController.presentedViewController == nil else { + assertionFailure("presentingViewController is already presenting a view controller") + let error = CardScanSheetError.unknown( + debugDescription: "presentingViewController is already presenting a view controller" + ) + completion(.failed(error: error)) + return + } + + let vc = SimpleScanViewController() + vc.delegate = self + + // Overwrite completion closure to retain self until called + let overwrittenCompletion: (CardScanSheetResult) -> Void = { status in + // Dismiss if necessary + if vc.presentingViewController != nil { + vc.dismiss(animated: true) { + completion(status) + } + } else { + completion(status) + } + self.completion = nil + } + self.completion = overwrittenCompletion + + presentingViewController.present(vc, animated: animated) + } + + // MARK: - Internal Properties + + /// A user-supplied completion block. Nil until `present` is called. + var completion: ((CardScanSheetResult) -> Void)? +} + +extension CardScanSheet: SimpleScanDelegate { + func userDidCancelSimple(_ scanViewController: SimpleScanViewController) { + completion?(.canceled) + } + + func userDidScanCardSimple( + _ scanViewController: SimpleScanViewController, + creditCard: CreditCard + ) { + let scannedCard = ScannedCard(scannedCard: creditCard) + + completion?(.completed(card: scannedCard)) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/CornerView.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/CornerView.swift new file mode 100644 index 00000000..1722fc48 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/CornerView.swift @@ -0,0 +1,72 @@ +import UIKit + +class CornerView: UIView { + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } + + override init( + frame: CGRect + ) { + super.init(frame: frame) + } + + func setFrameSize(roi: UIView) { + let borderWidth = self.layer.borderWidth + let width = roi.layer.bounds.width + 2 * borderWidth + let height = roi.layer.bounds.height + 2 * borderWidth + let cornerViewBoundRect = CGRect( + x: self.layer.bounds.origin.x, + y: self.layer.bounds.origin.y, + width: width, + height: height + ) + self.layer.bounds = cornerViewBoundRect + } + + func drawCorners() { + let maskShapeLayer = CAShapeLayer() + let maskPath = CGMutablePath() + + let boundX = self.layer.bounds.origin.x + let boundY = self.layer.bounds.origin.y + let boundWidth = self.layer.bounds.width + let boundHeight = self.layer.bounds.height + + let cornerMultiplier = CGFloat(0.1) + let cornerLength = self.layer.frame.width * cornerMultiplier + + // top left corner + maskPath.move(to: self.layer.bounds.origin) + maskPath.addLine(to: CGPoint(x: boundX + cornerLength, y: boundY)) + maskPath.addLine(to: CGPoint(x: boundX + cornerLength, y: boundY + cornerLength)) + maskPath.addLine(to: CGPoint(x: boundX, y: boundY + cornerLength)) + maskPath.closeSubpath() + + // top right corner + maskPath.move(to: CGPoint(x: boundWidth - cornerLength, y: boundY)) + maskPath.addLine(to: CGPoint(x: boundWidth, y: boundY)) + maskPath.addLine(to: CGPoint(x: boundWidth, y: boundY + cornerLength)) + maskPath.addLine(to: CGPoint(x: boundWidth - cornerLength, y: boundY + cornerLength)) + maskPath.closeSubpath() + + // bottom left corner + maskPath.move(to: CGPoint(x: boundX, y: boundHeight - cornerLength)) + maskPath.addLine(to: CGPoint(x: boundX + cornerLength, y: boundHeight - cornerLength)) + maskPath.addLine(to: CGPoint(x: boundX + cornerLength, y: boundHeight)) + maskPath.addLine(to: CGPoint(x: boundX, y: boundHeight)) + maskPath.closeSubpath() + + // bottom right corner + maskPath.move(to: CGPoint(x: boundWidth - cornerLength, y: boundHeight - cornerLength)) + maskPath.addLine(to: CGPoint(x: boundWidth, y: boundHeight - cornerLength)) + maskPath.addLine(to: CGPoint(x: boundWidth, y: boundHeight)) + maskPath.addLine(to: CGPoint(x: boundWidth - cornerLength, y: boundHeight)) + maskPath.closeSubpath() + + maskShapeLayer.path = maskPath + self.layer.mask = maskShapeLayer + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/InterfaceOrientation.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/InterfaceOrientation.swift new file mode 100644 index 00000000..95eb2b1c --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/InterfaceOrientation.swift @@ -0,0 +1,56 @@ +import AVFoundation +// +// InterfaceOrientation.swift +// CardScan +// +// Created by Jaime Park on 4/23/20. +// +import UIKit + +extension UIWindow { + static var interfaceOrientation: UIInterfaceOrientation { + return + UIApplication.shared.windows + .first? + .windowScene? + .interfaceOrientation ?? .unknown + } + + static var interfaceOrientationToString: String { + switch self.interfaceOrientation { + case .portrait: return "portrait" + case .portraitUpsideDown: return "portrait_upside_down" + case .landscapeRight: return "landscape_right" + case .landscapeLeft: return "landscape_left" + case .unknown: return "unknown" + @unknown default: + return "unknown" + } + } +} + +extension AVCaptureVideoOrientation { + init?( + deviceOrientation: UIDeviceOrientation + ) { + switch deviceOrientation { + case .portrait: self = .portrait + case .portraitUpsideDown: self = .portraitUpsideDown + case .landscapeLeft: self = .landscapeRight + case .landscapeRight: self = .landscapeLeft + default: return nil + } + } + + init?( + interfaceOrientation: UIInterfaceOrientation + ) { + switch interfaceOrientation { + case .portrait: self = .portrait + case .portraitUpsideDown: self = .portraitUpsideDown + case .landscapeLeft: self = .landscapeLeft + case .landscapeRight: self = .landscapeRight + default: return nil + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/PreviewView.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/PreviewView.swift new file mode 100644 index 00000000..9b1040db --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/PreviewView.swift @@ -0,0 +1,90 @@ +// Sample code project: AVCam-iOS: Using AVFoundation to Capture Images and Movies +// Version: 5.0 +// +// IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2015 Apple Inc. All Rights Reserved. + +import AVFoundation +import UIKit + +class PreviewView: UIView { + var videoPreviewLayer: AVCaptureVideoPreviewLayer { + guard let layer = layer as? AVCaptureVideoPreviewLayer else { + fatalError( + "Expected `AVCaptureVideoPreviewLayer` type for layer. Check PreviewView.layerClass implementation." + ) + } + layer.videoGravity = .resizeAspectFill + return layer + } + + var session: AVCaptureSession? { + get { + return videoPreviewLayer.session + } + set { + videoPreviewLayer.session = newValue + } + } + + // MARK: Initialization + + override init( + frame: CGRect + ) { + super.init(frame: frame) + } + + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } + + // MARK: UIView + + override class var layerClass: AnyClass { + return AVCaptureVideoPreviewLayer.self + } + + override func layoutSubviews() { + super.layoutSubviews() + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanBaseViewController.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanBaseViewController.swift new file mode 100644 index 00000000..8130d333 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanBaseViewController.swift @@ -0,0 +1,580 @@ +import AVKit +import UIKit +import Vision + +protocol TestingImageDataSource: AnyObject { + func nextSquareAndFullImage() -> CGImage? +} + +class ScanBaseViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate, + AfterPermissions, OcrMainLoopDelegate +{ + + lazy var testingImageDataSource: TestingImageDataSource? = { + var result: TestingImageDataSource? + #if targetEnvironment(simulator) + if ProcessInfo.processInfo.environment["UITesting"] != nil { + result = EndToEndTestingImageDataSource() + } + #endif // targetEnvironment(simulator) + return result + }() + + var includeCardImage = false + var showDebugImageView = false + + var scanEventsDelegate: ScanEvents? + + static var isAppearing = false + static var isPadAndFormsheet: Bool = false + static let machineLearningQueue = DispatchQueue(label: "CardScanMlQueue") + private let machineLearningSemaphore = DispatchSemaphore(value: 1) + + private weak var debugImageView: UIImageView? + private weak var previewView: PreviewView? + private weak var regionOfInterestLabel: UIView? + private weak var blurView: BlurView? + private weak var cornerView: CornerView? + private var regionOfInterestLabelFrame: CGRect? + private var previewViewFrame: CGRect? + + var videoFeed = VideoFeed() + var initialVideoOrientation: AVCaptureVideoOrientation { + if ScanBaseViewController.isPadAndFormsheet { + return AVCaptureVideoOrientation(interfaceOrientation: UIWindow.interfaceOrientation) + ?? .portrait + } else { + return .portrait + } + } + + var scannedCardImage: UIImage? + private var isNavigationBarHidden: Bool? + var hideNavigationBar: Bool? + var regionOfInterestCornerRadius = CGFloat(10.0) + private var calledOnScannedCard = false + + /// Flag to keep track of first time pan is observed + private var firstPanObserved: Bool = false + /// Flag to keep track of first time frame is processed + private var firstImageProcessed: Bool = false + + var mainLoop: MachineLearningLoop? + private func ocrMainLoop() -> OcrMainLoop? { + mainLoop.flatMap { $0 as? OcrMainLoop } + } + // this is a hack to avoid changing our interface + var predictedName: String? + + // Child classes should override these functions + func onScannedCard( + number: String, + expiryYear: String?, + expiryMonth: String?, + scannedImage: UIImage? + ) {} + func showCardNumber(_ number: String, expiry: String?) {} + func showWrongCard(number: String?, expiry: String?, name: String?) {} + func showNoCard() {} + func onCameraPermissionDenied(showedPrompt: Bool) {} + func useCurrentFrameNumber(errorCorrectedNumber: String?, currentFrameNumber: String) -> Bool { + return true + } + + // MARK: Inits + init() { + super.init(nibName: nil, bundle: nil) + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Torch Logic + func toggleTorch() { + self.ocrMainLoop()?.scanStats.torchOn = !(self.ocrMainLoop()?.scanStats.torchOn ?? false) + self.videoFeed.toggleTorch() + } + + func isTorchOn() -> Bool { + return self.videoFeed.isTorchOn() + } + + func hasTorchAndIsAvailable() -> Bool { + return self.videoFeed.hasTorchAndIsAvailable() + } + + func setTorchLevel(level: Float) { + if 0.0...1.0 ~= level { + self.videoFeed.setTorchLevel(level: level) + } + } + + static func configure(apiKey: String? = nil) { + // TODO: remove this and just use stripe's main configuration path + } + + static func supportedOrientationMaskOrDefault() -> UIInterfaceOrientationMask { + guard ScanBaseViewController.isAppearing else { + // If the ScanBaseViewController isn't appearing then fall back + // to getting the orientation mask from the infoDictionary, just like + // the system would do if the user didn't override the + // supportedInterfaceOrientationsFor method + let supportedOrientations = + (Bundle.main.infoDictionary?["UISupportedInterfaceOrientations"] as? [String]) ?? [ + "UIInterfaceOrientationPortrait" + ] + + let maskArray = supportedOrientations.map { option -> UIInterfaceOrientationMask in + switch option { + case "UIInterfaceOrientationPortrait": + return UIInterfaceOrientationMask.portrait + case "UIInterfaceOrientationPortraitUpsideDown": + return UIInterfaceOrientationMask.portraitUpsideDown + case "UIInterfaceOrientationLandscapeLeft": + return UIInterfaceOrientationMask.landscapeLeft + case "UIInterfaceOrientationLandscapeRight": + return UIInterfaceOrientationMask.landscapeRight + default: + return UIInterfaceOrientationMask.portrait + } + } + + let mask: UIInterfaceOrientationMask = maskArray.reduce( + UIInterfaceOrientationMask.portrait + ) { result, element in + return UIInterfaceOrientationMask(rawValue: result.rawValue | element.rawValue) + } + + return mask + } + return ScanBaseViewController.isPadAndFormsheet ? .allButUpsideDown : .portrait + } + + static func isCompatible() -> Bool { + return self.isCompatible(configuration: ScanConfiguration()) + } + + static func isCompatible(configuration: ScanConfiguration) -> Bool { + // check to see if the user has already denined camera permission + let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + if authorizationStatus != .authorized && authorizationStatus != .notDetermined + && configuration.setPreviouslyDeniedDevicesAsIncompatible + { + return false + } + + // make sure that we don't run on iPhone 6 / 6plus or older + if configuration.runOnOldDevices { + return true + } + + return true + } + + func cancelScan() { + guard let ocrMainLoop = ocrMainLoop() else { + return + } + ocrMainLoop.userCancelled() + } + + func setupMask() { + guard let roi = self.regionOfInterestLabel else { return } + guard let blurView = self.blurView else { return } + blurView.maskToRoi(roi: roi) + } + + func setUpCorners() { + guard let roi = self.regionOfInterestLabel else { return } + guard let corners = self.cornerView else { return } + corners.setFrameSize(roi: roi) + corners.drawCorners() + } + + func permissionDidComplete(granted: Bool, showedPrompt: Bool) { + self.ocrMainLoop()?.scanStats.permissionGranted = granted + if !granted { + self.onCameraPermissionDenied(showedPrompt: showedPrompt) + } + ScanAnalyticsManager.shared.logCameraPermissionsTask(success: granted) + } + + // you must call setupOnViewDidLoad before calling this function and you have to call + // this function to get the camera going + func startCameraPreview() { + self.videoFeed.requestCameraAccess(permissionDelegate: self) + } + + internal func invokeFakeLoop() { + guard let dataSource = testingImageDataSource else { + return + } + + guard let fullTestingImage = dataSource.nextSquareAndFullImage() else { + return + } + + guard let roiFrame = self.regionOfInterestLabelFrame, + let previewViewFrame = self.previewViewFrame, + let roiRectInPixels = ScannedCardImageData.convertToPreviewLayerRect( + captureDeviceImage: fullTestingImage, + viewfinderRect: roiFrame, + previewViewRect: previewViewFrame + ) + else { + return + } + + mainLoop?.push( + imageData: ScannedCardImageData( + previewLayerImage: fullTestingImage, + previewLayerViewfinderRect: roiRectInPixels + ) + ) + } + + internal func startFakeCameraLoop() { + let timer = Timer(timeInterval: 0.1, repeats: true) { [weak self] _ in + self?.invokeFakeLoop() + } + RunLoop.main.add(timer, forMode: .default) + } + + func isSimulator() -> Bool { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + } + + func setupOnViewDidLoad( + regionOfInterestLabel: UIView, + blurView: BlurView, + previewView: PreviewView, + cornerView: CornerView?, + debugImageView: UIImageView?, + torchLevel: Float? + ) { + + self.regionOfInterestLabel = regionOfInterestLabel + self.blurView = blurView + self.previewView = previewView + self.debugImageView = debugImageView + self.debugImageView?.contentMode = .scaleAspectFit + self.cornerView = cornerView + ScanBaseViewController.isPadAndFormsheet = + UIDevice.current.userInterfaceIdiom == .pad && self.modalPresentationStyle == .formSheet + + setNeedsStatusBarAppearanceUpdate() + regionOfInterestLabel.layer.masksToBounds = true + regionOfInterestLabel.layer.cornerRadius = self.regionOfInterestCornerRadius + regionOfInterestLabel.layer.borderColor = UIColor.white.cgColor + regionOfInterestLabel.layer.borderWidth = 2.0 + + if !ScanBaseViewController.isPadAndFormsheet { + UIDevice.current.setValue(UIDeviceOrientation.portrait.rawValue, forKey: "orientation") + } + + mainLoop = createOcrMainLoop() + + if testingImageDataSource != nil { + self.ocrMainLoop()?.imageQueueSize = 20 + } + + self.ocrMainLoop()?.mainLoopDelegate = self + self.previewView?.videoPreviewLayer.session = self.videoFeed.session + + self.videoFeed.pauseSession() + // Apple example app sets up in viewDidLoad: https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/avcam_building_a_camera_app + self.videoFeed.setup( + captureDelegate: self, + initialVideoOrientation: self.initialVideoOrientation, + completion: { success in + if self.previewView?.videoPreviewLayer.connection?.isVideoOrientationSupported + ?? false + { + self.previewView?.videoPreviewLayer.connection?.videoOrientation = + self.initialVideoOrientation + } + if let level = torchLevel { + self.setTorchLevel(level: level) + } + + if !success && self.testingImageDataSource != nil && self.isSimulator() { + self.startFakeCameraLoop() + } + } + ) + } + + func createOcrMainLoop() -> OcrMainLoop? { + OcrMainLoop() + } + + override var shouldAutorotate: Bool { + return true + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return ScanBaseViewController.isPadAndFormsheet ? .allButUpsideDown : .portrait + } + + override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { + return ScanBaseViewController.isPadAndFormsheet ? UIWindow.interfaceOrientation : .portrait + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + override func viewWillTransition( + to size: CGSize, + with coordinator: UIViewControllerTransitionCoordinator + ) { + super.viewWillTransition(to: size, with: coordinator) + + if let videoFeedConnection = self.videoFeed.videoDeviceConnection, + videoFeedConnection.isVideoOrientationSupported + { + videoFeedConnection.videoOrientation = + AVCaptureVideoOrientation(deviceOrientation: UIDevice.current.orientation) + ?? .portrait + } + if let previewViewConnection = self.previewView?.videoPreviewLayer.connection, + previewViewConnection.isVideoOrientationSupported + { + previewViewConnection.videoOrientation = + AVCaptureVideoOrientation(deviceOrientation: UIDevice.current.orientation) + ?? .portrait + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + ScanBaseViewController.isAppearing = true + // Set beginning of scan session + ScanAnalyticsManager.shared.setScanSessionStartTime(time: Date()) + // Check and log torch availability + ScanAnalyticsManager.shared.logTorchSupportTask( + supported: videoFeed.hasTorchAndIsAvailable() + ) + self.ocrMainLoop()?.reset() + self.calledOnScannedCard = false + self.videoFeed.willAppear() + self.isNavigationBarHidden = self.navigationController?.isNavigationBarHidden ?? true + let hideNavigationBar = self.hideNavigationBar ?? true + self.navigationController?.setNavigationBarHidden(hideNavigationBar, animated: animated) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.view.layoutIfNeeded() + guard let roiFrame = self.regionOfInterestLabel?.frame, + let previewViewFrame = self.previewView?.frame + else { return } + // store .frame to avoid accessing UI APIs in the machineLearningQueue + self.regionOfInterestLabelFrame = roiFrame + self.previewViewFrame = previewViewFrame + self.setUpCorners() + self.setupMask() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.ocrMainLoop()?.scanStats.orientation = UIWindow.interfaceOrientationToString + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.videoFeed.willDisappear() + self.navigationController?.setNavigationBarHidden( + self.isNavigationBarHidden ?? false, + animated: animated + ) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + ScanBaseViewController.isAppearing = false + } + + func getScanStats() -> ScanStats { + return self.ocrMainLoop()?.scanStats ?? ScanStats() + } + + func captureOutput( + _ output: AVCaptureOutput, + didOutput sampleBuffer: CMSampleBuffer, + from connection: AVCaptureConnection + ) { + if self.machineLearningSemaphore.wait(timeout: .now()) == .success { + guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), + let fullCameraImage = pixelBuffer.cgImage() else { + self.machineLearningSemaphore.signal() + return + } + + ScanBaseViewController.machineLearningQueue.async { [weak self] in + self?.captureOutputWork(fullCameraImage: fullCameraImage) + self?.machineLearningSemaphore.signal() + } + } + } + + func captureOutputWork(fullCameraImage: CGImage) { + // confirm videoGravity settings in previewView. Calculations based on .resizeAspectFill + DispatchQueue.main.async { + assert(self.previewView?.videoPreviewLayer.videoGravity == .resizeAspectFill) + } + + guard let roiFrame = self.regionOfInterestLabelFrame, + let previewViewFrame = self.previewViewFrame, + let scannedImageData = ScannedCardImageData( + captureDeviceImage: fullCameraImage, + viewfinderRect: roiFrame, + previewViewRect: previewViewFrame + ) + else { + return + } + + // we allow apps that integrate to supply their own sequence of images + // for use in testing + if let dataSource = testingImageDataSource { + guard let fullTestingImage = dataSource.nextSquareAndFullImage() else { + return + } + mainLoop?.push( + imageData: ScannedCardImageData( + previewLayerImage: fullTestingImage, + previewLayerViewfinderRect: roiFrame + ) + ) + } else { + mainLoop?.push(imageData: scannedImageData) + } + } + + func updateDebugImageView(image: UIImage) { + self.debugImageView?.image = image + if self.debugImageView?.isHidden ?? false { + self.debugImageView?.isHidden = false + } + } + + // MARK: - OcrMainLoopComplete logic + func complete(creditCardOcrResult: CreditCardOcrResult) { + ocrMainLoop()?.mainLoopDelegate = nil + // Stop the previewing when we are done + self.previewView?.videoPreviewLayer.session?.stopRunning() + // Log total frames processed + ScanAnalyticsManager.shared.logMainLoopImageProcessedRepeatingTask( + .init(executions: self.getScanStats().scans) + ) + ScanAnalyticsManager.shared.logScanActivityTaskFromStartTime(event: .cardScanned) + + ScanBaseViewController.machineLearningQueue.async { + self.scanEventsDelegate?.onScanComplete(scanStats: self.getScanStats()) + } + + // hack to work around having to change our interface + predictedName = creditCardOcrResult.name + self.onScannedCard( + number: creditCardOcrResult.number, + expiryYear: creditCardOcrResult.expiryYear, + expiryMonth: creditCardOcrResult.expiryMonth, + scannedImage: scannedCardImage + ) + } + + func prediction( + prediction: CreditCardOcrPrediction, + imageData: ScannedCardImageData, + state: MainLoopState + ) { + if !firstImageProcessed { + ScanAnalyticsManager.shared.logScanActivityTaskFromStartTime( + event: .firstImageProcessed + ) + firstImageProcessed = true + } + + if self.showDebugImageView { + let numberBoxes = prediction.numberBoxes?.map { (UIColor.blue, $0) } ?? [] + let expiryBoxes = prediction.expiryBoxes?.map { (UIColor.red, $0) } ?? [] + let nameBoxes = prediction.nameBoxes?.map { (UIColor.green, $0) } ?? [] + + if self.debugImageView?.isHidden ?? false { + self.debugImageView?.isHidden = false + } + + self.debugImageView?.image = prediction.image.drawBoundingBoxesOnImage( + boxes: numberBoxes + expiryBoxes + nameBoxes + ) + } + + if prediction.number != nil && self.includeCardImage { + self.scannedCardImage = UIImage(cgImage: prediction.image) + } + + let isFlashForcedOn: Bool + switch state { + case .ocrForceFlash: isFlashForcedOn = true + default: isFlashForcedOn = false + } + + if let number = prediction.number { + if !firstPanObserved { + ScanAnalyticsManager.shared.logScanActivityTaskFromStartTime(event: .ocrPanObserved) + firstPanObserved = true + } + + let expiry = prediction.expiryObject() + + ScanBaseViewController.machineLearningQueue.async { + self.scanEventsDelegate?.onNumberRecognized( + number: number, + expiry: expiry, + imageData: imageData, + centeredCardState: prediction.centeredCardState, + flashForcedOn: isFlashForcedOn + ) + } + } else { + ScanBaseViewController.machineLearningQueue.async { + self.scanEventsDelegate?.onFrameDetected( + imageData: imageData, + centeredCardState: prediction.centeredCardState, + flashForcedOn: isFlashForcedOn + ) + } + } + } + + func showCardDetails(number: String?, expiry: String?, name: String?) { + guard let number = number else { return } + showCardNumber(number, expiry: expiry) + } + + func showCardDetailsWithFlash(number: String?, expiry: String?, name: String?) { + if !isTorchOn() { toggleTorch() } + guard let number = number else { return } + showCardNumber(number, expiry: expiry) + } + + func shouldUsePrediction( + errorCorrectedNumber: String?, + prediction: CreditCardOcrPrediction + ) -> Bool { + guard let predictedNumber = prediction.number else { return true } + return useCurrentFrameNumber( + errorCorrectedNumber: errorCorrectedNumber, + currentFrameNumber: predictedNumber + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanConfiguration.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanConfiguration.swift new file mode 100644 index 00000000..6c0c1ff5 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanConfiguration.swift @@ -0,0 +1,11 @@ +import Foundation + +enum ScanPerformance: Int { + case fast + case accurate +} + +class ScanConfiguration: NSObject { + var runOnOldDevices = false + var setPreviouslyDeniedDevicesAsIncompatible = false +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanEventsProtocol.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanEventsProtocol.swift new file mode 100644 index 00000000..f5e32c48 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanEventsProtocol.swift @@ -0,0 +1,26 @@ +// +// This protocol provides extensibility for inspecting scanning results as they +// happen. As the model detects a cc number it will invoke `onNumberRecognized` +// and when it's done it notifies via `onScanComplete`. +// +// Both of these methods will always be invoked on the machineLearningQueue +// serial dispatch queue. +// + +import CoreGraphics + +protocol ScanEvents { + mutating func onNumberRecognized( + number: String, + expiry: Expiry?, + imageData: ScannedCardImageData, + centeredCardState: CenteredCardState?, + flashForcedOn: Bool + ) + mutating func onScanComplete(scanStats: ScanStats) + mutating func onFrameDetected( + imageData: ScannedCardImageData, + centeredCardState: CenteredCardState?, + flashForcedOn: Bool + ) +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanStats.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanStats.swift new file mode 100644 index 00000000..23219299 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/ScanStats.swift @@ -0,0 +1,84 @@ +// +// ScanStats.swift +// CardScan +// +// Created by Sam King on 11/13/18. +// +import CoreGraphics +import Foundation +import UIKit + +struct ScanStats { + var startTime = Date() + var scans = 0 + var flatDigitsRecognized = 0 + var flatDigitsDetected = 0 + var embossedDigitsRecognized = 0 + var embossedDigitsDetected = 0 + var torchOn = false + var orientation = "Portrait" + var success: Bool? + var endTime: Date? + var model: String? + var algorithm: String? + var bin: String? + var lastFlatBoxes: [CGRect]? + var lastEmbossedBoxes: [CGRect]? + var deviceType: String? + var numberRect: CGRect? + var expiryBoxes: [CGRect]? + var cardsDetected = 0 + var permissionGranted: Bool? + var userCanceled: Bool = false + + init() { + var systemInfo = utsname() + uname(&systemInfo) + var deviceType = "" + for char in Mirror(reflecting: systemInfo.machine).children { + guard let charDigit = (char.value as? Int8) else { + return + } + + if charDigit == 0 { + break + } + + deviceType += String(UnicodeScalar(UInt8(charDigit))) + } + + self.deviceType = deviceType + } + + func toDictionaryForAnalytics() -> [String: Any] { + return [ + "scans": self.scans, + "cards_detected": self.cardsDetected, + "torch_on": self.torchOn, + "orientation": self.orientation, + "success": self.success ?? false, + "duration": self.duration(), + "model": self.model ?? "unknown", + "permission_granted": self.permissionGranted.map { $0 ? "granted" : "denied" } + ?? "not_determined", + "device_type": self.deviceType ?? "", + "user_canceled": self.userCanceled, + ] + } + + func duration() -> Double { + guard let endTime = self.endTime else { + return 0.0 + } + + return endTime.timeIntervalSince(self.startTime) + } + + func image(from base64String: String?) -> UIImage? { + guard let string = base64String else { + return nil + } + + return Data(base64Encoded: string).flatMap { UIImage(data: $0) } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/SimpleScanViewController.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/SimpleScanViewController.swift new file mode 100644 index 00000000..27fdfb07 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/SimpleScanViewController.swift @@ -0,0 +1,542 @@ +@_spi(STP) import StripeCore +import UIKit + +// This class is all programmatic UI with a small bit of logic to handle +// the events that ScanBaseViewController expects subclasses to implement. +// Our goal is to have a fully featured Card Scan implementation with a +// minimal UI that people can customize fully. You can use this directly or +// you can subclass and customize it. If you'd like to use an off-the-shelf +// design as well, we suggest using the `ScanViewController`, which uses +// mature and well tested UI design patterns. +// +// The default UI looks something like this, with most of the constraints +// shown: +// +// ------------------------------------ +// | | | | +// |-Cancel Torch-| +// | | +// | | +// | | +// | | +// | | +// |------------Scan Card-------------| +// | | | +// | ------------------------------ | +// | | | | +// | | | | +// | | | | +// | |--4242 4242 4242 4242--| | +// | || 05/23 | | +// | ||-Sam King | | +// | | | | | +// | ------------------------------ | +// | | | | | +// | | | | | +// | | Enable camera permissions | | +// | | | | | +// | | | | | +// | |---To scan your card you...---| | +// | | +// | | +// | | +// ------------------------------------ +// +// For the UI we separate out the key components into three parts: +// - Five `*String` variables that we use to set the copy +// - For each component or group of components we have: +// - `setup*Ui` functions for setting the visual look and feel +// - `setup*Constraints for setting up autolayout +// - We have top level `setupUiComponents` and `setupConstraints` functions that do +// a small bit of setup and call the appropriate setup functions for each +// components +// +// And to customize the UI you can either override any of these functions or you +// can access components directly to adjust. Also, you're welcome to copy and paste +// this code and customize it to fit your needs -- we're fine with whatever makes +// the most sense for your app. + +protocol SimpleScanDelegate: AnyObject { + func userDidCancelSimple(_ scanViewController: SimpleScanViewController) + func userDidScanCardSimple( + _ scanViewController: SimpleScanViewController, + creditCard: CreditCard + ) +} + +class SimpleScanViewController: ScanBaseViewController { + + // used by ScanBase + var previewView: PreviewView = PreviewView() + var blurView: BlurView = BlurView() + var roiView: UIView = UIView() + var cornerView: CornerView? + + // our UI components + var descriptionText = UILabel() + var privacyLinkText = UITextView() + var privacyLinkTextHeightConstraint: NSLayoutConstraint? + + var closeButton: UIButton = { + var button = UIButton(type: .system) + button.setTitleColor(.white, for: .normal) + button.tintColor = .white + button.setTitle(SimpleScanViewController.closeButtonString, for: .normal) + return button + }() + + var torchButton: UIButton = { + var button = UIButton(type: .system) + button.setTitleColor(.white, for: .normal) + button.tintColor = .white + button.setTitle(SimpleScanViewController.torchButtonString, for: .normal) + return button + }() + + private var debugView: UIImageView? + var enableCameraPermissionsButton = UIButton(type: .system) + var enableCameraPermissionsText = UILabel() + + // Dynamic card details + var numberText = UILabel() + var expiryText = UILabel() + var nameText = UILabel() + var expiryLayoutView = UIView() + + // String + static var descriptionString = String.Localized.scan_card_title_capitalization + static var enableCameraPermissionString = String.Localized.enable_camera_access + static var enableCameraPermissionsDescriptionString = String.Localized.update_phone_settings + static var closeButtonString = String.Localized.close + static var torchButtonString = String.Localized.torch + static var privacyLinkString = String.Localized.scanCardExpectedPrivacyLinkText() + + weak var delegate: SimpleScanDelegate? + var scanPerformancePriority: ScanPerformance = .fast + var maxErrorCorrectionDuration: Double = 4.0 + + // MARK: Inits + override init() { + super.init() + if UIDevice.current.userInterfaceIdiom == .pad { + // For the iPad you can use the full screen style but you have to select "requires full screen" in + // the Info.plist to lock it in portrait mode. For iPads, we recommend using a formSheet, which + // handles all orientations correctly. + self.modalPresentationStyle = .formSheet + } else { + self.modalPresentationStyle = .fullScreen + } + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + setupUiComponents() + setupConstraints() + + setupOnViewDidLoad( + regionOfInterestLabel: roiView, + blurView: blurView, + previewView: previewView, + cornerView: cornerView, + debugImageView: debugView, + torchLevel: 1.0 + ) + + setUpMainLoop(errorCorrectionDuration: maxErrorCorrectionDuration) + + startCameraPreview() + } + + // Removing targets manually since we are allowing custom buttons which retains button reference -> + // ARC doesn't automatically decrement its reference count -> + // Targets gets added on every setUpUi call. + // + // Figure out a better way of allow custom buttons programmatically instead of whole UI buttons. + override func viewDidDisappear(_ animated: Bool) { + closeButton.removeTarget(self, action: #selector(cancelButtonPress), for: .touchUpInside) + torchButton.removeTarget(self, action: #selector(torchButtonPress), for: .touchUpInside) + } + + func setUpMainLoop(errorCorrectionDuration: Double) { + if scanPerformancePriority == .accurate { + let mainLoop = self.mainLoop as? OcrMainLoop + mainLoop?.errorCorrection = ErrorCorrection( + stateMachine: OcrAccurateMainLoopStateMachine( + maxErrorCorrection: maxErrorCorrectionDuration + ) + ) + } + } + + // MARK: - Visual and UI event setup for UI components + func setupUiComponents() { + view.backgroundColor = .white + regionOfInterestCornerRadius = 15.0 + + let children: [UIView] = [ + previewView, blurView, roiView, descriptionText, closeButton, torchButton, numberText, + expiryText, nameText, expiryLayoutView, enableCameraPermissionsButton, + enableCameraPermissionsText, privacyLinkText, + ] + for child in children { + self.view.addSubview(child) + } + + setupPreviewViewUi() + setupBlurViewUi() + setupRoiViewUi() + setupCloseButtonUi() + setupTorchButtonUi() + setupDescriptionTextUi() + setupCardDetailsUi() + setupDenyUi() + setupPrivacyLinkTextUi() + + if showDebugImageView { + setupDebugViewUi() + } + } + + func setupPreviewViewUi() { + // no ui setup + } + + func setupBlurViewUi() { + blurView.backgroundColor = #colorLiteral( + red: 0.2411109507, + green: 0.271378696, + blue: 0.3280351758, + alpha: 0.7020547945 + ) + } + + func setupRoiViewUi() { + roiView.layer.borderColor = UIColor.white.cgColor + } + + func setupCloseButtonUi() { + closeButton.addTarget(self, action: #selector(cancelButtonPress), for: .touchUpInside) + } + + func setupTorchButtonUi() { + torchButton.addTarget(self, action: #selector(torchButtonPress), for: .touchUpInside) + } + + func setupDescriptionTextUi() { + descriptionText.text = SimpleScanViewController.descriptionString + descriptionText.textColor = .white + descriptionText.textAlignment = .center + descriptionText.font = descriptionText.font.withSize(30) + } + + func setupCardDetailsUi() { + numberText.isHidden = true + numberText.textColor = .white + numberText.textAlignment = .center + numberText.font = numberText.font.withSize(48) + numberText.adjustsFontSizeToFitWidth = true + numberText.minimumScaleFactor = 0.2 + + expiryText.isHidden = true + expiryText.textColor = .white + expiryText.textAlignment = .center + expiryText.font = expiryText.font.withSize(20) + + nameText.isHidden = true + nameText.textColor = .white + nameText.font = expiryText.font.withSize(20) + } + + func setupDenyUi() { + let text = SimpleScanViewController.enableCameraPermissionString + let attributedString = NSMutableAttributedString(string: text) + attributedString.addAttribute( + NSAttributedString.Key.underlineColor, + value: UIColor.white, + range: NSRange(location: 0, length: text.count) + ) + attributedString.addAttribute( + NSAttributedString.Key.foregroundColor, + value: UIColor.white, + range: NSRange(location: 0, length: text.count) + ) + attributedString.addAttribute( + NSAttributedString.Key.underlineStyle, + value: NSUnderlineStyle.single.rawValue, + range: NSRange(location: 0, length: text.count) + ) + let font = + enableCameraPermissionsButton.titleLabel?.font.withSize(20) + ?? UIFont.systemFont(ofSize: 20.0) + attributedString.addAttribute( + NSAttributedString.Key.font, + value: font, + range: NSRange(location: 0, length: text.count) + ) + enableCameraPermissionsButton.setAttributedTitle(attributedString, for: .normal) + enableCameraPermissionsButton.isHidden = true + + enableCameraPermissionsButton.addTarget( + self, + action: #selector(enableCameraPermissionsPress), + for: .touchUpInside + ) + + enableCameraPermissionsText.text = + SimpleScanViewController.enableCameraPermissionsDescriptionString + enableCameraPermissionsText.textColor = .white + enableCameraPermissionsText.textAlignment = .center + enableCameraPermissionsText.font = enableCameraPermissionsText.font.withSize(17) + enableCameraPermissionsText.numberOfLines = 3 + enableCameraPermissionsText.isHidden = true + } + + func setupPrivacyLinkTextUi() { + if let attributedString = SimpleScanViewController.privacyLinkString { + privacyLinkText.attributedText = attributedString + } + + privacyLinkText.textColor = .white + privacyLinkText.textAlignment = .center + privacyLinkText.font = descriptionText.font.withSize(14) + privacyLinkText.isEditable = false + privacyLinkText.dataDetectorTypes = .link + privacyLinkText.isScrollEnabled = false + privacyLinkText.backgroundColor = .clear + privacyLinkText.linkTextAttributes = [ + .foregroundColor: UIColor.white + ] + privacyLinkText.accessibilityIdentifier = "Privacy Link Text" + } + + func setupDebugViewUi() { + debugView = UIImageView() + guard let debugView = debugView else { return } + self.view.addSubview(debugView) + } + + // MARK: - Autolayout constraints + func setupConstraints() { + let children: [UIView] = [ + previewView, blurView, roiView, descriptionText, closeButton, torchButton, numberText, + expiryText, nameText, expiryLayoutView, enableCameraPermissionsButton, + enableCameraPermissionsText, privacyLinkText, + ] + for child in children { + child.translatesAutoresizingMaskIntoConstraints = false + } + + setupPreviewViewConstraints() + setupBlurViewConstraints() + setupRoiViewConstraints() + setupCloseButtonConstraints() + setupTorchButtonConstraints() + setupDescriptionTextConstraints() + setupCardDetailsConstraints() + setupDenyConstraints() + setupPrivacyLinkTextConstraints() + + if showDebugImageView { + setupDebugViewConstraints() + } + } + + func setupPreviewViewConstraints() { + // make it full screen + previewView.setAnchorsEqual(to: self.view) + } + + func setupBlurViewConstraints() { + blurView.setAnchorsEqual(to: self.previewView) + } + + func setupRoiViewConstraints() { + roiView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true + roiView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = + true + roiView.heightAnchor.constraint(equalTo: roiView.widthAnchor, multiplier: 1.0 / 1.586) + .isActive = true + roiView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + } + + func setupCloseButtonConstraints() { + let margins = view.layoutMarginsGuide + closeButton.topAnchor.constraint(equalTo: margins.topAnchor, constant: 16.0).isActive = true + closeButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true + } + + func setupTorchButtonConstraints() { + let margins = view.layoutMarginsGuide + torchButton.topAnchor.constraint(equalTo: margins.topAnchor, constant: 16.0).isActive = true + torchButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true + } + + func setupDescriptionTextConstraints() { + descriptionText.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32).isActive = + true + descriptionText.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32) + .isActive = true + descriptionText.bottomAnchor.constraint(equalTo: roiView.topAnchor, constant: -16).isActive = + true + } + + func setupCardDetailsConstraints() { + numberText.leadingAnchor.constraint(equalTo: roiView.leadingAnchor, constant: 32).isActive = + true + numberText.trailingAnchor.constraint(equalTo: roiView.trailingAnchor, constant: -32) + .isActive = true + numberText.centerYAnchor.constraint(equalTo: roiView.centerYAnchor).isActive = true + + nameText.leadingAnchor.constraint(equalTo: numberText.leadingAnchor).isActive = true + nameText.bottomAnchor.constraint(equalTo: roiView.bottomAnchor, constant: -16).isActive = + true + + expiryLayoutView.topAnchor.constraint(equalTo: numberText.bottomAnchor).isActive = true + expiryLayoutView.bottomAnchor.constraint(equalTo: nameText.topAnchor).isActive = true + expiryLayoutView.leadingAnchor.constraint(equalTo: numberText.leadingAnchor).isActive = true + expiryLayoutView.trailingAnchor.constraint(equalTo: numberText.trailingAnchor).isActive = + true + + expiryText.leadingAnchor.constraint(equalTo: expiryLayoutView.leadingAnchor).isActive = true + expiryText.trailingAnchor.constraint(equalTo: expiryLayoutView.trailingAnchor).isActive = + true + expiryText.centerYAnchor.constraint(equalTo: expiryLayoutView.centerYAnchor).isActive = true + } + + func setupDenyConstraints() { + NSLayoutConstraint.activate([ + enableCameraPermissionsButton.topAnchor.constraint( + equalTo: privacyLinkText.bottomAnchor, + constant: 32 + ), + enableCameraPermissionsButton.centerXAnchor.constraint(equalTo: roiView.centerXAnchor), + + enableCameraPermissionsText.topAnchor.constraint( + equalTo: enableCameraPermissionsButton.bottomAnchor, + constant: 32 + ), + enableCameraPermissionsText.leadingAnchor.constraint(equalTo: roiView.leadingAnchor), + enableCameraPermissionsText.trailingAnchor.constraint(equalTo: roiView.trailingAnchor), + ]) + } + + func setupPrivacyLinkTextConstraints() { + NSLayoutConstraint.activate([ + privacyLinkText.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32), + privacyLinkText.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32), + privacyLinkText.topAnchor.constraint(equalTo: roiView.bottomAnchor, constant: 16), + ]) + + privacyLinkTextHeightConstraint = privacyLinkText.heightAnchor.constraint( + equalToConstant: 0 + ) + } + + func setupDebugViewConstraints() { + guard let debugView = debugView else { return } + debugView.translatesAutoresizingMaskIntoConstraints = false + + debugView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + debugView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + debugView.widthAnchor.constraint(equalToConstant: 240).isActive = true + debugView.heightAnchor.constraint(equalTo: debugView.widthAnchor, multiplier: 1.0).isActive = + true + } + + // MARK: - Override some ScanBase functions + override func onScannedCard( + number: String, + expiryYear: String?, + expiryMonth: String?, + scannedImage: UIImage? + ) { + let card = CreditCard(number: number) + card.expiryMonth = expiryMonth + card.expiryYear = expiryYear + card.name = predictedName + card.image = scannedImage + + delegate?.userDidScanCardSimple(self, creditCard: card) + } + + func showScannedCardDetails(prediction: CreditCardOcrPrediction) { + guard let number = prediction.number else { + return + } + + numberText.text = CreditCardUtils.format(number: number) + if numberText.isHidden { + numberText.fadeIn() + } + + if let expiry = prediction.expiryForDisplay { + expiryText.text = expiry + if expiryText.isHidden { + expiryText.fadeIn() + } + } + + if let name = prediction.name { + nameText.text = name + if nameText.isHidden { + nameText.fadeIn() + } + } + } + + override func prediction( + prediction: CreditCardOcrPrediction, + imageData: ScannedCardImageData, + state: MainLoopState + ) { + super.prediction(prediction: prediction, imageData: imageData, state: state) + + showScannedCardDetails(prediction: prediction) + } + + override func onCameraPermissionDenied(showedPrompt: Bool) { + descriptionText.isHidden = true + torchButton.isHidden = true + + enableCameraPermissionsButton.isHidden = false + enableCameraPermissionsText.isHidden = false + privacyLinkTextHeightConstraint?.isActive = true + } + + // MARK: - UI event handlers + @objc func cancelButtonPress() { + delegate?.userDidCancelSimple(self) + self.cancelScan() + } + + @objc func torchButtonPress() { + toggleTorch() + } + + /// Warning: if the user navigates to settings and updates the setting, it'll suspend your app. + @objc func enableCameraPermissionsPress() { + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString), + UIApplication.shared.canOpenURL(settingsUrl) + else { + return + } + + UIApplication.shared.open(settingsUrl) + } +} + +extension UIView { + func setAnchorsEqual(to otherView: UIView) { + self.topAnchor.constraint(equalTo: otherView.topAnchor).isActive = true + self.leadingAnchor.constraint(equalTo: otherView.leadingAnchor).isActive = true + self.trailingAnchor.constraint(equalTo: otherView.trailingAnchor).isActive = true + self.bottomAnchor.constraint(equalTo: otherView.bottomAnchor).isActive = true + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/Torch.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/Torch.swift new file mode 100644 index 00000000..267272a0 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/Torch.swift @@ -0,0 +1,48 @@ +import AVFoundation +import Foundation + +struct Torch { + enum State { + case off + case on + } + let device: AVCaptureDevice? + var state: State + var lastStateChange: Date + var level: Float + + init( + device: AVCaptureDevice + ) { + self.state = .off + self.lastStateChange = Date() + if device.hasTorch { + self.device = device + if device.isTorchActive { self.state = .on } + } else { + self.device = nil + } + self.level = 1.0 + } + + /// TODO(jaimepark): Refactor + mutating func toggle() { + self.state = self.state == .on ? .off : .on + do { + try self.device?.lockForConfiguration() + if self.state == .on { + do { + try self.device?.setTorchModeOn(level: self.level) + } catch { + // no-op + } + } else { + self.device?.torchMode = .off + } + self.device?.unlockForConfiguration() + } catch { + // no-op + } + } + +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/UI/VideoFeed.swift b/StripeCardScan/StripeCardScan/Source/CardScan/UI/VideoFeed.swift new file mode 100644 index 00000000..038fe110 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/UI/VideoFeed.swift @@ -0,0 +1,238 @@ +import AVKit +import VideoToolbox + +protocol AfterPermissions { + func permissionDidComplete(granted: Bool, showedPrompt: Bool) +} + +class VideoFeed { + private enum SessionSetupResult { + case success + case notAuthorized + case configurationFailed + } + + let session = AVCaptureSession() + private var isSessionRunning = false + private let sessionQueue = DispatchQueue(label: "session queue") + private var setupResult: SessionSetupResult = .success + var videoDeviceInput: AVCaptureDeviceInput! + var videoDevice: AVCaptureDevice? + var videoDeviceConnection: AVCaptureConnection? + var torch: Torch? + + func pauseSession() { + self.sessionQueue.suspend() + } + + func requestCameraAccess(permissionDelegate: AfterPermissions?) { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + self.sessionQueue.resume() + DispatchQueue.main.async { + permissionDelegate?.permissionDidComplete(granted: true, showedPrompt: false) + } + case .notDetermined: + AVCaptureDevice.requestAccess( + for: .video, + completionHandler: { granted in + if !granted { + self.setupResult = .notAuthorized + } + self.sessionQueue.resume() + DispatchQueue.main.async { + permissionDelegate?.permissionDidComplete( + granted: granted, + showedPrompt: true + ) + } + } + ) + + default: + // The user has previously denied access. + self.setupResult = .notAuthorized + DispatchQueue.main.async { + permissionDelegate?.permissionDidComplete(granted: false, showedPrompt: false) + } + } + } + + func setup( + captureDelegate: AVCaptureVideoDataOutputSampleBufferDelegate, + initialVideoOrientation: AVCaptureVideoOrientation, + completion: @escaping ((_ success: Bool) -> Void) + ) { + sessionQueue.async { + self.configureSession( + captureDelegate: captureDelegate, + initialVideoOrientation: initialVideoOrientation, + completion: completion + ) + } + } + + func configureSession( + captureDelegate: AVCaptureVideoDataOutputSampleBufferDelegate, + initialVideoOrientation: AVCaptureVideoOrientation, + completion: @escaping ((_ success: Bool) -> Void) + ) { + if setupResult != .success { + DispatchQueue.main.async { completion(false) } + return + } + + session.beginConfiguration() + + do { + var defaultVideoDevice: AVCaptureDevice? + + // The triple and dualWide cameras have a 0.5x lens for better macro focus. + // If none are available, use the default wide angle camera. + let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: + [.builtInTripleCamera, .builtInDualWideCamera, .builtInWideAngleCamera], + mediaType: .video, + position: .back) + if let captureDevice = discoverySession.devices.first { + defaultVideoDevice = captureDevice + } else if let frontCameraDevice = AVCaptureDevice.default( + .builtInWideAngleCamera, + for: .video, + position: .front + ) { + // In some cases where users break their phones, the back camera is not available. + // In this case, we should default to the front wide angle camera. + defaultVideoDevice = frontCameraDevice + } + + guard let myVideoDevice = defaultVideoDevice else { + setupResult = .configurationFailed + session.commitConfiguration() + DispatchQueue.main.async { completion(false) } + return + } + + self.videoDevice = myVideoDevice + self.torch = Torch(device: myVideoDevice) + let videoDeviceInput = try AVCaptureDeviceInput(device: myVideoDevice) + + self.setupVideoDeviceDefaults() + + if session.canAddInput(videoDeviceInput) { + session.addInput(videoDeviceInput) + self.videoDeviceInput = videoDeviceInput + } else { + setupResult = .configurationFailed + session.commitConfiguration() + DispatchQueue.main.async { completion(false) } + return + } + + let videoDeviceOutput = AVCaptureVideoDataOutput() + videoDeviceOutput.videoSettings = [ + kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String: Int( + kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange + ), + ] + + videoDeviceOutput.alwaysDiscardsLateVideoFrames = true + let captureSessionQueue = DispatchQueue(label: "camera output queue") + videoDeviceOutput.setSampleBufferDelegate(captureDelegate, queue: captureSessionQueue) + guard session.canAddOutput(videoDeviceOutput) else { + setupResult = .configurationFailed + session.commitConfiguration() + DispatchQueue.main.async { completion(false) } + return + } + session.addOutput(videoDeviceOutput) + + if session.canSetSessionPreset(.high) { + session.sessionPreset = .high + } + + self.videoDeviceConnection = videoDeviceOutput.connection(with: .video) + if self.videoDeviceConnection?.isVideoOrientationSupported ?? false { + self.videoDeviceConnection?.videoOrientation = initialVideoOrientation + } + } catch { + setupResult = .configurationFailed + session.commitConfiguration() + DispatchQueue.main.async { completion(false) } + return + } + + session.commitConfiguration() + DispatchQueue.main.async { completion(true) } + } + + func setupVideoDeviceDefaults() { + guard let videoDevice = self.videoDevice else { + return + } + + guard (try? videoDevice.lockForConfiguration()) != nil else { + return + } + + if videoDevice.isFocusModeSupported(.continuousAutoFocus) { + videoDevice.focusMode = .continuousAutoFocus + if videoDevice.isSmoothAutoFocusSupported { + videoDevice.isSmoothAutoFocusEnabled = true + } + } + + if videoDevice.isExposureModeSupported(.continuousAutoExposure) { + videoDevice.exposureMode = .continuousAutoExposure + } + + if videoDevice.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) { + videoDevice.whiteBalanceMode = .continuousAutoWhiteBalance + } + + if videoDevice.isLowLightBoostSupported { + videoDevice.automaticallyEnablesLowLightBoostWhenAvailable = true + } + videoDevice.unlockForConfiguration() + } + + // MARK: - Torch Logic + func toggleTorch() { + self.torch?.toggle() + } + + func isTorchOn() -> Bool { + return self.torch?.state == Torch.State.on + } + + func hasTorchAndIsAvailable() -> Bool { + let hasTorch = self.torch?.device?.hasTorch ?? false + let isTorchAvailable = self.torch?.device?.isTorchAvailable ?? false + return hasTorch && isTorchAvailable + } + + func setTorchLevel(level: Float) { + self.torch?.level = level + } + + // MARK: - VC Lifecycle Logic + func willAppear() { + sessionQueue.async { + switch self.setupResult { + case .success: + self.session.startRunning() + self.isSessionRunning = self.session.isRunning + case _: + break + } + } + } + + func willDisappear() { + sessionQueue.async { + if self.setupResult == .success { + self.session.stopRunning() + self.isSessionRunning = self.session.isRunning + } + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Utils/AppInfoUtils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Utils/AppInfoUtils.swift new file mode 100644 index 00000000..edeebd75 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Utils/AppInfoUtils.swift @@ -0,0 +1,43 @@ +// +// AppInfoUtils.swift +// CardScan +// +// Created by Jaime Park on 4/15/21. +// + +import Foundation + +struct AppInfoUtils { + static let appPackageName: String? = getAppPackageName() + static let applicationId: String? = nil + static let libraryPackageName: String? = getLibraryPackageName() + static let sdkVersion: String = getSdkVersion() + static let sdkVersionCode: Int? = nil + static let sdkFlavor: String? = nil + static let isDebugBuild: Bool = getIsDebugBuild() + + static func getAppPackageName() -> String { + return Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? "unknown" + } + + static func getLibraryPackageName() -> String? { + return Bundle.main.bundleIdentifier + } + + static func getSdkVersion() -> String { + return Bundle.main.infoDictionary?["CFBundleShortVersionString"].flatMap { $0 as? String } + ?? "unknown" + } + + static func getBuildVersion() -> String { + return Bundle.main.infoDictionary?["CFBundleVersion"].flatMap { $0 as? String } ?? "unknown" + } + + static func getIsDebugBuild() -> Bool { + #if DEBUG + return true + #else + return false + #endif + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Utils/AtomicPropertyWrapper.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Utils/AtomicPropertyWrapper.swift new file mode 100644 index 00000000..6507162e --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Utils/AtomicPropertyWrapper.swift @@ -0,0 +1,43 @@ +// +// AtomicPropertyWrapper.swift +// StripeCardScan +// +// Created by Scott Grant on 7/5/22. +// + +import Foundation + +@propertyWrapper +class AtomicProperty { + + private var value: Value + + private lazy var lock: os_unfair_lock_t = { + let lock = os_unfair_lock_t.allocate(capacity: 1) + lock.initialize(to: os_unfair_lock_s()) + return lock + }() + + init( + wrappedValue value: Value + ) { + self.value = value + } + + deinit { + lock.deallocate() + } + + var wrappedValue: Value { + get { + os_unfair_lock_lock(lock) + defer { os_unfair_lock_unlock(lock) } + return value + } + set { + os_unfair_lock_lock(lock) + defer { os_unfair_lock_unlock(lock) } + value = newValue + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardScan/Utils/DeviceUtils.swift b/StripeCardScan/StripeCardScan/Source/CardScan/Utils/DeviceUtils.swift new file mode 100644 index 00000000..9625eae9 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardScan/Utils/DeviceUtils.swift @@ -0,0 +1,82 @@ +// +// DeviceUtils.swift +// CardScan +// +// Created by Jaime Park on 4/15/21. +// + +import CoreTelephony +import Foundation +import UIKit + +struct ClientIdsUtils { + static let vendorId: String? = getVendorId() + + static internal func getVendorId() -> String? { + return UIDevice.current.identifierForVendor?.uuidString + } +} + +struct DeviceUtils { + static let name: String = getDeviceType() + static let build: String = getBuildVersion() + static let bootCount: Int? = nil + static let locale: String? = getDeviceLocale() + static let carrier: String? = getCarrier() + static let networkOperator: String? = nil + static let phoneType: Int? = nil + static let phoneCount: Int? = nil + + static let osVersion: String = getOsVersion() + static let platform: String = "ios" + + static internal func getDeviceType() -> String { + var systemInfo = utsname() + uname(&systemInfo) + var deviceType = "" + for char in Mirror(reflecting: systemInfo.machine).children { + guard let charDigit = (char.value as? Int8) else { + return "" + } + + if charDigit == 0 { + break + } + + deviceType += String(UnicodeScalar(UInt8(charDigit))) + } + + return deviceType + } + + static internal func getBuildVersion() -> String { + return Bundle.main.infoDictionary?["CFBundleVersion"].flatMap { $0 as? String } ?? "0000" + } + + static internal func getDeviceLocale() -> String? { + return NSLocale.preferredLanguages.first + } + + static func getVendorId() -> String { + return UIDevice.current.identifierForVendor?.uuidString ?? "" + } + + static internal func getCarrier() -> String? { + let networkInfo = CTTelephonyNetworkInfo() + guard + let firstNamedCarrier = networkInfo.serviceSubscriberCellularProviders?.first(where: { + $0.value.carrierName != nil + })?.value + else { + return nil + } + + return firstNamedCarrier.carrierName + } + + static internal func getOsVersion() -> String { + let version = ProcessInfo().operatingSystemVersion + let osVersion = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + return osVersion + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/CardImageVerificationDetailsResponse.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/CardImageVerificationDetailsResponse.swift new file mode 100644 index 00000000..b16522e3 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/CardImageVerificationDetailsResponse.swift @@ -0,0 +1,68 @@ +// +// CardImageVerificationDetailsResponse.swift +// StripeCardScan +// +// Created by Jaime Park on 9/16/21. +// + +import Foundation +@_spi(STP) import StripeCore + +struct CardImageVerificationExpectedCard: Decodable { + let last4: String? + let issuer: String? +} + +struct CardImageVerificationImageSettings: Decodable { + var compressionRatio: Double? = 0.8 + var imageSize: [Double]? = [1080, 1920] +} + +enum CardImageVerificationFormat: String, SafeEnumCodable { + case heic = "heic" + case jpeg = "jpeg" + case webp = "webp" + case unparsable +} + +struct CardImageVerificationAcceptedImageConfigs: Decodable { + internal let defaultSettings: CardImageVerificationImageSettings? + internal let formatSettings: [CardImageVerificationFormat: CardImageVerificationImageSettings?]? + let preferredFormats: [CardImageVerificationFormat]? + + init( + defaultSettings: CardImageVerificationImageSettings? = CardImageVerificationImageSettings(), + formatSettings: [CardImageVerificationFormat: CardImageVerificationImageSettings?]? = nil, + preferredFormats: [CardImageVerificationFormat]? = [.jpeg] + ) { + self.defaultSettings = defaultSettings + self.formatSettings = formatSettings + self.preferredFormats = preferredFormats + } +} + +struct CardImageVerificationDetailsResponse: Decodable { + let expectedCard: CardImageVerificationExpectedCard + let acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs? +} + +extension CardImageVerificationAcceptedImageConfigs { + func imageSettings(format: CardImageVerificationFormat) -> CardImageVerificationImageSettings { + var result = CardImageVerificationImageSettings() + + if let defaultSettings = defaultSettings { + result.compressionRatio = defaultSettings.compressionRatio ?? result.compressionRatio + result.imageSize = defaultSettings.imageSize ?? result.imageSize + } + + if let formatSpecificSettings = formatSettings?[format], + let formatSpecificSettings = formatSpecificSettings + { + result.compressionRatio = + formatSpecificSettings.compressionRatio ?? result.compressionRatio + result.imageSize = formatSpecificSettings.imageSize ?? result.imageSize + } + + return result + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload+Common.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload+Common.swift new file mode 100644 index 00000000..cc9e2454 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload+Common.swift @@ -0,0 +1,43 @@ +// +// ScanStatsPayload+Common.swift +// StripeCardScan +// +// Created by Jaime Park on 12/9/21. +// + +import Foundation +@_spi(STP) import StripeCore + +extension ScanAnalyticsPayload { + /// Default app info used when uploading scan stats + struct AppInfo: Encodable { + let appPackageName = Bundle.stp_applicationName() ?? "" + let build = Bundle.buildVersion() ?? "" + let isDebugBuild = AppInfoUtils.getIsDebugBuild() + let sdkVersion = StripeAPIConfiguration.STPSDKVersion + } + + /// Default device info used when uploading scan stats + struct DeviceInfo: Encodable { + /// API requirement but have no purpose + let deviceId = "Redacted" + let deviceType = DeviceUtils.getDeviceType() + let osVersion = DeviceUtils.getOsVersion() + let platform = "iOS" + /// API requirement but have no purpose + let vendorId = "Redacted" + } + + /// Configuration values set when before running a scan flow + struct ConfigurationInfo: Encodable { + let strictModeFrames: Int + } + + /// Information about the verification payload creation + struct PayloadInfo: Encodable, Equatable { + let imageCompressionType: String + let imageCompressionQuality: Double + /// Byte count of the image payload after it has been compressed and b64 encoded + let imagePayloadSize: Int + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload+Tasks.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload+Tasks.swift new file mode 100644 index 00000000..a028467b --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload+Tasks.swift @@ -0,0 +1,21 @@ +// +// ScanStatsPayload+Tasks.swift +// StripeCardScan +// +// Created by Jaime Park on 5/13/22. +// + +import Foundation + +/// Struct used to track a repeating event +struct ScanAnalyticsRepeatingTask: Encodable, Equatable { + /// Repeated tasks should record how many times the tasks has been repeated + let executions: Int +} + +/// Struct used to track a non-repeating event +struct ScanAnalyticsNonRepeatingTask: Encodable, Equatable { + let result: String + let startedAtMs: Int + let durationMs: Int +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload.swift new file mode 100644 index 00000000..17dec4e7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/Scan Analytics/ScanStatsPayload.swift @@ -0,0 +1,62 @@ +// +// ScanStatsPayload.swift +// StripeCardScan +// +// Created by Jaime Park on 12/9/21. +// + +import Foundation +@_spi(STP) import StripeCore + +/// Parent payload structure for uploading scan stats +struct ScanStatsPayload: Encodable { + let clientSecret: String + let payload: ScanAnalyticsPayload +} + +struct ScanAnalyticsPayload: Encodable { + let app = AppInfo() + let configuration: ConfigurationInfo + let device = DeviceInfo() + /// API requirement but have no purpose + let instanceId: String = UUID().uuidString + let payloadInfo: PayloadInfo? + let payloadVersion = "2" + /// API requirement but have no purpose + let scanId: String = UUID().uuidString + let scanStats: ScanStatsTasks +} + +struct ScanStatsTasks: Encodable { + let repeatingTasks: RepeatingTasks + let tasks: NonRepeatingTasks +} + +struct NonRepeatingTasks: Encodable { + let cameraPermission: [ScanAnalyticsNonRepeatingTask] + let completionLoopDuration: [ScanAnalyticsNonRepeatingTask] + let imageCompressionDuration: [ScanAnalyticsNonRepeatingTask] + let mainLoopDuration: [ScanAnalyticsNonRepeatingTask] + let scanActivity: [ScanAnalyticsNonRepeatingTask] + let torchSupported: [ScanAnalyticsNonRepeatingTask] + + init( + cameraPermissionTask: ScanAnalyticsNonRepeatingTask, + completionLoopDuration: ScanAnalyticsNonRepeatingTask, + imageCompressionDuration: ScanAnalyticsNonRepeatingTask, + mainLoopDuration: ScanAnalyticsNonRepeatingTask, + scanActivityTasks: [ScanAnalyticsNonRepeatingTask], + torchSupportedTask: ScanAnalyticsNonRepeatingTask + ) { + self.cameraPermission = [cameraPermissionTask] + self.completionLoopDuration = [completionLoopDuration] + self.imageCompressionDuration = [imageCompressionDuration] + self.mainLoopDuration = [mainLoopDuration] + self.scanActivity = scanActivityTasks + self.torchSupported = [torchSupportedTask] + } +} + +struct RepeatingTasks: Encodable { + let mainLoopImagesProcessed: ScanAnalyticsRepeatingTask +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/VerificationFramesData.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/VerificationFramesData.swift new file mode 100644 index 00000000..1d61cfea --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/VerificationFramesData.swift @@ -0,0 +1,46 @@ +// +// VerificationFramesData.swift +// StripeCardScan +// +// Created by Jaime Park on 11/19/21. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +struct VerificationFramesData: Encodable { + /// A base64 encoding of a scanned card image + let imageData: Data? + /// The bounds of the card view finder (measured in pixels) + let viewfinderMargins: ViewFinderMargins +} + +// Bounds of the scanner's card viewfinder (measured in pixels) +// ---------------------------------------------- +// | | | +// | upper | +// | 300 | +// | | | +// | ------------300------------ | +// | | | | +// | 1 | | +// |--left--0 |--right--| +// | 100 0---4242 4242 4242 4242---| 400 | +// | | 05/23 | | +// | --------------------------- | +// | | | +// | lower | +// | 400 | +// | | | +// ---------------------------------------------- +struct ViewFinderMargins: Encodable { + /// The amount of pixels from the left-most bound of the image to the left-most bound of the viewfinder + let left: Int + /// The amount of pixels from the top-most bound of the image to the top-most bound of the viewfinder + let upper: Int + /// The amount of pixels from the left-most bound of the image to the right-most bound of the viewfinder + let right: Int + /// The amount of pixels from the top-most bound of the image to the bottom-most bound of the viewfinder + let lower: Int +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/VerifyFrames.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/VerifyFrames.swift new file mode 100644 index 00000000..da43dd59 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/Models/VerifyFrames.swift @@ -0,0 +1,15 @@ +// +// VerifyFrames.swift +// StripeCardScan +// +// Created by Jaime Park on 11/19/21. +// + +import Foundation +@_spi(STP) import StripeCore + +struct VerifyFrames: Encodable { + let clientSecret: String + /// A base64 encoding of 5 `VerificationFramesData` entries + let verificationFramesData: String +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Api/STPAPIClient+CardImageVerification.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/STPAPIClient+CardImageVerification.swift new file mode 100644 index 00000000..076875eb --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Api/STPAPIClient+CardImageVerification.swift @@ -0,0 +1,95 @@ +// +// STPAPIClient+CardImageVerification.swift +// StripeCardScan +// +// Created by Jaime Park on 9/15/21. +// + +import Foundation +@_spi(STP) import StripeCore + +extension STPAPIClient { + /// Request used in the beginning of the scan flow to gather CIV details and update the UI with last4 and issuer + func fetchCardImageVerificationDetails( + cardImageVerificationSecret: String, + cardImageVerificationId: String + ) -> Promise { + let parameters: [String: Any] = ["client_secret": cardImageVerificationSecret] + let endpoint = APIEndpoints.fetchCardImageVerificationDetails(id: cardImageVerificationId) + return self.post(resource: endpoint, parameters: parameters) + } + + /// Request used to complete the verification flow by submitting the verification frames + func submitVerificationFrames( + cardImageVerificationId: String, + verifyFrames: VerifyFrames + ) -> Promise { + let endpoint = APIEndpoints.submitVerificationFrames(id: cardImageVerificationId) + return self.post(resource: endpoint, object: verifyFrames) + } + + func submitVerificationFrames( + cardImageVerificationId: String, + cardImageVerificationSecret: String, + verificationFramesData: [VerificationFramesData] + ) -> Promise { + do { + /// TODO: Replace this with writing the JSON to a string instead of a data + /// Encode the array of verification frames data into JSON + let jsonEncoder = JSONEncoder() + jsonEncoder.keyEncodingStrategy = .convertToSnakeCase + let jsonVerificationFramesData = try jsonEncoder.encode(verificationFramesData) + + /// Turn the JSON data into a string + let verificationFramesDataString = + String(data: jsonVerificationFramesData, encoding: .utf8) ?? "" + + /// Create a `VerifyFrames` object + let verifyFrames = VerifyFrames( + clientSecret: cardImageVerificationSecret, + verificationFramesData: verificationFramesDataString + ) + + return self.submitVerificationFrames( + cardImageVerificationId: cardImageVerificationId, + verifyFrames: verifyFrames + ) + } catch let error { + let promise = Promise() + promise.reject(with: error) + return promise + } + } + + /// Request used to upload analytics of a card scanning session + /// This will be a fire-and-forget request + @discardableResult + func uploadScanStats( + cardImageVerificationId: String, + cardImageVerificationSecret: String, + scanAnalyticsPayload: ScanAnalyticsPayload + ) -> Promise { + /// Create URL with endpoint + let endpoint = APIEndpoints.uploadScanStats(id: cardImageVerificationId) + /// Create scan stats payload with secret key + let payload = ScanStatsPayload( + clientSecret: cardImageVerificationSecret, + payload: scanAnalyticsPayload + ) + return self.post(resource: endpoint, object: payload) + } +} + +private struct APIEndpoints { + static func fetchCardImageVerificationDetails(id: String) -> String { + return "card_image_verifications/\(id)/initialize_client" + } + + static func submitVerificationFrames(id: String) -> String { + return "card_image_verifications/\(id)/verify_frames" + } + + static func uploadScanStats(id: String) -> String { + return "card_image_verifications/\(id)/scan_stats" + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Bouncer.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Bouncer.swift new file mode 100644 index 00000000..82ac4e6d --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Bouncer.swift @@ -0,0 +1,22 @@ +import Foundation + +class Bouncer: NSObject { + + // This is the configuration CardVerify users should use + static func configuration() -> ScanConfiguration { + let configuration = ScanConfiguration() + configuration.runOnOldDevices = true + return configuration + } + + // Call this method before scanning any cards + static func configure(apiKey: String, useExperimentalScreenDetectionModel: Bool = false) { + ScanBaseViewController.configure(apiKey: apiKey) + } + + static var useFlashFlow = false + + static func isCompatible() -> Bool { + return ScanBaseViewController.isCompatible(configuration: configuration()) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CancellationReason.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CancellationReason.swift new file mode 100644 index 00000000..45de40da --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CancellationReason.swift @@ -0,0 +1,18 @@ +// +// CancellationReason.swift +// StripeCardScan +// +// Created by Jaime Park on 11/17/21. +// + +import Foundation + +/// The reason of the user initiated scan cancellation +public enum CancellationReason: String, Equatable { + /// User pressed the back button + case back + /// User closed the sheet view + case closed + /// User pressed the button which indicates that they can not scan the expected card + case userCannotScan +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationController.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationController.swift new file mode 100644 index 00000000..cdd1e3a9 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationController.swift @@ -0,0 +1,160 @@ +// +// CardImageVerificationController.swift +// StripeCardScan +// +// Created by Jaime Park on 11/17/21. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +protocol CardImageVerificationControllerDelegate: AnyObject { + func cardImageVerificationController( + _ controller: CardImageVerificationController, + didFinishWithResult result: CardImageVerificationSheetResult + ) +} + +class CardImageVerificationController { + weak var delegate: CardImageVerificationControllerDelegate? + + private let intent: CardImageVerificationIntent + private let configuration: CardImageVerificationSheet.Configuration + + init( + intent: CardImageVerificationIntent, + configuration: CardImageVerificationSheet.Configuration + ) { + self.intent = intent + self.configuration = configuration + } + + func present( + with expectedCard: CardImageVerificationExpectedCard, + and acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs?, + from presentingViewController: UIViewController, + animated: Bool = true + ) { + /// Guard against basic user error + guard presentingViewController.presentedViewController == nil else { + assertionFailure("presentingViewController is already presenting a view controller") + let error = CardScanSheetError.unknown( + debugDescription: "presentingViewController is already presenting a view controller" + ) + self.delegate?.cardImageVerificationController( + self, + didFinishWithResult: .failed(error: error) + ) + return + } + + // TODO(jaimepark): Create controller that has configurable view and handles coordination / business logic + if let expectedCardLast4 = expectedCard.last4 { + /// Create the view controller for card-set-verification with expected card's last4 and issuer + let vc = VerifyCardViewController( + acceptedImageConfigs: acceptedImageConfigs, + configuration: configuration, + expectedCardLast4: expectedCardLast4, + expectedCardIssuer: expectedCard.issuer + ) + vc.verifyDelegate = self + presentingViewController.present(vc, animated: animated) + } else { + /// Create the view controller for card-add-verification + let vc = VerifyCardAddViewController( + acceptedImageConfigs: acceptedImageConfigs, + configuration: configuration + ) + vc.verifyDelegate = self + presentingViewController.present(vc, animated: animated) + } + } + + func dismissWithResult( + _ presentingViewController: UIViewController, + result: CardImageVerificationSheetResult + ) { + /// Fire-and-forget uploading the scan stats + ScanAnalyticsManager.shared.generateScanAnalyticsPayload(with: configuration) { + [weak self] payload in + guard let self = self, + let payload = payload + else { + return + } + + self.configuration.apiClient.uploadScanStats( + cardImageVerificationId: self.intent.id, + cardImageVerificationSecret: self.intent.clientSecret, + scanAnalyticsPayload: payload + ) + } + + /// Dismiss the view controller + presentingViewController.dismiss(animated: true) { [weak self] in + guard let self = self else { return } + self.delegate?.cardImageVerificationController(self, didFinishWithResult: result) + } + } +} + +// MARK: Verify Card Add Delegate +extension CardImageVerificationController: VerifyViewControllerDelegate { + /// User scanned a card successfully. Submit verification frames data to complete verification flow + func verifyViewControllerDidFinish( + _ viewController: UIViewController, + verificationFramesData: [VerificationFramesData], + scannedCard: ScannedCard + ) { + let completionLoopTask = TrackableTask() + + /// Submit verification frames and wait for response for verification flow completion + configuration.apiClient.submitVerificationFrames( + cardImageVerificationId: intent.id, + cardImageVerificationSecret: intent.clientSecret, + verificationFramesData: verificationFramesData + ).observe { [weak self] result in + switch result { + case .success: + completionLoopTask.trackResult(.success) + ScanAnalyticsManager.shared.trackCompletionLoopDuration(task: completionLoopTask) + + self?.dismissWithResult( + viewController, + result: .completed(scannedCard: scannedCard) + ) + case .failure(let error): + completionLoopTask.trackResult(.failure) + ScanAnalyticsManager.shared.trackCompletionLoopDuration(task: completionLoopTask) + + self?.dismissWithResult( + viewController, + result: .failed(error: error) + ) + } + } + } + + /// User canceled the verification flow + func verifyViewControllerDidCancel( + _ viewController: UIViewController, + with reason: CancellationReason + ) { + dismissWithResult( + viewController, + result: .canceled(reason: reason) + ) + } + + /// Verification flow has failed + func verifyViewControllerDidFail( + _ viewController: UIViewController, + with error: Error + ) { + dismissWithResult( + viewController, + result: .failed(error: error) + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationIntent.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationIntent.swift new file mode 100644 index 00000000..4ce8a07f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationIntent.swift @@ -0,0 +1,16 @@ +// +// CardImageVerificationIntent.swift +// StripeCardScan +// +// Created by Jaime Park on 11/22/21. +// + +import Foundation + +/// An internal type representing a Card Image Verification Intent +struct CardImageVerificationIntent { + /// The card verification intent id + let id: String + /// The card verification intent client secret + let clientSecret: String +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationSheet.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationSheet.swift new file mode 100644 index 00000000..309ce7a1 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationSheet.swift @@ -0,0 +1,127 @@ +// +// CardImageVerificationSheet.swift +// StripeCardScan +// +// Created by Jaime Park on 11/17/21. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/// Typealias for backwards compatibility of new error type name +typealias CardImageVerificationSheetError = CardScanSheetError + +/// The result of an attempt to finish an card image verification flow +@frozen public enum CardImageVerificationSheetResult { + /// User completed the verification flow + case completed(scannedCard: ScannedCard) + /// User canceled out of the flow + case canceled(reason: CancellationReason) + /// Failed with error + case failed(error: Error) +} + +/// A drop-in class that presents a sheet for a user to verify their credit card +final public class CardImageVerificationSheet { + /// Initializes an `CardImageVerificationSheet` + /// - Parameters: + /// - cardImageVerificationIntentId: The id of a Stripe CardImageVerificationIntent object. + /// - cardImageVerificationIntentSecret: The client secret of a Stripe CardImageVerificationIntent object. + public init( + cardImageVerificationIntentId: String, + cardImageVerificationIntentSecret: String, + configuration: Configuration = Configuration() + ) { + // TODO(jaimepark): Add api analytics client as a param when integrating Stripe analytics + // TODO(jaimepark): Link public documentation for CIV intent when ready + self.configuration = configuration + self.intent = CardImageVerificationIntent( + id: cardImageVerificationIntentId, + clientSecret: cardImageVerificationIntentSecret + ) + } + + /// Presents a sheet for a customer to verify their card. + /// - Parameters: + /// - presentingViewController: The view controller to present the card image verification sheet. + /// - completion: Called with the result of the card image verification flow after the sheet is dismissed. + public func present( + from presentingViewController: UIViewController, + completion: @escaping (CardImageVerificationSheetResult) -> Void, + animated: Bool = true + ) { + /// Overwrite completion closure to retain self until called + let completion: (CardImageVerificationSheetResult) -> Void = { result in + completion(result) + self.completion = nil + } + self.completion = completion + + /// Configure the card image verification controller after retrieving the CIV details + load( + civId: intent.id, + civSecret: intent.clientSecret + ) { result in + switch result { + case .success(let response): + /// Initialize the civ controller + let cardImageVerificationController = + CardImageVerificationController( + intent: self.intent, + configuration: self.configuration + ) + cardImageVerificationController.delegate = self + /// Keep reference to the civ controller + self.verificationController = cardImageVerificationController + + /// Present the verify view controller + cardImageVerificationController.present( + with: response.expectedCard, + and: response.acceptedImageConfigs, + from: presentingViewController, + animated: animated + ) + case .failure(let error): + self.completion?(.failed(error: error)) + } + } + } + + private let configuration: Configuration + private let intent: CardImageVerificationIntent + /// Completion block called when the sheet is closed or fails to open + private var completion: ((CardImageVerificationSheetResult) -> Void)? + private var verificationController: CardImageVerificationController? +} + +extension CardImageVerificationSheet { + fileprivate typealias Result = Swift.Result + + /// Fetches the CIV optional card details + fileprivate func load( + civId: String, + civSecret: String, + completion: @escaping ((Result) -> Void) + ) { + /// Clear the scan analytics manager since it is the beginning of a new session + ScanAnalyticsManager.shared.reset() + + configuration.apiClient.fetchCardImageVerificationDetails( + cardImageVerificationSecret: civSecret, + cardImageVerificationId: civId + ).observe { result in + completion(result) + } + } +} + +// MARK: Card Image Verification Controller Delegate +extension CardImageVerificationSheet: CardImageVerificationControllerDelegate { + func cardImageVerificationController( + _ controller: CardImageVerificationController, + didFinishWithResult result: CardImageVerificationSheetResult + ) { + self.completion?(result) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationSheetConfiguration.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationSheetConfiguration.swift new file mode 100644 index 00000000..a92fbe8c --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardImageVerificationSheetConfiguration.swift @@ -0,0 +1,43 @@ +// +// CardImageVerificationSheetConfiguration.swift +// StripeCardScan +// +// Created by Jaime Park on 3/11/22. +// + +import Foundation +import UIKit + +// MARK: - Configuration +extension CardImageVerificationSheet { + public struct Configuration { + /// The API client instance used to make requests to Stripe + public var apiClient: STPAPIClient = STPAPIClient.shared + + /// The amount of frames that must have a centered, focused card before the + /// scan is allowed to terminate. This is an `experimental` feature that should + /// only be used with guidance from Stripe support. + @_spi(STP) public var strictModeFrames: StrictModeFrameCount = .none + + public init() {} + } + + /// Enum describing the amount of frames that must have a centered, focused card before the + /// scan is allowed to terminate. This is an `experimental` feature that should + /// only be used with guidance from Stripe support. + @_spi(STP) public enum StrictModeFrameCount: Int, Equatable { + case `none` + case low + case medium + case high + + internal var totalFrameCount: Int { + switch self { + case .none: return 0 + case .low: return 1 + case .medium: return CardVerifyFraudData.maxCompletionLoopFrames / 2 + case .high: return CardVerifyFraudData.maxCompletionLoopFrames + } + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardScanSheetError.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardScanSheetError.swift new file mode 100644 index 00000000..c727ad38 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/CardScanSheetError.swift @@ -0,0 +1,35 @@ +// +// CardScanSheetError.swift +// StripeCardScan +// +// Created by Jaime Park on 11/17/21. +// + +import Foundation +@_spi(STP) import StripeCore + +/// Errors specific to the `CardImageVerificationSheet`. +public enum CardScanSheetError: Error { + /// The provided client secret is invalid. + case invalidClientSecret + /// An unknown error. + case unknown(debugDescription: String) +} + +extension CardScanSheetError: LocalizedError { + /// Localized description of the error + public var localizedDescription: String { + return NSError.stp_unexpectedErrorMessage() + } +} + +extension CardScanSheetError: CustomDebugStringConvertible { + public var debugDescription: String { + switch self { + case .invalidClientSecret: + return "Invalid client secret" + case .unknown(let debugDescription): + return debugDescription + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Helpers.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Helpers.swift new file mode 100644 index 00000000..f5057f05 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Helpers.swift @@ -0,0 +1,20 @@ +// +// ScanAnalyticsManager+Helpers.swift +// StripeCardScan +// +// Created by Jaime Park on 12/9/21. +// + +import UIKit + +extension Date { + var millisecondsSince1970: Int { + Int((self.timeIntervalSince1970 * 1000.0).rounded()) + } +} + +extension TimeInterval { + var milliseconds: Int { + Int((self * 1000.0).rounded()) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Managers.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Managers.swift new file mode 100644 index 00000000..442b4226 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Managers.swift @@ -0,0 +1,51 @@ +// +// ScanAnalyticsManager+Managers.swift +// StripeCardScan +// +// Created by Jaime Park on 12/13/21. +// + +import Foundation + +/// Manager used to aggregate all non-repeating tasks +struct NonRepeatingTasksManager { + /// Default unknown values + var cameraPermissionTask: TrackableTask = TrackableTask() + var completionLoopDuration: TrackableTask = TrackableTask() + var imageCompressionDuration: TrackableTask = TrackableTask() + var mainLoopDuration: TrackableTask = TrackableTask() + var scanActivityTasks: [TrackableTask] = [] + var torchSupportedTask: TrackableTask = TrackableTask() + + /// Create API model + func generateNonRepeatingTasks() -> NonRepeatingTasks { + func unwrapTaskOrDefault(_ task: TrackableTask) -> ScanAnalyticsNonRepeatingTask { + return task.toAPIModel() + ?? .init( + result: ScanAnalyticsEvent.unknown.rawValue, + startedAtMs: -1, + durationMs: -1 + ) + } + + return .init( + cameraPermissionTask: unwrapTaskOrDefault(cameraPermissionTask), + completionLoopDuration: unwrapTaskOrDefault(completionLoopDuration), + imageCompressionDuration: unwrapTaskOrDefault(imageCompressionDuration), + mainLoopDuration: unwrapTaskOrDefault(mainLoopDuration), + scanActivityTasks: scanActivityTasks.compactMap { $0.toAPIModel() }, + torchSupportedTask: unwrapTaskOrDefault(torchSupportedTask) + ) + } +} + +/// Manager used to aggregate all repeating tasks +struct RepeatingTasksManager { + /// Default to negative number frames processed + var mainLoopImagesProcessed: ScanAnalyticsRepeatingTask = .init(executions: -1) + + /// Create API model + func generateRepeatingTasks() -> RepeatingTasks { + return .init(mainLoopImagesProcessed: mainLoopImagesProcessed) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Tasks.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Tasks.swift new file mode 100644 index 00000000..a16e4174 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager+Tasks.swift @@ -0,0 +1,80 @@ +// +// ScanAnalyticsTasks.swift +// StripeCardScan +// +// Created by Jaime Park on 12/9/21. +// + +import Foundation + +/// Events to be logged during a scanning session +enum ScanAnalyticsEvent: String { + /// Non-repeating tasks + case success = "success" + case failure = "failure" + case torchSupported = "supported" + case torchUnsupported = "unsupported" + case firstImageProcessed = "first_image_processed" + case ocrPanObserved = "ocr_pan_observed" + case cardScanned = "card_scanned" + case enterCardManually = "enter_card_manually" + case unknown = "unknown" + case userCanceled = "user_canceled" + case userMissingCard = "user_missing_card" +} + +extension ScanAnalyticsNonRepeatingTask { + /// Many events will have a fixed start time. The duration will be measured from when the task is created. + init( + event: ScanAnalyticsEvent, + startTime: Date, + endTime: Date = Date() + ) { + self.init( + event: event, + startTime: startTime, + duration: endTime.timeIntervalSince(startTime) + ) + } + + init( + event: ScanAnalyticsEvent, + startTime: Date, + duration: TimeInterval + ) { + self.init( + result: event.rawValue, + startedAtMs: startTime.millisecondsSince1970, + durationMs: duration.milliseconds + ) + } +} + +/// Task object used to track the start time and duration. Internal object used for ScanAnalyticsManager +class TrackableTask { + var startTime: Date + var duration: TimeInterval? + var result: ScanAnalyticsEvent? + + init( + startTime: Date = Date() + ) { + self.startTime = startTime + } + + func trackResult(_ result: ScanAnalyticsEvent, recordDuration: Bool = true) { + self.duration = recordDuration ? Date().timeIntervalSince(startTime) : -1 + self.result = result + } + + func toAPIModel() -> ScanAnalyticsNonRepeatingTask? { + guard + let result = result, + let duration = duration + else { + return nil + } + + return .init(event: result, startTime: startTime, duration: duration) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager.swift new file mode 100644 index 00000000..903f9a53 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScanAnalyticsManager.swift @@ -0,0 +1,160 @@ +// +// ScanAnalyticsManager.swift +// StripeCardScan +// +// Created by Jaime Park on 12/9/21. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +typealias PayloadInfo = ScanAnalyticsPayload.PayloadInfo + +/// Manager used to aggregate scan analytics +class ScanAnalyticsManager { + /// Shared scan analytics manager singleton + static private(set) var shared = ScanAnalyticsManager() + + private let mutexQueue = DispatchQueue(label: "com.stripe.ScanAnalyticsManager.MutexQueue") + /// The start of the scanning session + private var startTime: Date? + private var nonRepeatingTaskManager = NonRepeatingTasksManager() + private var payloadInfo: PayloadInfo? + private var repeatingTaskManager = RepeatingTasksManager() + + init() {} + + func setScanSessionStartTime(time: Date) { + mutexQueue.async { + self.startTime = time + } + } + + /// Create ScanAnalyticsPayload API model + func generateScanAnalyticsPayload( + with configuration: CardImageVerificationSheet.Configuration, + completion: @escaping ( + ScanAnalyticsPayload? + ) -> Void + ) { + mutexQueue.async { [weak self] in + guard let self = self else { + completion(nil) + return + } + + let configuration = ScanAnalyticsPayload.ConfigurationInfo( + strictModeFrames: configuration.strictModeFrames.totalFrameCount + ) + + let scanStatsTasks = ScanStatsTasks( + repeatingTasks: self.repeatingTaskManager.generateRepeatingTasks(), + tasks: self.nonRepeatingTaskManager.generateNonRepeatingTasks() + ) + + DispatchQueue.main.async { + completion( + ScanAnalyticsPayload( + configuration: configuration, + payloadInfo: self.payloadInfo, + scanStats: scanStatsTasks + ) + ) + } + } + } + + /// Keep track of most recent camera permission status + func logCameraPermissionsTask(success: Bool) { + mutexQueue.async { [weak self] in + /// Duration is not relevant for this task + let task = TrackableTask() + task.trackResult(success ? .success : .failure, recordDuration: false) + self?.nonRepeatingTaskManager.cameraPermissionTask = task + } + } + + /// Keep track of most recent torch status + func logTorchSupportTask(supported: Bool) { + mutexQueue.async { [weak self] in + /// Duration is not relevant for this task + let task = TrackableTask() + task.trackResult(supported ? .torchSupported : .torchUnsupported, recordDuration: false) + self?.nonRepeatingTaskManager.torchSupportedTask = task + } + } + + /// Keep track of scan activities of duration from beginning of scan session + func logScanActivityTaskFromStartTime(event: ScanAnalyticsEvent) { + mutexQueue.async { [weak self] in + guard let startTime = self?.startTime else { + assertionFailure("startTime is not set") + return + } + + let task = TrackableTask(startTime: startTime) + task.trackResult(event) + self?.logScanActivityTask(task: task) + } + } + + /// Keep track of scan activities and their start time, duration is not as important + func logScanActivityTask(event: ScanAnalyticsEvent) { + let task = TrackableTask(startTime: Date()) + task.trackResult(event, recordDuration: false) + self.logScanActivityTask(task: task) + } + + /// Keep track of the start time and duration of a scan event + func logScanActivityTask(task: TrackableTask) { + mutexQueue.async { [weak self] in + self?.nonRepeatingTaskManager.scanActivityTasks.append(task) + } + } + + /// Keep track of how many frames were processed this session + func logMainLoopImageProcessedRepeatingTask(_ task: ScanAnalyticsRepeatingTask) { + mutexQueue.async { [weak self] in + self?.repeatingTaskManager.mainLoopImagesProcessed = task + } + } + + /// Keep track of the payload data used for the verification payload creation + func logPayloadInfo(with payloadInfo: PayloadInfo) { + mutexQueue.async { [weak self] in + self?.payloadInfo = payloadInfo + } + } + + /// Keep track of the start time and duration of the completion loop + func trackCompletionLoopDuration(task: TrackableTask) { + mutexQueue.async { [weak self] in + self?.nonRepeatingTaskManager.completionLoopDuration = task + } + } + + /// Keep track of the start time and duration of the image compression + func trackImageCompressionDuration(task: TrackableTask) { + mutexQueue.async { [weak self] in + self?.nonRepeatingTaskManager.imageCompressionDuration = task + } + } + + /// Keep track of the start time and duration of the main loop + func trackMainLoopDuration(task: TrackableTask) { + mutexQueue.async { [weak self] in + self?.nonRepeatingTaskManager.mainLoopDuration = task + } + } + + /// Clear the scan analytics manager instance + func reset() { + mutexQueue.async { [weak self] in + self?.startTime = nil + self?.nonRepeatingTaskManager = NonRepeatingTasksManager() + self?.payloadInfo = nil + self?.repeatingTaskManager = RepeatingTasksManager() + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCard.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCard.swift new file mode 100644 index 00000000..c0dae918 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCard.swift @@ -0,0 +1,40 @@ +// +// ScannedCard.swift +// StripeCardScan +// +// Created by Jaime Park on 11/17/21. +// + +import Foundation + +/// An struct that contains the PAN of the scanned card during +/// the card image verification flow +public struct ScannedCard: Equatable { + public let pan: String + public let expiryMonth: String? + public let expiryYear: String? + public let name: String? + + init( + pan: String, + expiryMonth: String? = nil, + expiryYear: String? = nil, + name: String? = nil + ) { + self.pan = pan + self.expiryMonth = expiryMonth + self.expiryYear = expiryYear + self.name = name + } +} + +extension ScannedCard { + init(scannedCard: CreditCard) { + self.init( + pan: scannedCard.number, + expiryMonth: scannedCard.expiryMonth, + expiryYear: scannedCard.expiryYear, + name: scannedCard.name + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCardImageData+Verification.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCardImageData+Verification.swift new file mode 100644 index 00000000..354671a4 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCardImageData+Verification.swift @@ -0,0 +1,137 @@ +// +// VerificationScannedCardImageData.swift +// StripeCardScan +// +// Created by Jaime Park on 11/21/21. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/// Image configurations used for verification flow +typealias ImageConfig = CardImageVerificationAcceptedImageConfigs + +struct CardImageVerificationImageMetadata { + let imageData: Data + let imageSize: CGSize + let compressionType: CardImageVerificationFormat + let compressionQuality: Double +} + +/// Methods used to transform a scanned card image data into `VerificationFramesData` +extension ScannedCardImageData { + /// Returns a `VerificationFramesData` object from the scanned image data + func toVerificationFramesData( + imageConfig: ImageConfig? + ) -> (VerificationFramesData, CardImageVerificationImageMetadata) { + let config = imageConfig ?? ImageConfig() + let encodedImageMetadata = toExpectedImageFormat( + image: previewLayerImage, + imageConfig: config + ) + let imageData = encodedImageMetadata.imageData + let size = encodedImageMetadata.imageSize + + // make sure to adjust the size of our viewFinderRect if jpeg conversion resized the image + let scaleX = size.width / CGFloat(previewLayerImage.width) + let scaleY = size.height / CGFloat(previewLayerImage.height) + let viewfinderMargins = toViewfinderMargins( + viewfinderRect: previewLayerViewfinderRect, + scaleX: scaleX, + scaleY: scaleY + ) + + return ( + VerificationFramesData(imageData: imageData, viewfinderMargins: viewfinderMargins), + encodedImageMetadata + ) + } + + /// Converts a CGImage into a base64 encoded string of a jpeg image + private func toExpectedImageFormat( + image: CGImage, + imageConfig: ImageConfig + ) -> CardImageVerificationImageMetadata { + /// Convert CGImage to UIImage + let uiImage = UIImage(cgImage: image) + + /// TODO(jaimepark): Resize with aspect ratio maintained if image is bigger than 1080 x 1920 + + for format in imageConfig.preferredFormats ?? [] { + if !isImageFormatSupported(format: format) { + continue + } + + let compressedImage = compressedImageForFormat( + image: uiImage, + format: format, + imageConfig: imageConfig + ) + if compressedImage.imageData.count > 0 { + return compressedImage + } + } + + return compressedImageForFormat(image: uiImage, format: .jpeg, imageConfig: imageConfig) + } + + /// Converts the view finder CGRect into a ViewFinderMargins object + private func toViewfinderMargins( + viewfinderRect: CGRect, + scaleX: CGFloat, + scaleY: CGFloat + ) -> ViewFinderMargins { + let left: Int = Int(viewfinderRect.origin.x * scaleX) + let right: Int = Int((viewfinderRect.width + viewfinderRect.origin.x) * scaleX) + let upper: Int = Int(viewfinderRect.origin.y * scaleY) + let lower: Int = Int((viewfinderRect.height + viewfinderRect.origin.y) * scaleY) + + return ViewFinderMargins(left: left, upper: upper, right: right, lower: lower) + } + + private func isImageFormatSupported(format: CardImageVerificationFormat) -> Bool { + format == .heic || format == .jpeg + } + + private func compressedImageForFormat( + image: UIImage, + format: CardImageVerificationFormat, + imageConfig: ImageConfig + ) -> CardImageVerificationImageMetadata { + let imageSettings = imageConfig.imageSettings(format: format) + let compressionRatio = imageSettings.compressionRatio ?? 1 + var result: CardImageVerificationImageMetadata = + .init( + imageData: Data(), + imageSize: .zero, + compressionType: format, + compressionQuality: compressionRatio + ) + + switch format { + case .heic: + let imageDataAndSize = image.heicDataAndDimensions(compressionQuality: compressionRatio) + + result = .init( + imageData: imageDataAndSize.imageData, + imageSize: imageDataAndSize.imageSize, + compressionType: format, + compressionQuality: compressionRatio + ) + case .jpeg: + let imageDataAndSize = image.jpegDataAndDimensions(compressionQuality: compressionRatio) + + result = .init( + imageData: imageDataAndSize.imageData, + imageSize: imageDataAndSize.imageSize, + compressionType: format, + compressionQuality: compressionRatio + ) + case .webp, .unparsable: + assertionFailure("Unsupported format requested for image.") + } + + return result + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCardImageData.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCardImageData.swift new file mode 100644 index 00000000..9f25d0e0 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/ScannedCardImageData.swift @@ -0,0 +1,96 @@ +// +// ScannedCardImageDataProtocol.swift +// StripeCardScan +// +// Created by Jaime Park on 11/21/21. +// + +import Foundation +import UIKit + +/// Data structure representing an image frame captured during the scanning flow. +struct ScannedCardImageData { + /// The image of the scanned card after it has been converted from AVCaptureSession to the video preview layer coordinate system + let previewLayerImage: CGImage + /// The viewfinder bounds after it has been converted from the AVCaptureSession to the video preview layer coordinate system + let previewLayerViewfinderRect: CGRect + + init( + previewLayerImage: CGImage, + previewLayerViewfinderRect: CGRect + ) { + self.previewLayerImage = previewLayerImage + self.previewLayerViewfinderRect = previewLayerViewfinderRect + } + + init?( + captureDeviceImage: CGImage, + viewfinderRect: CGRect, + previewViewRect: CGRect + ) { + guard + let previewLayerImage = + ScannedCardImageData + .convertToPreviewLayerImage( + captureDeviceImage: captureDeviceImage, + viewfinderRect: viewfinderRect, + previewViewRect: previewViewRect + ), + let previewLayerViewfinderRect = + ScannedCardImageData + .convertToPreviewLayerRect( + captureDeviceImage: captureDeviceImage, + viewfinderRect: viewfinderRect, + previewViewRect: previewViewRect + ) + else { + return nil + } + + self.init( + previewLayerImage: previewLayerImage, + previewLayerViewfinderRect: previewLayerViewfinderRect + ) + } +} + +/// TODO(jaimepark): Update conversion methods to calculate based on only AVCaputureSessionPreviewLayer. +/// Currently, .FullScreenAndRoi returns both the converted image and view finder rect. Once the conversion logic +/// is updated, the params will be updated and these functions will be DRY-ed up. +extension ScannedCardImageData { + /// Using legacy SDK logic, returns the AVCaptureDevice-to-preview view layer converted image + static func convertToPreviewLayerImage( + captureDeviceImage: CGImage, + viewfinderRect: CGRect, + previewViewRect: CGRect + ) -> CGImage? { + guard + let (convertedImage, _) = + captureDeviceImage.toFullScreenAndRoi( + previewViewFrame: previewViewRect, + regionOfInterestLabelFrame: viewfinderRect + ) + else { + return nil + } + return convertedImage + } + + /// Using legacy SDK logic, returns the AVCaptureDevice-to-preview view layer converted view finder rect + static func convertToPreviewLayerRect( + captureDeviceImage: CGImage, + viewfinderRect: CGRect, + previewViewRect: CGRect + ) -> CGRect? { + guard + let (_, convertedRect) = + captureDeviceImage.toFullScreenAndRoi( + previewViewFrame: previewViewRect, + regionOfInterestLabelFrame: viewfinderRect + ) + else { + return nil + } + return convertedRect + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/StripeCore+Import.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/StripeCore+Import.swift new file mode 100644 index 00000000..f9310dd4 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Card Image Verification/StripeCore+Import.swift @@ -0,0 +1,9 @@ +// +// StripeCore+Import.swift +// StripeCardScan +// +// Created by Jaime Park on 11/18/21. +// + +import Foundation +@_exported import StripeCore diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/CardBase.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/CardBase.swift new file mode 100644 index 00000000..050f5a3a --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/CardBase.swift @@ -0,0 +1,46 @@ +// +// CardBase.swift +// CardScan +// +// Created by Jaime Park on 1/31/20. +// + +import Foundation + +class CardBase: NSObject { + var last4: String + var bin: String? + var expMonth: String? + var expYear: String? + var isNewCard: Bool = false + + init( + last4: String, + bin: String?, + expMonth: String? = nil, + expYear: String? = nil + ) { + self.last4 = last4 + self.bin = bin + self.expMonth = expMonth + self.expYear = expYear + } + + func expiryForDisplay() -> String? { + guard let month = self.expMonth, let year = self.expYear else { + return nil + } + + return CreditCardUtils.formatExpirationDate(expMonth: month, expYear: year) + } + + func toOcrJson() -> [String: Any] { + var ocrJson: [String: Any] = [:] + ocrJson["last4"] = self.last4 + ocrJson["bin"] = self.bin + ocrJson["exp_month"] = self.expMonth + ocrJson["exp_year"] = self.expYear + + return ocrJson + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/CardScanFraudData.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/CardScanFraudData.swift new file mode 100644 index 00000000..9c5bccd3 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/CardScanFraudData.swift @@ -0,0 +1,180 @@ +/// This is our completion loop buffer. +/// +/// In general, our goal is to capture frames that will work best with our fraud models while capturing fraud behavior. As such: +/// 1. We give top priority to frames where the UX model finds a card _and_ we perform OCR on the frame successfully +/// 2. Next priority goes to frames that the UX model finds a card but it could _not_ perform OCR on the frame +/// 3. Lowest priority goes to frames that pass OCR but _not_ the UX model +/// +/// One consequence of this priority is that even if we perform OCR successfully we could end up with all frames _without_ +/// OCR because we give priority to UX. +/// +/// We can assume that any frames that pass OCR will be recent (within the last 2-3 seconds). This is _always_ true for +/// OCR, but for UX we might get a few older frames because our logic for starting non number side card scans requires a few +/// consecutive frames where the UX model detects a card. +/// +/// # Correctness +/// +/// This interface is thread safe, but all shared state access needs to happen in the `mutexQueue`. The mutexQueue +/// enforces ordering constraints and will process frames in the correct order as defined by the `ScanEvents` calling +/// sequence. +/// + +import CoreGraphics +import UIKit + +class CardScanFraudData: ScanEvents { + let mutexQueue = DispatchQueue(label: "Completion loop mutex queue") + + var last4: String? + var hasModelBeenCalled = false + var framesWithCards: [ScannedCardImageData] = [] + var framesWithCardsAndOcr: [ScannedCardImageData] = [] + var ocrOnlyFrames: [ScannedCardImageData] = [] + var framesWithFlashCardsAndOcr: [ScannedCardImageData] = [] + var framesWithFlashAndCards: [ScannedCardImageData] = [] + var framesWithFlashAndOcr: [ScannedCardImageData] = [] + let kMaxScans = 5 + let kMaxFlashScans = 3 + var requireOcrBeforeCapturingUxOnlyFrames = true + + var debugRetainImages = false + // Note: Only access these arrays on the main loop + var savedSquareImages: [CGImage]? + var savedFullImages: [CGImage]? + var savedNumberBoxes: [[CGRect]]? + + init() {} + + func onFrameDetected( + imageData: ScannedCardImageData, + centeredCardState: CenteredCardState?, + flashForcedOn: Bool + ) { + mutexQueue.async { + if self.hasModelBeenCalled { + return + } + + let hasCard = centeredCardState?.hasCard() ?? false + + let ocrFrameCount = self.framesWithCardsAndOcr.count + self.ocrOnlyFrames.count + + if hasCard && (ocrFrameCount > 0 || !self.requireOcrBeforeCapturingUxOnlyFrames) { + if flashForcedOn { + self.framesWithFlashAndCards.append(imageData) + } else { + self.framesWithCards.append(imageData) + } + } + + self.balanceFrames() + } + } + + func onNumberRecognized( + number: String, + expiry: Expiry?, + imageData: ScannedCardImageData, + centeredCardState: CenteredCardState?, + flashForcedOn: Bool + ) { + mutexQueue.async { + if self.hasModelBeenCalled { + return + } + + let hasCard = centeredCardState?.hasCard() ?? false + let scannedLastFour = String(number.suffix(4)) + + /// This method is used to put the frame data in it's appropriate list given the `flashForcedOn` and `hasCard` flag, + /// This method should be called on when we know that we want to keep the frame. + func appendFrameData(flashForcedOn: Bool, hasCard: Bool) { + if flashForcedOn { + if hasCard { + self.framesWithFlashCardsAndOcr.append(imageData) + } else { + self.framesWithFlashAndOcr.append(imageData) + } + } else if hasCard { + self.framesWithCardsAndOcr.append(imageData) + } else { + self.ocrOnlyFrames.append(imageData) + } + } + + // Check if we have a card set to be challenged + if let challengedLast4 = self.last4 { + guard challengedLast4 == scannedLastFour else { + // The set card to be challenged doesn't match the scanned card. + // Don't use this frame at all. + return + } + + // Given that the challenged card matches the frame's pan + last, put frame in appropriate list + appendFrameData(flashForcedOn: flashForcedOn, hasCard: hasCard) + + } else { + // If we don't have a card set to be challenged just add frameData + appendFrameData(flashForcedOn: flashForcedOn, hasCard: hasCard) + } + + self.balanceFrames() + } + } + + private func balanceFrames() { + self.framesWithCardsAndOcr = Array(self.framesWithCardsAndOcr.suffix(kMaxScans)) + + // make sure that we have a total of kMaxScans across OCR+UX, UX, and OCR frames giving priority to OCR+UX + let framesWithCardsToHold = + [kMaxScans - self.framesWithCardsAndOcr.count, 0].max() ?? kMaxScans + self.framesWithCards = Array(self.framesWithCards.suffix(framesWithCardsToHold)) + + let ocrFramesToHold = + [kMaxScans - self.framesWithCardsAndOcr.count - self.framesWithCards.count, 0].max() + ?? kMaxScans + self.ocrOnlyFrames = Array(self.ocrOnlyFrames.suffix(ocrFramesToHold)) + + // separately, keep kMaxFlashScans number of frames with the flash on + self.framesWithFlashCardsAndOcr = Array( + self.framesWithFlashCardsAndOcr.suffix(kMaxFlashScans) + ) + + let framesWithFlashCardsToHold = + [kMaxFlashScans - self.framesWithFlashCardsAndOcr.count, 0].max() ?? kMaxFlashScans + self.framesWithFlashAndCards = Array( + self.framesWithFlashAndCards.suffix(framesWithFlashCardsToHold) + ) + + let ocrFlashFramesToHold = + [ + kMaxFlashScans - self.framesWithFlashCardsAndOcr.count + - self.framesWithFlashAndCards.count, 0, + ].max() ?? kMaxFlashScans + self.framesWithFlashAndOcr = Array(self.framesWithFlashAndOcr.suffix(ocrFlashFramesToHold)) + } + + func onResultReady(scannedCardImagesData: [ScannedCardImageData]) { + // TODO: Run verification pipeline and report back + } + + func getCompletionLoopFrames() -> [ScannedCardImageData] { + let completionLoopFrames = + self.framesWithFlashAndOcr + self.framesWithFlashAndCards + + self.framesWithFlashCardsAndOcr + self.ocrOnlyFrames + self.framesWithCards + + self.framesWithCardsAndOcr + return Array(completionLoopFrames.suffix(kMaxScans + kMaxFlashScans)) + } + + func onScanComplete(scanStats: ScanStats) { + mutexQueue.async { + if self.hasModelBeenCalled { + return + } + self.hasModelBeenCalled = true + + let completionLoopFrames = self.getCompletionLoopFrames() + self.onResultReady(scannedCardImagesData: completionLoopFrames) + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/CardScanMisc.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/CardScanMisc.swift new file mode 100644 index 00000000..b05d8a16 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/CardScanMisc.swift @@ -0,0 +1,41 @@ +import AVKit + +protocol CaptureOutputDelegate { + func capture( + _ output: AVCaptureOutput, + didOutput sampleBuffer: CMSampleBuffer, + from connection: AVCaptureConnection + ) +} + +class CreditCard: NSObject { + var number: String + var expiryMonth: String? + var expiryYear: String? + var name: String? + var image: UIImage? + var cvv: String? + var postalCode: String? + + init( + number: String + ) { + self.number = number + } + + func expiryForDisplay() -> String? { + guard var month = self.expiryMonth, var year = self.expiryYear else { + return nil + } + + if month.count == 1 { + month = "0" + month + } + + if year.count == 4 { + year = String(year.suffix(2)) + } + + return "\(month)/\(year)" + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/CardVerifyFraudData.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/CardVerifyFraudData.swift new file mode 100644 index 00000000..99969102 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/CardVerifyFraudData.swift @@ -0,0 +1,75 @@ +import Foundation + +class CardVerifyFraudData: CardScanFraudData { + // one subtlety we have is that we might try to get results before the + // model is done running. Thus we record the model results for this object + // and keep track of any callers that try to get a response too early + // and notify them later. + // + // All data access is on the main queue + var verificationFrameDataResults: [VerificationFramesData]? + var resultCallbacks: [((_ response: [VerificationFramesData]) -> Void)] = [] + + var acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs? + + static let maxCompletionLoopFrames = 5 + + init( + last4: String? = nil, + acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs? = nil + ) { + super.init() + self.last4 = last4 + self.acceptedImageConfigs = acceptedImageConfigs + } + + override func onResultReady(scannedCardImagesData: [ScannedCardImageData]) { + DispatchQueue.main.async { + let imageCompressionTask = TrackableTask() + let processedVerificationFrames = scannedCardImagesData.compactMap { + $0.toVerificationFramesData(imageConfig: self.acceptedImageConfigs) + } + imageCompressionTask.trackResult( + processedVerificationFrames.count > 0 ? .success : .failure + ) + + let verificationFramesData = processedVerificationFrames.compactMap { $0.0 } + /// Use the image metadata from the most recent frame + let verificationImageMetadata = processedVerificationFrames.compactMap { $0.1 }.last + + /// Calculate the compressed and b64 encoded image sizes in bytes + let totalImagePayloadSizeInBytes = verificationFramesData.compactMap { $0.imageData } + .reduce(0) { $0 + $1.count } + + /// Log the verification payload info + image compression duration + ScanAnalyticsManager.shared.trackImageCompressionDuration(task: imageCompressionTask) + ScanAnalyticsManager.shared.logPayloadInfo( + with: .init( + imageCompressionType: verificationImageMetadata?.compressionType.rawValue + ?? "unknown", + imageCompressionQuality: verificationImageMetadata?.compressionQuality ?? 0.0, + imagePayloadSize: totalImagePayloadSizeInBytes + ) + ) + + self.verificationFrameDataResults = verificationFramesData + + for complete in self.resultCallbacks { + complete(verificationFramesData) + } + + self.resultCallbacks = [] + } + } + + func result(complete: @escaping ([VerificationFramesData]) -> Void) { + DispatchQueue.main.async { + guard let results = self.verificationFrameDataResults else { + self.resultCallbacks.append(complete) + return + } + + complete(results) + } + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/CardVerifyStateMachine.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/CardVerifyStateMachine.swift new file mode 100644 index 00000000..e4928770 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/CardVerifyStateMachine.swift @@ -0,0 +1,325 @@ +// +// CardVerifyStateMachine.swift +// CardVerify +// +// Created by Adam Wushensky on 8/7/20. +// + +import Foundation + +typealias StrictModeFramesCount = CardImageVerificationSheet.StrictModeFrameCount + +protocol CardVerifyStateMachineProtocol { + var requiredLastFour: String? { get } + var requiredBin: String? { get } + var strictModeFramesCount: StrictModeFramesCount { get } + var visibleMatchingCardCount: Int { get set } + + func resetCountAndReturnToInitialState() -> MainLoopState + func determineFinishedState() -> MainLoopState +} + +class CardVerifyStateMachine: OcrMainLoopStateMachine, CardVerifyStateMachineProtocol { + var requiredLastFour: String? + var requiredBin: String? + var strictModeFramesCount: StrictModeFramesCount + var visibleMatchingCardCount: Int = 0 + + let ocrAndCardStateDurationSeconds = 1.5 + let ocrOnlyStateDurationSeconds = 1.5 + let ocrDelayForCardStateDurationSeconds = 2.0 + let ocrIncorrectDurationSeconds = 2.0 + let ocrForceFlashDurationSeconds = 1.5 + + init( + requiredLastFour: String? = nil, + requiredBin: String? = nil, + strictModeFramesCount: CardImageVerificationSheet.StrictModeFrameCount + ) { + self.requiredLastFour = requiredLastFour + self.requiredBin = requiredBin + self.strictModeFramesCount = strictModeFramesCount + } + + convenience init( + requiredLastFour: String? = nil, + requiredBin: String? = nil + ) { + self.init( + requiredLastFour: requiredLastFour, + requiredBin: requiredBin, + strictModeFramesCount: .none + ) + } + + func resetCountAndReturnToInitialState() -> MainLoopState { + visibleMatchingCardCount = 0 + return .initial + } + + func determineFinishedState() -> MainLoopState { + if Bouncer.useFlashFlow { + return .ocrForceFlash + } + + /// The ocr and card state timer has elapsed. If visible card count hasn't been met within the time limit, then reset the timer and try again + return visibleMatchingCardCount >= strictModeFramesCount.totalFrameCount + ? .finished : resetCountAndReturnToInitialState() + } + + override func transition(prediction: CreditCardOcrPrediction) -> MainLoopState? { + let frameHasOcr = prediction.number != nil + let frameOcrMatchesRequiredLastFour = + requiredLastFour == nil + || String(prediction.number?.suffix(4) ?? "") == requiredLastFour + let frameOcrMatchesRequiredBin = + requiredBin == nil || String(prediction.number?.prefix(6) ?? "") == requiredBin + let frameOcrMatchesRequired = frameOcrMatchesRequiredBin && frameOcrMatchesRequiredLastFour + let frameHasCard = prediction.centeredCardState?.hasCard() ?? false + let secondsInState = -startTimeForCurrentState.timeIntervalSinceNow + + if frameHasCard && frameOcrMatchesRequired { + visibleMatchingCardCount += 1 + } + + switch (self.state, secondsInState, frameHasOcr, frameHasCard, frameOcrMatchesRequired) { + // MARK: Initial State + case (.initial, _, true, true, true): + // successful OCR and card + return .ocrAndCard + case (.initial, _, true, _, false): + // saw an incorrect card + return .ocrIncorrect + case (.initial, _, _, true, _): + // got a frame with a card + return .cardOnly + case (.initial, _, true, _, true): + // successful OCR and the card matches required + return .ocrOnly + + // MARK: Card Only State + case (.cardOnly, _, true, _, false): + // if we're cardOnly and we get a frame with OCR and it does not match the required card + return .ocrIncorrect + case (.cardOnly, _, true, _, true): + // if we're cardonly and we get a frame with OCR and it matches the required card + return .ocrAndCard + + // MARK: OCR Only State + case (.ocrOnly, _, _, true, _): + // if we're ocrOnly and we get a card + return .ocrAndCard + case (.ocrOnly, self.ocrOnlyStateDurationSeconds..., _, _, _): + // ocrOnly times out without getting a card + return .ocrDelayForCard + + // MARK: OCR and Card State + case (.ocrAndCard, self.ocrAndCardStateDurationSeconds..., _, _, _): + return determineFinishedState() + + // MARK: OCR Incorrect State + case (.ocrIncorrect, _, true, false, true): + // if we're ocrIncorrect and we get a valid pan + return .ocrOnly + case (.ocrIncorrect, _, true, true, true): + // if we're ocrIncorrect and we get a valid pan and card + return .ocrAndCard + case (.ocrIncorrect, _, true, _, false): + // if we're ocrIncorrect and we get another bad pan, restart the timer + return .ocrIncorrect + case (.ocrIncorrect, self.ocrIncorrectDurationSeconds..., _, _, _): + // if we're ocrIncorrect and the timer has elapsed + return resetCountAndReturnToInitialState() + + // MARK: OCR Delay for Card State + case (.ocrDelayForCard, _, _, true, _): + // if we're ocrDelayForCard and we get a card + return .ocrAndCard + case (.ocrDelayForCard, self.ocrDelayForCardStateDurationSeconds..., _, _, _): + // if we're ocrDelayForCard and we time out + return determineFinishedState() + + // MARK: OCR Force Flash State + case (.ocrForceFlash, self.ocrForceFlashDurationSeconds..., _, _, _): + return .finished + default: + return nil + } + } + + override func reset() -> MainLoopStateMachine { + return CardVerifyStateMachine( + requiredLastFour: requiredLastFour, + requiredBin: requiredBin, + strictModeFramesCount: strictModeFramesCount + ) + } +} + +class CardVerifyAccurateStateMachine: OcrMainLoopStateMachine, CardVerifyStateMachineProtocol { + var requiredLastFour: String? + var requiredBin: String? + var strictModeFramesCount: StrictModeFramesCount + var visibleMatchingCardCount: Int = 0 + + var hasNamePrediction = false + var hasExpiryPrediction = false + + let ocrAndCardStateDurationSeconds = 1.5 + let ocrOnlyStateDurationSeconds = 1.5 + let ocrDelayForCardStateDurationSeconds = 2.0 + let ocrIncorrectDurationSeconds = 2.0 + let ocrForceFlashDurationSeconds = 1.5 + var nameExpiryDurationSeconds = 4.0 + + init( + requiredLastFour: String? = nil, + requiredBin: String? = nil, + maxNameExpiryDurationSeconds: Double, + strictModeFramesCount: StrictModeFramesCount + ) { + self.requiredLastFour = requiredLastFour + self.requiredBin = requiredBin + self.nameExpiryDurationSeconds = maxNameExpiryDurationSeconds + self.strictModeFramesCount = strictModeFramesCount + } + + convenience init( + requiredLastFour: String? = nil, + requiredBin: String? = nil, + maxNameExpiryDurationSeconds: Double + ) { + self.init( + requiredLastFour: requiredLastFour, + requiredBin: requiredBin, + maxNameExpiryDurationSeconds: maxNameExpiryDurationSeconds, + strictModeFramesCount: .none + ) + } + + func resetCountAndReturnToInitialState() -> MainLoopState { + visibleMatchingCardCount = 0 + return .initial + } + + func determineFinishedState() -> MainLoopState { + if Bouncer.useFlashFlow { + return .ocrForceFlash + } + + /// The ocr and card state timer has elapsed. If visible card count hasn't been met within the time limit, then reset the timer and try again + return visibleMatchingCardCount >= strictModeFramesCount.totalFrameCount + ? .finished : resetCountAndReturnToInitialState() + } + + override func transition(prediction: CreditCardOcrPrediction) -> MainLoopState? { + hasExpiryPrediction = hasExpiryPrediction || prediction.expiryForDisplay != nil + hasNamePrediction = hasNamePrediction || prediction.name != nil + + let frameHasOcr = prediction.number != nil + let frameOcrMatchesRequiredLastFour = + requiredLastFour == nil + || String(prediction.number?.suffix(4) ?? "") == requiredLastFour + let frameOcrMatchesRequiredBin = + requiredBin == nil || String(prediction.number?.prefix(6) ?? "") == requiredBin + let frameOcrMatchesRequired = frameOcrMatchesRequiredBin && frameOcrMatchesRequiredLastFour + let frameHasCard = prediction.centeredCardState?.hasCard() ?? false + let hasNameAndExpiry = hasNamePrediction && hasExpiryPrediction + let secondsInState = -startTimeForCurrentState.timeIntervalSinceNow + + if frameHasCard && frameOcrMatchesRequired { + visibleMatchingCardCount += 1 + } + + switch ( + self.state, secondsInState, frameHasOcr, frameHasCard, frameOcrMatchesRequired, + hasNameAndExpiry + ) { + // MARK: Initial State + case (.initial, _, true, true, true, _): + // successful OCR and card + return .ocrAndCard + case (.initial, _, true, _, false, _): + // saw an incorrect card + return .ocrIncorrect + case (.initial, _, _, true, _, _): + // got a frame with a card + return .cardOnly + case (.initial, _, true, _, true, _): + // successful OCR and the card matches required + return .ocrOnly + + // MARK: Card Only State + case (.cardOnly, _, true, _, false, _): + // if we're cardOnly and we get a frame with OCR and it does not match the required card + return .ocrIncorrect + case (.cardOnly, _, true, _, true, _): + // if we're cardonly and we get a frame with OCR and it matches the required card + return .ocrAndCard + + // MARK: Ocr Only State + case (.ocrOnly, _, _, true, _, _): + // if we're ocrOnly and we get a card + return .ocrAndCard + case (.ocrOnly, self.ocrOnlyStateDurationSeconds..., _, _, _, _): + // ocrOnly times out without getting a card + return .ocrDelayForCard + + // MARK: Ocr Incorrect State + case (.ocrIncorrect, _, true, false, true, _): + // if we're ocrIncorrect and we get a valid pan + return .ocrOnly + case (.ocrIncorrect, _, true, true, true, _): + // if we're ocrIncorrect and we get a valid pan and card + return .ocrAndCard + case (.ocrIncorrect, _, true, _, false, _): + // if we're ocrIncorrect and we get another bad pan, restart the timer + return .ocrIncorrect + case (.ocrIncorrect, self.ocrIncorrectDurationSeconds..., _, _, _, _): + // if we're ocrIncorrect and the timer has elapsed + return .initial + + // MARK: Ocr and Card State + case (.ocrAndCard, self.ocrAndCardStateDurationSeconds..., _, _, _, false): + // if we're in ocr&card and dont have name&expiry + return .nameAndExpiry + case (.ocrAndCard, self.ocrAndCardStateDurationSeconds..., _, _, _, true): + // if we're in ocr&card and we have name&expiry + return determineFinishedState() + + // MARK: Ocr Delay For Card State + case (.ocrDelayForCard, self.ocrDelayForCardStateDurationSeconds..., _, _, _, false): + // if we're ocrDelayForCard, we time out, and we dont have name&expiry + return .nameAndExpiry + case (.ocrDelayForCard, self.ocrDelayForCardStateDurationSeconds..., _, _, _, true): + // if we're ocrDelayForCard, we time out but we have name&expiry + return determineFinishedState() + case (.ocrDelayForCard, _, _, true, _, _): + // if we're ocrDelayForCard and we get a card + return .ocrAndCard + + // MARK: Name And Expiry State + case (.nameAndExpiry, self.nameExpiryDurationSeconds..., _, _, _, _): + // if we're checking for name&expiry and we time out + return Bouncer.useFlashFlow ? .ocrForceFlash : .finished + case (.nameAndExpiry, _, _, _, _, true): + // if we're checking for name&expiry and we find name&expiry + return determineFinishedState() + + // MARK: Ocr Force Flash State + case (.ocrForceFlash, self.ocrForceFlashDurationSeconds..., _, _, _, _): + return .finished + default: + return nil + } + } + + override func reset() -> MainLoopStateMachine { + return CardVerifyAccurateStateMachine( + requiredLastFour: requiredLastFour, + requiredBin: requiredBin, + maxNameExpiryDurationSeconds: nameExpiryDurationSeconds, + strictModeFramesCount: strictModeFramesCount + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/FadeInAnimation.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/FadeInAnimation.swift new file mode 100644 index 00000000..6197d4da --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/FadeInAnimation.swift @@ -0,0 +1,67 @@ +// +// FadeInAnimation.swift +// CardScan +// +// Created by Jaime Park on 8/16/19. +// + +import UIKit + +extension UIView { + + func fadeIn(_ duration: TimeInterval? = 0.4, onCompletion: (() -> Void)? = nil) { + self.alpha = 0 + self.isHidden = false + UIView.animate( + withDuration: duration!, + animations: { self.alpha = 1 }, + completion: { (_: Bool) in + if let complete = onCompletion { complete() } + } + ) + } + + func fadeBorderColorIn( + _ duration: TimeInterval? = 0.4, + withColor: UIColor, + onCompletion: (() -> Void)? = nil + ) { + self.isHidden = false + UIView.animate( + withDuration: duration!, + animations: { self.layer.borderColor = withColor.cgColor }, + completion: { (_: Bool) in + if let complete = onCompletion { complete() } + } + ) + } + + func fadeOut(_ duration: TimeInterval? = 0.4, onCompletion: (() -> Void)? = nil) { + self.alpha = 1 + self.isHidden = true + UIView.animate( + withDuration: duration!, + animations: { self.alpha = 0 }, + completion: { (_: Bool) in + if let complete = onCompletion { complete() } + } + ) + } +} + +extension UILabel { + func fadeTextColorIn( + _ duration: TimeInterval? = 0.4, + withColor: UIColor, + onCompletion: (() -> Void)? = nil + ) { + self.isHidden = false + UIView.animate( + withDuration: duration!, + animations: { self.textColor = withColor }, + completion: { (_: Bool) in + if let complete = onCompletion { complete() } + } + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/FrameData.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/FrameData.swift new file mode 100644 index 00000000..3a734b44 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/FrameData.swift @@ -0,0 +1,40 @@ +import UIKit + +struct FrameData { + let bin: String? + let last4: String? + let expiry: Expiry? + let numberBoundingBox: CGRect? + let numberBoxesInFullImageFrame: [CGRect]? + let croppedCardSize: CGSize + let squareCardImage: CGImage + let fullCardImage: CGImage + let centeredCardState: CenteredCardState? + let ocrSuccess: Bool + let uxFrameConfidenceValues: UxFrameConfidenceValues? + let flashForcedOn: Bool + + func toDictForOcrFrame() -> [String: Any] { + var numberBox: [String: Any]? + + if let numberBoundingBox = self.numberBoundingBox { + numberBox = [ + "x_min": numberBoundingBox.minX / CGFloat(croppedCardSize.width), + "y_min": numberBoundingBox.minY / CGFloat(croppedCardSize.height), + "width": numberBoundingBox.width / CGFloat(croppedCardSize.width), + "height": numberBoundingBox.height / CGFloat(croppedCardSize.height), + "label": -1, + "confidence": 1, + ] + } + + var result: [String: Any] = [:] + result["bin"] = self.bin + result["last4"] = self.last4 + result["number_box"] = numberBox + result["exp_month"] = (self.expiry?.month).map { String($0) } + result["exp_year"] = (self.expiry?.year).map { String($0) } + + return result + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/EndToEndTestDataSource.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/EndToEndTestDataSource.swift new file mode 100644 index 00000000..65a3abd7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/EndToEndTestDataSource.swift @@ -0,0 +1,48 @@ +// +// EndToEndTestDataSource.swift +// StripeCardScanTests +// +// Created by Scott Grant on 9/25/22. +// + +#if targetEnvironment(simulator) + + import Foundation + import UIKit + + class EndToEndTestingImageDataSource: TestingImageDataSource { + lazy var testImages: [UIImage] = { + let bundle = Bundle(for: EndToEndTestingImageDataSource.self) + let path = bundle.url(forResource: "synthetic_test_image", withExtension: "jpg")! + let image = UIImage(contentsOfFile: path.path)! + return [image] + }() + + lazy var currentTestImages: [CGImage]? = { + self.testImages.compactMap { $0.cgImage } + }() + + func nextSquareAndFullImage() -> CGImage? { + guard let targetSize = UIApplication.shared.windows.first?.frame.size else { + return nil + } + + guard let fullCardImage = self.currentTestImages?.first else { + return nil + } + + let resultImage = fullCardImage.extendedEdges(targetSize: targetSize) + + self.currentTestImages = self.currentTestImages?.dropFirst().map { $0 } + + guard let testImageCount = self.currentTestImages?.count else { return nil } + + if testImageCount == 0 { + self.currentTestImages = self.testImages.compactMap { $0.cgImage } + } + + return resultImage + } + } + +#endif // targetEnvironment(simulator) diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/STPLocalizedString.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/STPLocalizedString.swift new file mode 100644 index 00000000..e43018bf --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/STPLocalizedString.swift @@ -0,0 +1,16 @@ +// +// STPLocalizedString.swift +// StripeCardScan +// +// Created by Sam King on 12/8/21. +// + +import Foundation +@_spi(STP) import StripeCore + +@inline(__always) func STPLocalizedString(_ key: String, _ comment: String?) -> String { + return STPLocalizationUtils.localizedStripeString( + forKey: key, + bundleLocator: StripeCardScanBundleLocator.self + ) +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/String+Localized.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/String+Localized.swift new file mode 100644 index 00000000..049d9402 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/Helpers/String+Localized.swift @@ -0,0 +1,46 @@ +// +// String+Localized.swift +// StripeCardScan +// +// Created by Sam King on 12/8/21. +// + +import Foundation +@_spi(STP) import StripeCore + +extension String.Localized { + static var card_doesnt_match: String { + return STPLocalizedString( + "Card doesn't match", + "Label of the error message when the scanned card mismatches the card on file" + ) + } + + static var torch: String { + return STPLocalizedString( + "Torch", + "Label for the button that toggles the camera's torch" + ) + } + + static var enable_camera_access: String { + return STPLocalizedString( + "Enable camera access", + "Label for button to take customer to camera settings" + ) + } + + static var update_phone_settings: String { + return STPLocalizedString( + "To scan your card you'll need to update your phone settings", + "Label to explain that they need to update phone settings to scan" + ) + } + + static var enter_card_details_manually: String { + return STPLocalizedString( + "Enter card details manually", + "Label for button to enter card details manually instead of scanning" + ) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/PaymentCard.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/PaymentCard.swift new file mode 100644 index 00000000..ab82cb6d --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/PaymentCard.swift @@ -0,0 +1,76 @@ +// +// CreditCard.swift +// CardScan +// +// Created by Jaime Park on 7/22/19. +// + +import Foundation +import UIKit + +class PaymentCard: CardBase { + var number: String + var cvv: String? + var zip: String? + var network: CardNetwork + + enum Network: Int { + case VISA, MASTERCARD, AMEX, DISCOVER, UNIONPAY, UNKNOWN + + func toCardNetwork() -> CardNetwork { + switch self { + case .VISA: return CardNetwork.VISA + case .MASTERCARD: return CardNetwork.MASTERCARD + case .AMEX: return CardNetwork.AMEX + case .DISCOVER: return CardNetwork.DISCOVER + case .UNIONPAY: return CardNetwork.UNIONPAY + default: return CardNetwork.UNKNOWN + } + } + } + + init( + number: String, + expiryMonth: String?, + expiryYear: String?, + network: Network? + ) { + self.number = number + self.network = + network?.toCardNetwork() ?? CreditCardUtils.determineCardNetwork(cardNumber: number) + super.init( + last4: String(number.suffix(4)), + bin: nil, + expMonth: expiryMonth, + expYear: expiryYear + ) + } + + init( + last4: String, + bin: String?, + expiryMonth: String?, + expiryYear: String?, + network: Network? + ) { + self.number = last4 + self.network = network?.toCardNetwork() ?? CardNetwork.UNKNOWN + super.init(last4: last4, bin: bin, expMonth: expiryMonth, expYear: expiryYear) + } + + func isValidCvv() -> Bool { + guard let cvv = self.cvv else { + return false + } + + return CreditCardUtils.isValidCvv(cvv: cvv, network: self.network) + } + + func isValidDate() -> Bool { + guard let month = self.expMonth, let year = self.expYear else { + return false + } + + return CreditCardUtils.isValidDate(expMonth: month, expYear: year) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/SimpleScanViewController+Verify.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/SimpleScanViewController+Verify.swift new file mode 100644 index 00000000..29ee43c7 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/SimpleScanViewController+Verify.swift @@ -0,0 +1,24 @@ +import UIKit + +extension SimpleScanViewController { + func showFullScreenActivityIndicator() { + let container = UIView() + self.view.addSubview(container) + container.translatesAutoresizingMaskIntoConstraints = false + container.setAnchorsEqual(to: self.view) + container.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.7462275257) + + let activityIndicator = UIActivityIndicatorView() + + activityIndicator.style = .large + activityIndicator.color = .white + + activityIndicator.isHidden = false + activityIndicator.startAnimating() + container.addSubview(activityIndicator) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + + activityIndicator.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true + activityIndicator.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/StripeCardScanBundleLocator.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/StripeCardScanBundleLocator.swift new file mode 100644 index 00000000..a8bdb54a --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/StripeCardScanBundleLocator.swift @@ -0,0 +1,20 @@ +// +// StripeCardScanBundleLocator.swift +// StripeCardScan +// +// Created by Sam King on 11/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// :nodoc: +final class StripeCardScanBundleLocator: BundleLocatorProtocol { + static let internalClass: AnyClass = StripeCardScanBundleLocator.self + static let bundleName = "StripeCardScanBundle" + #if SWIFT_PACKAGE + static let spmResourcesBundle = Bundle.module + #endif + static let resourcesBundle = StripeCardScanBundleLocator.computeResourcesBundle() +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/UxAnalyzer.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/UxAnalyzer.swift new file mode 100644 index 00000000..6982eb1b --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/UxAnalyzer.swift @@ -0,0 +1,131 @@ +// +// Created by Sam King on 3/20/20. +// Copyright © 2020 Sam King. All rights reserved. +// +import UIKit + +@_spi(STP) public class UxAnalyzer: CreditCardOcrImplementation { + @AtomicProperty var uxModel: UxModel? + + static let uxResource = "UxModel" + static let uxExtension = "mlmodelc" + + let ocr: CreditCardOcrImplementation + + init( + with ocr: CreditCardOcrImplementation + ) { + self.ocr = ocr + uxModel = UxAnalyzer.loadModelFromBundle() + super.init(dispatchQueue: ocr.dispatchQueue) + } + + init( + asyncWith ocr: CreditCardOcrImplementation + ) { + self.ocr = ocr + super.init(dispatchQueue: ocr.dispatchQueue) + loadModel() + } + + @_spi(STP) public static func loadModelFromBundle() -> UxModel? { + let bundle = StripeCardScanBundleLocator.resourcesBundle + guard let url = bundle.url(forResource: UxAnalyzer.uxResource, withExtension: UxAnalyzer.uxExtension) else { + return nil + } + + return try? UxModel(contentsOf: url) + } + + override func recognizeCard( + in fullImage: CGImage, + roiRectangle: CGRect + ) -> CreditCardOcrPrediction { + guard let imageForUxModel = fullImage.squareImageForUxModel(roiRectangle: roiRectangle), + let uxModelPixelBuf = UIImage(cgImage: imageForUxModel).pixelBuffer( + width: 224, + height: 224 + ) + else { + return CreditCardOcrPrediction.emptyPrediction(cgImage: fullImage) + } + + // we already have parallel inference at the analyzer level so no need to run this prediction + // in parallel with the OCR prediction. Plus, this is iOS so the uxmodel prediction will be fast + guard let uxModel = uxModel, + let prediction = try? uxModel.prediction(input1: uxModelPixelBuf) + else { + return CreditCardOcrPrediction.emptyPrediction(cgImage: fullImage) + } + + return ocr.recognizeCard(in: fullImage, roiRectangle: roiRectangle).with( + uxPrediction: prediction + ) + } + + private func loadModel() { + guard + let uxModelUrl = StripeCardScanBundleLocator.resourcesBundle.url( + forResource: UxAnalyzer.uxResource, + withExtension: UxAnalyzer.uxExtension + ) + else { + return + } + + UxModel.asyncLoad(contentsOf: uxModelUrl) { [weak self] result in + switch result { + case .success(let model): + self?.uxModel = model + case .failure(let error): + assertionFailure("Error loading model: \(error.localizedDescription)") + } + } + } +} + +extension UxModelOutput { + func argMax() -> Int { + return self.argAndValueMax().0 + } + + func argAndValueMax() -> (Int, Double) { + var maxIdx = -1 + var maxValue = NSNumber(value: -1.0) + for idx in 0..<3 { + let index: [NSNumber] = [NSNumber(value: idx)] + let value = self.output1[index] + if value.doubleValue > maxValue.doubleValue { + maxIdx = idx + maxValue = value + } + } + + return (maxIdx, maxValue.doubleValue) + } + + func cardCenteredState() -> CenteredCardState { + switch self.argMax() { + case 0: + return .nonNumberSide + case 2: + return .numberSide + default: + return .noCard + } + } + + func confidenceValues() -> (Double, Double, Double)? { + let idxRange = 0..<3 + let indexValues = idxRange.map { [NSNumber(value: $0)] } + var confidenceValues = indexValues.map { self.output1[$0].doubleValue } + + guard let pan = confidenceValues.popLast(), let noCard = confidenceValues.popLast(), + let noPan = confidenceValues.popLast() + else { + return nil + } + + return (pan, noPan, noCard) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/UxAndOcrMainLoop.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/UxAndOcrMainLoop.swift new file mode 100644 index 00000000..74fe4742 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/UxAndOcrMainLoop.swift @@ -0,0 +1,17 @@ +class UxAndOcrMainLoop: OcrMainLoop { + init( + stateMachine: MainLoopStateMachine + ) { + super.init(analyzers: []) + + errorCorrection = ErrorCorrection(stateMachine: stateMachine) + + let ssdOcr = SSDCreditCardOcr(dispatchQueueLabel: "Ux+Ocr queue") + let appleOcr = AppleCreditCardOcr(dispatchQueueLabel: "apple queue") + + let ocrImplementations = [ + UxAnalyzer(asyncWith: ssdOcr), UxAnalyzer(asyncWith: appleOcr), + ] + setupMl(ocrImplementations: ocrImplementations) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/VerifyCardAddViewController.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/VerifyCardAddViewController.swift new file mode 100644 index 00000000..9da63e8f --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/VerifyCardAddViewController.swift @@ -0,0 +1,202 @@ +@_spi(STP) import StripeCore +import UIKit + +/// This class is a first cut on providing verification on card add (i.e., Zero Fraud). Currently it includes a manual entry button +/// and navigation to the `CardEntryViewController` where the user can complete the information that they add. + +class VerifyCardAddViewController: SimpleScanViewController { + typealias StrictModeFramesCount = CardImageVerificationSheet.StrictModeFrameCount + /// Set this variable to `false` to force the user to scan their card _without_ the option to enter all details manually + static var enableManualCardEntry = true + var enableManualEntry = enableManualCardEntry + + static var manualCardEntryButton = UIButton(type: .system) + static var closeButton: UIButton? + static var torchButton: UIButton? + + var debugRetainCompletionLoopImages = false + + static var manualCardEntryText = String.Localized.enter_card_details_manually + + // TODO(jaimepark): Remove on consolidation + weak var verifyDelegate: VerifyViewControllerDelegate? + + private let acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs? + private let configuration: CardImageVerificationSheet.Configuration + private let mainLoopDurationTask: TrackableTask + + init( + acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs?, + configuration: CardImageVerificationSheet.Configuration + ) { + self.acceptedImageConfigs = acceptedImageConfigs + self.configuration = configuration + self.mainLoopDurationTask = TrackableTask() + super.init() + } + + required init?(coder: NSCoder) { fatalError("not supported") } + + override func viewDidLoad() { + let fraudData = CardVerifyFraudData(acceptedImageConfigs: acceptedImageConfigs) + + if debugRetainCompletionLoopImages { + fraudData.debugRetainImages = true + } + + scanEventsDelegate = fraudData + + super.viewDidLoad() + } + + override func createOcrMainLoop() -> OcrMainLoop? { + var uxAndOcrMainLoop = UxAndOcrMainLoop( + stateMachine: CardVerifyStateMachine( + strictModeFramesCount: configuration.strictModeFrames + ) + ) + + if scanPerformancePriority == .accurate { + uxAndOcrMainLoop = UxAndOcrMainLoop( + stateMachine: CardVerifyAccurateStateMachine( + requiredLastFour: nil, + requiredBin: nil, + maxNameExpiryDurationSeconds: maxErrorCorrectionDuration, + strictModeFramesCount: configuration.strictModeFrames + ) + ) + } + + return uxAndOcrMainLoop + } + // MARK: - Set Up Manual Card Entry Button + override func setupUiComponents() { + if let closeButton = VerifyCardAddViewController.closeButton { + self.closeButton = closeButton + } + + if let torchButton = VerifyCardAddViewController.torchButton { + self.torchButton = torchButton + } + + super.setupUiComponents() + self.view.addSubview(VerifyCardAddViewController.manualCardEntryButton) + VerifyCardAddViewController.manualCardEntryButton.translatesAutoresizingMaskIntoConstraints = + false + setUpManualCardEntryButtonUI() + } + + override func setupConstraints() { + super.setupConstraints() + setUpManualCardEntryButtonConstraints() + } + + func setUpManualCardEntryButtonUI() { + VerifyCardAddViewController.manualCardEntryButton.isHidden = !enableManualEntry + + let text = VerifyCardAddViewController.manualCardEntryText + let attributedString = NSMutableAttributedString(string: text) + attributedString.addAttribute( + NSAttributedString.Key.underlineColor, + value: UIColor.white, + range: NSRange(location: 0, length: text.count) + ) + attributedString.addAttribute( + NSAttributedString.Key.foregroundColor, + value: UIColor.white, + range: NSRange(location: 0, length: text.count) + ) + attributedString.addAttribute( + NSAttributedString.Key.underlineStyle, + value: NSUnderlineStyle.single.rawValue, + range: NSRange(location: 0, length: text.count) + ) + let font = + VerifyCardAddViewController.manualCardEntryButton.titleLabel?.font.withSize(20) + ?? UIFont.systemFont(ofSize: 20.0) + attributedString.addAttribute( + NSAttributedString.Key.font, + value: font, + range: NSRange(location: 0, length: text.count) + ) + + VerifyCardAddViewController.manualCardEntryButton.setAttributedTitle( + attributedString, + for: .normal + ) + VerifyCardAddViewController.manualCardEntryButton.titleLabel?.textColor = .white + VerifyCardAddViewController.manualCardEntryButton.addTarget( + self, + action: #selector(manualCardEntryButtonPress), + for: .touchUpInside + ) + } + + func setUpManualCardEntryButtonConstraints() { + VerifyCardAddViewController.manualCardEntryButton.centerXAnchor.constraint( + equalTo: enableCameraPermissionsButton.centerXAnchor + ).isActive = true + VerifyCardAddViewController.manualCardEntryButton.centerYAnchor.constraint( + equalTo: enableCameraPermissionsButton.centerYAnchor + ).isActive = true + } + + // MARK: - Override some ScanBase functions + override func onScannedCard( + number: String, + expiryYear: String?, + expiryMonth: String?, + scannedImage: UIImage? + ) { + let card = CreditCard(number: number) + card.expiryYear = expiryYear + card.expiryMonth = expiryMonth + card.name = predictedName + + showFullScreenActivityIndicator() + guard let fraudData = self.scanEventsDelegate.flatMap({ $0 as? CardVerifyFraudData }) else { + self.verifyDelegate?.verifyViewControllerDidFail( + self, + with: CardScanSheetError.unknown(debugDescription: "CardVerifyFraudData not found") + ) + return + } + + fraudData.result { verificationFramesData in + /// Frames have been processed and the main loop completed, log accordingly + self.mainLoopDurationTask.trackResult(.success) + ScanAnalyticsManager.shared.trackMainLoopDuration(task: self.mainLoopDurationTask) + + self.verifyDelegate?.verifyViewControllerDidFinish( + self, + verificationFramesData: verificationFramesData, + scannedCard: ScannedCard(scannedCard: card) + ) + } + } + + override func onCameraPermissionDenied(showedPrompt: Bool) { + super.onCameraPermissionDenied(showedPrompt: showedPrompt) + + if enableManualEntry { + enableCameraPermissionsButton.isHidden = true + } + } + + // MARK: - UI event handlers and other navigation functions + override func cancelButtonPress() { + /// User canceled the scan before the main loop completed, log with a failure + mainLoopDurationTask.trackResult(.failure) + ScanAnalyticsManager.shared.trackMainLoopDuration(task: mainLoopDurationTask) + ScanAnalyticsManager.shared.logScanActivityTask(event: .userCanceled) + verifyDelegate?.verifyViewControllerDidCancel(self, with: .back) + } + + @objc func manualCardEntryButtonPress() { + /// User canceled the exited before the main loop completed, log with a failure + mainLoopDurationTask.trackResult(.failure) + ScanAnalyticsManager.shared.trackMainLoopDuration(task: mainLoopDurationTask) + ScanAnalyticsManager.shared.logScanActivityTask(event: .userMissingCard) + verifyDelegate?.verifyViewControllerDidCancel(self, with: .userCannotScan) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/VerifyCardViewController.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/VerifyCardViewController.swift new file mode 100644 index 00000000..97a1bfe5 --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/VerifyCardViewController.swift @@ -0,0 +1,310 @@ +@_spi(STP) import StripeCore +/// Our high-level goal with this class is to implement the logic needed for our card verify check while +/// adding minimal UI effects, and for any UI effects that we do add make them easily customized +/// via overriding functions. +/// +/// This class builds off of the `SimpleScanViewController` for the UI, see that class or +/// our [docs ](https://docs.getbouncer.com/card-scan/ios-integration-guide/ios-customization-guide) +/// for more information on how to customize the look and feel of this view controller. +import UIKit + +/// TODO(jaimepark): Consolidate both add flow and card-set flow into a single view controller. +/// This means replacing `VerifyCardViewControllerDelegate` and `VerifyCardAddViewControllerDelegate` with this one. +protocol VerifyViewControllerDelegate: AnyObject { + /// TODO(jaimepark): Change view controller type after consolidation + + /// The scanning portion of the flow finished. Finish off verification flow by submitting verification frames data. + func verifyViewControllerDidFinish( + _ viewController: UIViewController, + verificationFramesData: [VerificationFramesData], + scannedCard: ScannedCard + ) + + /// User canceled the verification flow + func verifyViewControllerDidCancel( + _ viewController: UIViewController, + with reason: CancellationReason + ) + + /// The verification flow failed + func verifyViewControllerDidFail( + _ viewController: UIViewController, + with error: Error + ) +} + +class VerifyCardViewController: SimpleScanViewController { + typealias StrictModeFramesCount = CardImageVerificationSheet.StrictModeFrameCount + + // our UI components + var cardDescriptionText = UILabel() + static var closeButton: UIButton? + static var torchButton: UIButton? + + // configuration + private let expectedCardLast4: String + private let expectedCardIssuer: String? + private let acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs? + private let configuration: CardImageVerificationSheet.Configuration + + // TODO(jaimepark): Put card brands from `Stripe` into `StripeCore` + var cardNetwork: CardNetwork? + + // String + static var wrongCardString = String.Localized.card_doesnt_match + + // for debugging + var debugRetainCompletionLoopImages = false + + weak var verifyDelegate: VerifyViewControllerDelegate? + + private var lastWrongCard: Date? + private let mainLoopDurationTask: TrackableTask + + var userId: String? + + init( + acceptedImageConfigs: CardImageVerificationAcceptedImageConfigs?, + configuration: CardImageVerificationSheet.Configuration, + expectedCardLast4: String, + expectedCardIssuer: String? + ) { + self.acceptedImageConfigs = acceptedImageConfigs + self.configuration = configuration + self.expectedCardLast4 = expectedCardLast4 + self.expectedCardIssuer = expectedCardIssuer + self.mainLoopDurationTask = TrackableTask() + + super.init() + } + + required init?(coder: NSCoder) { fatalError("not supported") } + + override func viewDidLoad() { + // setup our ML so that we use the UX model + OCR in the main loop + let fraudData = CardVerifyFraudData( + last4: expectedCardLast4, + acceptedImageConfigs: acceptedImageConfigs + ) + if debugRetainCompletionLoopImages { + fraudData.debugRetainImages = true + } + + scanEventsDelegate = fraudData + + super.viewDidLoad() + } + + override func createOcrMainLoop() -> OcrMainLoop? { + var uxAndOcrMainLoop = UxAndOcrMainLoop( + stateMachine: CardVerifyStateMachine( + requiredLastFour: expectedCardLast4, + requiredBin: nil, + strictModeFramesCount: configuration.strictModeFrames + ) + ) + + if scanPerformancePriority == .accurate { + uxAndOcrMainLoop = UxAndOcrMainLoop( + stateMachine: CardVerifyAccurateStateMachine( + requiredLastFour: expectedCardLast4, + requiredBin: nil, + maxNameExpiryDurationSeconds: maxErrorCorrectionDuration, + strictModeFramesCount: configuration.strictModeFrames + ) + ) + } + + return uxAndOcrMainLoop + } + + // MARK: - UI effects and customizations for the VerifyCard flow + + override func setupUiComponents() { + if let closeButton = VerifyCardViewController.closeButton { + self.closeButton = closeButton + } + + if let torchButton = VerifyCardViewController.torchButton { + self.torchButton = torchButton + } + + super.setupUiComponents() + + let children: [UIView] = [cardDescriptionText] + for child in children { + self.view.addSubview(child) + } + + setupCardDescriptionTextUI() + } + + func setupCardDescriptionTextUI() { + // TODO(jaimepark): Update text ui with viewmodel + // let network = bin.map { CreditCardUtils.determineCardNetwork(cardNumber: $0) } + // var text = "\(network.map { $0.toString() } ?? cardNetwork?.toString() ?? "")" + let text = "\(expectedCardIssuer ?? "") •••• \(expectedCardLast4)" + + cardDescriptionText.textColor = .white + cardDescriptionText.textAlignment = .center + cardDescriptionText.numberOfLines = 2 + cardDescriptionText.text = text + cardDescriptionText.isHidden = false + + } + + // MARK: - Autolayout constraints + override func setupConstraints() { + let children: [UIView] = [cardDescriptionText] + for child in children { + child.translatesAutoresizingMaskIntoConstraints = false + } + + super.setupConstraints() + + setupCardDescriptionTextConstraints() + } + + func setupCardDescriptionTextConstraints() { + cardDescriptionText.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32) + .isActive = true + cardDescriptionText.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32) + .isActive = true + cardDescriptionText.bottomAnchor.constraint(equalTo: roiView.topAnchor, constant: -16) + .isActive = true + } + + override func setupDescriptionTextConstraints() { + descriptionText.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32).isActive = + true + descriptionText.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32) + .isActive = true + descriptionText.bottomAnchor.constraint( + equalTo: cardDescriptionText.topAnchor, + constant: -16 + ).isActive = true + } + + // MARK: - Override some ScanBase functions + override func onScannedCard( + number: String, + expiryYear: String?, + expiryMonth: String?, + scannedImage: UIImage? + ) { + + let card = CreditCard(number: number) + card.expiryMonth = expiryMonth + card.expiryYear = expiryYear + card.name = predictedName + card.image = scannedImage + + showFullScreenActivityIndicator() + + guard let fraudData = self.scanEventsDelegate.flatMap({ $0 as? CardVerifyFraudData }) else { + self.verifyDelegate?.verifyViewControllerDidFail( + self, + with: CardScanSheetError.unknown(debugDescription: "CardVerifyFraudData not found") + ) + return + } + + fraudData.result { verificationFramesData in + /// Frames have been processed and the main loop completed, log accordingly + self.mainLoopDurationTask.trackResult(.success) + ScanAnalyticsManager.shared.trackMainLoopDuration(task: self.mainLoopDurationTask) + + self.verifyDelegate?.verifyViewControllerDidFinish( + self, + verificationFramesData: verificationFramesData, + scannedCard: ScannedCard(scannedCard: card) + ) + } + } + + override func showScannedCardDetails(prediction: CreditCardOcrPrediction) {} + + override func showCardNumber(_ number: String, expiry: String?) { + DispatchQueue.main.async { + self.numberText.text = CreditCardUtils.format(number: number) + if self.numberText.isHidden { + self.numberText.fadeIn() + } + + if let expiry = expiry { + self.expiryText.text = expiry + if self.expiryText.isHidden { + self.expiryText.fadeIn() + } + } + + if let predictedName = self.predictedName { + self.nameText.text = predictedName + if self.nameText.isHidden { + self.nameText.fadeIn() + } + } + + if !self.descriptionText.isHidden { + self.descriptionText.fadeOut() + } + } + } + + override func showWrongCard(number: String?, expiry: String?, name: String?) { + DispatchQueue.main.async { + self.descriptionText.text = VerifyCardViewController.wrongCardString + + if !self.numberText.isHidden { + self.numberText.fadeOut() + } + + if !self.expiryText.isHidden { + self.expiryText.fadeOut() + } + + if !self.nameText.isHidden { + self.nameText.fadeOut() + } + + self.roiView.layer.borderColor = UIColor.red.cgColor + self.lastWrongCard = Date() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self = self else { return } + guard let lastWrongCard = self.lastWrongCard else { return } + if -lastWrongCard.timeIntervalSinceNow >= 0.5 { + self.showNoCard() + } + } + } + } + + override func showNoCard() { + DispatchQueue.main.async { + self.roiView.layer.borderColor = UIColor.white.cgColor + + self.descriptionText.text = SimpleScanViewController.descriptionString + + if !self.numberText.isHidden { + self.numberText.fadeOut() + } + + if !self.expiryText.isHidden { + self.expiryText.fadeOut() + } + + if !self.nameText.isHidden { + self.nameText.fadeOut() + } + } + } + + // MARK: - UI event handlers + override func cancelButtonPress() { + /// User canceled the scan before the main loop completed, log with a failure + mainLoopDurationTask.trackResult(.failure) + ScanAnalyticsManager.shared.trackMainLoopDuration(task: mainLoopDurationTask) + ScanAnalyticsManager.shared.logScanActivityTask(event: .userCanceled) + verifyDelegate?.verifyViewControllerDidCancel(self, with: .back) + } +} diff --git a/StripeCardScan/StripeCardScan/Source/CardVerify/ZoomedInCGImage.swift b/StripeCardScan/StripeCardScan/Source/CardVerify/ZoomedInCGImage.swift new file mode 100644 index 00000000..a0d5756c --- /dev/null +++ b/StripeCardScan/StripeCardScan/Source/CardVerify/ZoomedInCGImage.swift @@ -0,0 +1,161 @@ +// +// ZoomedInCGImage.swift +// CardScan +// +// Created by Jaime Park on 6/19/20. +// + +import UIKit + +class ZoomedInCGImage { + private let image: CGImage + private let imageWidth: CGFloat + private let imageHeight: CGFloat + private let imageMidHeight: CGFloat + private let imageMidWidth: CGFloat + private let imageCenterMinX: CGFloat + private let imageCenterMaxX: CGFloat + private let imageCenterMinY: CGFloat + private let imageCenterMaxY: CGFloat + + private let finalCropWidth: CGFloat = 448.0 + private let finalCropHeight: CGFloat = 448.0 + private let cropQuarterWidth: CGFloat = 112.0 // finalCropWidth / 4 + private let cropQuarterHeight: CGFloat = 112.0 // finalCropHeight / 4 + + init( + image: CGImage + ) { + self.image = image + self.imageWidth = CGFloat(image.width) + self.imageHeight = CGFloat(image.height) + self.imageMidHeight = imageHeight / 2.0 + self.imageMidWidth = imageWidth / 2.0 + self.imageCenterMinX = imageMidWidth - cropQuarterWidth + self.imageCenterMaxX = imageMidWidth + cropQuarterWidth + self.imageCenterMinY = imageMidHeight - cropQuarterHeight + self.imageCenterMaxY = imageMidHeight + cropQuarterHeight + } + + // Create a zoomed-in image by resizing image pieces in a 3x3 grid, row by row with createResizeLayer + func zoomedInImage() -> UIImage? { + guard + let topLayer = createResizeLayer( + yMin: 0.0, + imageHeight: imageCenterMinY, + cropHeight: cropQuarterHeight + ) + else { return nil } + guard + let midLayer = createResizeLayer( + yMin: imageCenterMinY, + imageHeight: cropQuarterHeight * 2, + cropHeight: cropQuarterHeight * 2 + ) + else { return nil } + guard + let bottomLayer = createResizeLayer( + yMin: imageCenterMaxY, + imageHeight: imageCenterMinY, + cropHeight: cropQuarterHeight + ) + else { return nil } + + let zoomedImageSize = CGSize(width: finalCropWidth, height: finalCropHeight) + UIGraphicsBeginImageContextWithOptions(zoomedImageSize, true, 1.0) + + topLayer.draw(in: CGRect(x: 0.0, y: 0.0, width: finalCropWidth, height: cropQuarterHeight)) + midLayer.draw( + in: CGRect( + x: 0.0, + y: cropQuarterHeight, + width: finalCropWidth, + height: cropQuarterHeight * 2 + ) + ) + bottomLayer.draw( + in: CGRect( + x: 0.0, + y: cropQuarterHeight * 3, + width: finalCropWidth, + height: cropQuarterHeight + ) + ) + + let zoomedImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return zoomedImage + } + + // Resize layer starting at image height coordinate (yMin) with the height within the image (imageHeight) resizing to the input cropping height (cropHeight) + private func createResizeLayer( + yMin: CGFloat, + imageHeight: CGFloat, + cropHeight: CGFloat + ) -> UIImage? { + let leftCropRect = CGRect(x: 0.0, y: yMin, width: imageCenterMinX, height: imageHeight) + let midCropRect = CGRect( + x: imageCenterMinX, + y: yMin, + width: cropQuarterWidth * 2, + height: imageHeight + ) + let rightCropRect = CGRect( + x: imageCenterMaxX, + y: yMin, + width: imageCenterMinX, + height: imageHeight + ) + + guard let leftCropImage = self.image.cropping(to: leftCropRect), + let leftResizedImage = resize( + image: leftCropImage, + targetSize: CGSize(width: cropQuarterWidth, height: cropHeight) + ) + else { return nil } + + guard let midCropImage = self.image.cropping(to: midCropRect), + let midResizedImage = resize( + image: midCropImage, + targetSize: CGSize(width: cropQuarterWidth * 2, height: cropHeight) + ) + else { return nil } + + guard let rightCropImage = self.image.cropping(to: rightCropRect), + let rightResizedImage = resize( + image: rightCropImage, + targetSize: CGSize(width: cropQuarterWidth, height: cropHeight) + ) + else { return nil } + + let layerSize = CGSize(width: finalCropWidth, height: cropHeight) + UIGraphicsBeginImageContextWithOptions(layerSize, false, 1.0) + + leftResizedImage.draw( + in: CGRect(x: 0.0, y: 0.0, width: cropQuarterWidth, height: cropHeight) + ) + midResizedImage.draw( + in: CGRect(x: cropQuarterWidth, y: 0.0, width: cropQuarterWidth * 2, height: cropHeight) + ) + rightResizedImage.draw( + in: CGRect(x: cropQuarterWidth * 3, y: 0.0, width: cropQuarterWidth, height: cropHeight) + ) + + let layerImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return layerImage + } + + private func resize(image: CGImage, targetSize: CGSize) -> UIImage? { + let image = UIImage(cgImage: image) + let rect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height) + + UIGraphicsBeginImageContextWithOptions(targetSize, true, 1.0) + image.draw(in: rect) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } +} diff --git a/StripeCardScan/StripeCardScan/StripeCardScan.h b/StripeCardScan/StripeCardScan/StripeCardScan.h new file mode 100644 index 00000000..ab708ca0 --- /dev/null +++ b/StripeCardScan/StripeCardScan/StripeCardScan.h @@ -0,0 +1,18 @@ +// +// StripeCardScan.h +// StripeCardScan +// +// Created by Sam King on 11/8/21. +// + +#import + +//! Project version number for StripeCardScan. +FOUNDATION_EXPORT double StripeCardScanVersionNumber; + +//! Project version string for StripeCardScan. +FOUNDATION_EXPORT const unsigned char StripeCardScanVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeCardScan/StripeCardScanTests/Helpers/CardScanMockData.swift b/StripeCardScan/StripeCardScanTests/Helpers/CardScanMockData.swift new file mode 100644 index 00000000..29a818e2 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Helpers/CardScanMockData.swift @@ -0,0 +1,37 @@ +// +// CardScanMockData.swift +// StripeCardScanTests +// +// Created by Jaime Park on 11/16/21. +// + +import Foundation +import StripeCoreTestUtils + +@testable import StripeCardScan + +// note: This class is to find the test bundle +private class ClassForBundle {} + +// MARK: Responses +enum CardImageVerificationDetailsResponseMock: String, MockData { + var bundle: Bundle { return Bundle(for: ClassForBundle.self) } + + typealias ResponseType = CardImageVerificationDetailsResponse + + case cardImageVerification_cardSet_200 = "CardImageVerification_CardSet_200" + case cardImageVerification_cardAdd_200 = "CardImageVerification_CardAdd_200" +} + +struct CIVIntentMockData { + static let id = "civ_1234" + static let clientSecret = "civ_client_secret_1234" + + static var intent: CardImageVerificationIntent = { + let intent = CardImageVerificationIntent( + id: id, + clientSecret: clientSecret + ) + return intent + }() +} diff --git a/StripeCardScan/StripeCardScanTests/Helpers/Data+Sha256.swift b/StripeCardScan/StripeCardScanTests/Helpers/Data+Sha256.swift new file mode 100644 index 00000000..a9b4ce8d --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Helpers/Data+Sha256.swift @@ -0,0 +1,26 @@ +// +// Data+Sha256.swift +// StripeCardScanTests +// +// Created by Scott Grant on 9/21/22. +// + +import CommonCrypto +import Foundation + +// Adapted from https://stackoverflow.com/questions/25388747/sha256-in-swift +extension Data { + + /// A String containing the Sha256 hash of this Data's contents. + var sha256: String { + return digest().base64EncodedString() + } + + private func digest() -> Data { + let digestLength = Int(CC_SHA256_DIGEST_LENGTH) + var hash = [UInt8](repeating: 0, count: digestLength) + CC_SHA256([UInt8](self), UInt32(self.count), &hash) + + return Data(bytes: hash, count: digestLength) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Helpers/ImageHelpers.swift b/StripeCardScan/StripeCardScanTests/Helpers/ImageHelpers.swift new file mode 100644 index 00000000..76e6ae42 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Helpers/ImageHelpers.swift @@ -0,0 +1,40 @@ +// +// ImageHelpers.swift +// StripeCardScanTests +// +// Created by Sam King on 11/29/21. +// + +import UIKit + +struct ImageHelpers { + static func getTestImageAndRoiRectangle() -> (UIImage, CGRect) { + let bundle = Bundle(for: UxModelTests.self) + let path = bundle.url(forResource: "synthetic_test_image", withExtension: "jpg")! + let image = UIImage(contentsOfFile: path.path)! + let cardWidth = CGFloat(977.0) + let cardHeight = CGFloat(616.0) + let imageWidth = image.size.width + let imageHeight = image.size.height + + let roiRectangle = CGRect( + x: (imageWidth - cardWidth) * 0.5, + y: (imageHeight - cardHeight) * 0.5, + width: cardWidth, + height: cardHeight + ) + + return (image, roiRectangle) + } + + static func createBlankCGImage() -> CGImage { + let rect = CGRect(origin: .zero, size: CGSize(width: 1, height: 1)) + UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) + UIColor.black.setFill() + UIRectFill(rect) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image!.cgImage! + } +} diff --git a/StripeCardScan/StripeCardScanTests/Helpers/ScannedCardDetails.swift b/StripeCardScan/StripeCardScanTests/Helpers/ScannedCardDetails.swift new file mode 100644 index 00000000..f47362a5 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Helpers/ScannedCardDetails.swift @@ -0,0 +1,32 @@ +// +// ScannedCardDetails.swift +// StripeCardScanTests +// +// Created by Jaime Park on 3/10/22. +// + +import CoreGraphics +import Foundation + +@testable import StripeCardScan + +struct ScannedCardDetails { + let number: String + let iin: String + let last4: String + let scannedImageData: ScannedCardImageData + + init( + number: String + ) { + self.number = number + self.iin = String(number.prefix(6)) + self.last4 = String(number.suffix(4)) + + /// Create discernable `ScannedCardImageData` by setting the preview layer rect to the iin & last4 + self.scannedImageData = ScannedCardImageData( + previewLayerImage: ImageHelpers.createBlankCGImage(), + previewLayerViewfinderRect: CGRect(x: 0, y: 0, width: Int(iin)!, height: Int(last4)!) + ) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Helpers/String+Sha256.swift b/StripeCardScan/StripeCardScanTests/Helpers/String+Sha256.swift new file mode 100644 index 00000000..f9f0e036 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Helpers/String+Sha256.swift @@ -0,0 +1,19 @@ +// +// String+Sha256.swift +// StripeCardScanTests +// +// Created by Scott Grant on 9/21/22. +// + +import Foundation + +extension String { + /// A String containing the Sha256 hash of this String's contents. + var sha256: String? { + guard let stringData = self.data(using: .utf8) else { + return nil + } + + return stringData.sha256 + } +} diff --git a/StripeCardScan/StripeCardScanTests/Info.plist b/StripeCardScan/StripeCardScanTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripeCardScan/StripeCardScanTests/Mock Data/JSON/CardImageVerification_CardAdd_200.json b/StripeCardScan/StripeCardScanTests/Mock Data/JSON/CardImageVerification_CardAdd_200.json new file mode 100644 index 00000000..adbeec02 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Mock Data/JSON/CardImageVerification_CardAdd_200.json @@ -0,0 +1,31 @@ +{ + "accepted_image_configs": { + "default_settings": { + "compression_ratio": 0.6, + "image_count": 5, + "image_size": [ + 1080, + 1920 + ] + }, + "format_settings": { + "heic": { + "compression_ratio": 0.5 + }, + "jpeg": {}, + "webp": { + "image_count": 3 + } + }, + "preferred_formats": [ + "heic", + "webp", + "jpeg" + ] + }, + "expected_card": { + "issuer": null, + "last4": null + }, + "use_api_version": 2 +} diff --git a/StripeCardScan/StripeCardScanTests/Mock Data/JSON/CardImageVerification_CardSet_200.json b/StripeCardScan/StripeCardScanTests/Mock Data/JSON/CardImageVerification_CardSet_200.json new file mode 100644 index 00000000..5798d365 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Mock Data/JSON/CardImageVerification_CardSet_200.json @@ -0,0 +1,32 @@ +{ + "accepted_image_configs": { + "default_settings": { + "compression_ratio": 0.8, + "image_size": [ + 1080, + 1920 + ] + }, + "format_settings": { + "heic": { + "compression_ratio": 0.5 + }, + "webp": { + "compression_ratio": 0.7, + "image_size": [ + 2160, + 1920 + ] + } + }, + "preferred_formats": [ + "heic", + "webp", + "jpeg" + ] + }, + "expected_card": { + "last4": "4242", + "issuer": "Visa" + } +} diff --git a/StripeCardScan/StripeCardScanTests/Resources/synthetic_test_image.jpg b/StripeCardScan/StripeCardScanTests/Resources/synthetic_test_image.jpg new file mode 100644 index 00000000..c63e5e56 Binary files /dev/null and b/StripeCardScan/StripeCardScanTests/Resources/synthetic_test_image.jpg differ diff --git a/StripeCardScan/StripeCardScanTests/Unit/API Bindings/STPAPIClient+CardImageVerificationTest.swift b/StripeCardScan/StripeCardScanTests/Unit/API Bindings/STPAPIClient+CardImageVerificationTest.swift new file mode 100644 index 00000000..3a0d0c3a --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/API Bindings/STPAPIClient+CardImageVerificationTest.swift @@ -0,0 +1,383 @@ +// +// STPAPIClient+CardImageVerificationTest.swift +// StripeCardScanTests +// +// Created by Jaime Park on 11/16/21. +// + +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import XCTest + +@testable import StripeCardScan +@testable@_spi(STP) import StripeCore + +class STPAPIClient_CardImageVerificationTest: APIStubbedTestCase { + /// The following test is mocking a flow where the merchant has set a card during the CIV intent creation. + /// It will check the following: + /// 1. The request URL has been constructed properly: /v1/card_image_verifications/:id/initialize_client + /// 2. The request body contains the client secret + /// 3. The response from request has details of card set during the CIV intent creation + func testFetchCardImageVerificationDetails_CardSet() throws { + let mockResponse = + try CardImageVerificationDetailsResponseMock.cardImageVerification_cardSet_200.data() + + /// Stub the request to get details of CIV intent + stub { request in + guard let httpBody = request.ohhttpStubs_httpBody else { + XCTFail("Expected an httpBody but found none") + return false + } + + XCTAssertNotNil(request.url) + XCTAssertEqual( + request.url?.absoluteString.contains( + "v1/card_image_verifications/\(CIVIntentMockData.id)/initialize_client" + ), + true + ) + XCTAssertEqual( + String(data: httpBody, encoding: .utf8), + "client_secret=\(CIVIntentMockData.clientSecret)" + ) + XCTAssertEqual(request.httpMethod, "POST") + + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + + let exp = expectation(description: "Request completed") + + /// Make request to get card details + let apiClient = stubbedAPIClient() + let promise = apiClient.fetchCardImageVerificationDetails( + cardImageVerificationSecret: CIVIntentMockData.clientSecret, + cardImageVerificationId: CIVIntentMockData.id + ) + + promise.observe { result in + switch result { + case .success(let response): + XCTAssertEqual(response.expectedCard.last4, "4242") + XCTAssertEqual(response.expectedCard.issuer, "Visa") + + XCTAssertNotNil(response.acceptedImageConfigs) + + XCTAssertEqual( + response.acceptedImageConfigs?.preferredFormats, + [.heic, .webp, .jpeg] + ) + + XCTAssertEqual( + response.acceptedImageConfigs?.defaultSettings?.compressionRatio, + 0.8 + ) + XCTAssertEqual( + response.acceptedImageConfigs?.defaultSettings?.imageSize, + [1080.0, 1920.0] + ) + + if let formatSettings = response.acceptedImageConfigs?.formatSettings { + let webpSettings = formatSettings[.webp] + XCTAssertEqual(webpSettings??.compressionRatio, 0.7) + XCTAssertEqual(webpSettings??.imageSize, [2160.0, 1920.0]) + } else { + XCTFail("Format Settings failed to parse") + } + case .failure(let error): + XCTFail("Request returned error \(error)") + } + exp.fulfill() + } + + wait(for: [exp], timeout: 1) + } + + /// The following test is mocking a flow where the merchant has not set a card during the CIV intent creation. + /// It will check the following: + /// 1. The request URL has been constructed properly: /v1/card_image_verifications/:id/initialize_client + /// 2. The request body contains the client secret + /// 3. The response from request is empty + func testFetchCardImageVerificationDetails_CardAdd() throws { + let mockResponse = + try CardImageVerificationDetailsResponseMock.cardImageVerification_cardAdd_200.data() + + /// Stub the request to get details of CIV intent + stub { request in + guard let httpBody = request.ohhttpStubs_httpBody else { + XCTFail("Expected an httpBody but found none") + return false + } + + XCTAssertNotNil(request.url) + XCTAssertEqual(request.httpMethod, "POST") + + if let url = request.url { + XCTAssertTrue( + url.path + == "/v1/card_image_verifications/\(CIVIntentMockData.id)/initialize_client" + || url.path + == "/v1/card_image_verifications/\(CIVIntentMockData.id)/scan_stats" + ) + + let bodyString = String(data: httpBody, encoding: .utf8)! + XCTAssertTrue( + bodyString.hasPrefix("client_secret=\(CIVIntentMockData.clientSecret)") + ) + } + + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + + let exp = expectation(description: "Request completed") + + /// Make request to get card details + let apiClient = stubbedAPIClient() + let promise = apiClient.fetchCardImageVerificationDetails( + cardImageVerificationSecret: CIVIntentMockData.clientSecret, + cardImageVerificationId: CIVIntentMockData.id + ) + + promise.observe { result in + switch result { + case .success(let response): + XCTAssertNil(response.expectedCard.last4) + XCTAssertNil(response.expectedCard.issuer) + case .failure(let error): + XCTFail("Request returned error \(error)") + } + exp.fulfill() + } + + wait(for: [exp], timeout: 1) + } + + /// The following test is mocking a flow where the collected verification frames are submitted to the server + /// It will check the following: + /// 1. The request URL has been constructed properly: /v1/card_image_verifications/:id/verify_frames + /// 2. The request body contains `client_secret` and `verification_frames_data` + /// 3. The response from request is empty + func testSubmitVerificationFrames() throws { + let base64EncodedVerificationFrames = "base64_encoded_list_of_verify_frames" + let mockResponse = "{}".data(using: .utf8)! + let mockParameter = VerifyFrames( + clientSecret: CIVIntentMockData.clientSecret, + verificationFramesData: base64EncodedVerificationFrames + ) + + /// Stub the request to submit verify frames + stub { request in + guard let httpBody = request.ohhttpStubs_httpBody else { + XCTFail("Expected an httpBody but found none") + return false + } + + XCTAssertNotNil(request.url) + XCTAssertEqual( + request.url?.absoluteString.contains( + "v1/card_image_verifications/\(CIVIntentMockData.id)/verify_frames" + ), + true + ) + XCTAssertEqual( + String(data: httpBody, encoding: .utf8), + "client_secret=\(CIVIntentMockData.clientSecret)&verification_frames_data=\(base64EncodedVerificationFrames)" + ) + XCTAssertEqual(request.httpMethod, "POST") + + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + + let exp = expectation(description: "Request completed") + + /// Make request to submit verification frames + let apiClient = stubbedAPIClient() + let promise = apiClient.submitVerificationFrames( + cardImageVerificationId: CIVIntentMockData.id, + verifyFrames: mockParameter + ) + + promise.observe { result in + switch result { + /// The successful response is an empty struct + case .success: + XCTAssert(true, "A response has been returned") + case .failure(let error): + XCTFail("Request returned error \(error)") + } + exp.fulfill() + } + + wait(for: [exp], timeout: 1) + } + + /// The following test is mocking a flow where the collected verification frames are submitted to the server + /// It will check the following. This test is using the expanded version of the request `submitVerificationFrames`: + /// 1. The request URL has been constructed properly: /v1/card_image_verifications/:id/verify_frames + /// 2. The request body contains `client_secret` and `verification_frames_data` + /// 3. The response from request is empty + func testSubmitVerificationFrames_Expanded() throws { + let verificationFrameData = VerificationFramesData( + imageData: "image_data".data(using: .utf8)!, + viewfinderMargins: ViewFinderMargins(left: 0, upper: 0, right: 0, lower: 0) + ) + + let mockResponse = "{}".data(using: .utf8)! + + /// The list of verification frame datas are encoded with snake_case before converting to a `VerifyFrames` object + let jsonEncoder = JSONEncoder() + jsonEncoder.keyEncodingStrategy = .convertToSnakeCase + let jsonVerificationFramesData = try jsonEncoder.encode([verificationFrameData]) + + /// Turn the JSON data into a string + let verificationFramesDataString = + String(data: jsonVerificationFramesData, encoding: .utf8) ?? "" + + let urlEncodedString = URLEncoder.string(byURLEncoding: verificationFramesDataString) + + // Stub the request to submit verify frames + stub { request in + guard let httpBody = request.ohhttpStubs_httpBody else { + XCTFail("Expected an httpBody but found none") + return false + } + + XCTAssertNotNil(request.url) + XCTAssertEqual( + request.url?.absoluteString.contains( + "v1/card_image_verifications/\(CIVIntentMockData.id)/verify_frames" + ), + true + ) + XCTAssertEqual( + String(data: httpBody, encoding: .utf8), + "client_secret=\(CIVIntentMockData.clientSecret)&verification_frames_data=\(urlEncodedString)" + ) + XCTAssertEqual(request.httpMethod, "POST") + + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + + let exp = expectation(description: "Request completed") + + /// Make request to submit verification frames + let apiClient = stubbedAPIClient() + let promise = apiClient.submitVerificationFrames( + cardImageVerificationId: CIVIntentMockData.id, + cardImageVerificationSecret: CIVIntentMockData.clientSecret, + verificationFramesData: [verificationFrameData] + ) + + promise.observe { result in + switch result { + /// The successful response is an empty struct + case .success: + XCTAssert(true, "A response has been returned") + case .failure(let error): + XCTFail("Request returned error \(error)") + } + exp.fulfill() + } + + wait(for: [exp], timeout: 1) + } + + /// The following test is mocking a flow where the collected scan analytics are uploaded to the server + /// It will check the following + /// 1. The request URL has been constructed properly: /v1/card_image_verifications/:id/scan_stats + /// 2. The response from request is empty + func testUploadScanStats() throws { + let startDate = Date() + let mockResponse = "{}".data(using: .utf8)! + let payload: ScanAnalyticsPayload = .init( + configuration: .init(strictModeFrames: 0), + payloadInfo: .init( + imageCompressionType: "heic", + imageCompressionQuality: 0.8, + imagePayloadSize: 4000 + ), + scanStats: .init( + repeatingTasks: .init( + mainLoopImagesProcessed: .init(executions: 1) + ), + tasks: .init( + cameraPermissionTask: .init(event: .success, startTime: startDate), + completionLoopDuration: .init(event: .success, startTime: startDate), + imageCompressionDuration: .init(event: .success, startTime: startDate), + mainLoopDuration: .init(event: .success, startTime: startDate), + scanActivityTasks: [ + .init(event: .torchSupported, startTime: startDate), + .init(event: .torchSupported, startTime: startDate), + ], + torchSupportedTask: .init(event: .torchSupported, startTime: startDate) + ) + ) + ) + + /// Stub the request to upload scan stats + /// Check request body more closely in a different test + stub { request in + /// Check that the http body exists + guard let httpBody = request.ohhttpStubs_httpBody, + let httpBodyQueryString = String(data: httpBody, encoding: .utf8) + else { + XCTFail("Expected an httpBody but found none") + return false + } + + XCTAssertNotNil(request.url) + XCTAssertEqual( + request.url?.absoluteString.contains( + "v1/card_image_verifications/\(CIVIntentMockData.id)/scan_stats" + ), + true + ) + /// Just check the existence of the parent-level payload fields + /// In-depth form data checking will be done in separate unit test + XCTAssertTrue( + httpBodyQueryString.contains("client_secret=\(CIVIntentMockData.clientSecret)"), + "http body does not contain client secret" + ) + XCTAssertTrue( + httpBodyQueryString.contains("payload["), + "http body does any payload info" + ) + XCTAssertEqual(request.httpMethod, "POST") + + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + + let exp = expectation(description: "Request completed") + + /// Make request to upload scan stats + let apiClient = stubbedAPIClient() + let promise = apiClient.uploadScanStats( + cardImageVerificationId: CIVIntentMockData.id, + cardImageVerificationSecret: CIVIntentMockData.clientSecret, + scanAnalyticsPayload: payload + ) + + promise.observe { result in + switch result { + /// The successful response is an empty struct + case .success: + XCTAssert(true, "A response has been returned") + case .failure(let error): + XCTFail("Request returned error \(error)") + } + exp.fulfill() + } + + wait(for: [exp], timeout: 1) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/API Bindings/ScanStatsPayloadAPIBindingsTests.swift b/StripeCardScan/StripeCardScanTests/Unit/API Bindings/ScanStatsPayloadAPIBindingsTests.swift new file mode 100644 index 00000000..22a4123c --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/API Bindings/ScanStatsPayloadAPIBindingsTests.swift @@ -0,0 +1,357 @@ +// +// ScanStatsPayloadAPIBindingsTests.swift +// StripeCardScanTests +// +// Created by Jaime Park on 12/9/21. +// + +import XCTest + +@testable import StripeCardScan +@testable@_spi(STP) import StripeCore + +/// This test will check that the json and url encoding of the scan stats payload is structured as expected +class ScanStatsPayloadAPIBindingsTests: XCTestCase { + var startDate: Date! + var startDateMs: Int! + var nonRepeatingTasks: NonRepeatingTasks! + var repeatingTasks: RepeatingTasks! + var scanStatsTasks: ScanStatsTasks! + + override func setUp() { + let date = Date() + self.startDate = date + self.startDateMs = date.millisecondsSince1970 + + self.nonRepeatingTasks = .init( + cameraPermissionTask: .init( + result: ScanAnalyticsEvent.success.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ), + completionLoopDuration: .init( + result: ScanAnalyticsEvent.success.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ), + imageCompressionDuration: .init( + result: ScanAnalyticsEvent.success.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ), + mainLoopDuration: .init( + result: ScanAnalyticsEvent.success.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ), + scanActivityTasks: [ + .init( + result: ScanAnalyticsEvent.firstImageProcessed.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ), + .init( + result: ScanAnalyticsEvent.ocrPanObserved.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ), + ], + torchSupportedTask: .init( + result: ScanAnalyticsEvent.torchSupported.rawValue, + startedAtMs: startDateMs, + durationMs: -1 + ) + ) + self.repeatingTasks = .init( + mainLoopImagesProcessed: .init(executions: -1) + ) + self.scanStatsTasks = .init( + repeatingTasks: repeatingTasks, + tasks: nonRepeatingTasks + ) + } + + /// Check that scan stats tasks is encoded properly + func testScanStatsTasks() throws { + /// Check that scan stats tasks is encoded properly + let jsonDictionary = try scanStatsTasks.encodeJSONDictionary() + let tasksDictionary = jsonDictionary["tasks"] as! [String: Any] + let repeatingTaskDictionary = jsonDictionary["repeating_tasks"] as! [String: Any] + + XCTAssertEqual(tasksDictionary.count, 6) + XCTAssertEqual(repeatingTaskDictionary.count, 1) + } + + /// Check that non repeating tasks are encoded properly + func testNonRepeatingTasks() throws { + /// Check that the JSON dictionary is formed properly + let jsonDictionary = try nonRepeatingTasks.encodeJSONDictionary() + let jsonCameraPermissions = jsonDictionary["camera_permission"] as! [[String: Any]] + XCTAssertEqual(jsonCameraPermissions.count, 1) + XCTAssertEqual(jsonCameraPermissions[0]["result"] as! String, "success") + XCTAssertEqual(jsonCameraPermissions[0]["started_at_ms"] as? Int, startDateMs) + XCTAssertEqual(jsonCameraPermissions[0]["duration_ms"] as? Int, -1) + + let jsonTorchSupported = jsonDictionary["torch_supported"] as! [[String: Any]] + XCTAssertEqual(jsonTorchSupported.count, 1) + XCTAssertEqual(jsonTorchSupported[0]["result"] as! String, "supported") + XCTAssertEqual(jsonTorchSupported[0]["started_at_ms"] as? Int, startDateMs) + XCTAssertEqual(jsonTorchSupported[0]["duration_ms"] as? Int, -1) + + let jsonScanActivities = jsonDictionary["scan_activity"] as! [[String: Any]] + XCTAssertEqual(jsonScanActivities.count, 2) + XCTAssertEqual(jsonScanActivities[0]["result"] as! String, "first_image_processed") + XCTAssertEqual(jsonScanActivities[0]["started_at_ms"] as? Int, startDateMs) + XCTAssertEqual(jsonScanActivities[0]["duration_ms"] as? Int, -1) + XCTAssertEqual(jsonScanActivities[1]["result"] as! String, "ocr_pan_observed") + XCTAssertEqual(jsonScanActivities[1]["started_at_ms"] as? Int, startDateMs) + XCTAssertEqual(jsonScanActivities[1]["duration_ms"] as? Int, -1) + } + + /// Check that repeating tasks are encoded properly + func testRepeatingTasks() throws { + /// Check that the JSON dictionary is formed properly + let jsonDictionary = try repeatingTasks.encodeJSONDictionary() + let jsonMainLoop = jsonDictionary["main_loop_images_processed"] as! [String: Any] + XCTAssertEqual(jsonMainLoop["executions"] as? Int, -1) + } + + /// Check that the url encoded query string is structured properly + func testScanStatsPayloadQueryString() throws { + let scanStatsPayload: ScanStatsPayload = .init( + clientSecret: CIVIntentMockData.clientSecret, + payload: .init( + configuration: .init(strictModeFrames: 5), + payloadInfo: .init( + imageCompressionType: "heic", + imageCompressionQuality: 0.8, + imagePayloadSize: 4000 + ), + scanStats: scanStatsTasks + ) + ) + let jsonDictionary = try scanStatsPayload.encodeJSONDictionary() + /// Create query string + let queryString = URLEncoder.queryString(from: jsonDictionary) + + /// Check client secret + XCTAssertTrue( + queryString.contains("client_secret=civ_client_secret_1234"), + "client secret in query string is incorrect" + ) + /// Check that instance id exists (can't compare string since uuid is random) + XCTAssertTrue( + queryString.contains("payload[instance_id]="), + "instance id in query string dne" + ) + /// Check payload version + XCTAssertTrue( + queryString.contains("payload[payload_version]=2"), + "payload version in query string dne/is incorrect" + ) + /// Check that scan id exists (can't compare string since uuid is random) + XCTAssertTrue(queryString.contains("payload[scan_id]="), "scan id in query string dne") + /// Check that all the app info exists + XCTAssertTrue( + queryString.contains("payload[app][app_package_name]=xctest"), + "app: app package name in query string is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[app][is_debug_build]=true"), + "app: is debug build in query string is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[app][build]="), + "app: build in query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[app][sdk_version]=\(StripeAPIConfiguration.STPSDKVersion)" + ), + "app: sdk version in query string is incorrect" + ) + /// Check that all the configuration info exists + XCTAssertTrue( + queryString.contains("payload[configuration][strict_mode_frames]=5"), + "configuration: strict mode frames is incorrect" + ) + /// Check that all the payload info exists + XCTAssertTrue( + queryString.contains("payload[payload_info][image_compression_type]=heic"), + "payload info: image_compression_type is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[payload_info][image_compression_quality]=0.8"), + "payload info: image_compression_quality is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[payload_info][image_payload_size]=4000"), + "payload info: image_payload_size is incorrect" + ) + /// Check that all the device info exists + #if arch(x86_64) + XCTAssertTrue( + queryString.contains("payload[device][device_type]=x86_64"), + "device: device type in query string is incorrect" + ) + #elseif arch(arm64) + XCTAssertTrue( + queryString.contains("payload[device][device_type]=arm64"), + "device: device type in query string is incorrect" + ) + #endif + XCTAssertTrue( + queryString.contains("payload[device][device_id]=Redacted"), + "device: device id in query string dne" + ) + XCTAssertTrue( + queryString.contains("payload[device][os_version]="), + "device: os version in query string is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[device][platform]=iOS"), + "device: platform in query string is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[device][vendor_id]=Redacted"), + "device: vendor id in query string dne" + ) + /// Check that repeating tasks: main loop images processed exists + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][repeating_tasks][main_loop_images_processed][executions]=-1" + ), + "repeating tasks: main loop image in query string is incorrect" + ) + /// Check that all the camera permissions info exists + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][camera_permission][0][duration_ms]=-1" + ), + "Camera permission duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][camera_permission][0][result]=success" + ), + "Camera permission result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][camera_permission][0][started_at_ms]=\(startDateMs!)" + ), + "Camera permission start time query string is incorrect" + ) + /// Check that all the torch supported info exists + XCTAssertTrue( + queryString.contains("payload[scan_stats][tasks][torch_supported][0][duration_ms]=-1"), + "Torch supported duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][torch_supported][0][result]=supported" + ), + "Torch supported result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][torch_supported][0][started_at_ms]=\(startDateMs!)" + ), + "Torch supported start time query string is incorrect" + ) + + /// Check that all the main_loop_duration info exists + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][main_loop_duration][0][duration_ms]=-1" + ), + "main_loop_duration duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][main_loop_duration][0][result]=success" + ), + "main_loop_duration result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][main_loop_duration][0][started_at_ms]=\(startDateMs!)" + ), + "main_loop_duration start time query string is incorrect" + ) + + /// Check that all the image_compression_duration info exists + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][image_compression_duration][0][duration_ms]=-1" + ), + "image_compression_durationn duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][image_compression_duration][0][result]=success" + ), + "image_compression_duration result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][image_compression_duration][0][started_at_ms]=\(startDateMs!)" + ), + "image_compression_duration start time query string is incorrect" + ) + + /// Check that all the completion_loop_duration info exists + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][completion_loop_duration][0][duration_ms]=-1" + ), + "completion_loop_duration duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][completion_loop_duration][0][result]=success" + ), + "completion_loop_duration result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][completion_loop_duration][0][started_at_ms]=\(startDateMs!)" + ), + "completion_loop_duration start time query string is incorrect" + ) + + /// Check that all scan activities exists + XCTAssertTrue( + queryString.contains("payload[scan_stats][tasks][scan_activity][0][duration_ms]=-1"), + "Scan activity[0] duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][scan_activity][0][result]=first_image_processed" + ), + "Scan activity[0] result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][scan_activity][0][started_at_ms]=\(startDateMs!)" + ), + "Scan activity[0] start time query string is incorrect" + ) + XCTAssertTrue( + queryString.contains("payload[scan_stats][tasks][scan_activity][1][duration_ms]=-1"), + "Scan activity[1] duration query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][scan_activity][1][result]=ocr_pan_observed" + ), + "Scan activity[1] result query string is incorrect" + ) + XCTAssertTrue( + queryString.contains( + "payload[scan_stats][tasks][scan_activity][1][started_at_ms]=\(startDateMs!)" + ), + "Scan activity[1] start time query string is incorrect" + ) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/API Bindings/VerifyFramesAPIBindingsTests.swift b/StripeCardScan/StripeCardScanTests/Unit/API Bindings/VerifyFramesAPIBindingsTests.swift new file mode 100644 index 00000000..64df6671 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/API Bindings/VerifyFramesAPIBindingsTests.swift @@ -0,0 +1,71 @@ +// +// VerifyFramesAPIBindingsTests.swift +// StripeCardScanTests +// +// Created by Jaime Park on 11/24/21. +// + +import XCTest + +@testable import StripeCardScan +@testable@_spi(STP) import StripeCore + +/// These tests are used to see if the encodable object has been encoded as expected by checking the following: +/// 1. The keys are properly set (snake case) +/// 2. The values are properly set +class VerifyFramesAPIBindingsTests: XCTestCase { + // The expected structure: + // { + // client_secret: "secret", + // verification_frames_data: "verification_frames_data" + // } + func testVerifyFrames() throws { + let verifyFrames = VerifyFrames( + clientSecret: CIVIntentMockData.clientSecret, + verificationFramesData: "verification_frames_data" + ) + + /// encodeJSONDictionary used when forming the request body + let jsonDictionary = try verifyFrames.encodeJSONDictionary() + + XCTAssertEqual(jsonDictionary["client_secret"] as! String, CIVIntentMockData.clientSecret) + XCTAssertEqual( + jsonDictionary["verification_frames_data"] as! String, + "verification_frames_data" + ) + } + + // The expected structure: + // { + // image_data: "image_data", + // viewfinder_margins: { + // left: 0, + // upper: 0, + // right: 0, + // lower: 0 + // } + // } + func testVerificationFramesData() throws { + let testData = "image_data".data(using: .utf8)! + + let verificationFramesData = VerificationFramesData( + imageData: testData, + viewfinderMargins: ViewFinderMargins( + left: 0, + upper: 0, + right: 0, + lower: 0 + ) + ) + + /// encodeJSONDictionary used when forming the request body + let jsonDictionary = try verificationFramesData.encodeJSONDictionary() + let jsonDictionaryViewfinderMargins = jsonDictionary["viewfinder_margins"] as! [String: Any] + + XCTAssertEqual(jsonDictionary["image_data"] as! String, "aW1hZ2VfZGF0YQ==") + XCTAssertEqual(jsonDictionaryViewfinderMargins["left"] as! Int, 0) + XCTAssertEqual(jsonDictionaryViewfinderMargins["upper"] as! Int, 0) + XCTAssertEqual(jsonDictionaryViewfinderMargins["right"] as! Int, 0) + XCTAssertEqual(jsonDictionaryViewfinderMargins["lower"] as! Int, 0) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/CardImageVerificationControllerTests.swift b/StripeCardScan/StripeCardScanTests/Unit/CardImageVerificationControllerTests.swift new file mode 100644 index 00000000..104c6537 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/CardImageVerificationControllerTests.swift @@ -0,0 +1,170 @@ +// +// CardImageVerificationControllerTests.swift +// StripeCardScanTests +// +// Created by Jaime Park on 11/18/21. +// + +import OHHTTPStubs +import OHHTTPStubsSwift +import StripeCoreTestUtils +import UIKit +import XCTest + +@testable import StripeCardScan + +class CardImageVerificationControllerTests: APIStubbedTestCase { + private let expectedCard = CardImageVerificationExpectedCard(last4: "1234", issuer: nil) + private var result: CardImageVerificationSheetResult? + private var resultExp: XCTestExpectation! + private var verifyFramesRequestExp: XCTestExpectation! + private var scanStatsRequestExp: XCTestExpectation! + private var baseViewController: UIViewController! + private var verificationSheetController: CardImageVerificationController! + + private let mockVerificationFrameData = VerificationFramesData( + imageData: "image_data".data(using: .utf8)!, + viewfinderMargins: ViewFinderMargins(left: 0, upper: 0, right: 0, lower: 0) + ) + + override func setUp() { + super.setUp() + self.resultExp = XCTestExpectation(description: "CIV Sheet result has been stored") + self.verifyFramesRequestExp = XCTestExpectation( + description: "A verify frames request has been stubbed" + ) + self.scanStatsRequestExp = XCTestExpectation( + description: "A scan stats request has been stubbed" + ) + self.baseViewController = UIViewController() + + var configuration = CardImageVerificationSheet.Configuration() + configuration.apiClient = stubbedAPIClient() + + let verificationSheetController = CardImageVerificationController( + intent: CIVIntentMockData.intent, + configuration: configuration + ) + verificationSheetController.delegate = self + + self.verificationSheetController = verificationSheetController + } + + /// This test simulates the verification view controller closing on back button press + func testFlowCanceled_Back() { + stubUploadScanStats() + + /// Invoke a `VerifyCardAddViewController` being created by not passing an expected card + verificationSheetController.present(with: expectedCard, and: nil, from: baseViewController) + verificationSheetController.verifyViewControllerDidCancel( + baseViewController, + with: .back + ) + + guard case .canceled(reason: .back) = result else { + XCTFail("Expected .canceled(reason: .back)") + return + } + + wait(for: [resultExp, scanStatsRequestExp], timeout: 1) + } + + /// This test simulates the verification view controller closing by pressing the manual button + func testFlowCanceled_Close() { + stubUploadScanStats() + + /// Invoke a `VerifyCardAddViewController` being created by not passing an expected card + verificationSheetController.present(with: expectedCard, and: nil, from: baseViewController) + verificationSheetController.verifyViewControllerDidCancel( + baseViewController, + with: .closed + ) + + guard case .canceled(reason: .closed) = result else { + XCTFail("Expected .canceled(reason: .closed)") + return + } + + wait(for: [resultExp, scanStatsRequestExp], timeout: 1) + } + + /// This test simulates the verification view controller completing the scan flow + func testFlowCompleted() { + stubSubmitVerificationFrames() + stubUploadScanStats() + + /// Invoke a `VerifyCardAddViewController` being created by not passing an expected card + verificationSheetController.present(with: expectedCard, and: nil, from: baseViewController) + + /// Mock the event where the scanning is complete and the verification frames data is passed back to be submitted for completion + verificationSheetController.verifyViewControllerDidFinish( + baseViewController, + verificationFramesData: [mockVerificationFrameData], + scannedCard: ScannedCard(pan: "4242") + ) + + /// Wait for submitVerificationFrames request to be made and the result to return + wait(for: [resultExp, verifyFramesRequestExp, scanStatsRequestExp], timeout: 1) + + guard case .completed(scannedCard: ScannedCard(pan: "4242")) = result else { + XCTFail("Expected .completed(scannedCard: ScannedCard(pan: \"4242\")") + return + } + } +} + +extension CardImageVerificationControllerTests: CardImageVerificationControllerDelegate { + func cardImageVerificationController( + _ controller: CardImageVerificationController, + didFinishWithResult result: CardImageVerificationSheetResult + ) { + self.result = result + resultExp.fulfill() + } +} + +extension CardImageVerificationControllerTests { + func stubSubmitVerificationFrames() { + let mockResponse = "{}".data(using: .utf8)! + + /// Stub the request to submit verify frames + stub { [weak self] request in + guard let requestUrl = request.url, + /// Check that the request is a POST request with an endpoint with the CIV id + requestUrl.absoluteString.contains( + "v1/card_image_verifications/\(CIVIntentMockData.id)/verify_frames" + ), + request.httpMethod == "POST" + else { + return false + } + + self?.verifyFramesRequestExp.fulfill() + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + } + + func stubUploadScanStats() { + let mockResponse = "{}".data(using: .utf8)! + + /// Stub the request to submit verify frames + stub { [weak self] request in + guard let requestUrl = request.url, + /// Check that the request is a POST request with an endpoint with the CIV id + requestUrl.absoluteString.contains( + "v1/card_image_verifications/\(CIVIntentMockData.id)/scan_stats" + ), + request.httpMethod == "POST" + else { + return false + } + + self?.scanStatsRequestExp.fulfill() + return true + } response: { _ in + return HTTPStubsResponse(data: mockResponse, statusCode: 200, headers: nil) + } + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/CardImageVerificationDetailsResponseTest.swift b/StripeCardScan/StripeCardScanTests/Unit/CardImageVerificationDetailsResponseTest.swift new file mode 100644 index 00000000..708f9c72 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/CardImageVerificationDetailsResponseTest.swift @@ -0,0 +1,81 @@ +// +// CardImageVerificationDetailsResponseTest.swift +// StripeCardScanTests +// +// Created by Scott Grant on 5/11/22. +// + +import XCTest + +@testable@_spi(STP) import StripeCardScan +@testable@_spi(STP) import StripeCore + +class CardImageVerificationDetailsResponseTest: XCTestCase { + + func testExample() throws { + let json = """ + { + "accepted_image_configs": { + "default_settings": { + "compression_ratio": 0.8, + "image_size": [ + 1080, + 1920 + ] + }, + "format_settings": { + "heic": { + "compression_ratio": 0.5 + }, + "webp": { + "compression_ratio": 0.7, + "image_size": [ + 2160, + 1920 + ] + } + }, + "preferred_formats": [ + "heic", + "webp", + "jpeg" + ] + }, + "expected_card": { + "last4": "9012", + "issuer": "Visa" + } + } + """ + let jsonData = json.data(using: .utf8)! + + let responseObject: CardImageVerificationDetailsResponse = + try StripeJSONDecoder.decode(jsonData: jsonData) + + let acceptedImageConfigs = responseObject.acceptedImageConfigs + + let heicSettings = acceptedImageConfigs?.imageSettings(format: .heic) + XCTAssertNotNil(heicSettings) + + if let heicSettings = heicSettings { + XCTAssertEqual(heicSettings.compressionRatio!, 0.5) + XCTAssertEqual(heicSettings.imageSize!, [1080.0, 1920.0]) + } + + let jpegSettings = acceptedImageConfigs?.imageSettings(format: .jpeg) + XCTAssertNotNil(jpegSettings) + + if let jpegSettings = jpegSettings { + XCTAssertEqual(jpegSettings.compressionRatio!, 0.8) + XCTAssertEqual(jpegSettings.imageSize!, [1080.0, 1920.0]) + } + + let webpSettings = acceptedImageConfigs?.imageSettings(format: .webp) + XCTAssertNotNil(webpSettings) + + if let webpSettings = webpSettings { + XCTAssertEqual(webpSettings.compressionRatio!, 0.7) + XCTAssertEqual(webpSettings.imageSize!, [2160.0, 1920.0]) + } + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/ImageCompressionTests.swift b/StripeCardScan/StripeCardScanTests/Unit/ImageCompressionTests.swift new file mode 100644 index 00000000..7243a405 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/ImageCompressionTests.swift @@ -0,0 +1,133 @@ +// +// ImageCompressionTests.swift +// StripeCardScanTests +// +// Created by Sam King on 11/29/21. +// + +import CoreServices +import UniformTypeIdentifiers +import XCTest + +@testable@_spi(STP) import StripeCardScan + +class ImageCompressionTests: XCTestCase { + + var image: CGImage? + var originalImageSize: CGSize? + var roiRectangle: CGRect? + + override func setUpWithError() throws { + let (image, roiRectangle) = ImageHelpers.getTestImageAndRoiRectangle() + + self.originalImageSize = image.size + self.image = image.cgImage + self.roiRectangle = roiRectangle + } + + func testFullSize() throws { + guard let image = image, let originalImageSize = originalImageSize, + let roiRectangle = roiRectangle + else { + throw "invalid setup" + } + + let scannedCard = ScannedCardImageData( + previewLayerImage: image, + previewLayerViewfinderRect: roiRectangle + ) + let (verificationFrame, _) = scannedCard.toVerificationFramesData(imageConfig: nil) + let imageData = verificationFrame.imageData + let newImage = UIImage(data: imageData!) + XCTAssertNotNil(newImage) + XCTAssertEqual(newImage?.size, originalImageSize) + XCTAssertTrue(verificationFrame.viewfinderMargins.equal(to: roiRectangle)) + } + + func testJPEG() throws { + guard let image = image, let originalImageSize = originalImageSize, + let roiRectangle = roiRectangle + else { + throw "invalid setup" + } + + let scannedCard = ScannedCardImageData( + previewLayerImage: image, + previewLayerViewfinderRect: roiRectangle + ) + let (verificationFrame, metadata) = scannedCard.toVerificationFramesData( + imageConfig: ImageConfig(preferredFormats: [.jpeg]) + ) + let imageData = verificationFrame.imageData + let newImage = UIImage(data: imageData!) + XCTAssertEqual(metadata.compressionType, .jpeg) + XCTAssertEqual(metadata.compressionQuality, 0.8) + XCTAssertNotNil(newImage) + XCTAssertEqual(newImage?.size, originalImageSize) + XCTAssertNotNil(newImage?.cgImage?.utType) + if let type = newImage?.cgImage?.utType { + if #available(iOS 14.0, *) { + XCTAssertEqual(type as String, UTType.jpeg.identifier) + } else { + XCTAssertEqual(type, kUTTypeJPEG) + } + } + XCTAssertTrue(verificationFrame.viewfinderMargins.equal(to: roiRectangle)) + } + + func testHEIC() throws { + guard let image = image, let originalImageSize = originalImageSize, + let roiRectangle = roiRectangle + else { + throw "invalid setup" + } + + let scannedCard = ScannedCardImageData( + previewLayerImage: image, + previewLayerViewfinderRect: roiRectangle + ) + let (verificationFrame, metadata) = scannedCard.toVerificationFramesData( + imageConfig: ImageConfig(preferredFormats: [.heic]) + ) + let imageData = verificationFrame.imageData + let newImage = UIImage(data: imageData!) + XCTAssertEqual(metadata.compressionType, .heic) + XCTAssertEqual(metadata.compressionQuality, 0.8) + XCTAssertNotNil(newImage) + XCTAssertEqual(newImage?.size, originalImageSize) + XCTAssertNotNil(newImage?.cgImage?.utType) + if let type = newImage?.cgImage?.utType { + if #available(iOS 14.0, *) { + XCTAssertEqual(type as String, UTType.heic.identifier) + } else { + XCTAssertEqual(type as String, "public.heic") + } + } + XCTAssertTrue(verificationFrame.viewfinderMargins.equal(to: roiRectangle)) + } +} + +extension ViewFinderMargins { + func equal(to rect: CGRect) -> Bool { + let left = Int(rect.origin.x) + let right = Int(rect.origin.x + rect.size.width) + let upper = Int(rect.origin.y) + let lower = Int(rect.origin.y + rect.size.height) + + return left == self.left && right == self.right && upper == self.upper + && lower == self.lower + } +} + +extension CGRect { + func scale(byX scaleX: CGFloat, byY scaleY: CGFloat) -> CGRect { + return CGRect( + x: self.origin.x * scaleX, + y: self.origin.y * scaleY, + width: self.size.width * scaleX, + height: self.size.height * scaleY + ) + } +} + +extension String: Error {} diff --git a/StripeCardScan/StripeCardScanTests/Unit/ML Models/UxModelTests.swift b/StripeCardScan/StripeCardScanTests/Unit/ML Models/UxModelTests.swift new file mode 100644 index 00000000..9c9a192f --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/ML Models/UxModelTests.swift @@ -0,0 +1,48 @@ +// +// UxModelTests.swift +// CardVerifyTests +// +// Created by Sam King on 8/14/21. +// + +import XCTest + +@testable@_spi(STP) import StripeCardScan + +class UxModelTests: XCTestCase { + + var image: CGImage? + var roiRectangle: CGRect? + + override func setUpWithError() throws { + let (image, roiRectangle) = ImageHelpers.getTestImageAndRoiRectangle() + + self.image = image.cgImage + self.roiRectangle = roiRectangle + + } + + func testUxAndAppleAnalyzer() throws { + guard let image = image, let roiRectangle = roiRectangle else { + XCTAssert(false) + return + } + let ocr = AppleCreditCardOcr(dispatchQueueLabel: "test") + let uxAnalyzer = UxAnalyzer(with: ocr) + + let prediction = uxAnalyzer.recognizeCard(in: image, roiRectangle: roiRectangle) + XCTAssert(prediction.centeredCardState == .numberSide) + } + + func testUxAndSsdAnalyzer() throws { + guard let image = image, let roiRectangle = roiRectangle else { + XCTAssert(false) + return + } + let ocr = SSDCreditCardOcr(dispatchQueueLabel: "test") + let uxAnalyzer = UxAnalyzer(with: ocr) + + let prediction = uxAnalyzer.recognizeCard(in: image, roiRectangle: roiRectangle) + XCTAssert(prediction.centeredCardState == .numberSide) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/ScanAnalyticsManagerTests.swift b/StripeCardScan/StripeCardScanTests/Unit/ScanAnalyticsManagerTests.swift new file mode 100644 index 00000000..a2b06da5 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/ScanAnalyticsManagerTests.swift @@ -0,0 +1,165 @@ +// +// ScanAnalyticsManagerTests.swift +// StripeCardScanTests +// +// Created by Jaime Park on 5/10/22. +// + +import OHHTTPStubs +import StripeCoreTestUtils +import XCTest + +@testable@_spi(STP) import StripeCardScan + +class ScanAnalyticsManagerTests: XCTestCase { + private var scanAnalyticsManager: ScanAnalyticsManager! + private var generatePayloadExp: XCTestExpectation! + + override func setUp() { + super.setUp() + self.scanAnalyticsManager = ScanAnalyticsManager() + self.generatePayloadExp = expectation( + description: "Successfully generated the scan analytics payload" + ) + } + + /// This test checks that the scan analytics manager aggregates tasks and generates the payload object properly + func testGeneratePayload() { + let startTime = Date() + let task: TrackableTask = { + let task = TrackableTask() + task.trackResult(.success) + return task + }() + let payloadInfo = ScanAnalyticsPayload.PayloadInfo( + imageCompressionType: "heic", + imageCompressionQuality: 0.8, + imagePayloadSize: 4000 + ) + /// Log scan activity repeating and non-repeating tasks + scanAnalyticsManager.setScanSessionStartTime(time: startTime) + scanAnalyticsManager.logCameraPermissionsTask(success: false) + scanAnalyticsManager.logMainLoopImageProcessedRepeatingTask(.init(executions: 100)) + scanAnalyticsManager.logPayloadInfo(with: payloadInfo) + scanAnalyticsManager.logScanActivityTask(event: .firstImageProcessed) + scanAnalyticsManager.logTorchSupportTask(supported: false) + scanAnalyticsManager.trackCompletionLoopDuration(task: task) + scanAnalyticsManager.trackImageCompressionDuration(task: task) + scanAnalyticsManager.trackMainLoopDuration(task: task) + + /// Override tasks when values have changed + scanAnalyticsManager.logCameraPermissionsTask(success: true) + scanAnalyticsManager.logTorchSupportTask(supported: true) + + scanAnalyticsManager.generateScanAnalyticsPayload(with: .init()) { + [weak self] scanAnalyticsPayload in + guard let payload = scanAnalyticsPayload else { + XCTFail("Did not generate scan analytics payload") + return + } + + self?.generatePayloadExp.fulfill() + + /// Check the populated configuration and payload info + XCTAssertEqual(payload.configuration.strictModeFrames, 0) + XCTAssertEqual(payload.payloadInfo, payloadInfo) + + /// Check the populated scan activity + let payloadScanStats = payload.scanStats + XCTAssertEqual( + payloadScanStats.tasks.cameraPermission.first?.result, + ScanAnalyticsEvent.success.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.completionLoopDuration.first?.result, + ScanAnalyticsEvent.success.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.imageCompressionDuration.first?.result, + ScanAnalyticsEvent.success.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.mainLoopDuration.first?.result, + ScanAnalyticsEvent.success.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.torchSupported.first?.result, + ScanAnalyticsEvent.torchSupported.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.scanActivity.first?.result, + ScanAnalyticsEvent.firstImageProcessed.rawValue + ) + XCTAssertEqual( + payloadScanStats.repeatingTasks.mainLoopImagesProcessed, + .init(executions: 100) + ) + } + + wait(for: [generatePayloadExp], timeout: 1) + } + + /// This test checks that the scan analytics manager resets properly + func testReset() { + let startTime = Date() + /// Log scan activity repeating and non-repeating tasks + scanAnalyticsManager.setScanSessionStartTime(time: startTime) + scanAnalyticsManager.logCameraPermissionsTask(success: false) + scanAnalyticsManager.logMainLoopImageProcessedRepeatingTask(.init(executions: 100)) + scanAnalyticsManager.logScanActivityTaskFromStartTime(event: .firstImageProcessed) + scanAnalyticsManager.logTorchSupportTask(supported: false) + + /// Reset the scan analytics manager + scanAnalyticsManager.reset() + + scanAnalyticsManager.generateScanAnalyticsPayload(with: .init()) { + [weak self] scanAnalyticsPayload in + guard let payload = scanAnalyticsPayload else { + XCTFail("Did not generate scan analytics payload") + return + } + + self?.generatePayloadExp.fulfill() + + /// Check the populated configuration + XCTAssertEqual(payload.configuration.strictModeFrames, 0) + XCTAssertNil( + payload.payloadInfo, + "A reset scan analytics manager should have a nil payload info" + ) + + /// Check the reset scan activity + let payloadScanStats = payload.scanStats + XCTAssertEqual( + payloadScanStats.tasks.cameraPermission.first?.result, + ScanAnalyticsEvent.unknown.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.completionLoopDuration.first?.result, + ScanAnalyticsEvent.unknown.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.imageCompressionDuration.first?.result, + ScanAnalyticsEvent.unknown.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.mainLoopDuration.first?.result, + ScanAnalyticsEvent.unknown.rawValue + ) + XCTAssertEqual( + payloadScanStats.tasks.torchSupported.first?.result, + ScanAnalyticsEvent.unknown.rawValue + ) + XCTAssertEqual( + payloadScanStats.repeatingTasks.mainLoopImagesProcessed, + .init(executions: -1) + ) + XCTAssert( + payloadScanStats.tasks.scanActivity.isEmpty, + "A reset scan analytics manager should have an empty scan activity list" + ) + } + + wait(for: [generatePayloadExp], timeout: 1) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/StrictModeFramesTest.swift b/StripeCardScan/StripeCardScanTests/Unit/StrictModeFramesTest.swift new file mode 100644 index 00000000..e0966e54 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/StrictModeFramesTest.swift @@ -0,0 +1,159 @@ +// +// StrictModeFramesTest.swift +// StripeCardScanTests +// +// Created by Jaime Park on 3/10/22. +// + +import XCTest + +@testable@_spi(STP) import StripeCardScan + +class StrictFramesTests: XCTestCase { + let correctCardNumber = ScannedCardDetails(number: "0000111122223333") + let incorrectCardNumber = ScannedCardDetails(number: "7777888899990000") + + /// Test to check that `visibleMatchingCardCount` increments properly + func testVisibleMatchingCardCount() { + let cardVerifyStateMachine = CardVerifyStateMachine( + requiredLastFour: correctCardNumber.last4 + ) + /// First frame detected is a mismatch frame with a card present + transition( + stateMachine: cardVerifyStateMachine, + prediction: mismatchNumberPrediction(cardVisible: true) + ) + XCTAssertEqual(cardVerifyStateMachine.visibleMatchingCardCount, 0) + + /// Simulate transitioning state machine with 9 matching frames + var totalMatchedFramesWithCard = 9 + repeat { + transition( + stateMachine: cardVerifyStateMachine, + prediction: matchNumberPrediction(cardVisible: true) + ) + totalMatchedFramesWithCard -= 1 + } while totalMatchedFramesWithCard > 0 + + XCTAssertEqual(cardVerifyStateMachine.visibleMatchingCardCount, 9) + + /// Simulate transitioning to a mismatching frame. Visible card count should not change. + transition( + stateMachine: cardVerifyStateMachine, + prediction: mismatchNumberPrediction(cardVisible: true) + ) + XCTAssertEqual(cardVerifyStateMachine.visibleMatchingCardCount, 9) + } + + func testCardVerifyStateMachine_NotStrict_Success() { + let cardVerifyStateMachine = CardVerifyStateMachine( + requiredLastFour: correctCardNumber.last4 + ) + + /// Mock that: + /// 1. We are currently in the `ocrAndCard` state + /// 2. We have been in the `ocrAndCard` state for 1.5 seconds (the exact timeout limit) + /// 3. We have already accounted for 1 frames that have matching pan & detected a card + cardVerifyStateMachine.state = .ocrAndCard + cardVerifyStateMachine.startTimeForCurrentState = Date().addingTimeInterval(-1.5) + cardVerifyStateMachine.visibleMatchingCardCount = 1 + + /// Run through 1 more frame with matching pan & card detected fulfilling the strictModeFrame amount (0) + transition( + stateMachine: cardVerifyStateMachine, + prediction: matchNumberPrediction(cardVisible: true) + ) + + XCTAssertEqual(cardVerifyStateMachine.visibleMatchingCardCount, 2) + XCTAssertEqual(cardVerifyStateMachine.state, .finished) + } + + func testCardVerifyStateMachine_Strict_Success() { + /// Configure the Bouncer session with strict mode frames + let cardVerifyStateMachine = CardVerifyStateMachine( + requiredLastFour: correctCardNumber.last4, + strictModeFramesCount: .high + ) + + /// Mock that: + /// 1. We are currently in the `ocrAndCard` state + /// 2. We have been in the `ocrAndCard` state for 3 seconds (well passed the timeout limit) + /// 3. We have already accounted for 4 frames that have matching pan & detected a card + cardVerifyStateMachine.state = .ocrAndCard + cardVerifyStateMachine.startTimeForCurrentState = Date().addingTimeInterval(-3) + cardVerifyStateMachine.visibleMatchingCardCount = 4 + + /// Run through 1 more frame with matching pan & card detected fulfilling the strictModeFrame amount (5) + transition( + stateMachine: cardVerifyStateMachine, + prediction: matchNumberPrediction(cardVisible: true) + ) + + XCTAssertEqual(cardVerifyStateMachine.visibleMatchingCardCount, 5) + XCTAssertEqual(cardVerifyStateMachine.state, .finished) + } + + func testCardVerifyStateMachine_Reset() { + /// Configure the Bouncer session with strict mode frames + let cardVerifyStateMachine = CardVerifyStateMachine( + requiredLastFour: correctCardNumber.last4, + strictModeFramesCount: .high + ) + + /// Mock that: + /// 1. We are currently in the `ocrAndCard` state + /// 2. We have been in the `ocrAndCard` state for 3 seconds (well passed the timeout limit) + /// 3. We have only found 1 matching frame; Not enough to pass strict mode + cardVerifyStateMachine.state = .ocrAndCard + cardVerifyStateMachine.startTimeForCurrentState = Date().addingTimeInterval(-3) + cardVerifyStateMachine.visibleMatchingCardCount = 1 + + /// Run through 1 more frame with matching pan to trigger reset to initial state + transition( + stateMachine: cardVerifyStateMachine, + prediction: matchNumberPrediction(cardVisible: true) + ) + XCTAssertEqual(cardVerifyStateMachine.state, .initial) + XCTAssertEqual(cardVerifyStateMachine.visibleMatchingCardCount, 0) + + } +} + +extension StrictFramesTests { + func transition(stateMachine: CardVerifyStateMachine, prediction: CreditCardOcrPrediction) { + _ = stateMachine.event(prediction: prediction) + } + + func mismatchNumberPrediction(cardVisible: Bool) -> CreditCardOcrPrediction { + return generateOcrPrediction( + withNumber: incorrectCardNumber.number, + withCenteredCardState: cardVisible ? .numberSide : .noCard + ) + } + + func matchNumberPrediction(cardVisible: Bool) -> CreditCardOcrPrediction { + return generateOcrPrediction( + withNumber: correctCardNumber.number, + withCenteredCardState: cardVisible ? .numberSide : .noCard + ) + } + + func generateOcrPrediction( + withNumber number: String, + withCenteredCardState centeredCardState: CenteredCardState + ) -> CreditCardOcrPrediction { + return CreditCardOcrPrediction( + image: ImageHelpers.createBlankCGImage(), + ocrCroppingRectangle: CGRect(), + number: number, + expiryMonth: nil, + expiryYear: nil, + name: nil, + computationTime: 0.0, + numberBoxes: nil, + expiryBoxes: nil, + nameBoxes: nil, + centeredCardState: centeredCardState + ) + } +} diff --git a/StripeCardScan/StripeCardScanTests/Unit/StringResourceTests.swift b/StripeCardScan/StripeCardScanTests/Unit/StringResourceTests.swift new file mode 100644 index 00000000..01644c20 --- /dev/null +++ b/StripeCardScan/StripeCardScanTests/Unit/StringResourceTests.swift @@ -0,0 +1,27 @@ +// +// ImageCompressionTests.swift +// StripeCardScanTests +// +// Created by Scott Grant on 05/18/22. +// + +import CoreServices +import UniformTypeIdentifiers +import XCTest + +@testable@_spi(STP) import StripeCore + +class StringResourceTests: XCTestCase { + let privacyLinkExpectedSha = "lv51crZ0rBIUPUOnQm9zFlMPCrUEI+GVsa4QyHifTw0=" + + func testPrivacyLinkText() throws { + STPLocalizationUtils.overrideLanguage(to: "en") + + // This string is expected to go unaltered in the UI for CardScan. Changing it is against + // the terms of service for Stripe Card Scan. + let privacyLinkString = String.Localized.scan_card_privacy_link_text + XCTAssertEqual(privacyLinkString.sha256, privacyLinkExpectedSha) + + STPLocalizationUtils.overrideLanguage(to: nil) + } +} diff --git a/StripeCore/StripeCore.xcodeproj/project.pbxproj b/StripeCore/StripeCore.xcodeproj/project.pbxproj new file mode 100644 index 00000000..57134203 --- /dev/null +++ b/StripeCore/StripeCore.xcodeproj/project.pbxproj @@ -0,0 +1,1355 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 01C16F6C63483A646A8C368E /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 35087E242A157890B5FAC5A4 /* OHHTTPStubsSwift */; }; + 02A26B79617FAE660C9EB506 /* StripeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC0AF5BF0A4799D2C0C7445 /* StripeError.swift */; }; + 0709F5D265CC641E6DE1011D /* URLSession+Retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B76840742D2CAF2931355A /* URLSession+Retry.swift */; }; + 08871FCD9E9E47681135431B /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D4E87D67740BF3C05638FD /* XCTest.framework */; }; + 096274D0729AA8849FAD103C /* PaymentsSDKVariant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFB15A6996F610D627A42B3 /* PaymentsSDKVariant.swift */; }; + 0A78AD04075C43A4059C344E /* STPAnalyticsClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B16FDA9232BD4FBD4B13EC2 /* STPAnalyticsClientTest.swift */; }; + 0F4A1BAE6774B90C72F578CC /* MockAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C7099CB854E1E90424235C /* MockAnalyticsClient.swift */; }; + 12FF091C555F75B914464475 /* STPMultipartFormDataEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D803C2F907529E780B0296 /* STPMultipartFormDataEncoder.swift */; }; + 17CE96B50813CF626293CBF9 /* URLEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C51179DB520568C246BF3AF0 /* URLEncoder.swift */; }; + 2991461DD354A6124CCF78DA /* STPLocalizationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325E8108336F1178A10D139C /* STPLocalizationUtils.swift */; }; + 2AA9B01C8A2D2BADC4619629 /* NSCharacterSet+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5151C3B6CB4130E9C259A6 /* NSCharacterSet+StripeCore.swift */; }; + 2B98F4F0120888B12EF3B181 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CFF68ABB8E67E83695FAD8EA /* Localizable.strings */; }; + 2D7A4FDBED7E3FA3D17BBB54 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40A77DE176741B3A542FE890 /* StripeCore.framework */; }; + 31AD3BDC2B0C23E40080C800 /* Locale+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD3BDB2B0C23E40080C800 /* Locale+StripeCore.swift */; }; + 31CDFC302BA372B200B3DD91 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 31CDFC2F2BA372B200B3DD91 /* PrivacyInfo.xcprivacy */; }; + 330FDCF901D11882D4866DDE /* APIStubbedTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF82F68E8D3A8286FD31DB13 /* APIStubbedTestCase.swift */; }; + 33E1AA9687131A7ECF384848 /* STPSnapshotTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF15BAFA3E58D68668EE6DCC /* STPSnapshotTestCase.swift */; }; + 35931C64F06BEB233A219869 /* NetworkDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19F97D1606B517158C7F75A /* NetworkDetector.swift */; }; + 3815D229613D52D7799805B0 /* AnalyticsClientV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192E71FE64C5FDE929992CC4 /* AnalyticsClientV2.swift */; }; + 3B27DDDDC91F1599BF1469BB /* UserDefaults+PaymentsCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F11FF08733CF61D880640D /* UserDefaults+PaymentsCore.swift */; }; + 3B9D69AB1CB61725C7A012B6 /* StripeServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCF062352C38A5173260C46A /* StripeServiceError.swift */; }; + 3D90376B1883E4BE64712197 /* MockAnalyticsClientV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27EB57C159E5F10F50D071E5 /* MockAnalyticsClientV2.swift */; }; + 3E9FC2CD06E1D5F6B09872E9 /* AnalyticsClientV2Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440D8DFB25A1F7FBC01BE1D7 /* AnalyticsClientV2Test.swift */; }; + 40AF3B2EB6B82C9A4DD61033 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 9AB19C81E50535A3407582B7 /* OHHTTPStubs */; }; + 42DE35681C71A931F65E0E7D /* Enums+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A8030BF88608CA86E295F18 /* Enums+CustomStringConvertible.swift */; }; + 44DE84C8BFB403E1FB7E2E82 /* StripeJSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3D6ADD516777DE13E79792 /* StripeJSONDecoder.swift */; }; + 4506A7016EA7C45796D3A30D /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758B9C9C2252A91FE4221702 /* STPLocalizedString.swift */; }; + 45DAE581F74EF7E11C64212B /* InstallMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E64986F72C7BD8B1105A95 /* InstallMethod.swift */; }; + 48A6CCB4008A5060C2655C5F /* XCTestCase+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DE48D0086BED21F9E837D0B /* XCTestCase+Stripe.swift */; }; + 4910B9282C3D8F3F00B030D4 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4910B9272C3D8F3F00B030D4 /* Result+Extensions.swift */; }; + 492039932CA47A8600CE2072 /* ElementsSessionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492039922CA47A8600CE2072 /* ElementsSessionContext.swift */; }; + 493B33062CA3015600E3622F /* LinkMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 493B33052CA3015600E3622F /* LinkMode.swift */; }; + 49E8CE2A2CD146CE0009DFBB /* KeyedEncodingContainer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E8CE292CD146CE0009DFBB /* KeyedEncodingContainer+Extensions.swift */; }; + 49ECDA412CA340E100F647F0 /* AsyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ECDA402CA340E100F647F0 /* AsyncTests.swift */; }; + 49F3828D2CC02D43001CE69A /* BillingAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F3828C2CC02D43001CE69A /* BillingAddress.swift */; }; + 4B2FAC57E03D8654A177C408 /* Dictionary+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7727AEEFD2FC880BADDA1872 /* Dictionary+Stripe.swift */; }; + 53D46A03B77577EE21F4B166 /* StripeCodableTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FCE36551600C3E53BEAF8F0 /* StripeCodableTest.swift */; }; + 552DA7969984C443617DBC3E /* STPMultipartFormDataPart.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C72BA9C44FF60A0E7BEF76 /* STPMultipartFormDataPart.swift */; }; + 5553D952F91D193D453D777D /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77CAB1A5AC107D8756CA1CBF /* Async.swift */; }; + 563A42FA383FA9AA5FA4CDCE /* String+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7507497162F5684AEA59E301 /* String+StripeCore.swift */; }; + 59CA874015261241AC255907 /* FileDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C174081A48DD86978D270D /* FileDownloader.swift */; }; + 5E807567D7320A7D512127AF /* StripeCore.h in Headers */ = {isa = PBXBuildFile; fileRef = E60F4A38EEF5EA11568B3A64 /* StripeCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 62FD088E003BE06F5413FB4F /* StripeCoreBundleLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEA5353BC5359E08128E116A /* StripeCoreBundleLocator.swift */; }; + 631D09E67497B49BBCA26192 /* UIView+StripeCoreTestingUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4E4C534285F49A97A04D2B8 /* UIView+StripeCoreTestingUtils.swift */; }; + 677951C643328D76E46720A5 /* StripeAPIConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CB3702691056D3404A8C5F /* StripeAPIConfiguration.swift */; }; + 6A05FB452BCF24100001D128 /* FinancialConnectionsSDKResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A05FB442BCF24100001D128 /* FinancialConnectionsSDKResult.swift */; }; + 6A05FB472BCF24370001D128 /* FinancialConnectionsLinkedBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A05FB462BCF24370001D128 /* FinancialConnectionsLinkedBank.swift */; }; + 6A05FB492BCF244A0001D128 /* InstantDebitsLinkedBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A05FB482BCF244A0001D128 /* InstantDebitsLinkedBank.swift */; }; + 6A05FB4B2BCF245C0001D128 /* FinancialConnectionsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A05FB4A2BCF245C0001D128 /* FinancialConnectionsEvent.swift */; }; + 6A52ABC06783A90B9E339948 /* StripeFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73CE623A81057C4063A1E0C4 /* StripeFile.swift */; }; + 6B4156FCFAEDD1C73DC6EDAD /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 23D22B2C40BA7C182BCE50B2 /* iOSSnapshotTestCase */; }; + 6B9282392C94A99400277765 /* STPAnalyticsEventTranslatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9282382C94A99400277765 /* STPAnalyticsEventTranslatorTest.swift */; }; + 6B987BDA2C910F5500DDFDB3 /* STPAnalyticsEventTranslator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B987BD92C910F5500DDFDB3 /* STPAnalyticsEventTranslator.swift */; }; + 6B987BDD2C9111BC00DDFDB3 /* Notification+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B987BDC2C9111BC00DDFDB3 /* Notification+Stripe.swift */; }; + 6B9C7B832BC73B1C007D5A28 /* AnalyticsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9C7B822BC73B1C007D5A28 /* AnalyticsHelper.swift */; }; + 6D68B868938BAB15A843B33C /* TestJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF290DF69F5FD1004BBDECA /* TestJSONEncoder.swift */; }; + 6ED5C41DBDAB475BF1119E98 /* UnknownFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64635404BD4D5D62486A7626 /* UnknownFields.swift */; }; + 700E9DAD0407455F11F9F03E /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A4F598BF353D8335B0340D5 /* StripeCoreTestUtils.framework */; }; + 7133C71AC18633F59BA18F57 /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 6FFF766FF9F630392C6D446A /* iOSSnapshotTestCase */; }; + 71CD1AE29AA09552DF61131E /* StripeJSONShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86C675ABC9D68378DC699DED /* StripeJSONShared.swift */; }; + 72DA29CA8A750E8B00DBF3D4 /* STPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C51E3FA5EE3587BB7BBC634 /* STPError.swift */; }; + 766FE8E61B44967F057ED424 /* AnalyticLoggableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B890F162E1C247D5CA1A9E6 /* AnalyticLoggableError.swift */; }; + 772292156A4A80CEA9D9C487 /* FinancialConnectionsSDKInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC3BCEEECB3E1485B18F0C4 /* FinancialConnectionsSDKInterface.swift */; }; + 79DA4102C501FC2E53D946B5 /* STPAPIClient+ErrorResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E70193A9CA42A0C53E48C1 /* STPAPIClient+ErrorResponseTest.swift */; }; + 8310D598D6D40BAD23880D3F /* StripeCoreTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = C666CC926642D7AA76E75B5B /* StripeCoreTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 83790210FFC2DD764C042C8E /* STPDispatchFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 591E592C9F3E5D4CB08A1847 /* STPDispatchFunctions.swift */; }; + 838BDB53C4F6AC3C0567DE6A /* KeyPathExpectation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561E5F74D9E5676F6E72E93F /* KeyPathExpectation.swift */; }; + 84487D8E9B08106C89753536 /* Error_SerializeForLoggingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3CA4B1855BF8D2F08F275A9 /* Error_SerializeForLoggingTest.swift */; }; + 87274985CE5E750FA8D34648 /* EmptyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00D8E07F0DDBA1577A52156C /* EmptyResponse.swift */; }; + 87536D729B8201085E380C32 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D4E87D67740BF3C05638FD /* XCTest.framework */; }; + 89DB623A200678B4E9845AF2 /* FraudDetectionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E919FBEB852CFEA9517FCBDC /* FraudDetectionData.swift */; }; + 8AD68C8D00A0BCF94E5230DC /* UIImage+StripeCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28769EBA666E0BDFC69954F /* UIImage+StripeCoreTests.swift */; }; + 917B4E193E5A1233F1A2E80E /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40A77DE176741B3A542FE890 /* StripeCore.framework */; }; + 920832EE256E377572DD41EB /* STPTelemetryClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A466F6075DCC39D5A976FB22 /* STPTelemetryClient.swift */; }; + 934CCB00769674F13192A126 /* Dictionary+StripeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34895253739089BC125D2625 /* Dictionary+StripeTests.swift */; }; + 95156E152471058151076A51 /* Analytic.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABF11F814A986AA710410FF8 /* Analytic.swift */; }; + 970D95FEA3BC216351DE3C5E /* StripeJSONEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD027F7695EC6F59CF32F4B9 /* StripeJSONEncoder.swift */; }; + 97D0B2120678A75F75648D84 /* STPAppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9077630E4A5307B83B4BA19E /* STPAppInfo.swift */; }; + 9843D9B7D886C373C7AC71E4 /* NSMutableURLRequest+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BD3D02EBA8A732A9C832925 /* NSMutableURLRequest+Stripe.swift */; }; + 9DCBC08C182ED76A962961E7 /* NSURLComponents+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D475CD59778FAC1028670AB5 /* NSURLComponents+Stripe.swift */; }; + 9FBA50345D53E82AA974F672 /* STPAPIClient+FileUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FC2FC535DAF9B340F9EA75 /* STPAPIClient+FileUpload.swift */; }; + A454F368C505B2D80F0D8B19 /* PluginDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDBCD70CB220014972B49A5 /* PluginDetector.swift */; }; + A4DBBFD379C4E0120BD25C56 /* STPAPIClient+EmptyResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC70CDF482E22A29B11466F7 /* STPAPIClient+EmptyResponseTest.swift */; }; + A50CB2ACAC1DCF9539D76F25 /* NSArray+StripeCoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2949BE5129DC4BBCD69B05 /* NSArray+StripeCoreTest.swift */; }; + A62AEDF871AC89489FE19A13 /* ServerErrorMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C188AD8AE244CD6076679095 /* ServerErrorMapper.swift */; }; + A987DFFA404EBD0788DAB21A /* UIImage+StripeCoreTestingUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1678E0A16D46DA2B4D3B3ECD /* UIImage+StripeCoreTestingUtils.swift */; }; + AE26BFFBE9B1242E10CA052F /* STPAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DD5B6F1152FD1C4A508A103 /* STPAnalyticsClient.swift */; }; + B35FD03DD246CC4DD6C4F3C0 /* Decimal+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A516593E48DCFD5D98D702 /* Decimal+StripeCore.swift */; }; + B644BC402BB234D6001FA436 /* STPAssert.swift in Sources */ = {isa = PBXBuildFile; fileRef = B644BC3F2BB234D6001FA436 /* STPAssert.swift */; }; + B67F2CA22BAB8B690011E34A /* AnalyticLoggableErrorV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67F2CA12BAB8B690011E34A /* AnalyticLoggableErrorV2.swift */; }; + B67F2CA42BAB91860011E34A /* AnalyticLoggableErrorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67F2CA32BAB91860011E34A /* AnalyticLoggableErrorTest.swift */; }; + B6D129B2DC90FA1F8A1F5BCB /* UIActivityIndicatorView+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 599C8A64EB3988BCE32E2B05 /* UIActivityIndicatorView+Stripe.swift */; }; + B6DBB2BF2BA8C4E400783D15 /* STPAnalyticsClient+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DBB2BE2BA8C4E300783D15 /* STPAnalyticsClient+Error.swift */; }; + C164984958CDC2C9CA4B6316 /* STPAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9ED2ADD9406CF7534BB6C9 /* STPAPIClient.swift */; }; + C318A6B6CD599B06DA7CE706 /* NSBundle+Stripe_AppName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E852B53CF75A119D3810B41 /* NSBundle+Stripe_AppName.swift */; }; + C5BF4B8AE85FF72EC6382EC0 /* NSError+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E415507B0152B07796114CC /* NSError+Stripe.swift */; }; + C7C0EC68130760F73201F81B /* StripeCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49424775D3233411D9C2473B /* StripeCodable.swift */; }; + C9B6C451F9A46C9FB031CE95 /* STPURLCallbackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7647C1B64CA0F0087327413 /* STPURLCallbackHandler.swift */; }; + C9C320ADCCF1548D6562CE94 /* File_IdentityDocument.json in Resources */ = {isa = PBXBuildFile; fileRef = DC24A98C4020646F99456187 /* File_IdentityDocument.json */; }; + CA09DC1EC4142701B31F9673 /* UIImage+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D4100901F9445AC1FD453A /* UIImage+StripeCore.swift */; }; + CAF857D45689FBEF17627E80 /* BundleLocatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937066801E91C99C50192364 /* BundleLocatorProtocol.swift */; }; + CB0E9DC82CB9C79E00E083D1 /* LinkBankPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB0E9DC72CB9C79E00E083D1 /* LinkBankPaymentMethod.swift */; }; + CB1FB2383FAEE0194C39E4DE /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E86AC7DD5F4DE2780E0AC425 /* OHHTTPStubsSwift */; }; + CB8A47A5FD057112CB607DE9 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5334916D2A4F927645C2569 /* MockData.swift */; }; + D144C3A657E5C16975CB2191 /* NSError+StripeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23506F3E93ECA5A96DCE7E31 /* NSError+StripeCore.swift */; }; + D22FAB2F1AE9AE43C1808747 /* StripeAPIConfiguration+Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F1BFF01B1EA57F6EA7BFBF1 /* StripeAPIConfiguration+Version.swift */; }; + D8FEF16798A791F5BF7EA4B2 /* NSArray+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21608B7BC24190523363CB3D /* NSArray+Stripe.swift */; }; + DA5A05459309B9B77ACDD736 /* STPDeviceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C2AA21DAF340432CA98C67C /* STPDeviceUtils.swift */; }; + DAD4099D03E43A0CA89464CD /* StripeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4336F071B0CE13306C8EB93 /* StripeAPI.swift */; }; + DF1EC524A1915E344687F5AC /* UIFont+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EAF12F161469C070B822369 /* UIFont+Stripe.swift */; }; + DFF3092E51B6C3ED81AB1448 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FF688F66CD047D08B3AE0CB /* String+Localized.swift */; }; + E2B25D45D457A76A782D9089 /* STPAnalyticEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B6C28853EF0316366FB8DC4 /* STPAnalyticEvent.swift */; }; + E344C20A07D8B8F33B530974 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B2A342F16484705840F1B5 /* TestConstants.swift */; }; + E6EF91C32CB9DC410082DD1B /* Locale+StripeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6EF91C22CB9DC3C0082DD1B /* Locale+StripeTests.swift */; }; + EFE476BA387E91BE1D5D3E1D /* URLRequest+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B75708617FB765AB211FD9A /* URLRequest+StripeTest.swift */; }; + EFF90360C85642F7F2898186 /* URLEncoderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2E1D80D0342CF09CB05415 /* URLEncoderTest.swift */; }; + F5DB5D52E2668136FF6D70D6 /* NSMutableURLRequest+StripeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CC52EF207F05E0EFAEACD8 /* NSMutableURLRequest+StripeTest.swift */; }; + F628BBE9FDA9D3A217ACA753 /* STPNumericStringValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D856053F99E32515DEDD8EDF /* STPNumericStringValidator.swift */; }; + F990430B20393BB003EAA3F7 /* STPAnalyticsClient+StripeCoreTestingUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB161994BFDC59E28D9D7A5 /* STPAnalyticsClient+StripeCoreTestingUtils.swift */; }; + FB9C2A2A491ED0B8C9864B10 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 047A18B360C8583235A2ABFC /* OHHTTPStubs */; }; + FCACE815CE9073F3FE18C185 /* test_image.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CB1E5F31B0941D8B096B360 /* test_image.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 773BD5B9B4846D879C13CFE1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CBF07079FA432D2BFCE24022 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3E987A4F06C4DE962E9F3CB8; + remoteInfo = StripeCoreTestUtils; + }; + 7FEF9BDDDD738CE997EC053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CBF07079FA432D2BFCE24022 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7877B31445857B119EA45445; + remoteInfo = StripeCore; + }; + E1EC5611DA9FE94C4E0EF3A8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CBF07079FA432D2BFCE24022 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7877B31445857B119EA45445; + remoteInfo = StripeCore; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 18178F9338B4379A125C292F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 5D04C08A37B43A01ADAAA114 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 5F3E583CF98B20ACEDBC39ED /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 00D8E07F0DDBA1577A52156C /* EmptyResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyResponse.swift; sourceTree = ""; }; + 013DF1F8EC4A4FFA1A38B725 /* StripeCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 019EC578C08FE61F858E1F1F /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 02D48D61A84BE5788EA61E59 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 06A516593E48DCFD5D98D702 /* Decimal+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+StripeCore.swift"; sourceTree = ""; }; + 0ACE2C3A8889C655E58EEA67 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 0B6C28853EF0316366FB8DC4 /* STPAnalyticEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticEvent.swift; sourceTree = ""; }; + 160FD8F504CCAC864C27AA62 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + 1678E0A16D46DA2B4D3B3ECD /* UIImage+StripeCoreTestingUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+StripeCoreTestingUtils.swift"; sourceTree = ""; }; + 181E67908573FC358CCED4BF /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + 192E71FE64C5FDE929992CC4 /* AnalyticsClientV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsClientV2.swift; sourceTree = ""; }; + 1DD5B6F1152FD1C4A508A103 /* STPAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticsClient.swift; sourceTree = ""; }; + 1EB161994BFDC59E28D9D7A5 /* STPAnalyticsClient+StripeCoreTestingUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+StripeCoreTestingUtils.swift"; sourceTree = ""; }; + 1FCE36551600C3E53BEAF8F0 /* StripeCodableTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeCodableTest.swift; sourceTree = ""; }; + 2147B1CA0A1B65566D7AB7C6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 21608B7BC24190523363CB3D /* NSArray+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSArray+Stripe.swift"; sourceTree = ""; }; + 21E64986F72C7BD8B1105A95 /* InstallMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallMethod.swift; sourceTree = ""; }; + 23506F3E93ECA5A96DCE7E31 /* NSError+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+StripeCore.swift"; sourceTree = ""; }; + 27EB57C159E5F10F50D071E5 /* MockAnalyticsClientV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsClientV2.swift; sourceTree = ""; }; + 2B16FDA9232BD4FBD4B13EC2 /* STPAnalyticsClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticsClientTest.swift; sourceTree = ""; }; + 2B75708617FB765AB211FD9A /* URLRequest+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+StripeTest.swift"; sourceTree = ""; }; + 2BD3D02EBA8A732A9C832925 /* NSMutableURLRequest+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableURLRequest+Stripe.swift"; sourceTree = ""; }; + 2DE48D0086BED21F9E837D0B /* XCTestCase+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Stripe.swift"; sourceTree = ""; }; + 2F1BFF01B1EA57F6EA7BFBF1 /* StripeAPIConfiguration+Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeAPIConfiguration+Version.swift"; sourceTree = ""; }; + 303695D2ECDFFCA9C1B68E53 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 31402722B97FC2D9A3A74E73 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 31AD3BDB2B0C23E40080C800 /* Locale+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeCore.swift"; sourceTree = ""; }; + 31CDFC2F2BA372B200B3DD91 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 325E8108336F1178A10D139C /* STPLocalizationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizationUtils.swift; sourceTree = ""; }; + 32CB3702691056D3404A8C5F /* StripeAPIConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeAPIConfiguration.swift; sourceTree = ""; }; + 33A34DF206B0980BA1D2258F /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + 34895253739089BC125D2625 /* Dictionary+StripeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+StripeTests.swift"; sourceTree = ""; }; + 34D803C2F907529E780B0296 /* STPMultipartFormDataEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMultipartFormDataEncoder.swift; sourceTree = ""; }; + 3A4F598BF353D8335B0340D5 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3FECC037162676AF2E8DCAEC /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + 3FFB15A6996F610D627A42B3 /* PaymentsSDKVariant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentsSDKVariant.swift; sourceTree = ""; }; + 40A497A121A7C792624E7948 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + 40A77DE176741B3A542FE890 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43D9EC920BB9059E0C652178 /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + 440D8DFB25A1F7FBC01BE1D7 /* AnalyticsClientV2Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsClientV2Test.swift; sourceTree = ""; }; + 45D4100901F9445AC1FD453A /* UIImage+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+StripeCore.swift"; sourceTree = ""; }; + 45F11FF08733CF61D880640D /* UserDefaults+PaymentsCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+PaymentsCore.swift"; sourceTree = ""; }; + 4689F6B4384244D9FD282560 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 48A3D6592296104A1512AE92 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 4910B9272C3D8F3F00B030D4 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = ""; }; + 492039922CA47A8600CE2072 /* ElementsSessionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementsSessionContext.swift; sourceTree = ""; }; + 493B33052CA3015600E3622F /* LinkMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkMode.swift; sourceTree = ""; }; + 49424775D3233411D9C2473B /* StripeCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeCodable.swift; sourceTree = ""; }; + 49538DBF8457D96707A2DA56 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 49E8CE292CD146CE0009DFBB /* KeyedEncodingContainer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyedEncodingContainer+Extensions.swift"; sourceTree = ""; }; + 49ECDA402CA340E100F647F0 /* AsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncTests.swift; sourceTree = ""; }; + 49F3828C2CC02D43001CE69A /* BillingAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BillingAddress.swift; sourceTree = ""; }; + 4A8030BF88608CA86E295F18 /* Enums+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Enums+CustomStringConvertible.swift"; sourceTree = ""; }; + 4C51E3FA5EE3587BB7BBC634 /* STPError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPError.swift; sourceTree = ""; }; + 4EC3BCEEECB3E1485B18F0C4 /* FinancialConnectionsSDKInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSDKInterface.swift; sourceTree = ""; }; + 4FF290DF69F5FD1004BBDECA /* TestJSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestJSONEncoder.swift; sourceTree = ""; }; + 536085BA191EC2942523A7DB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + 54D4E87D67740BF3C05638FD /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 561E5F74D9E5676F6E72E93F /* KeyPathExpectation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathExpectation.swift; sourceTree = ""; }; + 58D2EB990E533C5D42635676 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 591E592C9F3E5D4CB08A1847 /* STPDispatchFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPDispatchFunctions.swift; sourceTree = ""; }; + 599C8A64EB3988BCE32E2B05 /* UIActivityIndicatorView+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+Stripe.swift"; sourceTree = ""; }; + 59EBF56CBE900B3EE80E73A5 /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 5CE5D62D8BA864BFB38B70C5 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + 5EAF12F161469C070B822369 /* UIFont+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Stripe.swift"; sourceTree = ""; }; + 625636EFF4844186C7A31FAF /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + 64635404BD4D5D62486A7626 /* UnknownFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnknownFields.swift; sourceTree = ""; }; + 66CC52EF207F05E0EFAEACD8 /* NSMutableURLRequest+StripeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMutableURLRequest+StripeTest.swift"; sourceTree = ""; }; + 6A05FB442BCF24100001D128 /* FinancialConnectionsSDKResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSDKResult.swift; sourceTree = ""; }; + 6A05FB462BCF24370001D128 /* FinancialConnectionsLinkedBank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLinkedBank.swift; sourceTree = ""; }; + 6A05FB482BCF244A0001D128 /* InstantDebitsLinkedBank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantDebitsLinkedBank.swift; sourceTree = ""; }; + 6A05FB4A2BCF245C0001D128 /* FinancialConnectionsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsEvent.swift; sourceTree = ""; }; + 6B9282382C94A99400277765 /* STPAnalyticsEventTranslatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticsEventTranslatorTest.swift; sourceTree = ""; }; + 6B987BD92C910F5500DDFDB3 /* STPAnalyticsEventTranslator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticsEventTranslator.swift; sourceTree = ""; }; + 6B987BDC2C9111BC00DDFDB3 /* Notification+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Stripe.swift"; sourceTree = ""; }; + 6B9C7B822BC73B1C007D5A28 /* AnalyticsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsHelper.swift; sourceTree = ""; }; + 6CDBCD70CB220014972B49A5 /* PluginDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDetector.swift; sourceTree = ""; }; + 6E852B53CF75A119D3810B41 /* NSBundle+Stripe_AppName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSBundle+Stripe_AppName.swift"; sourceTree = ""; }; + 6EEB07003465364DBAFA7DEB /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + 6F9ED2ADD9406CF7534BB6C9 /* STPAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAPIClient.swift; sourceTree = ""; }; + 73CE623A81057C4063A1E0C4 /* StripeFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeFile.swift; sourceTree = ""; }; + 7507497162F5684AEA59E301 /* String+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+StripeCore.swift"; sourceTree = ""; }; + 758B9C9C2252A91FE4221702 /* STPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = ""; }; + 7727AEEFD2FC880BADDA1872 /* Dictionary+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Stripe.swift"; sourceTree = ""; }; + 77814365E9D13DD0A3EF0DCD /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 77CAB1A5AC107D8756CA1CBF /* Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Async.swift; sourceTree = ""; }; + 794AB67D466C8947FB4A7CFE /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + 7B890F162E1C247D5CA1A9E6 /* AnalyticLoggableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticLoggableError.swift; sourceTree = ""; }; + 7F2E1D80D0342CF09CB05415 /* URLEncoderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLEncoderTest.swift; sourceTree = ""; }; + 80C548FC21ABF10F4E88B0D0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 84E70193A9CA42A0C53E48C1 /* STPAPIClient+ErrorResponseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+ErrorResponseTest.swift"; sourceTree = ""; }; + 86C675ABC9D68378DC699DED /* StripeJSONShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeJSONShared.swift; sourceTree = ""; }; + 8B3D6ADD516777DE13E79792 /* StripeJSONDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeJSONDecoder.swift; sourceTree = ""; }; + 8CB1E5F31B0941D8B096B360 /* test_image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = test_image.png; sourceTree = ""; }; + 8D54A5C18C898B385629EB3D /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + 8F2949BE5129DC4BBCD69B05 /* NSArray+StripeCoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSArray+StripeCoreTest.swift"; sourceTree = ""; }; + 8FF688F66CD047D08B3AE0CB /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + 9077630E4A5307B83B4BA19E /* STPAppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAppInfo.swift; sourceTree = ""; }; + 918D27F150CD87E3A5E6F377 /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + 937066801E91C99C50192364 /* BundleLocatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleLocatorProtocol.swift; sourceTree = ""; }; + 971D5A6F04E598041BE789EA /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + 9761FF113E3F940E2B2BEBB1 /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + 9C2AA21DAF340432CA98C67C /* STPDeviceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPDeviceUtils.swift; sourceTree = ""; }; + 9D4EB9EA1128F9DA9120BB04 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 9E415507B0152B07796114CC /* NSError+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+Stripe.swift"; sourceTree = ""; }; + A0FBC76F73C3791701072BBA /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + A4336F071B0CE13306C8EB93 /* StripeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeAPI.swift; sourceTree = ""; }; + A466F6075DCC39D5A976FB22 /* STPTelemetryClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPTelemetryClient.swift; sourceTree = ""; }; + A4E4C534285F49A97A04D2B8 /* UIView+StripeCoreTestingUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+StripeCoreTestingUtils.swift"; sourceTree = ""; }; + A5334916D2A4F927645C2569 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; + A619F83E71E5076329177CED /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + A626F821ECEB25DFC007DB71 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + A7647C1B64CA0F0087327413 /* STPURLCallbackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPURLCallbackHandler.swift; sourceTree = ""; }; + AA435F92864CA975753865DF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + AAE1CFBD0403500A5DCBDE71 /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + ABF11F814A986AA710410FF8 /* Analytic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytic.swift; sourceTree = ""; }; + ACBD856B96CD58D2938B3F02 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + ACCAA72D98FBC827A38282BB /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + AD027F7695EC6F59CF32F4B9 /* StripeJSONEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeJSONEncoder.swift; sourceTree = ""; }; + AF1FC608B88ACEA4C7438866 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + AF82F68E8D3A8286FD31DB13 /* APIStubbedTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIStubbedTestCase.swift; sourceTree = ""; }; + B3CA4B1855BF8D2F08F275A9 /* Error_SerializeForLoggingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error_SerializeForLoggingTest.swift; sourceTree = ""; }; + B3F73CA77397EAE70480FF25 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + B644BC3F2BB234D6001FA436 /* STPAssert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STPAssert.swift; sourceTree = ""; }; + B67F2CA12BAB8B690011E34A /* AnalyticLoggableErrorV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticLoggableErrorV2.swift; sourceTree = ""; }; + B67F2CA32BAB91860011E34A /* AnalyticLoggableErrorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticLoggableErrorTest.swift; sourceTree = ""; }; + B6DBB2BE2BA8C4E300783D15 /* STPAnalyticsClient+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+Error.swift"; sourceTree = ""; }; + B8B76840742D2CAF2931355A /* URLSession+Retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Retry.swift"; sourceTree = ""; }; + BC5151C3B6CB4130E9C259A6 /* NSCharacterSet+StripeCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSCharacterSet+StripeCore.swift"; sourceTree = ""; }; + BCF062352C38A5173260C46A /* StripeServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeServiceError.swift; sourceTree = ""; }; + C188AD8AE244CD6076679095 /* ServerErrorMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerErrorMapper.swift; sourceTree = ""; }; + C1C174081A48DD86978D270D /* FileDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDownloader.swift; sourceTree = ""; }; + C3DCE66C04A91C235972687D /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + C51179DB520568C246BF3AF0 /* URLEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLEncoder.swift; sourceTree = ""; }; + C666CC926642D7AA76E75B5B /* StripeCoreTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeCoreTestUtils.h; sourceTree = ""; }; + CB0E9DC72CB9C79E00E083D1 /* LinkBankPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkBankPaymentMethod.swift; sourceTree = ""; }; + CB2721EE8E075E700FF3E58A /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + CC70CDF482E22A29B11466F7 /* STPAPIClient+EmptyResponseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+EmptyResponseTest.swift"; sourceTree = ""; }; + CD9288E147B8C9D33CCB5045 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; + CF15BAFA3E58D68668EE6DCC /* STPSnapshotTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSnapshotTestCase.swift; sourceTree = ""; }; + D28769EBA666E0BDFC69954F /* UIImage+StripeCoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+StripeCoreTests.swift"; sourceTree = ""; }; + D475CD59778FAC1028670AB5 /* NSURLComponents+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSURLComponents+Stripe.swift"; sourceTree = ""; }; + D856053F99E32515DEDD8EDF /* STPNumericStringValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPNumericStringValidator.swift; sourceTree = ""; }; + DC24A98C4020646F99456187 /* File_IdentityDocument.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = File_IdentityDocument.json; sourceTree = ""; }; + DDFAA07C8EBB5F5A5AC00E54 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + DE5E5D17713B48931D84BF42 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + DEA5353BC5359E08128E116A /* StripeCoreBundleLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeCoreBundleLocator.swift; sourceTree = ""; }; + E19F97D1606B517158C7F75A /* NetworkDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDetector.swift; sourceTree = ""; }; + E1C72BA9C44FF60A0E7BEF76 /* STPMultipartFormDataPart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMultipartFormDataPart.swift; sourceTree = ""; }; + E4B2A342F16484705840F1B5 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; + E60F4A38EEF5EA11568B3A64 /* StripeCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeCore.h; sourceTree = ""; }; + E6EF91C22CB9DC3C0082DD1B /* Locale+StripeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeTests.swift"; sourceTree = ""; }; + E919FBEB852CFEA9517FCBDC /* FraudDetectionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FraudDetectionData.swift; sourceTree = ""; }; + EA55726A0FE74A4D90A10C01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + ECF3D265DCDD0D64F6D7E6B2 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + EDBE83430B8FE494EF2AB5F3 /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + F2C7099CB854E1E90424235C /* MockAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsClient.swift; sourceTree = ""; }; + F4FC2FC535DAF9B340F9EA75 /* STPAPIClient+FileUpload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAPIClient+FileUpload.swift"; sourceTree = ""; }; + F66C883A10AA9BAB7456BF03 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + FEC0AF5BF0A4799D2C0C7445 /* StripeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeError.swift; sourceTree = ""; }; + FEC4F08F659CDD39ED1A2BAC /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + FF816C87A745BA4F9186BDF5 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + FFCE8DD57B10BD3C56885EA5 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 36C2CC9FAA8CF079757D35A7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 87536D729B8201085E380C32 /* XCTest.framework in Frameworks */, + 917B4E193E5A1233F1A2E80E /* StripeCore.framework in Frameworks */, + 6B4156FCFAEDD1C73DC6EDAD /* iOSSnapshotTestCase in Frameworks */, + 40AF3B2EB6B82C9A4DD61033 /* OHHTTPStubs in Frameworks */, + CB1FB2383FAEE0194C39E4DE /* OHHTTPStubsSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4417A26336170F53277E168C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 08871FCD9E9E47681135431B /* XCTest.framework in Frameworks */, + 2D7A4FDBED7E3FA3D17BBB54 /* StripeCore.framework in Frameworks */, + 700E9DAD0407455F11F9F03E /* StripeCoreTestUtils.framework in Frameworks */, + 7133C71AC18633F59BA18F57 /* iOSSnapshotTestCase in Frameworks */, + FB9C2A2A491ED0B8C9864B10 /* OHHTTPStubs in Frameworks */, + 01C16F6C63483A646A8C368E /* OHHTTPStubsSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BCAC06556AB210EEF302C16A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0B14FED7BBB8A76630B7C0D0 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 59EBF56CBE900B3EE80E73A5 /* Project-Debug.xcconfig */, + DDFAA07C8EBB5F5A5AC00E54 /* Project-Release.xcconfig */, + 9761FF113E3F940E2B2BEBB1 /* StripeiOS Tests-Debug.xcconfig */, + 33A34DF206B0980BA1D2258F /* StripeiOS Tests-Release.xcconfig */, + C3DCE66C04A91C235972687D /* StripeiOS-Debug.xcconfig */, + CB2721EE8E075E700FF3E58A /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 150799C744EFBDF77919FDCB /* Products */ = { + isa = PBXGroup; + children = ( + 40A77DE176741B3A542FE890 /* StripeCore.framework */, + 013DF1F8EC4A4FFA1A38B725 /* StripeCoreTests.xctest */, + 3A4F598BF353D8335B0340D5 /* StripeCoreTestUtils.framework */, + ); + name = Products; + sourceTree = ""; + }; + 1726B28CF173B9D1D6426896 /* Categories */ = { + isa = PBXGroup; + children = ( + 1EB161994BFDC59E28D9D7A5 /* STPAnalyticsClient+StripeCoreTestingUtils.swift */, + 1678E0A16D46DA2B4D3B3ECD /* UIImage+StripeCoreTestingUtils.swift */, + A4E4C534285F49A97A04D2B8 /* UIView+StripeCoreTestingUtils.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 29DB7151F72CD0D44389A6C8 /* Helpers */ = { + isa = PBXGroup; + children = ( + 7F2E1D80D0342CF09CB05415 /* URLEncoderTest.swift */, + 49ECDA402CA340E100F647F0 /* AsyncTests.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 3A3F265C90373C3EF2F61523 /* Categories */ = { + isa = PBXGroup; + children = ( + E6EF91C22CB9DC3C0082DD1B /* Locale+StripeTests.swift */, + 34895253739089BC125D2625 /* Dictionary+StripeTests.swift */, + 8F2949BE5129DC4BBCD69B05 /* NSArray+StripeCoreTest.swift */, + 66CC52EF207F05E0EFAEACD8 /* NSMutableURLRequest+StripeTest.swift */, + D28769EBA666E0BDFC69954F /* UIImage+StripeCoreTests.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 4A2CD3EB30A5C54998ED92DD /* Mock Files */ = { + isa = PBXGroup; + children = ( + 8CB1E5F31B0941D8B096B360 /* test_image.png */, + ); + path = "Mock Files"; + sourceTree = ""; + }; + 4AD775367138C40FE2C98BAF /* Connections Bindings */ = { + isa = PBXGroup; + children = ( + 4EC3BCEEECB3E1485B18F0C4 /* FinancialConnectionsSDKInterface.swift */, + 6A05FB442BCF24100001D128 /* FinancialConnectionsSDKResult.swift */, + 6A05FB462BCF24370001D128 /* FinancialConnectionsLinkedBank.swift */, + 6A05FB482BCF244A0001D128 /* InstantDebitsLinkedBank.swift */, + 6A05FB4A2BCF245C0001D128 /* FinancialConnectionsEvent.swift */, + 493B33052CA3015600E3622F /* LinkMode.swift */, + 492039922CA47A8600CE2072 /* ElementsSessionContext.swift */, + 49F3828C2CC02D43001CE69A /* BillingAddress.swift */, + CB0E9DC72CB9C79E00E083D1 /* LinkBankPaymentMethod.swift */, + ); + path = "Connections Bindings"; + sourceTree = ""; + }; + 4D7125C790190CE509BBA3DF /* UI */ = { + isa = PBXGroup; + children = ( + 599C8A64EB3988BCE32E2B05 /* UIActivityIndicatorView+Stripe.swift */, + 5EAF12F161469C070B822369 /* UIFont+Stripe.swift */, + ); + path = UI; + sourceTree = ""; + }; + 523A1ACB7F366E117F7FE2B4 /* Analytics */ = { + isa = PBXGroup; + children = ( + 440D8DFB25A1F7FBC01BE1D7 /* AnalyticsClientV2Test.swift */, + B3CA4B1855BF8D2F08F275A9 /* Error_SerializeForLoggingTest.swift */, + B67F2CA32BAB91860011E34A /* AnalyticLoggableErrorTest.swift */, + 2B16FDA9232BD4FBD4B13EC2 /* STPAnalyticsClientTest.swift */, + 6B9282382C94A99400277765 /* STPAnalyticsEventTranslatorTest.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 5308E014F5451516D5EF5314 /* StripeCoreTestUtils */ = { + isa = PBXGroup; + children = ( + 1726B28CF173B9D1D6426896 /* Categories */, + B761C6E8D9BC02D59DBE5A38 /* Mock Files */, + BECA727F3CB319C263B09720 /* Mocks */, + AF82F68E8D3A8286FD31DB13 /* APIStubbedTestCase.swift */, + 58D2EB990E533C5D42635676 /* Info.plist */, + 561E5F74D9E5676F6E72E93F /* KeyPathExpectation.swift */, + CF15BAFA3E58D68668EE6DCC /* STPSnapshotTestCase.swift */, + C666CC926642D7AA76E75B5B /* StripeCoreTestUtils.h */, + E4B2A342F16484705840F1B5 /* TestConstants.swift */, + 2B75708617FB765AB211FD9A /* URLRequest+StripeTest.swift */, + 2DE48D0086BED21F9E837D0B /* XCTestCase+Stripe.swift */, + ); + path = StripeCoreTestUtils; + sourceTree = ""; + }; + 596D621A8083DA1246F36BFB /* StripeCore */ = { + isa = PBXGroup; + children = ( + D053330294A0C39AE271E025 /* Resources */, + A42B59A6A0D70019833D6562 /* Source */, + 4689F6B4384244D9FD282560 /* Info.plist */, + E60F4A38EEF5EA11568B3A64 /* StripeCore.h */, + 31CDFC2F2BA372B200B3DD91 /* PrivacyInfo.xcprivacy */, + ); + path = StripeCore; + sourceTree = ""; + }; + 59A858862FE8D4B90A5D930F /* Categories */ = { + isa = PBXGroup; + children = ( + 06A516593E48DCFD5D98D702 /* Decimal+StripeCore.swift */, + 31AD3BDB2B0C23E40080C800 /* Locale+StripeCore.swift */, + 7727AEEFD2FC880BADDA1872 /* Dictionary+Stripe.swift */, + 4A8030BF88608CA86E295F18 /* Enums+CustomStringConvertible.swift */, + 21608B7BC24190523363CB3D /* NSArray+Stripe.swift */, + 6E852B53CF75A119D3810B41 /* NSBundle+Stripe_AppName.swift */, + BC5151C3B6CB4130E9C259A6 /* NSCharacterSet+StripeCore.swift */, + 9E415507B0152B07796114CC /* NSError+Stripe.swift */, + 23506F3E93ECA5A96DCE7E31 /* NSError+StripeCore.swift */, + 2BD3D02EBA8A732A9C832925 /* NSMutableURLRequest+Stripe.swift */, + D475CD59778FAC1028670AB5 /* NSURLComponents+Stripe.swift */, + 7507497162F5684AEA59E301 /* String+StripeCore.swift */, + 45D4100901F9445AC1FD453A /* UIImage+StripeCore.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 66BA4CFF0FEE2E4813FB4D31 /* Analytics */ = { + isa = PBXGroup; + children = ( + 6B987BDB2C91119100DDFDB3 /* Eventing */, + B6DBB2BE2BA8C4E300783D15 /* STPAnalyticsClient+Error.swift */, + ABF11F814A986AA710410FF8 /* Analytic.swift */, + 6B9C7B822BC73B1C007D5A28 /* AnalyticsHelper.swift */, + 7B890F162E1C247D5CA1A9E6 /* AnalyticLoggableError.swift */, + B67F2CA12BAB8B690011E34A /* AnalyticLoggableErrorV2.swift */, + 192E71FE64C5FDE929992CC4 /* AnalyticsClientV2.swift */, + E19F97D1606B517158C7F75A /* NetworkDetector.swift */, + 6CDBCD70CB220014972B49A5 /* PluginDetector.swift */, + 0B6C28853EF0316366FB8DC4 /* STPAnalyticEvent.swift */, + 1DD5B6F1152FD1C4A508A103 /* STPAnalyticsClient.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 69F23304563CCD7F2625E757 /* Localization */ = { + isa = PBXGroup; + children = ( + 325E8108336F1178A10D139C /* STPLocalizationUtils.swift */, + 758B9C9C2252A91FE4221702 /* STPLocalizedString.swift */, + 8FF688F66CD047D08B3AE0CB /* String+Localized.swift */, + ); + path = Localization; + sourceTree = ""; + }; + 6B987BDB2C91119100DDFDB3 /* Eventing */ = { + isa = PBXGroup; + children = ( + 6B987BD92C910F5500DDFDB3 /* STPAnalyticsEventTranslator.swift */, + 6B987BDC2C9111BC00DDFDB3 /* Notification+Stripe.swift */, + ); + path = Eventing; + sourceTree = ""; + }; + 6D488D8BF0ADC365242F73C8 /* API Bindings */ = { + isa = PBXGroup; + children = ( + C2430C1558B85CB4E87334A9 /* Models */, + 6F9ED2ADD9406CF7534BB6C9 /* STPAPIClient.swift */, + F4FC2FC535DAF9B340F9EA75 /* STPAPIClient+FileUpload.swift */, + 9077630E4A5307B83B4BA19E /* STPAppInfo.swift */, + 34D803C2F907529E780B0296 /* STPMultipartFormDataEncoder.swift */, + E1C72BA9C44FF60A0E7BEF76 /* STPMultipartFormDataPart.swift */, + A4336F071B0CE13306C8EB93 /* StripeAPI.swift */, + 32CB3702691056D3404A8C5F /* StripeAPIConfiguration.swift */, + 2F1BFF01B1EA57F6EA7BFBF1 /* StripeAPIConfiguration+Version.swift */, + FEC0AF5BF0A4799D2C0C7445 /* StripeError.swift */, + BCF062352C38A5173260C46A /* StripeServiceError.swift */, + ); + path = "API Bindings"; + sourceTree = ""; + }; + 705ECC4F6AE12F0021C3726D = { + isa = PBXGroup; + children = ( + 92F3912D23FF91835DDAFCEC /* Project */, + D8A0B2C221DBF456254B6A02 /* Frameworks */, + 150799C744EFBDF77919FDCB /* Products */, + ); + sourceTree = ""; + }; + 7FD20B12B83EC2F371B71C37 /* API Bindings */ = { + isa = PBXGroup; + children = ( + CC70CDF482E22A29B11466F7 /* STPAPIClient+EmptyResponseTest.swift */, + 84E70193A9CA42A0C53E48C1 /* STPAPIClient+ErrorResponseTest.swift */, + 1FCE36551600C3E53BEAF8F0 /* StripeCodableTest.swift */, + ); + path = "API Bindings"; + sourceTree = ""; + }; + 83EEC663D4FA92DC540C6CD8 /* Helpers */ = { + isa = PBXGroup; + children = ( + 77CAB1A5AC107D8756CA1CBF /* Async.swift */, + 937066801E91C99C50192364 /* BundleLocatorProtocol.swift */, + C1C174081A48DD86978D270D /* FileDownloader.swift */, + 21E64986F72C7BD8B1105A95 /* InstallMethod.swift */, + 3FFB15A6996F610D627A42B3 /* PaymentsSDKVariant.swift */, + C188AD8AE244CD6076679095 /* ServerErrorMapper.swift */, + B644BC3F2BB234D6001FA436 /* STPAssert.swift */, + 9C2AA21DAF340432CA98C67C /* STPDeviceUtils.swift */, + 591E592C9F3E5D4CB08A1847 /* STPDispatchFunctions.swift */, + 4C51E3FA5EE3587BB7BBC634 /* STPError.swift */, + D856053F99E32515DEDD8EDF /* STPNumericStringValidator.swift */, + A7647C1B64CA0F0087327413 /* STPURLCallbackHandler.swift */, + DEA5353BC5359E08128E116A /* StripeCoreBundleLocator.swift */, + C51179DB520568C246BF3AF0 /* URLEncoder.swift */, + B8B76840742D2CAF2931355A /* URLSession+Retry.swift */, + 4910B9272C3D8F3F00B030D4 /* Result+Extensions.swift */, + 49E8CE292CD146CE0009DFBB /* KeyedEncodingContainer+Extensions.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 8801864DFEC2305B2D7D9CB1 /* Localizations */ = { + isa = PBXGroup; + children = ( + CFF68ABB8E67E83695FAD8EA /* Localizable.strings */, + ); + path = Localizations; + sourceTree = ""; + }; + 92F3912D23FF91835DDAFCEC /* Project */ = { + isa = PBXGroup; + children = ( + 0B14FED7BBB8A76630B7C0D0 /* BuildConfigurations */, + 596D621A8083DA1246F36BFB /* StripeCore */, + 9EF2F5FC20874E0DD2A5D40C /* StripeCoreTests */, + 5308E014F5451516D5EF5314 /* StripeCoreTestUtils */, + ); + name = Project; + sourceTree = ""; + }; + 9EF2F5FC20874E0DD2A5D40C /* StripeCoreTests */ = { + isa = PBXGroup; + children = ( + 523A1ACB7F366E117F7FE2B4 /* Analytics */, + 7FD20B12B83EC2F371B71C37 /* API Bindings */, + 3A3F265C90373C3EF2F61523 /* Categories */, + D2E220B8DFC490E1837426FF /* External */, + 29DB7151F72CD0D44389A6C8 /* Helpers */, + 4A2CD3EB30A5C54998ED92DD /* Mock Files */, + EA55726A0FE74A4D90A10C01 /* Info.plist */, + ); + path = StripeCoreTests; + sourceTree = ""; + }; + A42B59A6A0D70019833D6562 /* Source */ = { + isa = PBXGroup; + children = ( + 66BA4CFF0FEE2E4813FB4D31 /* Analytics */, + 6D488D8BF0ADC365242F73C8 /* API Bindings */, + 59A858862FE8D4B90A5D930F /* Categories */, + E701DB3E90EB6EA0A1A8B54E /* Coder */, + 4AD775367138C40FE2C98BAF /* Connections Bindings */, + 83EEC663D4FA92DC540C6CD8 /* Helpers */, + 69F23304563CCD7F2625E757 /* Localization */, + CDC187FE4F4024F9D463D19E /* Telemetry */, + 4D7125C790190CE509BBA3DF /* UI */, + ); + path = Source; + sourceTree = ""; + }; + B761C6E8D9BC02D59DBE5A38 /* Mock Files */ = { + isa = PBXGroup; + children = ( + DC24A98C4020646F99456187 /* File_IdentityDocument.json */, + ); + path = "Mock Files"; + sourceTree = ""; + }; + BECA727F3CB319C263B09720 /* Mocks */ = { + isa = PBXGroup; + children = ( + F2C7099CB854E1E90424235C /* MockAnalyticsClient.swift */, + 27EB57C159E5F10F50D071E5 /* MockAnalyticsClientV2.swift */, + A5334916D2A4F927645C2569 /* MockData.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + C2430C1558B85CB4E87334A9 /* Models */ = { + isa = PBXGroup; + children = ( + 00D8E07F0DDBA1577A52156C /* EmptyResponse.swift */, + 73CE623A81057C4063A1E0C4 /* StripeFile.swift */, + ); + path = Models; + sourceTree = ""; + }; + CDC187FE4F4024F9D463D19E /* Telemetry */ = { + isa = PBXGroup; + children = ( + E919FBEB852CFEA9517FCBDC /* FraudDetectionData.swift */, + A466F6075DCC39D5A976FB22 /* STPTelemetryClient.swift */, + 45F11FF08733CF61D880640D /* UserDefaults+PaymentsCore.swift */, + ); + path = Telemetry; + sourceTree = ""; + }; + D053330294A0C39AE271E025 /* Resources */ = { + isa = PBXGroup; + children = ( + 8801864DFEC2305B2D7D9CB1 /* Localizations */, + ); + path = Resources; + sourceTree = ""; + }; + D2E220B8DFC490E1837426FF /* External */ = { + isa = PBXGroup; + children = ( + 4FF290DF69F5FD1004BBDECA /* TestJSONEncoder.swift */, + ); + path = External; + sourceTree = ""; + }; + D8A0B2C221DBF456254B6A02 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 54D4E87D67740BF3C05638FD /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E701DB3E90EB6EA0A1A8B54E /* Coder */ = { + isa = PBXGroup; + children = ( + 49424775D3233411D9C2473B /* StripeCodable.swift */, + 8B3D6ADD516777DE13E79792 /* StripeJSONDecoder.swift */, + AD027F7695EC6F59CF32F4B9 /* StripeJSONEncoder.swift */, + 86C675ABC9D68378DC699DED /* StripeJSONShared.swift */, + 64635404BD4D5D62486A7626 /* UnknownFields.swift */, + ); + path = Coder; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 05A892536A440B2BC219C11E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8310D598D6D40BAD23880D3F /* StripeCoreTestUtils.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6D58026052EFE4DFDBDD359E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E807567D7320A7D512127AF /* StripeCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 03B50F59824D1D18AA073D57 /* StripeCoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5BFC042D8F3E5E305D374495 /* Build configuration list for PBXNativeTarget "StripeCoreTests" */; + buildPhases = ( + 7E01B8C349DCABCBA8852FF2 /* Sources */, + C6C0F33F30CA8B01DF9A723B /* Resources */, + 18178F9338B4379A125C292F /* Embed Frameworks */, + 4417A26336170F53277E168C /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 1EC7711EFEF07D6C119D7A70 /* PBXTargetDependency */, + 3670D483B4DC94B06A1CDEF6 /* PBXTargetDependency */, + ); + name = StripeCoreTests; + packageProductDependencies = ( + 6FFF766FF9F630392C6D446A /* iOSSnapshotTestCase */, + 047A18B360C8583235A2ABFC /* OHHTTPStubs */, + 35087E242A157890B5FAC5A4 /* OHHTTPStubsSwift */, + ); + productName = StripeCoreTests; + productReference = 013DF1F8EC4A4FFA1A38B725 /* StripeCoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 3E987A4F06C4DE962E9F3CB8 /* StripeCoreTestUtils */ = { + isa = PBXNativeTarget; + buildConfigurationList = 793D3D10C90D10C84728C1DE /* Build configuration list for PBXNativeTarget "StripeCoreTestUtils" */; + buildPhases = ( + 05A892536A440B2BC219C11E /* Headers */, + FB221D4CA3C9AF254BA94D68 /* Sources */, + A6E0A12258BAD6C06BF7A2AE /* Resources */, + 5F3E583CF98B20ACEDBC39ED /* Embed Frameworks */, + 36C2CC9FAA8CF079757D35A7 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 08EA430A5FF96F1A708DE57B /* PBXTargetDependency */, + ); + name = StripeCoreTestUtils; + packageProductDependencies = ( + 23D22B2C40BA7C182BCE50B2 /* iOSSnapshotTestCase */, + 9AB19C81E50535A3407582B7 /* OHHTTPStubs */, + E86AC7DD5F4DE2780E0AC425 /* OHHTTPStubsSwift */, + ); + productName = StripeCoreTestUtils; + productReference = 3A4F598BF353D8335B0340D5 /* StripeCoreTestUtils.framework */; + productType = "com.apple.product-type.framework"; + }; + 7877B31445857B119EA45445 /* StripeCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = A8F477F78CC20A59C791D5F1 /* Build configuration list for PBXNativeTarget "StripeCore" */; + buildPhases = ( + 6D58026052EFE4DFDBDD359E /* Headers */, + 97036C8C1B79423F1F57DD1B /* Sources */, + 5442591D82093A8B0397F70D /* Resources */, + 5D04C08A37B43A01ADAAA114 /* Embed Frameworks */, + BCAC06556AB210EEF302C16A /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeCore; + productName = StripeCore; + productReference = 40A77DE176741B3A542FE890 /* StripeCore.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CBF07079FA432D2BFCE24022 /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = 490BD7E6715C734C2A99C55B /* Build configuration list for PBXProject "StripeCore" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = 705ECC4F6AE12F0021C3726D; + packageReferences = ( + 9F8CE18BDCF6289B6266E92D /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + 1E364BF0E57ED61D2A87D929 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + ); + productRefGroup = 150799C744EFBDF77919FDCB /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7877B31445857B119EA45445 /* StripeCore */, + 3E987A4F06C4DE962E9F3CB8 /* StripeCoreTestUtils */, + 03B50F59824D1D18AA073D57 /* StripeCoreTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5442591D82093A8B0397F70D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31CDFC302BA372B200B3DD91 /* PrivacyInfo.xcprivacy in Resources */, + 2B98F4F0120888B12EF3B181 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A6E0A12258BAD6C06BF7A2AE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C9C320ADCCF1548D6562CE94 /* File_IdentityDocument.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C6C0F33F30CA8B01DF9A723B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FCACE815CE9073F3FE18C185 /* test_image.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7E01B8C349DCABCBA8852FF2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B67F2CA42BAB91860011E34A /* AnalyticLoggableErrorTest.swift in Sources */, + A4DBBFD379C4E0120BD25C56 /* STPAPIClient+EmptyResponseTest.swift in Sources */, + 79DA4102C501FC2E53D946B5 /* STPAPIClient+ErrorResponseTest.swift in Sources */, + 53D46A03B77577EE21F4B166 /* StripeCodableTest.swift in Sources */, + 3E9FC2CD06E1D5F6B09872E9 /* AnalyticsClientV2Test.swift in Sources */, + 84487D8E9B08106C89753536 /* Error_SerializeForLoggingTest.swift in Sources */, + 0A78AD04075C43A4059C344E /* STPAnalyticsClientTest.swift in Sources */, + 934CCB00769674F13192A126 /* Dictionary+StripeTests.swift in Sources */, + E6EF91C32CB9DC410082DD1B /* Locale+StripeTests.swift in Sources */, + 49ECDA412CA340E100F647F0 /* AsyncTests.swift in Sources */, + A50CB2ACAC1DCF9539D76F25 /* NSArray+StripeCoreTest.swift in Sources */, + F5DB5D52E2668136FF6D70D6 /* NSMutableURLRequest+StripeTest.swift in Sources */, + 8AD68C8D00A0BCF94E5230DC /* UIImage+StripeCoreTests.swift in Sources */, + 6D68B868938BAB15A843B33C /* TestJSONEncoder.swift in Sources */, + EFF90360C85642F7F2898186 /* URLEncoderTest.swift in Sources */, + 6B9282392C94A99400277765 /* STPAnalyticsEventTranslatorTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97036C8C1B79423F1F57DD1B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 87274985CE5E750FA8D34648 /* EmptyResponse.swift in Sources */, + 6A52ABC06783A90B9E339948 /* StripeFile.swift in Sources */, + 6A05FB472BCF24370001D128 /* FinancialConnectionsLinkedBank.swift in Sources */, + 9FBA50345D53E82AA974F672 /* STPAPIClient+FileUpload.swift in Sources */, + 31AD3BDC2B0C23E40080C800 /* Locale+StripeCore.swift in Sources */, + C164984958CDC2C9CA4B6316 /* STPAPIClient.swift in Sources */, + 97D0B2120678A75F75648D84 /* STPAppInfo.swift in Sources */, + 12FF091C555F75B914464475 /* STPMultipartFormDataEncoder.swift in Sources */, + 552DA7969984C443617DBC3E /* STPMultipartFormDataPart.swift in Sources */, + DAD4099D03E43A0CA89464CD /* StripeAPI.swift in Sources */, + D22FAB2F1AE9AE43C1808747 /* StripeAPIConfiguration+Version.swift in Sources */, + 6B987BDA2C910F5500DDFDB3 /* STPAnalyticsEventTranslator.swift in Sources */, + 677951C643328D76E46720A5 /* StripeAPIConfiguration.swift in Sources */, + 6A05FB4B2BCF245C0001D128 /* FinancialConnectionsEvent.swift in Sources */, + 02A26B79617FAE660C9EB506 /* StripeError.swift in Sources */, + 3B9D69AB1CB61725C7A012B6 /* StripeServiceError.swift in Sources */, + 95156E152471058151076A51 /* Analytic.swift in Sources */, + 766FE8E61B44967F057ED424 /* AnalyticLoggableError.swift in Sources */, + 3815D229613D52D7799805B0 /* AnalyticsClientV2.swift in Sources */, + 35931C64F06BEB233A219869 /* NetworkDetector.swift in Sources */, + A454F368C505B2D80F0D8B19 /* PluginDetector.swift in Sources */, + E2B25D45D457A76A782D9089 /* STPAnalyticEvent.swift in Sources */, + B644BC402BB234D6001FA436 /* STPAssert.swift in Sources */, + AE26BFFBE9B1242E10CA052F /* STPAnalyticsClient.swift in Sources */, + B35FD03DD246CC4DD6C4F3C0 /* Decimal+StripeCore.swift in Sources */, + 4B2FAC57E03D8654A177C408 /* Dictionary+Stripe.swift in Sources */, + 42DE35681C71A931F65E0E7D /* Enums+CustomStringConvertible.swift in Sources */, + D8FEF16798A791F5BF7EA4B2 /* NSArray+Stripe.swift in Sources */, + 492039932CA47A8600CE2072 /* ElementsSessionContext.swift in Sources */, + C318A6B6CD599B06DA7CE706 /* NSBundle+Stripe_AppName.swift in Sources */, + 2AA9B01C8A2D2BADC4619629 /* NSCharacterSet+StripeCore.swift in Sources */, + C5BF4B8AE85FF72EC6382EC0 /* NSError+Stripe.swift in Sources */, + B67F2CA22BAB8B690011E34A /* AnalyticLoggableErrorV2.swift in Sources */, + D144C3A657E5C16975CB2191 /* NSError+StripeCore.swift in Sources */, + 9843D9B7D886C373C7AC71E4 /* NSMutableURLRequest+Stripe.swift in Sources */, + 9DCBC08C182ED76A962961E7 /* NSURLComponents+Stripe.swift in Sources */, + 563A42FA383FA9AA5FA4CDCE /* String+StripeCore.swift in Sources */, + CA09DC1EC4142701B31F9673 /* UIImage+StripeCore.swift in Sources */, + C7C0EC68130760F73201F81B /* StripeCodable.swift in Sources */, + 44DE84C8BFB403E1FB7E2E82 /* StripeJSONDecoder.swift in Sources */, + 970D95FEA3BC216351DE3C5E /* StripeJSONEncoder.swift in Sources */, + 71CD1AE29AA09552DF61131E /* StripeJSONShared.swift in Sources */, + 6ED5C41DBDAB475BF1119E98 /* UnknownFields.swift in Sources */, + 772292156A4A80CEA9D9C487 /* FinancialConnectionsSDKInterface.swift in Sources */, + 5553D952F91D193D453D777D /* Async.swift in Sources */, + CAF857D45689FBEF17627E80 /* BundleLocatorProtocol.swift in Sources */, + 59CA874015261241AC255907 /* FileDownloader.swift in Sources */, + 45DAE581F74EF7E11C64212B /* InstallMethod.swift in Sources */, + 096274D0729AA8849FAD103C /* PaymentsSDKVariant.swift in Sources */, + 49E8CE2A2CD146CE0009DFBB /* KeyedEncodingContainer+Extensions.swift in Sources */, + DA5A05459309B9B77ACDD736 /* STPDeviceUtils.swift in Sources */, + 4910B9282C3D8F3F00B030D4 /* Result+Extensions.swift in Sources */, + CB0E9DC82CB9C79E00E083D1 /* LinkBankPaymentMethod.swift in Sources */, + 83790210FFC2DD764C042C8E /* STPDispatchFunctions.swift in Sources */, + 72DA29CA8A750E8B00DBF3D4 /* STPError.swift in Sources */, + F628BBE9FDA9D3A217ACA753 /* STPNumericStringValidator.swift in Sources */, + C9B6C451F9A46C9FB031CE95 /* STPURLCallbackHandler.swift in Sources */, + A62AEDF871AC89489FE19A13 /* ServerErrorMapper.swift in Sources */, + B6DBB2BF2BA8C4E400783D15 /* STPAnalyticsClient+Error.swift in Sources */, + 6A05FB452BCF24100001D128 /* FinancialConnectionsSDKResult.swift in Sources */, + 49F3828D2CC02D43001CE69A /* BillingAddress.swift in Sources */, + 62FD088E003BE06F5413FB4F /* StripeCoreBundleLocator.swift in Sources */, + 17CE96B50813CF626293CBF9 /* URLEncoder.swift in Sources */, + 0709F5D265CC641E6DE1011D /* URLSession+Retry.swift in Sources */, + 6A05FB492BCF244A0001D128 /* InstantDebitsLinkedBank.swift in Sources */, + 2991461DD354A6124CCF78DA /* STPLocalizationUtils.swift in Sources */, + 4506A7016EA7C45796D3A30D /* STPLocalizedString.swift in Sources */, + DFF3092E51B6C3ED81AB1448 /* String+Localized.swift in Sources */, + 6B987BDD2C9111BC00DDFDB3 /* Notification+Stripe.swift in Sources */, + 89DB623A200678B4E9845AF2 /* FraudDetectionData.swift in Sources */, + 493B33062CA3015600E3622F /* LinkMode.swift in Sources */, + 920832EE256E377572DD41EB /* STPTelemetryClient.swift in Sources */, + 3B27DDDDC91F1599BF1469BB /* UserDefaults+PaymentsCore.swift in Sources */, + B6D129B2DC90FA1F8A1F5BCB /* UIActivityIndicatorView+Stripe.swift in Sources */, + DF1EC524A1915E344687F5AC /* UIFont+Stripe.swift in Sources */, + 6B9C7B832BC73B1C007D5A28 /* AnalyticsHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FB221D4CA3C9AF254BA94D68 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 330FDCF901D11882D4866DDE /* APIStubbedTestCase.swift in Sources */, + F990430B20393BB003EAA3F7 /* STPAnalyticsClient+StripeCoreTestingUtils.swift in Sources */, + A987DFFA404EBD0788DAB21A /* UIImage+StripeCoreTestingUtils.swift in Sources */, + 631D09E67497B49BBCA26192 /* UIView+StripeCoreTestingUtils.swift in Sources */, + 838BDB53C4F6AC3C0567DE6A /* KeyPathExpectation.swift in Sources */, + 0F4A1BAE6774B90C72F578CC /* MockAnalyticsClient.swift in Sources */, + 3D90376B1883E4BE64712197 /* MockAnalyticsClientV2.swift in Sources */, + CB8A47A5FD057112CB607DE9 /* MockData.swift in Sources */, + 33E1AA9687131A7ECF384848 /* STPSnapshotTestCase.swift in Sources */, + E344C20A07D8B8F33B530974 /* TestConstants.swift in Sources */, + EFE476BA387E91BE1D5D3E1D /* URLRequest+StripeTest.swift in Sources */, + 48A6CCB4008A5060C2655C5F /* XCTestCase+Stripe.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 08EA430A5FF96F1A708DE57B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCore; + target = 7877B31445857B119EA45445 /* StripeCore */; + targetProxy = 7FEF9BDDDD738CE997EC053B /* PBXContainerItemProxy */; + }; + 1EC7711EFEF07D6C119D7A70 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCore; + target = 7877B31445857B119EA45445 /* StripeCore */; + targetProxy = E1EC5611DA9FE94C4E0EF3A8 /* PBXContainerItemProxy */; + }; + 3670D483B4DC94B06A1CDEF6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeCoreTestUtils; + target = 3E987A4F06C4DE962E9F3CB8 /* StripeCoreTestUtils */; + targetProxy = 773BD5B9B4846D879C13CFE1 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + CFF68ABB8E67E83695FAD8EA /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 181E67908573FC358CCED4BF /* bg-BG */, + 77814365E9D13DD0A3EF0DCD /* ca-ES */, + 918D27F150CD87E3A5E6F377 /* cs-CZ */, + 31402722B97FC2D9A3A74E73 /* da */, + DE5E5D17713B48931D84BF42 /* de */, + ACCAA72D98FBC827A38282BB /* el-GR */, + 80C548FC21ABF10F4E88B0D0 /* en */, + 536085BA191EC2942523A7DB /* en-GB */, + 02D48D61A84BE5788EA61E59 /* es */, + 3FECC037162676AF2E8DCAEC /* es-419 */, + B3F73CA77397EAE70480FF25 /* et-EE */, + FFCE8DD57B10BD3C56885EA5 /* fi */, + 794AB67D466C8947FB4A7CFE /* fil */, + 48A3D6592296104A1512AE92 /* fr */, + 971D5A6F04E598041BE789EA /* fr-CA */, + 40A497A121A7C792624E7948 /* hr */, + ECF3D265DCDD0D64F6D7E6B2 /* hu */, + A619F83E71E5076329177CED /* id */, + 303695D2ECDFFCA9C1B68E53 /* it */, + A626F821ECEB25DFC007DB71 /* ja */, + 160FD8F504CCAC864C27AA62 /* ko */, + 43D9EC920BB9059E0C652178 /* lt-LT */, + FEC4F08F659CDD39ED1A2BAC /* lv-LV */, + 8D54A5C18C898B385629EB3D /* ms-MY */, + 5CE5D62D8BA864BFB38B70C5 /* mt */, + 0ACE2C3A8889C655E58EEA67 /* nb */, + 49538DBF8457D96707A2DA56 /* nl */, + EDBE83430B8FE494EF2AB5F3 /* nn-NO */, + CD9288E147B8C9D33CCB5045 /* pl-PL */, + 019EC578C08FE61F858E1F1F /* pt-BR */, + ACBD856B96CD58D2938B3F02 /* pt-PT */, + 625636EFF4844186C7A31FAF /* ro-RO */, + 9D4EB9EA1128F9DA9120BB04 /* ru */, + A0FBC76F73C3791701072BBA /* sk-SK */, + AAE1CFBD0403500A5DCBDE71 /* sl-SI */, + 2147B1CA0A1B65566D7AB7C6 /* sv */, + FF816C87A745BA4F9186BDF5 /* tr */, + F66C883A10AA9BAB7456BF03 /* vi */, + AA435F92864CA975753865DF /* zh-Hans */, + AF1FC608B88ACEA4C7438866 /* zh-Hant */, + 6EEB07003465364DBAFA7DEB /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1F32ABF11A7EE50134DB4EF3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DDFAA07C8EBB5F5A5AC00E54 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 406CE0D780344B0E0C1C3FD3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33A34DF206B0980BA1D2258F /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCoreTestUtils/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCoreTestUtils; + PRODUCT_NAME = StripeCoreTestUtils; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Release; + }; + 5B5D93072F435361816D6E2D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CB2721EE8E075E700FF3E58A /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeCore/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-core"; + PRODUCT_NAME = StripeCore; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; + 63A2A6AAA9584841EC2CE54A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9761FF113E3F940E2B2BEBB1 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCoreTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCoreTests; + PRODUCT_NAME = StripeCoreTests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Debug; + }; + 78054EDDE3060C858086FA90 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C3DCE66C04A91C235972687D /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeCore/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-core"; + PRODUCT_NAME = StripeCore; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; + 97012C251E738AC10133C8EE /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 59EBF56CBE900B3EE80E73A5 /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + A8FBA9F7BAC07A05CC29D4E6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9761FF113E3F940E2B2BEBB1 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCoreTestUtils/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCoreTestUtils; + PRODUCT_NAME = StripeCoreTestUtils; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Debug; + }; + DDEA9A9EE069588F2D4E29B2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33A34DF206B0980BA1D2258F /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeCoreTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeCoreTests; + PRODUCT_NAME = StripeCoreTests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 490BD7E6715C734C2A99C55B /* Build configuration list for PBXProject "StripeCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97012C251E738AC10133C8EE /* Debug */, + 1F32ABF11A7EE50134DB4EF3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5BFC042D8F3E5E305D374495 /* Build configuration list for PBXNativeTarget "StripeCoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63A2A6AAA9584841EC2CE54A /* Debug */, + DDEA9A9EE069588F2D4E29B2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 793D3D10C90D10C84728C1DE /* Build configuration list for PBXNativeTarget "StripeCoreTestUtils" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A8FBA9F7BAC07A05CC29D4E6 /* Debug */, + 406CE0D780344B0E0C1C3FD3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A8F477F78CC20A59C791D5F1 /* Build configuration list for PBXNativeTarget "StripeCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 78054EDDE3060C858086FA90 /* Debug */, + 5B5D93072F435361816D6E2D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 1E364BF0E57ED61D2A87D929 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; + 9F8CE18BDCF6289B6266E92D /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davidme-stripe/OHHTTPStubs"; + requirement = { + branch = "stripe-mock"; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 047A18B360C8583235A2ABFC /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; + 23D22B2C40BA7C182BCE50B2 /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; + 35087E242A157890B5FAC5A4 /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; + 6FFF766FF9F630392C6D446A /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; + 9AB19C81E50535A3407582B7 /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; + E86AC7DD5F4DE2780E0AC425 /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = CBF07079FA432D2BFCE24022 /* Project object */; +} diff --git a/StripeCore/StripeCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeCore/StripeCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeCore/StripeCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeCore/StripeCore.xcodeproj/xcshareddata/xcschemes/StripeCore.xcscheme b/StripeCore/StripeCore.xcodeproj/xcshareddata/xcschemes/StripeCore.xcscheme new file mode 100644 index 00000000..0bc1bf2d --- /dev/null +++ b/StripeCore/StripeCore.xcodeproj/xcshareddata/xcschemes/StripeCore.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeCore/StripeCore/Info.plist b/StripeCore/StripeCore/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeCore/StripeCore/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeCore/StripeCore/PrivacyInfo.xcprivacy b/StripeCore/StripeCore/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..2ee8cd9f --- /dev/null +++ b/StripeCore/StripeCore/PrivacyInfo.xcprivacy @@ -0,0 +1,33 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/StripeCore/StripeCore/Resources/Localizations/bg-BG.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..584a0a39 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/bg-BG.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Затваряне"; + +"Scan Card" = "Сканиране на картата"; + +"Scan card" = "Сканиране на карта"; + +"The IBAN you entered is invalid." = "IBAN, който сте въвели, е невалиден."; + +"There was an error processing your card -- try again in a few seconds" = "Възникна грешка при обработката на Вашата карта -- опитайте отново след няколко секунди"; + +"There was an unexpected error -- try again in a few seconds" = "Това беше неочаквана грешка -- опитайте отново след няколко секунди"; + +"Try again" = "Опитайте отново"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Използваме Stripe, за да потвърдим данните на картата Ви. Stripe може да използва и съхранява данните Ви в съответствие с правилата си за защита на личните данни. Научете повече"; + +"Your card has expired" = "Вашата карта е изтекла"; + +"Your card was declined" = "Вашата карта бе отхвърлена"; + +"Your card's expiration month is invalid" = "Месецът на валидност на Вашата карта е невалиден"; + +"Your card's expiration year is invalid" = "Годината на валидност на Вашата карта е невалидна"; + +"Your card's number is invalid" = "Номерът на Вашата карта е невалиден"; + +"Your card's security code is invalid" = "Кодът за сигурност на Вашата карта е невалиден"; + +"Your name is invalid." = "Името Ви е невалидно."; + +"Your payment method was declined." = "Начинът Ви на плащане беше отказан."; diff --git a/StripeCore/StripeCore/Resources/Localizations/ca-ES.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..02e221ca --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/ca-ES.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Tanca"; + +"Scan Card" = "Escanejar targeta"; + +"Scan card" = "Escanejar targeta"; + +"The IBAN you entered is invalid." = "L'IBAN és incorrecte."; + +"There was an error processing your card -- try again in a few seconds" = "Hi ha hagut un error processant la teva targeta - torna-ho a intentar en uns segons"; + +"There was an unexpected error -- try again in a few seconds" = "Hi ha hagut un error inesperat - torna-ho a intentar en uns segons"; + +"Try again" = "Intenta-ho un altre cop"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Emprem Stripe per verificar els detalls de la vostra targeta. És possible que Stripe faci ús i emmagatzemi les dades d'acord amb la política de privadesa. Més informació"; + +"Your card has expired" = "La teva targeta ha caducat"; + +"Your card was declined" = "S'ha rebutjat la teva targeta"; + +"Your card's expiration month is invalid" = "El mes de caducitat de la teva targeta no és vàlid"; + +"Your card's expiration year is invalid" = "L'any de caducitat de la teva targeta no és vàlid"; + +"Your card's number is invalid" = "El número de la teva targeta no és vàlid"; + +"Your card's security code is invalid" = "El codi de seguretat de la teva targeta no és vàlid"; + +"Your name is invalid." = "El nom no és vàlid."; + +"Your payment method was declined." = "S'ha rebutjat el mètode de pagament."; diff --git a/StripeCore/StripeCore/Resources/Localizations/cs-CZ.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..2602882f --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Zavřít"; + +"Scan Card" = "Naskenovat kartu"; + +"Scan card" = "Naskenovat kartu"; + +"The IBAN you entered is invalid." = "Číslo IBAN, které jste zadali, je neplatné."; + +"There was an error processing your card -- try again in a few seconds" = "Při zpracování Vaší karty došlo k chybě -- zkuste to za několik sekund znovu"; + +"There was an unexpected error -- try again in a few seconds" = "Došlo k neočekávané chybě -- zkuste to znovu za několik sekund"; + +"Try again" = "Zkusit znovu"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "K ověření údajů o vaší kartě používáme službu Stripe. Společnost Stripe může vaše údaje používat a ukládat v souladu se svými zásadami ochrany osobních údajů. Více informací"; + +"Your card has expired" = "Platnost Vaší karty uplynula"; + +"Your card was declined" = "Vaše karta byla odmítnuta"; + +"Your card's expiration month is invalid" = "Měsíc konce platnosti Vaší karty je neplatný"; + +"Your card's expiration year is invalid" = "Rok konce platnosti Vaší karty je neplatný"; + +"Your card's number is invalid" = "Číslo Vaší karty je neplatné"; + +"Your card's security code is invalid" = "Bezpečnostní kód Vaší karty je neplatný"; + +"Your name is invalid." = "Vaše jméno je neplatné."; + +"Your payment method was declined." = "Vaše platební metoda byla zamítnuta."; diff --git a/StripeCore/StripeCore/Resources/Localizations/da.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/da.lproj/Localizable.strings new file mode 100644 index 00000000..db47ad06 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/da.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Luk"; + +"Scan Card" = "Scan kort"; + +"Scan card" = "Scan kort"; + +"The IBAN you entered is invalid." = "Det indtastede IBAN-nummer er ugyldigt."; + +"There was an error processing your card -- try again in a few seconds" = "Der opstod en fejl under behandling af dit kort – prøv igen om et par sekunder"; + +"There was an unexpected error -- try again in a few seconds" = "Der opstod en uventet fejl – prøv igen om et par sekunder"; + +"Try again" = "Prøv igen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Vi bruger Stripe til at bekræfte dine kortoplysninger. Stripe kan bruge og gemme dine oplysninger i henhold til selskabets Privatlivspolitik. Lær mere"; + +"Your card has expired" = "Dit kort er udløbet"; + +"Your card was declined" = "Dit kort blev afvist"; + +"Your card's expiration month is invalid" = "Kortets udløbsmåned er ugyldig"; + +"Your card's expiration year is invalid" = "Kortets udløbsår er ugyldigt"; + +"Your card's number is invalid" = "Kortnummeret er ugyldigt"; + +"Your card's security code is invalid" = "Kortets sikkerhedskode er ugyldig"; + +"Your name is invalid." = "Dit navn er ugyldigt."; + +"Your payment method was declined." = "Din betalingsmetode blev afvist."; diff --git a/StripeCore/StripeCore/Resources/Localizations/de.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/de.lproj/Localizable.strings new file mode 100644 index 00000000..c8ebab3f --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/de.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Schließen"; + +"Scan Card" = "Karte scannen"; + +"Scan card" = "Karte scannen"; + +"The IBAN you entered is invalid." = "Die eingegebene IBAN ist ungültig."; + +"There was an error processing your card -- try again in a few seconds" = "Fehler bei der Verarbeitung Ihrer Karte – versuchen Sie es in ein paar Sekunden noch einmal."; + +"There was an unexpected error -- try again in a few seconds" = "Unerwarteter Fehler – versuchen Sie es in ein paar Sekunden noch einmal."; + +"Try again" = "Erneut versuchen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Wir nutzen Stripe, um Ihre Kartenangaben zu verifizieren. Stripe kann im Einklang mit seiner Datenschutzerklärung Ihre Daten verwenden und speichern. Mehr erfahren"; + +"Your card has expired" = "Ihre Karte ist abgelaufen."; + +"Your card was declined" = "Ihre Karte wurde abgelehnt."; + +"Your card's expiration month is invalid" = "Der Ablaufmonat Ihrer Karte ist ungültig"; + +"Your card's expiration year is invalid" = "Das Ablaufjahr Ihrer Karte ist ungültig."; + +"Your card's number is invalid" = "Die Kartennummer ist ungültig."; + +"Your card's security code is invalid" = "Der Sicherheitscode Ihrer Karte ist ungültig."; + +"Your name is invalid." = "Ihr Name ist ungültig."; + +"Your payment method was declined." = "Ihre Zahlungsmethode wurde abgelehnt."; diff --git a/StripeCore/StripeCore/Resources/Localizations/el-GR.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..aa5faa6d --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/el-GR.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Κλείσιμο"; + +"Scan Card" = "Σάρωση κάρτας"; + +"Scan card" = "Σάρωση κάρτας"; + +"The IBAN you entered is invalid." = "Ο κωδικός IBAN που εισαγάγατε δεν είναι έγκυρος."; + +"There was an error processing your card -- try again in a few seconds" = "Προέκυψε απροσδόκητο σφάλμα κατά την επεξεργασία της κάρτας σας -- δοκιμάστε ξανά σε μερικά δευτερόλεπτα"; + +"There was an unexpected error -- try again in a few seconds" = "Προέκυψε απροσδόκητο σφάλμα -- δοκιμάστε ξανά σε μερικά δευτερόλεπτα"; + +"Try again" = "Δοκιμάστε ξανά"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Χρησιμοποιούμε τη Stripe για να επαληθεύσουμε τα στοιχεία της κάρτας σας. Η Stripe μπορεί να χρησιμοποιεί και να αποθηκεύει τα δεδομένα σας σύμφωνα με την πολιτική απορρήτου της. Μάθετε περισσότερα"; + +"Your card has expired" = "Η κάρτα σας έχει λήξει"; + +"Your card was declined" = "Η κάρτα σας απορρίφθηκε"; + +"Your card's expiration month is invalid" = "Ο μήνας λήξης της κάρτας σας δεν είναι έγκυρος"; + +"Your card's expiration year is invalid" = "Το έτος λήξης της κάρτας σας δεν είναι έγκυρο"; + +"Your card's number is invalid" = "Ο αριθμός της κάρτας σας δεν είναι έγκυρος"; + +"Your card's security code is invalid" = "Ο κωδικός ασφαλείας της κάρτας σας δεν είναι έγκυρος"; + +"Your name is invalid." = "Το όνομά σας δεν είναι έγκυρο."; + +"Your payment method was declined." = "Η μέθοδος πληρωμής σας απορρίφθηκε."; diff --git a/StripeCore/StripeCore/Resources/Localizations/en-GB.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..c88d2cad --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/en-GB.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Close"; + +"Scan Card" = "Scan Card"; + +"Scan card" = "Scan card"; + +"The IBAN you entered is invalid." = "The IBAN you entered is invalid."; + +"There was an error processing your card -- try again in a few seconds" = "There was an error processing your card - please try again in a few seconds"; + +"There was an unexpected error -- try again in a few seconds" = "There was an unexpected error - please try again in a few seconds"; + +"Try again" = "Try again"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more"; + +"Your card has expired" = "Your card has expired"; + +"Your card was declined" = "Your card has been declined"; + +"Your card's expiration month is invalid" = "Your card's expiry month is invalid"; + +"Your card's expiration year is invalid" = "Your card's expiry year is invalid"; + +"Your card's number is invalid" = "Your card's number is invalid"; + +"Your card's security code is invalid" = "Your card's security code is invalid"; + +"Your name is invalid." = "Your name is invalid."; + +"Your payment method was declined." = "Your payment method was declined."; diff --git a/StripeCore/StripeCore/Resources/Localizations/en.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..23b8cfb6 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/en.lproj/Localizable.strings @@ -0,0 +1,48 @@ +/* Text for close button */ +"Close" = "Close"; + +/* Text for button to scan a credit card */ +"Scan Card" = "Scan Card"; + +/* Button title to open camera to scan credit/debit card */ +"Scan card" = "Scan card"; + +/* An error message displayed when the customer's iban is invalid. */ +"The IBAN you entered is invalid." = "The IBAN you entered is invalid."; + +/* Error when there is a problem processing the credit card */ +"There was an error processing your card -- try again in a few seconds" = "There was an error processing your card -- try again in a few seconds"; + +/* Unexpected error, such as a 500 from Stripe or a JSON parse error */ +"There was an unexpected error -- try again in a few seconds" = "There was an unexpected error -- try again in a few seconds"; + +/* Text for a retry button */ +"Try again" = "Try again"; + +/* Informational text informing the user that Stripe is used to process data and a link to Stripe's privacy policy */ +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more"; + +/* Error when the card has already expired */ +"Your card has expired" = "Your card has expired"; + +/* Error when the card was declined by the credit card networks */ +"Your card was declined" = "Your card was declined"; + +/* Error when the card's expiration month is not valid */ +"Your card's expiration month is invalid" = "Your card's expiration month is invalid"; + +/* Error when the card's expiration year is not valid */ +"Your card's expiration year is invalid" = "Your card's expiration year is invalid"; + +/* Error when the card number is not valid */ +"Your card's number is invalid" = "Your card's number is invalid"; + +/* Error when the card's CVC is not valid */ +"Your card's security code is invalid" = "Your card's security code is invalid"; + +/* Error when customer's name is invalid */ +"Your name is invalid." = "Your name is invalid."; + +/* Error message when a payment method gets declined. */ +"Your payment method was declined." = "Your payment method was declined."; + diff --git a/StripeCore/StripeCore/Resources/Localizations/es-419.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..300eb90e --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/es-419.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Cerrar"; + +"Scan Card" = "Escanear tarjeta"; + +"Scan card" = "Escanear tarjeta"; + +"The IBAN you entered is invalid." = "El IBAN que ingresaste no es válido."; + +"There was an error processing your card -- try again in a few seconds" = "Hubo un error al procesar tu tarjeta. Vuelve a intentar en unos segundos."; + +"There was an unexpected error -- try again in a few seconds" = "Se produjo un error inesperado. Vuelve a intentar en unos segundos."; + +"Try again" = "Reintentar"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Usamos Stripe para verificar los datos de tu tarjeta. Stripe puede usar y almacenar tus datos de acuerdo con la política de privacidad. Más información"; + +"Your card has expired" = "Tu tarjeta ha vencido."; + +"Your card was declined" = "Tu tarjeta fue rechazada."; + +"Your card's expiration month is invalid" = "El mes de vencimiento de tu tarjeta no es válido."; + +"Your card's expiration year is invalid" = "El año de vencimiento de tu tarjeta no es válido"; + +"Your card's number is invalid" = "El número de tarjeta no es válido"; + +"Your card's security code is invalid" = "El código de seguridad de tu tarjeta no es válido."; + +"Your name is invalid." = "El nombre no es válido."; + +"Your payment method was declined." = "Tu método de pago fue rechazado."; diff --git a/StripeCore/StripeCore/Resources/Localizations/es.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/es.lproj/Localizable.strings new file mode 100644 index 00000000..45c575ed --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/es.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Cerrar"; + +"Scan Card" = "Escanear tarjeta"; + +"Scan card" = "Escanear tarjeta"; + +"The IBAN you entered is invalid." = "El IBAN introducido no es válido."; + +"There was an error processing your card -- try again in a few seconds" = "Error al procesar la tarjeta. Vuelve a intentarlo en unos segundos."; + +"There was an unexpected error -- try again in a few seconds" = "Se ha producido un error inesperado. Vuelve a intentarlo en unos segundos"; + +"Try again" = "Reintentar"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Usamos Stripe para verificar los datos de tu tarjeta. Stripe puede usar y almacenar tus datos de acuerdo con la política de privacidad. Más información"; + +"Your card has expired" = "La tarjeta ha caducado"; + +"Your card was declined" = "La tarjeta ha sido rechazada"; + +"Your card's expiration month is invalid" = "El mes de caducidad de tu tarjeta no es válido"; + +"Your card's expiration year is invalid" = "El año de caducidad de tu tarjeta no es válido"; + +"Your card's number is invalid" = "El número de tarjeta no es válido"; + +"Your card's security code is invalid" = "El código de seguridad de tu tarjeta no es válido"; + +"Your name is invalid." = "El nombre no es válido."; + +"Your payment method was declined." = "Se ha rechazado tu método de pago."; diff --git a/StripeCore/StripeCore/Resources/Localizations/et-EE.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..773793ea --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/et-EE.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Sule"; + +"Scan Card" = "Skanni kaart"; + +"Scan card" = "Skanni kaart"; + +"The IBAN you entered is invalid." = "Sisestatud IBAN on kehtetu."; + +"There was an error processing your card -- try again in a few seconds" = "Kaardi töötlemisel esines tõrge – proovige mõne sekundi pärast uuesti"; + +"There was an unexpected error -- try again in a few seconds" = "Esines ootamatu tõrge – proovige mõne sekundi pärast uuesti"; + +"Try again" = "Proovi uuesti"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Kasutame teie kaardiandmete kontrollimiseks Stripe'i. Stripe võib teie andmeid kasutada ja säilitada vastavalt oma privaatsuspoliitikale. Vaata lähemalt"; + +"Your card has expired" = "Kaart on aegunud"; + +"Your card was declined" = "Kaart lükati tagasi"; + +"Your card's expiration month is invalid" = "Kaardi aegumiskuu on kehtetu"; + +"Your card's expiration year is invalid" = "Kaardi aegumisaasta on kehtetu"; + +"Your card's number is invalid" = "Kaardi number on kehtetu"; + +"Your card's security code is invalid" = "Kaardi turvakood on kehtetu"; + +"Your name is invalid." = "Teie nimi on kehtetu."; + +"Your payment method was declined." = "Teie makseviis lükati tagasi."; diff --git a/StripeCore/StripeCore/Resources/Localizations/fi.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/fi.lproj/Localizable.strings new file mode 100644 index 00000000..d1ce73d5 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/fi.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Sulje"; + +"Scan Card" = "Skannaa kortti"; + +"Scan card" = "Skannaa kortti"; + +"The IBAN you entered is invalid." = "Antamasi IBAN on virheellinen."; + +"There was an error processing your card -- try again in a few seconds" = "Kortin käsittelyssä tapahtui virhe – kokeile uudelleen muutaman sekunnin kuluttua"; + +"There was an unexpected error -- try again in a few seconds" = "Odottamaton virhe – kokeile uudelleen muutaman sekunnin kuluttua"; + +"Try again" = "Yritä uudelleen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Käytämme Stripeä korttitietojen vahvistamiseen. Stripe voi käyttää ja säilyttää tietojasi heidän tietosuojaselosteensa mukaisesti. Lue lisää"; + +"Your card has expired" = "Korttisi on vanhentunut"; + +"Your card was declined" = "Korttiasi ei hyväksytty"; + +"Your card's expiration month is invalid" = "Kortin erääntymiskuukausi ei kelpaa"; + +"Your card's expiration year is invalid" = "Kortin erääntymisvuosi ei kelpaa"; + +"Your card's number is invalid" = "Kortin numero ei kelpaa"; + +"Your card's security code is invalid" = "Kortin turvakoodi ei kelpaa"; + +"Your name is invalid." = "Nimi on virheellinen."; + +"Your payment method was declined." = "Maksutapasi on hylätty."; diff --git a/StripeCore/StripeCore/Resources/Localizations/fil.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/fil.lproj/Localizable.strings new file mode 100644 index 00000000..f03968ea --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/fil.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Isara"; + +"Scan Card" = "I-scan ang kard"; + +"Scan card" = "I-scan ang kard"; + +"The IBAN you entered is invalid." = "Ang IBAN na iniligay mo ay imbalido."; + +"There was an error processing your card -- try again in a few seconds" = "Nagkaroon ng kamalian sa pagproseso ng iyong kard -- subukin muli sa ilang segundo"; + +"There was an unexpected error -- try again in a few seconds" = "Nagkaroon ng di-inaasahang kamalian -- subukin muli sa ilang segundo"; + +"Try again" = "Subukan muli"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Gumagamit kami ng Stripe para i-verify ang mga detalye ng iyong kard. Maaaring gamitin at imbakin ng Stripe ang iyong data ayon sa patakaran sa privacy nito. Alamin ang higit pa"; + +"Your card has expired" = "Napaso na ang iyong kard"; + +"Your card was declined" = "Tinanggihan ang iyong kard"; + +"Your card's expiration month is invalid" = "Ang buwan ng pagkapaso ng iyong kard ay di balido"; + +"Your card's expiration year is invalid" = "Ang taon ng pagkapaso ng iyong kard ay di balido"; + +"Your card's number is invalid" = "Ang numero ng iyong kard ay di balido"; + +"Your card's security code is invalid" = "Ang security code ng iyong kard ay di balido"; + +"Your name is invalid." = "Ang iyong pangalan ay imbalido."; + +"Your payment method was declined." = "Tinanggihan ang iyong paraan ng pagbabayad."; diff --git a/StripeCore/StripeCore/Resources/Localizations/fr-CA.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..83832894 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/fr-CA.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Fermer"; + +"Scan Card" = "Scanner la carte"; + +"Scan card" = "Scanner la carte"; + +"The IBAN you entered is invalid." = "L'IBAN que vous avez saisi n'est pas valide."; + +"There was an error processing your card -- try again in a few seconds" = "Une erreur est survenue lors du traitement de votre carte. Réessayez dans quelques secondes."; + +"There was an unexpected error -- try again in a few seconds" = "Une erreur inattendue est survenue. Réessayez dans quelques secondes."; + +"Try again" = "Réessayer"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Nous faisons appel à Stripe pour vérifier les informations de votre carte. Stripe peut utiliser et stocker vos données conformément à sa politique de confidentialité. En savoir plus"; + +"Your card has expired" = "Votre carte a expiré"; + +"Your card was declined" = "Votre carte a été refusée"; + +"Your card's expiration month is invalid" = "Le mois d'expiration de votre carte n'est pas valide"; + +"Your card's expiration year is invalid" = "L'année d'expiration de votre carte n'est pas valide"; + +"Your card's number is invalid" = "Votre numéro de carte n'est pas valide"; + +"Your card's security code is invalid" = "Le code de sécurité de votre carte n'est pas valide"; + +"Your name is invalid." = "Votre nom n'est pas valide."; + +"Your payment method was declined." = "Votre moyen de paiement a été refusé."; diff --git a/StripeCore/StripeCore/Resources/Localizations/fr.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/fr.lproj/Localizable.strings new file mode 100644 index 00000000..f3ccd39e --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/fr.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Fermer"; + +"Scan Card" = "Scanner la carte"; + +"Scan card" = "Scanner la carte"; + +"The IBAN you entered is invalid." = "L'IBAN que vous avez saisi n'est pas valide."; + +"There was an error processing your card -- try again in a few seconds" = "Une erreur est survenue lors du traitement de votre carte. Réessayez dans quelques secondes."; + +"There was an unexpected error -- try again in a few seconds" = "Une erreur inattendue est survenue. Réessayez dans quelques secondes."; + +"Try again" = "Réessayer"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Nous faisons appel à Stripe pour vérifier les informations de votre carte. Stripe peut utiliser et stocker vos données conformément à sa politique de confidentialité. En savoir plus"; + +"Your card has expired" = "Votre carte est arrivée à expiration."; + +"Your card was declined" = "Votre carte a été refusée."; + +"Your card's expiration month is invalid" = "Le mois d'expiration de votre carte n'est pas valide."; + +"Your card's expiration year is invalid" = "L'année d'expiration de votre carte n'est pas valide."; + +"Your card's number is invalid" = "Votre numéro de carte n'est pas valide."; + +"Your card's security code is invalid" = "Le code de sécurité de votre carte n'est pas valide"; + +"Your name is invalid." = "Votre nom n'est pas valide."; + +"Your payment method was declined." = "Votre moyen de paiement a été refusé."; diff --git a/StripeCore/StripeCore/Resources/Localizations/hr.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/hr.lproj/Localizable.strings new file mode 100644 index 00000000..ce91f939 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/hr.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Zatvori"; + +"Scan Card" = "Skeniraj karticu"; + +"Scan card" = "Skeniraj karticu"; + +"The IBAN you entered is invalid." = "Uneseni IBAN nije valjan."; + +"There was an error processing your card -- try again in a few seconds" = "Došlo je do pogreške u obradi vaše kartice -- pokušajte ponovno za nekoliko sekundi"; + +"There was an unexpected error -- try again in a few seconds" = "Došlo je do neočekivane pogreške -- pokušajte ponovno za nekoliko sekundi"; + +"Try again" = "Pokušajte ponovo"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Koristimo Stripe za provjeru vaših podataka o kartici. Stripe može koristiti i pohranjivati vaše podatke u skladu sa svojim pravilima privatnosti. Saznajte više"; + +"Your card has expired" = "Vaša kartica je istekla"; + +"Your card was declined" = "Kartica je odbijena"; + +"Your card's expiration month is invalid" = "Mjesec isteka kartice nije valjan"; + +"Your card's expiration year is invalid" = "Godina isteka kartice nije valjana"; + +"Your card's number is invalid" = "Broj kartice nije valjan"; + +"Your card's security code is invalid" = "Sigurnosni kod vaše kartice nije valjan"; + +"Your name is invalid." = "Vaše ime je neispravno."; + +"Your payment method was declined." = "Vaš način plaćanja je odbijen."; diff --git a/StripeCore/StripeCore/Resources/Localizations/hu.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/hu.lproj/Localizable.strings new file mode 100644 index 00000000..2a5df893 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/hu.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Bezárás"; + +"Scan Card" = "Kártya beolvasása"; + +"Scan card" = "Kártya beolvasása"; + +"The IBAN you entered is invalid." = "A beírt IBAN-szám érvénytelen."; + +"There was an error processing your card -- try again in a few seconds" = "Hiba történt kártyája feldolgozása során, próbálja újra néhány másodperc múlva"; + +"There was an unexpected error -- try again in a few seconds" = "Váratlan hiba lépett fel, próbálja újra néhány másodperc múlva"; + +"Try again" = "Próbálja újra"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "A Stripe-ot használjuk a kártyájának az ellenőrzésére. A Stripe az adatvédelmi szabályzatának megfelelően használhatja és tárolhatja az Ön adatait. További tudnivalók"; + +"Your card has expired" = "A kártyája lejárt"; + +"Your card was declined" = "A kártyáját elutasítottuk"; + +"Your card's expiration month is invalid" = "Kártyájának lejárati hónapja érvénytelen"; + +"Your card's expiration year is invalid" = "A kártyája lejárati éve érvénytelen"; + +"Your card's number is invalid" = "Kártyaszáma érvénytelen"; + +"Your card's security code is invalid" = "Kártyájának biztonsági kódja érvénytelen"; + +"Your name is invalid." = "Érvénytelen név."; + +"Your payment method was declined." = "Fizetési módja elutasításra került."; diff --git a/StripeCore/StripeCore/Resources/Localizations/id.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/id.lproj/Localizable.strings new file mode 100644 index 00000000..0428f102 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/id.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Tutup"; + +"Scan Card" = "Pindai Kartu"; + +"Scan card" = "Pindai kartu"; + +"The IBAN you entered is invalid." = "IBAN yang Anda masukkan tidak valid."; + +"There was an error processing your card -- try again in a few seconds" = "Ada kesalahan saat memproses kartu Anda -- cobalah lagi dalam beberapa detik"; + +"There was an unexpected error -- try again in a few seconds" = "Ada kesalahan tak terduga -- cobalah lagi dalam beberapa detik"; + +"Try again" = "Coba lagi"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Kami menggunakan Stripe untuk memverifikasi detail kartu Anda. Stripe mungkin menggunakan dan menyimpan data Anda sesuai dengan kebijakan privasi mereka. Pelajari selengkapnya"; + +"Your card has expired" = "Kartu Anda telah kedaluwarsa"; + +"Your card was declined" = "Kartu Anda ditolak"; + +"Your card's expiration month is invalid" = "Bulan kedaluwarsa kartu Anda tidak valid"; + +"Your card's expiration year is invalid" = "Tahun kedaluwarsa kartu Anda tidak valid"; + +"Your card's number is invalid" = "Nomor kartu Anda tidak valid"; + +"Your card's security code is invalid" = "Kode keamanan kartu Anda tidak valid"; + +"Your name is invalid." = "Nama Anda tidak valid."; + +"Your payment method was declined." = "Metode pembayaran Anda ditolak."; diff --git a/StripeCore/StripeCore/Resources/Localizations/it.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/it.lproj/Localizable.strings new file mode 100644 index 00000000..3e02e03e --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/it.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Chiudi"; + +"Scan Card" = "Scansiona carta"; + +"Scan card" = "Scansiona carta"; + +"The IBAN you entered is invalid." = "L'IBAN inserito non è valido."; + +"There was an error processing your card -- try again in a few seconds" = "Si è verificato un errore durante l'elaborazione della carta. Riprova fra qualche secondo."; + +"There was an unexpected error -- try again in a few seconds" = "Si è verificato un errore imprevisto. Riprova fra qualche secondo."; + +"Try again" = "Riprova"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Utilizziamo Stripe per verificare i dati della carta. Come stabilito nella sua informativa sulla privacy, Stripe può utilizzare e conservare i tuoi dati. Ulteriori informazioni"; + +"Your card has expired" = "La carta è scaduta"; + +"Your card was declined" = "La carta è stata rifiutata"; + +"Your card's expiration month is invalid" = "Il mese di scadenza della carta non è valido"; + +"Your card's expiration year is invalid" = "L'anno di scadenza della carta non è valido"; + +"Your card's number is invalid" = "Il numero della carta non è valido"; + +"Your card's security code is invalid" = "Il codice di sicurezza della carta non è valido"; + +"Your name is invalid." = "Nome non valido."; + +"Your payment method was declined." = "La tua modalità di pagamento è stata rifiutata."; diff --git a/StripeCore/StripeCore/Resources/Localizations/ja.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/ja.lproj/Localizable.strings new file mode 100644 index 00000000..fb79bab8 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/ja.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "閉じる"; + +"Scan Card" = "クレジットカードをスキャン"; + +"Scan card" = "カードをスキャン"; + +"The IBAN you entered is invalid." = "入力した IBAN が無効です。"; + +"There was an error processing your card -- try again in a few seconds" = "クレジットカード情報の処理中にエラーが発生しました。数秒後にもう一度お試しください"; + +"There was an unexpected error -- try again in a few seconds" = "予期しないエラーが発生しました。数秒後にもう一度お試しください"; + +"Try again" = "もう一度お試しください"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Stripe を使用してカード詳細を確認します。Stripe はプライバシーポリシーに従ってお客様のデータを使用および保管することがあります。もっと知る"; + +"Your card has expired" = "クレジットカードの有効期限が切れています"; + +"Your card was declined" = "クレジットカードが拒否されました"; + +"Your card's expiration month is invalid" = "指定したクレジットカードの有効期限の月が無効です"; + +"Your card's expiration year is invalid" = "指定したクレジットカードの有効期限の年が無効です"; + +"Your card's number is invalid" = "指定したクレジットカードの番号が無効です"; + +"Your card's security code is invalid" = "指定したクレジットカードのセキュリティコードが無効です"; + +"Your name is invalid." = "名前が無効です。"; + +"Your payment method was declined." = "お客様の支払い方法が拒否されました。"; diff --git a/StripeCore/StripeCore/Resources/Localizations/ko.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/ko.lproj/Localizable.strings new file mode 100644 index 00000000..a6316bc9 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/ko.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "닫기"; + +"Scan Card" = "카드 스캔"; + +"Scan card" = "카드 스캔"; + +"The IBAN you entered is invalid." = "입력한 IBAN이 잘못되었습니다."; + +"There was an error processing your card -- try again in a few seconds" = "카드를 처리하는 동안 오류가 발생했습니다. 몇 초 후에 다시 시도하십시오."; + +"There was an unexpected error -- try again in a few seconds" = "예기치 않은 오류가 발생했습니다. 몇 초 후에 다시 시도하십시오."; + +"Try again" = "다시 시도"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Stripe를 통해 카드 세부사항을 확인합니다. Stripe에서 자사 개인정보정책에 따라 귀하의 데이터를 사용하고 저장할 수 있습니다. 자세히 알아보기"; + +"Your card has expired" = "카드가 만료되었습니다"; + +"Your card was declined" = "카드가 거절되었습니다"; + +"Your card's expiration month is invalid" = "카드의 만료 월이 유효하지 않습니다"; + +"Your card's expiration year is invalid" = "카드의 만료 연도가 유효하지 않습니다"; + +"Your card's number is invalid" = "카드 번호가 유효하지 않습니다"; + +"Your card's security code is invalid" = "카드의 보안 코드가 유효하지 않습니다"; + +"Your name is invalid." = "이름이 잘못되었습니다."; + +"Your payment method was declined." = "결제 방식이 거부되었습니다."; diff --git a/StripeCore/StripeCore/Resources/Localizations/lt-LT.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..bb550e46 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/lt-LT.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Užverti"; + +"Scan Card" = "Skenuoti kortelę"; + +"Scan card" = "Nuskaityti kortelę"; + +"The IBAN you entered is invalid." = "Įvedėte neteisingą IBAN."; + +"There was an error processing your card -- try again in a few seconds" = "Įvyko kortelės apdorojimo klaida, po kelių sekundžių bandykite dar kartą"; + +"There was an unexpected error -- try again in a few seconds" = "Įvyko netikėta klaida, po kelių sekundžių bandykite dar kartą"; + +"Try again" = "Bandyti dar kartą"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Naudojame „Stripe“, kad patikrintume jūsų kortelės duomenis. „Stripe“ gali naudoti ir saugoti jūsų duomenis pagal savo privatumo politiką. Sužinokite daugiau"; + +"Your card has expired" = "Kortelė baigė galioti"; + +"Your card was declined" = "Kortelė buvo atmesta"; + +"Your card's expiration month is invalid" = "Negaliojantis kortelės galiojimo pabaigos mėnuo"; + +"Your card's expiration year is invalid" = "Kortelės galiojimo pabaigos metai neteisingi"; + +"Your card's number is invalid" = "Negaliojantis kortelės numeris"; + +"Your card's security code is invalid" = "Kortelės saugos kodas negalioja"; + +"Your name is invalid." = "Jūsų vardas neteisingas."; + +"Your payment method was declined." = "Jūsų mokėjimo būdas buvo atmestas."; diff --git a/StripeCore/StripeCore/Resources/Localizations/lv-LV.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..36620f2b --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/lv-LV.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Aizvērt"; + +"Scan Card" = "Skenēt karti"; + +"Scan card" = "Skenēt karti"; + +"The IBAN you entered is invalid." = "Ievadītais IBAN nav derīgs."; + +"There was an error processing your card -- try again in a few seconds" = "Radās kļūda, apstrādājot jūsu karti — mēģiniet vēlreiz pēc dažām sekundēm"; + +"There was an unexpected error -- try again in a few seconds" = "Radusies negaidīta kļūda — mēģiniet vēlreiz pēc dažām sekundēm"; + +"Try again" = "Mēģināt vēlreiz"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Mēs izmantojam Stripe, lai verificētu jūsu kartes informāciju. Stripe var izmantot un uzglabāt jūsu datus saskaņā ar konfidencialitātes politiku. Uzzināt vairāk"; + +"Your card has expired" = "Kartes derīguma termiņš ir beidzies"; + +"Your card was declined" = "Karte tika noraidīta"; + +"Your card's expiration month is invalid" = "Kartes derīguma termiņa mēnesis nav derīgs"; + +"Your card's expiration year is invalid" = "Kartes derīguma termiņa gads nav derīgs"; + +"Your card's number is invalid" = "Kartes numurs nav derīgs"; + +"Your card's security code is invalid" = "Kartes drošības kods nav derīgs"; + +"Your name is invalid." = "Jūsu vārds nav derīgs."; + +"Your payment method was declined." = "Maksājuma veids tika noraidīts."; diff --git a/StripeCore/StripeCore/Resources/Localizations/ms-MY.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..6206e0b0 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/ms-MY.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Tutup"; + +"Scan Card" = "Imbas Kad"; + +"Scan card" = "Imbas kad"; + +"The IBAN you entered is invalid." = "IBAN yang anda masukkan tidak sah."; + +"There was an error processing your card -- try again in a few seconds" = "Ada ralat ketika memproses kad anda -- cuba lagi dalam masa beberapa saat"; + +"There was an unexpected error -- try again in a few seconds" = "Ada ralat yang tidak dijangka -- cuba lagi dalam masa beberapa saat"; + +"Try again" = "Cuba lagi"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Kami menggunakan Stripe untuk mengesahkan butiran kad anda. Stripe mungkin menggunakan dan menyimpan data anda menurut dasar privasi Stripe. Ketahui selanjutnya"; + +"Your card has expired" = "Kad anda telah tamat tempoh"; + +"Your card was declined" = "Kad anda telah ditolak"; + +"Your card's expiration month is invalid" = "Bulan tamat tempoh kad anda tidak sah"; + +"Your card's expiration year is invalid" = "Tahun tamat tempoh kad anda tidak sah"; + +"Your card's number is invalid" = "Nombor kad anda tidak sah"; + +"Your card's security code is invalid" = "Kod keselamatan kad anda tidak sah"; + +"Your name is invalid." = "Nama anda tidak sah."; + +"Your payment method was declined." = "Kaedah pembayaran anda ditolak."; diff --git a/StripeCore/StripeCore/Resources/Localizations/mt.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/mt.lproj/Localizable.strings new file mode 100644 index 00000000..546deb95 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/mt.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Agħlaq"; + +"Scan Card" = "Skennja l-Kard"; + +"Scan card" = "Skennja l-karta"; + +"The IBAN you entered is invalid." = "L-IBAN li daħħalt mhux tajjeb."; + +"There was an error processing your card -- try again in a few seconds" = "Kien hemm żball fl-ipproċessar tal-kard tiegħek -- erġa' pprova wara ftit sekondi"; + +"There was an unexpected error -- try again in a few seconds" = "Kien hemm żball mhux mistenni -- erġa' pprova wara ftit sekondi"; + +"Try again" = "Erġa' pprova"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Aħna naħdmu ma' Stripe biex nivverifikaw id-dettalji tal-karta tiegħek. Stripe tista' tuża u taħżen id-dejta tiegħek skont il-politika tal-privatezza tagħha. Sir af iktar"; + +"Your card has expired" = "Il-kard tiegħek skadiet"; + +"Your card was declined" = "Il-kard tiegħek ma ġietx aċċettata"; + +"Your card's expiration month is invalid" = "Ix-xahar tal-iskadenza tal-kard tiegħek mhux validu"; + +"Your card's expiration year is invalid" = "Is-sena tal-iskadenza tal-kard tiegħek mhijiex valida"; + +"Your card's number is invalid" = "In-numru tal-kard tiegħek mhux validu"; + +"Your card's security code is invalid" = "Il-kodiċi tas-sigurtà tal-kard tiegħek mhux validu"; + +"Your name is invalid." = "L-isem li daħħalt mhux tajjeb."; + +"Your payment method was declined." = "Il-metodu tal-pagament tiegħek m'għaddiex."; diff --git a/StripeCore/StripeCore/Resources/Localizations/nb.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/nb.lproj/Localizable.strings new file mode 100644 index 00000000..af0a3a1e --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/nb.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Lukk"; + +"Scan Card" = "Skann kort"; + +"Scan card" = "Skann kort"; + +"The IBAN you entered is invalid." = "IBAN-nummeret du la inn er ugyldig."; + +"There was an error processing your card -- try again in a few seconds" = "Det oppstod en feil ved prosessering av kortet -- prøv igjen om et par sekunder"; + +"There was an unexpected error -- try again in a few seconds" = "En uventet feil har oppstått -- prøv igjen om et par sekunder"; + +"Try again" = "Prøv igjen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Vi bruker Stripe til å verifisere kontoopplysningene. Stripe kan bruke og lagre opplysningene dine i henhold til personvernerklæringen sin. Finn ut mer"; + +"Your card has expired" = "Kortet ditt har utløpt"; + +"Your card was declined" = "Kortet ditt ble avvist"; + +"Your card's expiration month is invalid" = "Kortet sin utløpsmåned er ugyldig"; + +"Your card's expiration year is invalid" = "Kortet sitt utløpsår er ugyldig"; + +"Your card's number is invalid" = "Ugyldig kortnummer"; + +"Your card's security code is invalid" = "Ugyldig sikkerhetskode"; + +"Your name is invalid." = "Navnet er ugyldig."; + +"Your payment method was declined." = "Betalingsmetoden din ble avvist."; diff --git a/StripeCore/StripeCore/Resources/Localizations/nl.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/nl.lproj/Localizable.strings new file mode 100644 index 00000000..43792e41 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/nl.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Sluiten"; + +"Scan Card" = "Kaart scannen"; + +"Scan card" = "Betaalkaart scannen"; + +"The IBAN you entered is invalid." = "Het opgegeven IBAN-nummer is ongeldig."; + +"There was an error processing your card -- try again in a few seconds" = "Er is een fout met de verwerking van je kaart. Probeer het over enkele seconden opnieuw"; + +"There was an unexpected error -- try again in a few seconds" = "Er is een onverwachte fout opgetreden. Probeer het over enkele seconden opnieuw"; + +"Try again" = "Opnieuw proberen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "We gebruiken Stripe om je kaartgegevens te verifiëren. Stripe kan je gegevens gebruiken en bewaren volgens hun privacybeleid. Meer informatie"; + +"Your card has expired" = "Je kaart is vervallen"; + +"Your card was declined" = "Je kaart is geweigerd"; + +"Your card's expiration month is invalid" = "De vervalmaand van je kaart is ongeldig"; + +"Your card's expiration year is invalid" = "Het vervaljaar van je kaart is ongeldig"; + +"Your card's number is invalid" = "Je kaartnummer is ongeldig"; + +"Your card's security code is invalid" = "De beveiligingscode van je kaart is ongeldig"; + +"Your name is invalid." = "Je naam is ongeldig."; + +"Your payment method was declined." = "Je betaalmethode is geweigerd."; diff --git a/StripeCore/StripeCore/Resources/Localizations/nn-NO.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..70d88ad5 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/nn-NO.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Lukk"; + +"Scan Card" = "Skann kortet"; + +"Scan card" = "Skann kort"; + +"The IBAN you entered is invalid." = "Du skreiv inn eit ugyldig IBAN-nummer."; + +"There was an error processing your card -- try again in a few seconds" = "Det oppstod ei feil under behandlinga av kortet – prøv igjen om nokre få sekund"; + +"There was an unexpected error -- try again in a few seconds" = "Det oppstod ein uventa feil – prøv igjen om nokre få sekund"; + +"Try again" = "Prøv igjen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Vi brukar Stripe til å verifisere kortdetaljane dine. Stripe kan bruke og lagre dataa dine i samsvar med personvernerklæringa deira. Finn ut meir"; + +"Your card has expired" = "Kortet har gått ut"; + +"Your card was declined" = "Kortet vart avvist"; + +"Your card's expiration month is invalid" = "Utløpsmånaden på kortet er ugyldig"; + +"Your card's expiration year is invalid" = "Utløpsåret på kortet ditt er ugyldig"; + +"Your card's number is invalid" = "Kortnummeret ditt er ugyldig"; + +"Your card's security code is invalid" = "Sikkerheitskoden til kortet er ugyldig"; + +"Your name is invalid." = "Namnet ditt er ugyldig."; + +"Your payment method was declined." = "Betalingsmåten din vart avvist."; diff --git a/StripeCore/StripeCore/Resources/Localizations/pl-PL.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..7502f7eb --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/pl-PL.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Zamknij"; + +"Scan Card" = "Skanuj kartę"; + +"Scan card" = "Skanuj kartę"; + +"The IBAN you entered is invalid." = "Podany IBAN jest nieprawidłowy."; + +"There was an error processing your card -- try again in a few seconds" = "Podczas przetwarzania Twojej karty wystąpił błąd – spróbuj ponownie za kilka sekund"; + +"There was an unexpected error -- try again in a few seconds" = "Wystąpił nieoczekiwany błąd – spróbuj ponownie za kilka sekund"; + +"Try again" = "Spróbuj ponownie"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Korzystamy ze Stripe, aby weryfikować szczegóły Twojej karty. Stripe może używać Twoich danych i przechowywać je zgodnie ze swoją polityką prywatności. Dowiedz się więcej"; + +"Your card has expired" = "Ważność Twojej karty wygasła"; + +"Your card was declined" = "Karta została odrzucona"; + +"Your card's expiration month is invalid" = "Miesiąc daty ważności karty jest nieprawidłowy."; + +"Your card's expiration year is invalid" = "Rok daty ważności karty jest nieprawidłowy"; + +"Your card's number is invalid" = "Numer Twojej karty jest nieprawidłowy"; + +"Your card's security code is invalid" = "Kod bezpieczeństwa karty jest nieprawidłowy"; + +"Your name is invalid." = "Twoje imię i nazwisko jest nieprawidłowe."; + +"Your payment method was declined." = "Twoja metoda płatności została odrzucona."; diff --git a/StripeCore/StripeCore/Resources/Localizations/pt-BR.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..4cede820 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/pt-BR.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Fechar"; + +"Scan Card" = "Digitalizar cartão"; + +"Scan card" = "Ler cartão"; + +"The IBAN you entered is invalid." = "O IBAN inserido é inválido."; + +"There was an error processing your card -- try again in a few seconds" = "Erro ao processar o cartão – tente novamente em alguns segundos"; + +"There was an unexpected error -- try again in a few seconds" = "Erro inesperado – tente novamente em alguns segundos"; + +"Try again" = "Tentar novamente"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Usamos a Stripe para verificar os dados do seu cartão. A Stripe pode usar e armazenar seus dados de acordo com a política de privacidade da empresa. Saiba mais"; + +"Your card has expired" = "O cartão expirou"; + +"Your card was declined" = "O cartão foi recusado"; + +"Your card's expiration month is invalid" = "O mês de expiração doi cartão é inválido"; + +"Your card's expiration year is invalid" = "O ano de expiração do cartão é inválido"; + +"Your card's number is invalid" = "O número do cartão é inválido"; + +"Your card's security code is invalid" = "O código de segurança do cartão é inválido"; + +"Your name is invalid." = "Nome inválido."; + +"Your payment method was declined." = "A forma de pagamento foi recusada."; diff --git a/StripeCore/StripeCore/Resources/Localizations/pt-PT.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..ee185d0b --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/pt-PT.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Fechar"; + +"Scan Card" = "Ler cartão"; + +"Scan card" = "Ler cartão"; + +"The IBAN you entered is invalid." = "O IBAN que introduziu é inválido."; + +"There was an error processing your card -- try again in a few seconds" = "Ocorreu um erro ao processar o seu cartão -- tente novamente dentro de alguns segundos"; + +"There was an unexpected error -- try again in a few seconds" = "Ocorreu um erro inesperado -- tente novamente dentro de alguns segundos"; + +"Try again" = "Tentar novamente"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Utilizamos a Stripe para verificar os detalhes do seu cartão. A Stripe pode utilizar e armazenar os seus dados de acordo com a sua política de privacidade. Saiba mais"; + +"Your card has expired" = "O seu cartão expirou"; + +"Your card was declined" = "O seu cartão foi recusado"; + +"Your card's expiration month is invalid" = "O mês de validade do seu cartão é inválido"; + +"Your card's expiration year is invalid" = "O ano de validade do seu cartão é inválido"; + +"Your card's number is invalid" = "O número do seu cartão é inválido"; + +"Your card's security code is invalid" = "O código de segurança do seu cartão é inválido"; + +"Your name is invalid." = "O seu nome é inválido."; + +"Your payment method was declined." = "O seu método de pagamento foi recusado."; diff --git a/StripeCore/StripeCore/Resources/Localizations/ro-RO.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..c0ddbdc3 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/ro-RO.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Închidere"; + +"Scan Card" = "Scanare card"; + +"Scan card" = "Scanare card"; + +"The IBAN you entered is invalid." = "Codul IBAN pe care l-ați introdus nu este valid."; + +"There was an error processing your card -- try again in a few seconds" = "A apărut o eroare la procesarea cardului dvs. -- încercați din nou în câteva secunde"; + +"There was an unexpected error -- try again in a few seconds" = "A apărut o eroare neașteptată -- încercați din nou în câteva secunde"; + +"Try again" = "Încercați din nou"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Folosim Stripe pentru a verifica detaliile cardului dvs. Stripe poate utiliza și stoca datele dvs. în conformitate cu politica sa de confidențialitate. Aflați mai multe"; + +"Your card has expired" = "Cardul dvs. a expirat"; + +"Your card was declined" = "Cardul dvs. a fost respins"; + +"Your card's expiration month is invalid" = "Luna de expirare a cardului dvs. nu este validă"; + +"Your card's expiration year is invalid" = "Anul de expirare al cardului dvs. nu este valid"; + +"Your card's number is invalid" = "Numărul cardului dvs. nu este valid"; + +"Your card's security code is invalid" = "Codul de securitate al cardului dvs. nu este valid"; + +"Your name is invalid." = "Numele dvs. nu este valid."; + +"Your payment method was declined." = "Metoda dvs. de plată a fost respinsă."; diff --git a/StripeCore/StripeCore/Resources/Localizations/ru.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/ru.lproj/Localizable.strings new file mode 100644 index 00000000..3face220 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/ru.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Закрыть"; + +"Scan Card" = "Сканировать карту"; + +"Scan card" = "Сканировать карту"; + +"The IBAN you entered is invalid." = "Введенный код IBAN содержит ошибку."; + +"There was an error processing your card -- try again in a few seconds" = "При обработке карты произошла ошибка. Подождите несколько секунд и повторите попытку"; + +"There was an unexpected error -- try again in a few seconds" = "Произошла непредвиденная ошибка. Подождите несколько секунд и повторите попытку."; + +"Try again" = "Повторите попытку"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Мы используем Stripe для проверки данных вашей карты. Stripe может использовать и хранить ваши данные в соответствии со своей политикой конфиденциальности. Подробнее"; + +"Your card has expired" = "Срок действия карты истек"; + +"Your card was declined" = "Карта отклонена"; + +"Your card's expiration month is invalid" = "Недопустимый месяц окончания действия карты"; + +"Your card's expiration year is invalid" = "Недопустимый год окончания действия карты"; + +"Your card's number is invalid" = "Номер карты недействителен"; + +"Your card's security code is invalid" = "Недопустимый код CVV/CVC карты"; + +"Your name is invalid." = "Недействительное имя."; + +"Your payment method was declined." = "Не удалось выполнить оплату этим способом."; diff --git a/StripeCore/StripeCore/Resources/Localizations/sk-SK.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..b0d16f13 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/sk-SK.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Zatvoriť"; + +"Scan Card" = "Naskenovať kartu"; + +"Scan card" = "Naskenovať kartu"; + +"The IBAN you entered is invalid." = "Zadaný IBAN je neplatný."; + +"There was an error processing your card -- try again in a few seconds" = "Pri spracovaní vašej karty sa vyskytla chyba -- skúste znova o niekoľko sekúnd"; + +"There was an unexpected error -- try again in a few seconds" = "Vyskytla sa neočakávaná chyba -- skúste znova o niekoľko sekúnd"; + +"Try again" = "Skúsiť znova"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Na overenie údajov o vašej karte používame službu Stripe. Spoločnosť Stripe môže používať a uchovávať vaše údaje v súlade so zásadami ochrany osobných údajov. Ďalšie informácie"; + +"Your card has expired" = "Platnosť vašej karty vypršala"; + +"Your card was declined" = "Vaša karta bola odmietnutá"; + +"Your card's expiration month is invalid" = "Mesiac ukončenia platnosti vašej karty je neplatný"; + +"Your card's expiration year is invalid" = "Rok ukončenia platnosti vašej karty je neplatný"; + +"Your card's number is invalid" = "Číslo vašej karty je neplatné"; + +"Your card's security code is invalid" = "Bezpečnostný kód vašej karty je neplatný"; + +"Your name is invalid." = "Vaše meno je neplatné."; + +"Your payment method was declined." = "Váš spôsob platby bol odmietnutý."; diff --git a/StripeCore/StripeCore/Resources/Localizations/sl-SI.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..fb120e85 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/sl-SI.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Zapri"; + +"Scan Card" = "Optično preberi kartico"; + +"Scan card" = "Optično preberi kartico"; + +"The IBAN you entered is invalid." = "Vnesena koda IBAN ni veljavna."; + +"There was an error processing your card -- try again in a few seconds" = "Pri obdelavi vaše kartice je prišlo do napake. Poskusite znova čez nekaj sekund."; + +"There was an unexpected error -- try again in a few seconds" = "Prišlo je do nepričakovane napake. Poskusite znova čez nekaj sekund."; + +"Try again" = "Poskusi znova"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Za preverjanje podatkov o vaši kartici uporabljamo storitev Stripe. Stripe lahko vaše podatke uporablja in hrani v skladu s svojim pravilnikom o zasebnosti. Več informacij"; + +"Your card has expired" = "Vaša kartica je potekla"; + +"Your card was declined" = "Vaša kartica je bila zavrnjena"; + +"Your card's expiration month is invalid" = "Mesec poteka vaše kartice ni veljaven"; + +"Your card's expiration year is invalid" = "Leto poteka vaše kartice ni veljavno"; + +"Your card's number is invalid" = "Številka vaše kartice ni veljavna"; + +"Your card's security code is invalid" = "Varnostna koda vaše kartice ni veljavna"; + +"Your name is invalid." = "Vaše ime ni veljavno."; + +"Your payment method was declined." = "Vaš način plačila je bil zavrnjen."; diff --git a/StripeCore/StripeCore/Resources/Localizations/sv.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/sv.lproj/Localizable.strings new file mode 100644 index 00000000..46080f53 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/sv.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Stäng"; + +"Scan Card" = "Skanna kort"; + +"Scan card" = "Skanna kort"; + +"The IBAN you entered is invalid." = "Angivet IBAN är ogiltigt."; + +"There was an error processing your card -- try again in a few seconds" = "Ett fel uppstod vid behandlingen av ditt kort - försök igen om ett par sekunder"; + +"There was an unexpected error -- try again in a few seconds" = "Ett oväntat fel uppstod - försök igen om ett par sekunder"; + +"Try again" = "Försök igen"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Vi använder Stripe för att verifiera dina kortuppgifter. Stripe kan komma att använda och lagra dina uppgifter i enlighet med sin integritetspolicy. Läs mer"; + +"Your card has expired" = "Kortet har löpt ut"; + +"Your card was declined" = "Ditt kort nekades"; + +"Your card's expiration month is invalid" = "Kortets utgångsmånad är ogiltig"; + +"Your card's expiration year is invalid" = "Kortets utgångsår är ogiltigt"; + +"Your card's number is invalid" = "Kortnumret är ogiltigt"; + +"Your card's security code is invalid" = "Kortets säkerhetskod är ogiltig"; + +"Your name is invalid." = "Ditt namn är ogiltigt."; + +"Your payment method was declined." = "Din betalningsmetod godkändes inte."; diff --git a/StripeCore/StripeCore/Resources/Localizations/tr.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/tr.lproj/Localizable.strings new file mode 100644 index 00000000..362d4dc0 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/tr.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Kapat"; + +"Scan Card" = "Kartı Tara"; + +"Scan card" = "Kartı tara"; + +"The IBAN you entered is invalid." = "Girdiğiniz IBAN geçersiz."; + +"There was an error processing your card -- try again in a few seconds" = "Kartınızla işlem yapılırken bir hata oluştu -- birazdan tekrar deneyin"; + +"There was an unexpected error -- try again in a few seconds" = "Beklenmedik bir hata oluştu -- birazdan tekrar deneyin"; + +"Try again" = "Tekrar dene"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Kart bilgilerinizi doğrulamak için Stripe kullanıyoruz. Stripe verilerinizi gizlilik politikasına uygun şekilde kullanabilir ve saklayabilir.Daha fazlasını öğrenin"; + +"Your card has expired" = "Kartınızın kullanım süresi dolmuş"; + +"Your card was declined" = "Kartınız reddedildi"; + +"Your card's expiration month is invalid" = "Kartınızın son kullanma ayı geçersiz"; + +"Your card's expiration year is invalid" = "Kartınızın son kullanma yılı geçersiz"; + +"Your card's number is invalid" = "Kart numaranız geçersiz"; + +"Your card's security code is invalid" = "Kartınızın güvenlik kodu geçersiz"; + +"Your name is invalid." = "Adınız geçersiz."; + +"Your payment method was declined." = "Ödeme yönteminiz reddedildi."; diff --git a/StripeCore/StripeCore/Resources/Localizations/vi.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/vi.lproj/Localizable.strings new file mode 100644 index 00000000..964581ae --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/vi.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "Đóng"; + +"Scan Card" = "Quét thẻ"; + +"Scan card" = "Quét thẻ"; + +"The IBAN you entered is invalid." = "IBAN quý vị đã nhập không hợp lệ."; + +"There was an error processing your card -- try again in a few seconds" = "Có lỗi bất ngờ khi xử lý thẻ -- hãy thử lại sau vài giây"; + +"There was an unexpected error -- try again in a few seconds" = "Có lỗi bất ngờ -- hãy thử lại sau vài giây"; + +"Try again" = "Thử lại"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "Chúng tôi sử dụng Stripe để xác minh chi tiết thẻ của bạn. Stripe có thể sử dụng và lưu trữ dữ liệu của bạn theo chính sách quyền riêng tư của họ. Tìm hiểu thêm"; + +"Your card has expired" = "Thẻ đã hết hạn"; + +"Your card was declined" = "Thẻ bị từ chối"; + +"Your card's expiration month is invalid" = "Tháng hết hạn trên thẻ không hợp lệ"; + +"Your card's expiration year is invalid" = "Năm hết hạn trên thẻ không hợp lệ"; + +"Your card's number is invalid" = "Số thẻ không đúng"; + +"Your card's security code is invalid" = "Mã bảo mật của thẻ không đúng"; + +"Your name is invalid." = "Tên của quý vị không hợp lệ."; + +"Your payment method was declined." = "Phương thức thanh toán của quý vị đã bị từ chối."; diff --git a/StripeCore/StripeCore/Resources/Localizations/zh-HK.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..4fab6a8c --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/zh-HK.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "關閉"; + +"Scan Card" = "掃描銀行卡"; + +"Scan card" = "掃描卡"; + +"The IBAN you entered is invalid." = "您輸入的 IBAN 無效。"; + +"There was an error processing your card -- try again in a few seconds" = "處理您的卡時發生了錯誤 -- 請稍候再試"; + +"There was an unexpected error -- try again in a few seconds" = "發生了意外錯誤 -- 請稍候再試"; + +"Try again" = "重試"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "我們用 Stripe 來驗證您的銀行卡資訊。Stripe 可能會根據其私隱政策使用並存儲您的資料。瞭解更多"; + +"Your card has expired" = "您的卡已過期"; + +"Your card was declined" = "您的卡已被拒絕"; + +"Your card's expiration month is invalid" = "您的銀行卡的到期月份無效"; + +"Your card's expiration year is invalid" = "您的銀行卡的到期年份無效"; + +"Your card's number is invalid" = "您的卡號無效"; + +"Your card's security code is invalid" = "您的銀行卡的安全碼無效"; + +"Your name is invalid." = "您的姓名無效。"; + +"Your payment method was declined." = "您的支付方式被拒絕了。"; diff --git a/StripeCore/StripeCore/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..4f7e5dd1 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "关闭"; + +"Scan Card" = "扫描银行卡"; + +"Scan card" = "扫描卡"; + +"The IBAN you entered is invalid." = "您输入的 IBAN 无效。"; + +"There was an error processing your card -- try again in a few seconds" = "处理您的卡时发生错误 —— 请稍后再试"; + +"There was an unexpected error -- try again in a few seconds" = "发生了意外错误 —— 请过几秒钟再试"; + +"Try again" = "重试"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "我们用 Stripe 来验证您的银行卡信息。Stripe 可能会根据其隐私政策使用并存储您的数据。了解更多"; + +"Your card has expired" = "您的银行卡已过期"; + +"Your card was declined" = "您的卡已被拒绝"; + +"Your card's expiration month is invalid" = "您的银行卡的到期月份无效"; + +"Your card's expiration year is invalid" = "您的银行卡的到期年份无效。"; + +"Your card's number is invalid" = "您的卡号无效"; + +"Your card's security code is invalid" = "您的银行卡的安全码无效"; + +"Your name is invalid." = "您的姓名无效。"; + +"Your payment method was declined." = "您的支付方式被拒绝了。"; diff --git a/StripeCore/StripeCore/Resources/Localizations/zh-Hant.lproj/Localizable.strings b/StripeCore/StripeCore/Resources/Localizations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..4ae13cc1 --- /dev/null +++ b/StripeCore/StripeCore/Resources/Localizations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,31 @@ +"Close" = "關閉"; + +"Scan Card" = "掃描金融卡"; + +"Scan card" = "掃描卡"; + +"The IBAN you entered is invalid." = "您輸入的 IBAN 無效。"; + +"There was an error processing your card -- try again in a few seconds" = "處理您的卡時發生錯誤 -- 請等待幾秒後再試"; + +"There was an unexpected error -- try again in a few seconds" = "發生了意外錯誤 -- 請等待幾秒後再試一次"; + +"Try again" = "重試"; + +"We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more" = "我們用 Stripe 來驗證您的金融卡資訊。Stripe 可能會根據其隱私政策使用並存儲您的資料。瞭解更多"; + +"Your card has expired" = "您的卡已過期"; + +"Your card was declined" = "您的卡已被拒絕"; + +"Your card's expiration month is invalid" = "您的金融卡的到期月份無效"; + +"Your card's expiration year is invalid" = "您的金融卡的到期年份無效"; + +"Your card's number is invalid" = "您的卡號無效"; + +"Your card's security code is invalid" = "您的金融卡的安全碼無效"; + +"Your name is invalid." = "您的名稱無效。"; + +"Your payment method was declined." = "您的支付方式被拒絕了。"; diff --git a/StripeCore/StripeCore/Source/API Bindings/Models/EmptyResponse.swift b/StripeCore/StripeCore/Source/API Bindings/Models/EmptyResponse.swift new file mode 100644 index 00000000..a0da190c --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/Models/EmptyResponse.swift @@ -0,0 +1,14 @@ +// +// EmptyResponse.swift +// StripeCore +// +// Created by Jaime Park on 11/19/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// This is an object representing an empty response from a request. +@_spi(STP) public struct EmptyResponse: UnknownFieldsDecodable { + public var _allResponseFieldsStorage: NonEncodableParameters? +} diff --git a/StripeCore/StripeCore/Source/API Bindings/Models/StripeFile.swift b/StripeCore/StripeCore/Source/API Bindings/Models/StripeFile.swift new file mode 100644 index 00000000..49a625fb --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/Models/StripeFile.swift @@ -0,0 +1,45 @@ +// +// StripeFile.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation + +/// This is an object representing a file hosted on Stripe's servers. +/// +/// The file may have been uploaded by yourself using the +/// [create file](https://stripe.com/docs/api#create_file) request +/// (for example, when uploading dispute evidence) or it may have been created by Stripe +/// (for example, the results of a [Sigma scheduled query](#scheduled_queries)). +/// Related guide: [File Upload Guide](https://stripe.com/docs/file-upload). +@_spi(STP) public struct StripeFile: UnknownFieldsDecodable, Equatable { + @frozen public enum Purpose: String, SafeEnumCodable, Equatable { + // NOTE: If adding cases here that should also be available to the + // public API, please also add to `STPFilePurpose`. This is not + // necessary for cases that are only used internally. + + /// Dispute evidence file. + case disputeEvidence = "dispute_evidence" + /// Identity document file. + case identityDocument = "identity_document" + /// Identity document file used only internally. + case identityPrivate = "identity_private" + /// Not a valid purpose – only used for `SafeEnumCodable` conformance. + case unparsable = "" + } + /// Time at which the object was created. + /// + /// Measured in seconds since the Unix epoch. + public let created: Date + /// Unique identifier for the object. + public let id: String + /// The [purpose](https://stripe.com/docs/file-upload#uploading-a-file) of the uploaded file. + public let purpose: Purpose + /// The size in bytes of the file object. + public let size: Int + /// The type of the file returned (e.g., `csv`, `pdf`, `jpg`, or `png`). + public let type: String? + public var _allResponseFieldsStorage: NonEncodableParameters? +} diff --git a/StripeCore/StripeCore/Source/API Bindings/STPAPIClient+FileUpload.swift b/StripeCore/StripeCore/Source/API Bindings/STPAPIClient+FileUpload.swift new file mode 100644 index 00000000..36740aaa --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/STPAPIClient+FileUpload.swift @@ -0,0 +1,234 @@ +// +// STPAPIClient+FileUpload.swift +// StripeCore +// +// Created by Mel Ludowise on 11/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +extension StripeFile.Purpose { + /// See max purpose sizes https://stripe.com/docs/file-upload. + var maxBytes: Int? { + switch self { + case .identityDocument, + .identityPrivate: + return 16_000_000 + case .disputeEvidence: + return 5_000_000 + case .unparsable: + return nil + } + } +} + +/// STPAPIClient extensions to upload files. +extension STPAPIClient { + @_spi(STP) public typealias FileAndUploadMetrics = ( + file: StripeFile, + metrics: ImageUploadMetrics + ) + + /// Metrics returned in callback after image is uploaded to track performance. + @_spi(STP) public struct ImageUploadMetrics { + public let timeToUpload: TimeInterval + public let fileSizeBytes: Int + } + + @_spi(STP) public static let defaultImageFileName = "image" + + func data( + forUploadedImage image: UIImage, + compressionQuality: CGFloat, + purpose: String + ) -> Data { + // Get maxBytes if file purpose is known to the client + let maxBytes = StripeFile.Purpose(rawValue: purpose)?.maxBytes + return image.jpegDataAndDimensions( + maxBytes: maxBytes, + compressionQuality: compressionQuality + ).imageData + } + + /// Uses the Stripe file upload API to upload a JPEG encoded image. + /// + /// The image will be automatically resized down if: + /// 1. The given purpose is recognized by the client. + /// 2. It's larger than the maximum allowed file size for the given purpose. + /// + /// - Parameters: + /// - image: The image to be uploaded. + /// - compressionQuality: The compression quality to use when encoding the jpeg. + /// - purpose: The purpose of this file. + /// - fileName: The name of the uploaded file. The "jpeg" extension will + /// automatically be appended to this name. + /// - ownedBy: A Stripe-internal property that sets the owner of the file. + /// - ephemeralKeySecret: Authorization key, if applicable. + /// - completion: The callback to run with the returned Stripe file (and any + /// errors that may have occurred). + /// + /// - Note: + /// The provided `purpose` must match a supported Purpose by Stripe's File + /// Upload API, or the API will respond with an error. Generally, this should + /// match a value in `StripeFile.Purpose`, but can be specified by any string + /// when forwarding the value from a Stripe server response in situations + /// where the purpose is not yet encoded in the client SDK. + @_spi(STP) public func uploadImage( + _ image: UIImage, + compressionQuality: CGFloat = UIImage.defaultCompressionQuality, + purpose: String, + fileName: String = defaultImageFileName, + ownedBy: String? = nil, + ephemeralKeySecret: String? = nil, + completion: @escaping (Result) -> Void + ) { + uploadImageAndGetMetrics( + image, + compressionQuality: compressionQuality, + purpose: purpose, + fileName: fileName, + ownedBy: ownedBy, + ephemeralKeySecret: ephemeralKeySecret + ) { result in + completion(result.map { $0.file }) + } + } + + @_spi(STP) public func uploadImageAndGetMetrics( + _ image: UIImage, + compressionQuality: CGFloat = UIImage.defaultCompressionQuality, + purpose: String, + fileName: String = defaultImageFileName, + ownedBy: String? = nil, + ephemeralKeySecret: String? = nil, + completion: @escaping (Result) -> Void + ) { + let purposePart = STPMultipartFormDataPart() + purposePart.name = "purpose" + // `unparsable` is not a valid purpose + if purpose != StripeFile.Purpose.unparsable.rawValue, + let purposeData = purpose.data(using: .utf8) + { + purposePart.data = purposeData + } + + let imagePart = STPMultipartFormDataPart() + imagePart.name = "file" + imagePart.filename = "\(fileName).jpg" + imagePart.contentType = "image/jpeg" + imagePart.data = self.data( + forUploadedImage: image, + compressionQuality: compressionQuality, + purpose: purpose + ) + + let ownedByPart: STPMultipartFormDataPart? = ownedBy?.data(using: .utf8).map { ownedByData in + let part = STPMultipartFormDataPart() + part.name = "owned_by" + part.data = ownedByData + return part + } + + let boundary = STPMultipartFormDataEncoder.generateBoundary() + let parts = [purposePart, ownedByPart, imagePart].compactMap { $0 } + let data = STPMultipartFormDataEncoder.multipartFormData( + for: parts, + boundary: boundary + ) + + var request = configuredRequest( + for: URL(string: FileUploadURL)!, + using: ephemeralKeySecret + ) + request.httpMethod = HTTPMethod.post.rawValue + request.stp_setMultipartForm(data, boundary: boundary) + + let requestStartTime = Date() + sendRequest( + request: request, + completion: { (result: Result) in + let timeToUpload = Date().timeIntervalSince(requestStartTime) + completion( + result.map { + ( + file: $0, + metrics: .init( + timeToUpload: timeToUpload, + fileSizeBytes: imagePart.data?.count ?? 0 + ) + ) + } + ) + } + ) + } + + /// Uses the Stripe file upload API to upload a JPEG encoded image. + /// + /// The image will be automatically resized down if: + /// 1. The given purpose is recognized by the client. + /// 2. It's larger than the maximum allowed file size for the given purpose. + /// + /// - Parameters: + /// - image: The image to be uploaded. + /// - compressionQuality: The compression quality to use when encoding the jpeg. + /// - purpose: The purpose of this file. + /// - fileName: The name of the uploaded file. The "jpeg" extension will + /// automatically be appended to this name. + /// - ownedBy: A Stripe-internal property that sets the owner of the file. + /// - ephemeralKeySecret: Authorization key, if applicable. + /// + /// - Returns: A promise that resolves to a Stripe file, if successful, or an + /// error that may have occurred. + /// + /// - Note: + /// The provided `purpose` must match a supported Purpose by our API or the + /// API will return an error. Generally, this should match a value in + /// `StripeFile.Purpose`, but can be specified by any string for instances + /// where a Stripe endpoint needs to specify a newer purpose that the client + /// SDK does not recognize. + @_spi(STP) public func uploadImage( + _ image: UIImage, + compressionQuality: CGFloat = UIImage.defaultCompressionQuality, + purpose: String, + fileName: String = defaultImageFileName, + ownedBy: String? = nil, + ephemeralKeySecret: String? = nil + ) -> Future { + return uploadImageAndGetMetrics( + image, + compressionQuality: compressionQuality, + purpose: purpose, + fileName: fileName, + ownedBy: ownedBy, + ephemeralKeySecret: ephemeralKeySecret + ).chained { Promise(value: $0.file) } + } + + @_spi(STP) public func uploadImageAndGetMetrics( + _ image: UIImage, + compressionQuality: CGFloat = UIImage.defaultCompressionQuality, + purpose: String, + fileName: String = defaultImageFileName, + ownedBy: String? = nil, + ephemeralKeySecret: String? = nil + ) -> Future { + let promise = Promise() + uploadImageAndGetMetrics( + image, + compressionQuality: compressionQuality, + purpose: purpose, + fileName: fileName, + ownedBy: ownedBy, + ephemeralKeySecret: ephemeralKeySecret + ) { result in + promise.fullfill(with: result) + } + return promise + } + +} + +private let FileUploadURL = "https://uploads.stripe.com/v1/files" diff --git a/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift b/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift new file mode 100644 index 00000000..5791b014 --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/STPAPIClient.swift @@ -0,0 +1,577 @@ +// +// STPAPIClient.swift +// StripeCore +// +// Created by Jack Flintermann on 12/18/14. +// Copyright (c) 2014 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/// A client for making connections to the Stripe API. +@objc public class STPAPIClient: NSObject { + /// The current version of this library. + @objc public static let STPSDKVersion = StripeAPIConfiguration.STPSDKVersion + + /// A shared singleton API client. + /// + /// By default, the SDK uses this instance to make API requests + /// eg in STPPaymentHandler, STPPaymentContext, STPCustomerContext, etc. + @objc(sharedClient) public static let shared: STPAPIClient = { + let client = STPAPIClient() + return client + }() + + /// The client's publishable key. + /// + /// The default value is `StripeAPI.defaultPublishableKey`. + @objc public var publishableKey: String? { + get { + if let publishableKey = _publishableKey { + return publishableKey + } + return StripeAPI.defaultPublishableKey + } + set { + _publishableKey = newValue + Self.validateKey(newValue) + } + } + var _publishableKey: String? + + /// A publishable key that only contains publishable keys and not secret keys. + /// + /// If a secret key is found, returns "[REDACTED_LIVE_KEY]". + @_spi(STP) public var sanitizedPublishableKey: String? { + guard let publishableKey = publishableKey else { + return nil + } + + return (publishableKey.isSecretKey || publishableKeyIsUserKey) + ? "[REDACTED_LIVE_KEY]" : publishableKey + } + + // Stored STPPaymentConfiguration: Type checking handled in STPAPIClient+Payments.swift. + @_spi(STP) public var _stored_configuration: NSObject? + + /// In order to perform API requests on behalf of a connected account, e.g. to + /// create a Source or Payment Method on a connected account, set this property to the ID of the + /// account for which this request is being made. + /// + /// - seealso: https://stripe.com/docs/connect/authentication#authentication-via-the-stripe-account-header + @objc public var stripeAccount: String? + + /// Libraries wrapping the Stripe SDK should set this, so that Stripe can contact you + /// about future issues or critical updates. + /// + /// - seealso: https://stripe.com/docs/building-plugins#setappinfo + @objc public var appInfo: STPAppInfo? + + /// The API version used to communicate with Stripe. + @objc public static let apiVersion = APIVersion + + // MARK: Internal/private properties + @_spi(STP) public var apiURL: URL! = URL(string: APIBaseURL) + @_spi(STP) public var urlSession = URLSession( + configuration: StripeAPIConfiguration.sharedUrlSessionConfiguration + ) + + @_spi(STP) public var sourcePollers: [String: NSObject]? + @_spi(STP) public var sourcePollersQueue: DispatchQueue? + /// A set of beta headers to add to Stripe API requests e.g. `Set(["alipay_beta=v1"])`. + @_spi(STP) public var betas: Set = [] + + /// Returns `true` if `publishableKey` is actually a user key, `false` otherwise. + @_spi(STP) public var publishableKeyIsUserKey: Bool { + return publishableKey?.hasPrefix("uk_") ?? false + } + + /// Determines the `Stripe-Livemode` header value when the publishable key is a user key + @_spi(DashboardOnly) public var userKeyLiveMode = true + + // MARK: Initializers + override public init() { + sourcePollers = [:] + sourcePollersQueue = DispatchQueue(label: "com.stripe.sourcepollers") + } + + /// Initializes an API client with the given publishable key. + /// + /// - Parameter publishableKey: The publishable key to use. + /// - Returns: An instance of STPAPIClient. + @objc(initWithPublishableKey:) + public convenience init( + publishableKey: String + ) { + self.init() + self.publishableKey = publishableKey + } + + @_spi(STP) public func configuredRequest( + for url: URL, + using ephemeralKeySecret: String? = nil, + additionalHeaders: [String: String] = [:] + ) + -> URLRequest + { + var request = URLRequest(url: url) + var headers = defaultHeaders(ephemeralKeySecret: ephemeralKeySecret) + // additionalHeaders can overwrite defaultHeaders. + for (k, v) in additionalHeaders { headers[k] = v } + headers.forEach { key, value in + request.setValue(value, forHTTPHeaderField: key) + } + return request + } + + /// Headers common to all API requests for a given API Client. + func defaultHeaders(ephemeralKeySecret: String?) -> [String: String] { + var defaultHeaders: [String: String] = [:] + defaultHeaders["X-Stripe-User-Agent"] = STPAPIClient.stripeUserAgentDetails(with: appInfo) + var stripeVersion = APIVersion + for beta in betas { + stripeVersion += "; \(beta)" + } + defaultHeaders["Stripe-Version"] = stripeVersion + defaultHeaders["Stripe-Account"] = stripeAccount + for (k, v) in authorizationHeader(using: ephemeralKeySecret) { defaultHeaders[k] = v } + return defaultHeaders + } + + // MARK: Helpers + + static var didShowTestmodeKeyWarning = false + @_spi(STP) public class func validateKey(_ publishableKey: String?) { + guard NSClassFromString("XCTest") == nil else { + return // no asserts in unit tests + } + guard let publishableKey = publishableKey, !publishableKey.isEmpty else { + assertionFailure( + "You must use a valid publishable key. For more info, see https://stripe.com/docs/keys" + ) + return + } + let secretKey = publishableKey.hasPrefix("sk_") + assert( + !secretKey, + "You are using a secret key. Use a publishable key instead. For more info, see https://stripe.com/docs/keys" + ) + #if !DEBUG + if publishableKey.lowercased().hasPrefix("pk_test") && !didShowTestmodeKeyWarning { + print( + "ℹ️ You're using your Stripe testmode key. Make sure to use your livemode key when submitting to the App Store!" + ) + didShowTestmodeKeyWarning = true + } + #endif + } + + class func stripeUserAgentDetails(with appInfo: STPAppInfo?) -> String { + var details: [String: String] = [ + // This SDK isn't in Objective-C anymore, but we sometimes check for + // 'objective-c' to enable iOS SDK-specific behavior in the API. + "lang": "objective-c", + "bindings_version": STPSDKVersion, + ] + let version = UIDevice.current.systemVersion + if version != "" { + details["os_version"] = version + } + var systemInfo = utsname() + uname(&systemInfo) + + // Thanks to https://stackoverflow.com/questions/26028918/how-to-determine-the-current-iphone-device-model + let machineMirror = Mirror(reflecting: systemInfo.machine) + let deviceType = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + details["type"] = deviceType + let model = UIDevice.current.localizedModel + if model != "" { + details["model"] = model + } + + if let vendorIdentifier = UIDevice.current.identifierForVendor?.uuidString { + details["vendor_identifier"] = vendorIdentifier + } + if let appInfo = appInfo { + details["name"] = appInfo.name + details["partner_id"] = appInfo.partnerId + if appInfo.version != nil { + details["version"] = appInfo.version + } + if appInfo.url != nil { + details["url"] = appInfo.url + } + } + let data = try? JSONSerialization.data(withJSONObject: details, options: []) + return String(data: data ?? Data(), encoding: .utf8) ?? "" + } + + @_spi(STP) public func authorizationHeader( + using substituteAuthorizationBearer: String? = nil + ) -> [String: String] { + let authorizationBearer = substituteAuthorizationBearer ?? publishableKey ?? "" + var headers = ["Authorization": "Bearer " + authorizationBearer] + + if publishableKeyIsUserKey { + headers["Stripe-Livemode"] = userKeyLiveMode ? "true" : "false" + } + return headers + } + + @_spi(STP) public var isTestmode: Bool { + guard let publishableKey = publishableKey, !publishableKey.isEmpty else { + return false + } + return publishableKey.lowercased().hasPrefix("pk_test") || (publishableKeyIsUserKey && !userKeyLiveMode) + } +} + +private let APIVersion = "2020-08-27" +private let APIBaseURL = "https://api.stripe.com/v1" + +// MARK: Modern bindings +extension STPAPIClient { + /// Make a GET request using the passed parameters. + @_spi(STP) public func get( + resource: String, + parameters: [String: Any], + ephemeralKeySecret: String? = nil, + consumerPublishableKey: String? = nil, + completion: @escaping ( + Result + ) -> Void + ) { + request( + method: .get, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + resource: resource, + completion: completion + ) + } + + /// Make a GET request using the passed parameters. + @_spi(STP) public func get( + url: URL, + parameters: [String: Any], + ephemeralKeySecret: String? = nil, + consumerPublishableKey: String? = nil, + completion: @escaping ( + Result + ) -> Void + ) { + request( + method: .get, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + url: url, + completion: completion + ) + } + + /// Make a GET request using the passed parameters. + /// + /// - Returns: a promise that is fullfilled when the request is complete. + @_spi(STP) public func get( + resource: String, + parameters: [String: Any], + ephemeralKeySecret: String? = nil, + consumerPublishableKey: String? = nil + ) -> Promise { + return request( + method: .get, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + resource: resource + ) + } + + /// Make a POST request using the passed parameters. + @_spi(STP) public func post( + resource: String, + parameters: [String: Any], + ephemeralKeySecret: String? = nil, + consumerPublishableKey: String? = nil, + completion: @escaping (Result) -> Void + ) { + request( + method: .post, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + resource: resource, + completion: completion + ) + } + + /// Make a POST request using the passed parameters. + @_spi(STP) public func post( + url: URL, + parameters: [String: Any], + ephemeralKeySecret: String? = nil, + consumerPublishableKey: String? = nil, + completion: @escaping (Result) -> Void + ) { + request( + method: .post, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + url: url, + completion: completion + ) + } + + /// Make a POST request using the passed parameters. + /// + /// - Returns: a promise that is fullfilled when the request is complete. + @_spi(STP) public func post( + resource: String, + parameters: [String: Any], + ephemeralKeySecret: String? = nil, + consumerPublishableKey: String? = nil + ) -> Promise { + return request( + method: .post, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + resource: resource + ) + } + + func request( + method: HTTPMethod, + parameters: [String: Any], + ephemeralKeySecret: String?, + consumerPublishableKey: String?, + resource: String + ) -> Promise { + let promise = Promise() + self.request( + method: method, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + resource: resource + ) { result in + promise.fullfill(with: result) + } + return promise + } + + func request( + method: HTTPMethod, + parameters: [String: Any], + ephemeralKeySecret: String?, + consumerPublishableKey: String?, + resource: String, + completion: @escaping (Result) -> Void + ) { + let url = apiURL.appendingPathComponent(resource) + request( + method: method, + parameters: parameters, + ephemeralKeySecret: ephemeralKeySecret, + consumerPublishableKey: consumerPublishableKey, + url: url, + completion: completion + ) + } + + func request( + method: HTTPMethod, + parameters: [String: Any], + ephemeralKeySecret: String?, + consumerPublishableKey: String?, + url: URL, + completion: @escaping (Result) -> Void + ) { + var request = configuredRequest(for: url) + switch method { + case .get: + request.stp_addParameters(toURL: parameters) + case .post: + let formData = URLEncoder.queryString(from: parameters).data(using: .utf8) + request.httpBody = formData + request.setValue( + String(format: "%lu", UInt(formData?.count ?? 0)), + forHTTPHeaderField: "Content-Length" + ) + request.setValue( + "application/x-www-form-urlencoded", + forHTTPHeaderField: "Content-Type" + ) + #if DEBUG + if StripeAPIConfiguration.includeDebugParamsHeader { + request.setValue(URLEncoder.queryString(from: parameters), forHTTPHeaderField: "X-Stripe-Mock-Request") + } + #endif + } + + request.httpMethod = method.rawValue + for (k, v) in authorizationHeader(using: ephemeralKeySecret ?? consumerPublishableKey) { + request.setValue(v, forHTTPHeaderField: k) + } + + if consumerPublishableKey != nil { + // If we now have a consumer publishable key, we no longer send the connected account + // in the header, as otherwise the request will justifiably fail. + request.setValue(nil, forHTTPHeaderField: "Stripe-Account") + } + + self.sendRequest(request: request, completion: completion) + } + + /// Make a POST request using the passed Encodable object. + /// + /// - Returns: a promise that is fullfilled when the request is complete. + @_spi(STP) public func post( + resource: String, + object: I, + ephemeralKeySecret: String? = nil + ) -> Promise { + let promise = Promise() + self.post( + resource: resource, + object: object, + ephemeralKeySecret: ephemeralKeySecret + ) { result in + promise.fullfill(with: result) + } + return promise + } + + /// Make a POST request using the passed Encodable object. + @_spi(STP) public func post( + resource: String, + object: I, + ephemeralKeySecret: String? = nil, + completion: @escaping (Result) -> Void + ) { + let url = apiURL.appendingPathComponent(resource) + post( + url: url, + object: object, + ephemeralKeySecret: ephemeralKeySecret, + completion: completion + ) + } + + /// Make a POST request using the passed Encodable object. + @_spi(STP) public func post( + url: URL, + object: I, + ephemeralKeySecret: String? = nil, + completion: @escaping (Result) -> Void + ) { + do { + let jsonDictionary = try object.encodeJSONDictionary() + let formData = URLEncoder.queryString(from: jsonDictionary).data(using: .utf8) + var request = configuredRequest( + for: url, + using: ephemeralKeySecret, + additionalHeaders: [ + "Content-Length": String(format: "%lu", UInt(formData?.count ?? 0)), + "Content-Type": "application/x-www-form-urlencoded", + ] + ) + request.httpBody = formData + request.httpMethod = HTTPMethod.post.rawValue + + self.sendRequest(request: request, completion: completion) + } catch { + // JSONEncoder can only throw two possible exceptions: + // `invalidFloatingPointValue`, which will never be thrown because of + // our encoder's NonConformingFloatEncodingStrategy. + // The other is `invalidValue` if the top-level object doesn't encode any values. + // This should ~never happen, and if it does the object will be empty, + // so it should be safe to return the un-redacted underlying error. + DispatchQueue.main.async { + completion(.failure(error)) + } + } + } + + func sendRequest( + request: URLRequest, + completion: @escaping (Result) -> Void + ) { + urlSession.stp_performDataTask( + with: request, + completionHandler: { (data, response, error) in + DispatchQueue.main.async { + completion( + STPAPIClient.decodeResponse(data: data, error: error, response: response) + ) + } + } + ) + } + + @_spi(STP) public static func decodeResponse( + data: Data?, + error: Error?, + response: URLResponse? + ) -> Result { + if let error = error { + return .failure(error) + } + guard let data = data else { + return .failure(NSError.stp_genericFailedToParseResponseError()) + } + + do { + // HACK: We must first check if EmptyResponses contain an error since it'll always parse successfully. + if T.self == EmptyResponse.self, + let decodedStripeError = decodeStripeErrorResponse(data: data, response: response) + { + return .failure(decodedStripeError) + } + + let decodedObject: T = try StripeJSONDecoder.decode(jsonData: data) + return .success(decodedObject) + } catch { + // Try decoding the error from the service if one is available + if let decodedStripeError = decodeStripeErrorResponse(data: data, response: response) { + return .failure(decodedStripeError) + } else { + // Return decoding error directly + return .failure(error) + } + } + } + + /// Decodes request data to see if it can be parsed as a Stripe error. + static func decodeStripeErrorResponse( + data: Data, + response: URLResponse? + ) -> StripeError? { + var decodedError: StripeError? + + if let decodedErrorResponse: StripeAPIErrorResponse = try? StripeJSONDecoder.decode( + jsonData: data + ), + var apiError = decodedErrorResponse.error + { + apiError.statusCode = (response as? HTTPURLResponse)?.statusCode + apiError.requestID = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "request-id") + + decodedError = StripeError.apiError(apiError) + } + + return decodedError + } + + enum HTTPMethod: String { + case get = "GET" + case post = "POST" + } +} diff --git a/StripeCore/StripeCore/Source/API Bindings/STPAppInfo.swift b/StripeCore/StripeCore/Source/API Bindings/STPAppInfo.swift new file mode 100644 index 00000000..8799b592 --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/STPAppInfo.swift @@ -0,0 +1,45 @@ +// +// STPAppInfo.swift +// StripeCore +// +// Created by Yuki Tokuhiro on 6/20/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Libraries wrapping the Stripe SDK should use this object to provide information about the +/// library, and set it in on `STPAPIClient`. +/// +/// This information is passed to Stripe so that we can contact you about future issues or +/// critical updates. +/// - seealso: https://stripe.com/docs/building-plugins#setappinfo +@objc public class STPAppInfo: NSObject { + /// Initializes an instance of `STPAppInfo`. + /// + /// - Parameters: + /// - name: The name of your library (e.g. "MyAwesomeLibrary"). + /// - partnerId: Your Stripe Partner ID (e.g. "pp_partner_1234"). Required for Stripe Verified Partners, optional otherwise. + /// - version: The version of your library (e.g. "1.2.34"). Optional. + /// - url: The website for your library (e.g. "https://myawesomelibrary.info"). Optional. + @objc public init( + name: String, + partnerId: String?, + version: String?, + url: String? + ) { + self.name = name + self.partnerId = partnerId + self.version = version + self.url = url + } + + /// The name of your library (e.g. "MyAwesomeLibrary"). + @objc public private(set) var name: String + /// Your Stripe Partner ID (e.g. "pp_partner_1234"). + @objc public private(set) var partnerId: String? + /// The version of your library (e.g. "1.2.34"). + @objc public private(set) var version: String? + /// The website for your library (e.g. "https://myawesomelibrary.info"). + @objc public private(set) var url: String? +} diff --git a/StripeCore/StripeCore/Source/API Bindings/STPMultipartFormDataEncoder.swift b/StripeCore/StripeCore/Source/API Bindings/STPMultipartFormDataEncoder.swift new file mode 100644 index 00000000..c63e8d9f --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/STPMultipartFormDataEncoder.swift @@ -0,0 +1,38 @@ +// +// STPMultipartFormDataEncoder.swift +// StripeCore +// +// Created by Charles Scalesse on 12/1/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Encoder class to generate the HTTP body data for a multipart/form-data request. +/// +/// - seealso: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 +class STPMultipartFormDataEncoder: NSObject { + /// Generates the HTTP body data from an array of parts. + class func multipartFormData(for parts: [STPMultipartFormDataPart], boundary: String) -> Data { + var data = Data() + let boundaryData = "--\(boundary)\r\n".data(using: .utf8) + + for part in parts { + if let boundaryData = boundaryData { + data.append(boundaryData) + } + data.append(part.composedData()) + } + + if let data1 = "--\(boundary)--\r\n".data(using: .utf8) { + data.append(data1) + } + + return data + } + + /// Generates a unique boundary string to be used between parts. + class func generateBoundary() -> String { + return "Stripe-iOS-\(UUID().uuidString)" + } +} diff --git a/StripeCore/StripeCore/Source/API Bindings/STPMultipartFormDataPart.swift b/StripeCore/StripeCore/Source/API Bindings/STPMultipartFormDataPart.swift new file mode 100644 index 00000000..553e11a8 --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/STPMultipartFormDataPart.swift @@ -0,0 +1,63 @@ +// +// STPMultipartFormDataPart.swift +// StripeCore +// +// Created by Charles Scalesse on 12/1/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Represents a single part of a multipart/form-data upload. +/// +/// - seealso: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 +class STPMultipartFormDataPart: NSObject { + /// The data for this part. + var data: Data? + /// The name for this part. + var name: String? + /// The filename for this part. + /// + /// As a rule of thumb, this can be ommitted when the data is just an encoded string. + /// However, this is typically required for other types of binary file data (like images). + var filename: String? + /// The content type for this part. + /// + /// When omitted, the multipart/form-data standard assumes text/plain. + var contentType: String? + + // MARK: - Data Composition + + /// Returns the fully-composed data for this part. + func composedData() -> Data { + var data = Data() + + var contentDisposition = "Content-Disposition: form-data; name=\"\(name ?? "")\"" + if filename != nil { + contentDisposition += "; filename=\"\(filename ?? "")\"" + } + contentDisposition += "\r\n" + + if let data1 = contentDisposition.data(using: .utf8) { + data.append(data1) + } + + var contentType = "" + if let _contentType = self.contentType { + contentType.append("Content-Type: \(_contentType)\r\n") + } + contentType += "\r\n" + if let data1 = contentType.data(using: .utf8) { + data.append(data1) + } + + if let _data = self.data { + data.append(_data) + } + if let data1 = "\r\n".data(using: .utf8) { + data.append(data1) + } + + return data + } +} diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeAPI.swift b/StripeCore/StripeCore/Source/API Bindings/StripeAPI.swift new file mode 100644 index 00000000..5a1095be --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/StripeAPI.swift @@ -0,0 +1,197 @@ +// +// StripeAPI.swift +// StripeCore +// +// Created by Yuki Tokuhiro on 9/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit + +/// A top-level class that imports the rest of the Stripe SDK. +@objc public class StripeAPI: NSObject { + /// Set this to your Stripe publishable API key, obtained from https://dashboard.stripe.com/apikeys. + /// + /// Set this as early as possible in your application's lifecycle, preferably in your AppDelegate or SceneDelegate. + /// New instances of STPAPIClient will be initialized with this value. + /// @warning Make sure not to ship your test API keys to the App Store! This will log a warning if you use your test key in a release build. + @objc public static var defaultPublishableKey: String? + + /// Set this to your Stripe publishable API key, obtained from https://dashboard.stripe.com/apikeys. + /// + /// Set this as early as possible in your application's lifecycle, preferably in your AppDelegate or SceneDelegate. + /// New instances of STPAPIClient will be initialized with this value. + /// @warning Make sure not to ship your test API keys to the App Store! This will log a warning if you use your test key in a release build. + @objc public func setDefaultPublishableKey(_ publishableKey: String) { + StripeAPI.defaultPublishableKey = publishableKey + } + + /// A Boolean value that determines whether additional device data is sent to Stripe for fraud prevention. + /// + /// If YES, additional device signals will be sent to Stripe. + /// For more details on the information we collect, visit https://stripe.com/docs/disputes/prevention/advanced-fraud-detection + /// Disabling this setting will reduce Stripe's ability to protect your business from fraudulent payments. + /// The default value is YES. + @objc public static var advancedFraudSignalsEnabled: Bool = true + + /// If the SDK receives a "Too Many Requests" (429) status code from Stripe, + /// it will automatically retry the request. + /// + /// The default value is 3. + /// See https://stripe.com/docs/rate-limits for more information. + @objc public static var maxRetries = 3 + + // MARK: - Apple Pay + + /// Japanese users can enable JCB for Apple Pay by setting this to `YES`, + /// after they have been approved by JCB. + /// + /// The default value is NO. + /// @note JCB is only supported on iOS 10.1+ + @objc public class var jcbPaymentNetworkSupported: Bool { + get { + return self.additionalEnabledApplePayNetworks.contains(.JCB) + } + set(JCBPaymentNetworkSupported) { + if JCBPaymentNetworkSupported + && !self.additionalEnabledApplePayNetworks.contains(.JCB) + { + self.additionalEnabledApplePayNetworks = + self.additionalEnabledApplePayNetworks + [PKPaymentNetwork.JCB] + } else if !JCBPaymentNetworkSupported { + var updatedNetworks = self.additionalEnabledApplePayNetworks + updatedNetworks.removeAll { + $0 as AnyObject === PKPaymentNetwork.JCB as AnyObject + } + self.additionalEnabledApplePayNetworks = updatedNetworks + } + } + } + + /// The SDK accepts Amex, Mastercard, Visa, and Discover for Apple Pay. + /// + /// Set this property to enable other card networks in addition to these, such as .JCB or .cartesBancaires. + /// For example, `additionalEnabledApplePayNetworks = [.JCB]` enables JCB (note this requires onboarding from JCB and Stripe). + @objc public static var additionalEnabledApplePayNetworks: [PKPaymentNetwork] = [] { + didSet { + // Reset deviceSupportsApplePay for the updated network list: + _deviceSupportsApplePay = nil + } + } + + /// Whether or not this device is capable of using Apple Pay. + /// + /// This checks both whether the device supports Apple Pay, as well as whether or not they have + /// stored Apple Pay cards on their device. + /// + /// - Parameter paymentRequest: The return value of this method depends on the + /// `supportedNetworks` property of this payment request, which by default should be + /// `[.amex, .masterCard, .visa, .discover]`. + /// - Returns: whether or not the user is currently able to pay with Apple Pay. + @objc public class func canSubmitPaymentRequest(_ paymentRequest: PKPaymentRequest) -> Bool { + if !self.deviceSupportsApplePay() { + return false + } + if paymentRequest.merchantIdentifier.isEmpty { + return false + } + // "In versions of iOS prior to version 12.0 and watchOS prior to version 5.0, the amount of the grand total must be greater than zero." + return paymentRequest.paymentSummaryItems.last?.amount.floatValue ?? 0.0 >= 0 + } + + @_spi(STP) public class func supportedPKPaymentNetworks() -> [PKPaymentNetwork] { + return additionalEnabledApplePayNetworks + [ + .amex, + .masterCard, + .maestro, + .visa, + .discover, + ] + } + + /// Whether or not this can make Apple Pay payments via a card network supported + /// by Stripe. + /// + /// The Stripe supported Apple Pay card networks are: + /// American Express, Visa, Mastercard, Discover, Maestro. + /// Japanese users can enable JCB by setting `JCBPaymentNetworkSupported` to YES, + /// after they have been approved by JCB. + /// Users that have the Payment Method Cartes Bancaires set to Active, can enable it + /// by adding `.cartesBancaires` to the `additionalEnabledApplePayNetworks` list. + /// - Returns: YES if the device is currently able to make Apple Pay payments via one + /// of the supported networks. NO if the user does not have a saved card of a + /// supported type, or other restrictions prevent payment (such as parental controls). + @objc public class func deviceSupportsApplePay() -> Bool { + if let deviceSupportsApplePay = _deviceSupportsApplePay { + return deviceSupportsApplePay + } + let deviceSupportsApplePay = PKPaymentAuthorizationController.canMakePayments( + usingNetworks: self.supportedPKPaymentNetworks() + ) + _deviceSupportsApplePay = deviceSupportsApplePay + return deviceSupportsApplePay + } + + /// Cached value of deviceSupportsApplePay + /// `PKPaymentAuthorizationController.canMakePayments` is very slow on macOS Catalyst, so we only request once per process + /// or when the additional networks list changes. + /// This should only return `false` based on the current hardware or parental controls. We don't expect these to change + /// during the life of the process. + private static var _deviceSupportsApplePay: Bool? + + /// A convenience method to build a `PKPaymentRequest` with sane default values. + /// + /// You will still need to configure the `paymentSummaryItems` property to indicate + /// what the user is purchasing, as well as the optional `requiredShippingContactFields`, + /// `requiredBillingContactFields`, and `shippingMethods` properties to indicate + /// what additional contact information your application requires. + /// - Parameters: + /// - merchantIdentifier: Your Apple Merchant ID. + /// - countryCode: The two-letter code for the country where the payment + /// will be processed. This should be the country of your Stripe account. + /// - currencyCode: The three-letter code for the currency used by this + /// payment request. Apple Pay interprets the amounts provided by the summary items + /// attached to this request as amounts in this currency. + /// - Returns: a `PKPaymentRequest` with proper default values. + @objc(paymentRequestWithMerchantIdentifier:country:currency:) + public class func paymentRequest( + withMerchantIdentifier merchantIdentifier: String, + country countryCode: String, + currency currencyCode: String + ) -> PKPaymentRequest { + let paymentRequest = PKPaymentRequest() + paymentRequest.merchantIdentifier = merchantIdentifier + paymentRequest.supportedNetworks = self.supportedPKPaymentNetworks() + #if canImport(CompositorServices) + paymentRequest.merchantCapabilities = .threeDSecure + #else + paymentRequest.merchantCapabilities = .capability3DS + #endif + paymentRequest.countryCode = countryCode.uppercased() + paymentRequest.currencyCode = currencyCode.uppercased() + paymentRequest.requiredBillingContactFields = Set([.postalAddress]) + return paymentRequest + } + + // MARK: - URL callbacks + + /// Call this method in your app delegate whenever you receive an URL in your + /// app delegate for a Stripe callback. + /// + /// For convenience, you can pass all URL's you receive in your app delegate + /// to this method first, and check the return value + /// to easily determine whether it is a callback URL that Stripe will handle + /// or if your app should process it normally. + /// If you are using a universal link URL, you will receive the callback in `application:continueUserActivity:restorationHandler:` + /// To learn more about universal links, see https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html + /// If you are using a native scheme URL, you will receive the callback in `application:openURL:options:` + /// To learn more about native url schemes, see https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html#//apple_ref/doc/uid/TP40007072-CH6-SW10 + /// - Parameter url: The URL that you received in your app delegate + /// - Returns: YES if the URL is expected and will be handled by Stripe. NO otherwise. + @objc(handleStripeURLCallbackWithURL:) @discardableResult public static func handleURLCallback( + with url: URL + ) -> Bool { + return STPURLCallbackHandler.shared().handleURLCallback(url) + } +} diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeAPIConfiguration+Version.swift b/StripeCore/StripeCore/Source/API Bindings/StripeAPIConfiguration+Version.swift new file mode 100644 index 00000000..2758728e --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/StripeAPIConfiguration+Version.swift @@ -0,0 +1,19 @@ +// +// StripeAPIConfiguration+Version.swift +// +// This file was generated by update_version.sh +// Do not edit this file directly. +// Instead, edit the `VERSION` file and run `ci_scripts/update_version.sh` +// + +import Foundation + +extension StripeAPIConfiguration { + /// The current version of this library. + public static let STPSDKVersion = "24.1.2" + + // NOTE: `STPSDKVersion` must be a hard-coded static string instead of + // dynamically generated from the bundle's `CFBundleShortVersionString` to + // ensure the correct value is returned when the SDK is statically linked. + +} diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeAPIConfiguration.swift b/StripeCore/StripeCore/Source/API Bindings/StripeAPIConfiguration.swift new file mode 100644 index 00000000..d3a7c869 --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/StripeAPIConfiguration.swift @@ -0,0 +1,21 @@ +// +// StripeAPIConfiguration.swift +// StripeCore +// +// Created by Mel Ludowise on 5/17/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Shared configurations across all Stripe frameworks. +@_spi(STP) public struct StripeAPIConfiguration { + + public static let sharedUrlSessionConfiguration = URLSessionConfiguration.default + + #if DEBUG + /// If true, embed the params in an X-Stripe-Mock-Request header for network mocking. + @_spi(STP) public static var includeDebugParamsHeader = false + #endif + +} diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeError.swift b/StripeCore/StripeCore/Source/API Bindings/StripeError.swift new file mode 100644 index 00000000..b097131e --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/StripeError.swift @@ -0,0 +1,86 @@ +// +// StripeError.swift +// StripeCore +// +// Created by David Estes on 8/11/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Error codes returned from STPAPIClient. +@_spi(STP) public enum StripeError: Error { + /// The server returned an API error. + case apiError(StripeAPIError) + + /// The request was invalid. + case invalidRequest + + /// Localized description of the error. + public var localizedDescription: String { + return errorDescription ?? NSError.stp_unexpectedErrorMessage() + } +} + +extension StripeError: AnalyticLoggableError { + public var additionalNonPIIErrorDetails: [String: Any] { + [:] + } + public var analyticsErrorCode: String { + switch self { + case .invalidRequest: + "invalidRequest" + case .apiError(let stripeAPIError): + stripeAPIError.code ?? "" + } + } + + public var analyticsErrorType: String { + switch self { + case .invalidRequest: + return String(reflecting: type(of: self)) + case .apiError(let stripeAPIError): + return stripeAPIError.type.rawValue + } + } +} + +// MARK: - LocalizedError + +extension StripeError: LocalizedError { + @_spi(STP) public var errorDescription: String? { + switch self { + case .apiError(let apiError): + return apiError.errorUserInfoString(key: NSLocalizedDescriptionKey) + case .invalidRequest: + return nil + } + } + + @_spi(STP) public var failureReason: String? { + switch self { + case .apiError(let apiError): + return apiError.errorUserInfoString(key: NSLocalizedFailureReasonErrorKey) + case .invalidRequest: + return nil + } + } + + @_spi(STP) public var recoverySuggestion: String? { + switch self { + case .apiError(let apiError): + return apiError.errorUserInfoString(key: NSLocalizedRecoverySuggestionErrorKey) + case .invalidRequest: + return nil + } + } + + @_spi(STP) public var helpAnchor: String? { + switch self { + case .apiError(let apiError): + return apiError.errorUserInfoString(key: NSHelpAnchorErrorKey) + case .invalidRequest: + return nil + } + } +} diff --git a/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift b/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift new file mode 100644 index 00000000..2d4a80fb --- /dev/null +++ b/StripeCore/StripeCore/Source/API Bindings/StripeServiceError.swift @@ -0,0 +1,75 @@ +// +// StripeServiceError.swift +// StripeCore +// +// Created by David Estes on 8/11/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// An error returned from the Stripe API. +/// +/// https://stripe.com/docs/api/errors +@_spi(STP) public struct StripeAPIError: UnknownFieldsDecodable { + /// The type of error returned. + @_spi(STP) public var type: ErrorType + /// For some errors that could be handled programmatically, + /// a short string indicating the error code reported. + /// + /// https://stripe.com/docs/error-codes + @_spi(STP) public var code: String? + /// A URL to more information about the error code reported. + @_spi(STP) public var docUrl: URL? + /// A human-readable message providing more details about the error. + /// + /// For card errors, these messages can be shown to your users. + @_spi(STP) public var message: String? + /// If the error is parameter-specific, the parameter related to the error. + /// + /// For example, you can use this to display a message near the correct form field. + @_spi(STP) public var param: String? + /// The response’s HTTP status code. + @_spi(STP) public var statusCode: Int? + /// The Stripe API request ID, if available. Looks like `req_123`. + @_spi(STP) public var requestID: String? + + // More information may be available in `allResponseFields`, including + // the PaymentIntent or PaymentMethod. + + /// Types of errors presented by the API. + @_spi(STP) public enum ErrorType: String, SafeEnumCodable { + case apiError = "api_error" + case cardError = "card_error" + case idempotencyError = "idempotency_error" + case invalidRequestError = "invalid_request_error" + case unparsable + } + + public var _allResponseFieldsStorage: NonEncodableParameters? +} + +@_spi(STP) public struct StripeAPIErrorResponse: UnknownFieldsDecodable { + @_spi(STP) public var error: StripeAPIError? + + public var _allResponseFieldsStorage: NonEncodableParameters? +} + +extension NSError { + static func stp_error(from stripeApiError: StripeAPIError) -> NSError? { + return stp_error( + errorType: stripeApiError.type.rawValue, + stripeErrorCode: stripeApiError.code, + stripeErrorMessage: stripeApiError.message, + errorParam: stripeApiError.param, + declineCode: nil, + httpResponse: nil + ) + } +} + +extension StripeAPIError { + func errorUserInfoString(key: String) -> String? { + return NSError.stp_error(from: self)?.userInfo[key] as? String + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/Analytic.swift b/StripeCore/StripeCore/Source/Analytics/Analytic.swift new file mode 100644 index 00000000..50b99cb9 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/Analytic.swift @@ -0,0 +1,32 @@ +// +// Analytic.swift +// StripeCore +// +// Created by Mel Ludowise on 3/12/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// An analytic that can be logged to our analytics system. +@_spi(STP) public protocol Analytic { + var event: STPAnalyticEvent { get } + var params: [String: Any] { get } +} + +/// A generic analytic type. +/// +/// - NOTE: This should only be used to support legacy analytics. +/// Any new analytic events should create a new type and conform to `Analytic`. +@_spi(STP) public struct GenericAnalytic: Analytic { + public let event: STPAnalyticEvent + public let params: [String: Any] + + public init( + event: STPAnalyticEvent, + params: [String: Any] + ) { + self.event = event + self.params = params + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/AnalyticLoggableError.swift b/StripeCore/StripeCore/Source/Analytics/AnalyticLoggableError.swift new file mode 100644 index 00000000..b0da2089 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/AnalyticLoggableError.swift @@ -0,0 +1,140 @@ +// +// AnalyticLoggableError.swift +// StripeCore +// +// Created by Nick Porter on 9/2/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Conform your Error to this protocol to override the parameters that get logged when you either: +/// 1. Use `ErrorAnalytic` to send error analytics. +/// 2. Build your own `Analytic` and use `serializeForV1Analytics`. +@_spi(STP) public protocol AnalyticLoggableError: Error { + /// The value used for `"error_type"` in the analytics payload. + /// The default implementation uses `Error.extractErrorType` + var analyticsErrorType: String { get } + + /// The value used for `"error_code"` in the analytics payload. + /// The default implementation uses `Error.errorCode` + var analyticsErrorCode: String { get } + + /// Additional, non-PII/PDE details about the error. + /// If non-empty, this is sent as the value for `"error_details"` in the analytics payload. + var additionalNonPIIErrorDetails: [String: Any] { get } +} + +// MARK: Default implementation +@_spi(STP) extension AnalyticLoggableError { + var analyticsErrorType: String { + Self.extractErrorType(from: self) + } + + var analyticsErrorCode: String { + Self.extractErrorCode(from: self) + } + + var additionalNonPIIErrorDetails: [String: Any] { + return [:] + } +} + +extension AnalyticLoggableError where Self: Error {} + +// MARK: - Error extension methods that serialize errors for analytics logging +@_spi(STP) extension Error { + /// Serialize an Error for logging to q.stripe.com and the `sdk.analytics_events` table + /// + /// It sends the following fields: + /// - error_type: For Stripe API errors, the error’s [type](https://docs.stripe.com/api/errors#errors-type) e.g. “invalid_request_error”. + /// For Swift errors, the fully qualified type name e.g. “StripePaymentSheet.LinkURLGeneratorError”. + /// For NSErrors, the error domain e.g. “NSURLErrorDomain”. + /// - error_code: For Stripe API errors, the error's code e.g. "invalid_number". + /// For NSErrors, the error code e.g. “-1009”. + /// For Swift errors, the enum case name as a string for Swift errors e.g. “noPublishableKey”. + public func serializeForV1Analytics() -> [String: Any] { + let errorType: String = { + if let analyticLoggableError = self as? AnalyticLoggableError { + analyticLoggableError.analyticsErrorType + } else { + Self.extractErrorType(from: self) + } + }() + let errorCode: String = { + if let analyticLoggableError = self as? AnalyticLoggableError { + analyticLoggableError.analyticsErrorCode + } else { + Self.extractErrorCode(from: self) + } + }() + var params: [String: Any] = [ + "error_type": errorType, + "error_code": errorCode, + ] + params["request_id"] = Self.extractStripeAPIRequestID(from: self) + + if let analyticLoggableError = self as? AnalyticLoggableError, !analyticLoggableError.additionalNonPIIErrorDetails.isEmpty { + params["error_details"] = analyticLoggableError.additionalNonPIIErrorDetails + } + return params + } + + /// Extracts a value suitable for the `"error_type"` analytic parameter + /// - For Stripe API errors, the error’s [type](https://docs.stripe.com/api/errors#errors-type) e.g. “invalid_request_error”. + /// - For Swift errors, the fully qualified type name e.g. “StripePaymentSheet.LinkURLGeneratorError”. + /// - For NSErrors, the error domain e.g. “NSURLErrorDomain”. + static func extractErrorType(from error: Error) -> String { + if type(of: error) is NSError.Type { + // Note: checking `error as NSError` always succeeds because Swift errors are bridged - this ensures the error is an instance of NSError. + let error = error as NSError + if error.domain == STPError.stripeDomain, let stripeAPIErrorType = error.userInfo[STPError.stripeErrorTypeKey] as? String { + // For Stripe API Error, use the error type key's value + return stripeAPIErrorType + } else { + // For other NSErrors, use the domain + return "\(error.domain)" + } + } else { + // This is a Swift Error, use the qualified type name e.g. "Swift.DecodingError" or "StripePaymentSheet.PaymentSheetError" + return String(reflecting: type(of: error)) + } + } + + /// Extracts a value suitable for the `"error_code"` analytic parameter + /// - For Stripe API errors, the error's code e.g. "invalid_number". + /// - For NSErrors, the error code e.g. “-1009”. + /// - For Swift errors, the enum case name as a string for Swift errors e.g. “noPublishableKey”. + static func extractErrorCode(from error: Error) -> String { + // Note: We explicitly avoid using String(describing:) or similar to prevent the edge case where an Error conforms to CustomDebugStringConvertible or similar and puts PII in the `description` + let mirror = Mirror(reflecting: error) + if mirror.displayStyle == .enum { + if let caseLabel = mirror.children.first?.label { + // For enums with associated values, this returns the name of the case e.g. DecodingError.keyNotFound(...) -> "keyNotFound" + return caseLabel + } else { + // For enum cases without associated values, reflection does not contain the case name. Since enums can't contain stored properties (besides associated values), we can safely assume String(describing:) doesn't contain PII; any PII would need to have been captured in an associated value. + return "\(error)" + } + } + let error = error as NSError + if error.domain == STPError.stripeDomain, let stripeAPIErrorCode = error.userInfo[STPError.stripeErrorCodeKey] as? String { + // For Stripe API Error, use the error code key's value + return stripeAPIErrorCode + } else { + // Default: Cast to Error and use the code. + return String((error as NSError).code) + } + } + + static func extractStripeAPIRequestID(from error: Error) -> String? { + let error = error as NSError + if error.domain == STPError.stripeDomain { + return error.userInfo[STPError.stripeRequestIDKey] as? String + } else if let error = error as? StripeCore.StripeError, case let .apiError(apiError) = error { + return apiError.requestID + } else { + return nil + } + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/AnalyticLoggableErrorV2.swift b/StripeCore/StripeCore/Source/Analytics/AnalyticLoggableErrorV2.swift new file mode 100644 index 00000000..e70bfd6d --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/AnalyticLoggableErrorV2.swift @@ -0,0 +1,70 @@ +// +// AnalyticLoggableErrorV2.swift +// StripeCore +// +// Created by Nick Porter on 9/2/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Defines a common loggable error to our analytics service for Identity and FinancialConnections SDKs. +@_spi(STP) public protocol AnalyticLoggableErrorV2: Error { + + /// Serializes this error for analytics logging. + /// + /// - Returns: A dictionary representing this error, not containing any PII or PDE + func analyticLoggableSerializeForLogging() -> [String: Any] +} + +/// Error types that conform to this protocol and String-based RawRepresentable +/// will automatically serialize the rawValue for analytics logging. +@_spi(STP) public protocol AnalyticLoggableStringErrorV2: Error { + var loggableType: String { get } +} + +@_spi(STP) extension AnalyticLoggableStringErrorV2 +where Self: RawRepresentable, Self.RawValue == String { + public var loggableType: String { + return rawValue + } +} + +@_spi(STP) extension Error { + /// Serialize an Error for logging, suitable for the Identity and Financial Connections SDK. + public func serializeForV2Logging() -> [String: Any] { + if let loggableError = self as? AnalyticLoggableErrorV2 { + return loggableError.analyticLoggableSerializeForLogging() + } + let nsError = self as NSError + + var payload: [String: Any] = [ + "domain": nsError.domain, + ] + + if let stringError = self as? AnalyticLoggableStringErrorV2 { + payload["type"] = stringError.loggableType + } else { + payload["code"] = nsError.code + } + + return payload + } +} + +extension StripeError: AnalyticLoggableErrorV2 { + public func analyticLoggableSerializeForLogging() -> [String: Any] { + var code: Int + switch self { + case .apiError: + code = 0 + case .invalidRequest: + code = 1 + } + + return [ + "domain": (self as NSError).domain, + "code": code, + ] + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/AnalyticsClientV2.swift b/StripeCore/StripeCore/Source/Analytics/AnalyticsClientV2.swift new file mode 100644 index 00000000..b1f5592b --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/AnalyticsClientV2.swift @@ -0,0 +1,163 @@ +// +// AnalyticsClientV2.swift +// StripeCore +// +// Created by Mel Ludowise on 6/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/// Dependency-injectable protocol for `AnalyticsClientV2`. +@_spi(STP) public protocol AnalyticsClientV2Protocol { + var clientId: String { get } + + func log(eventName: String, parameters: [String: Any]) +} + +/// Logs analytics to `r.stripe.com`. +/// +/// To log analytics to the legacy `q.stripe.com`, use `STPAnalyticsClient`. +@_spi(STP) public class AnalyticsClientV2: AnalyticsClientV2Protocol { + + static let loggerUrl = URL(string: "https://r.stripe.com/0")! + + public let clientId: String + public let origin: String + + private(set) var urlSession: URLSession = URLSession( + configuration: StripeAPIConfiguration.sharedUrlSessionConfiguration + ) + + /// Instantiates an AnalyticsClient capable of logging to a specific events table. + /// + /// - Parameters: + /// - clientId: The client identifier corresponding to `client_config.yaml`. + /// - origin: The origin corresponding to `r.stripe.com.conf`. + public init( + clientId: String, + origin: String + ) { + self.clientId = clientId + self.origin = origin + } + + static let shouldCollectAnalytics: Bool = { + #if targetEnvironment(simulator) + return false + #else + return NSClassFromString("XCTest") == nil + #endif + }() + + var requestHeaders: [String: String] { + return [ + "user-agent": "Stripe/v1 ios/\(StripeAPIConfiguration.STPSDKVersion)", + "origin": origin, + ] + } + + /// Helper to serialize errors to a dictionary that can be included in event parameters. + /// + /// - Parameters: + /// - error: The error to serialize. + /// - filePath: Optionally include the filePath of the call site that threw + /// the error. Only the name of the file (e.g. "MyClass.swift") + /// will be serialized and not the full path. + /// - line: Optionally include the line number of the call site that threw the error. + public static func serialize( + error: Error, + filePath: StaticString?, + line: UInt? + ) -> [String: Any] { + + var payload = error.serializeForV2Logging() + + if let filePath = filePath { + // The full file path can contain the device name, so only include the file name + let fileName = NSString(string: "\(filePath)").lastPathComponent + payload["file"] = fileName + } + if let line = line { + payload["line"] = line + } + + return payload + } + + public func log(eventName: String, parameters: [String: Any]) { + let payload = payload(withEventName: eventName, parameters: parameters) + + #if DEBUG + let jsonString = String( + data: try! JSONSerialization.data( + withJSONObject: payload, + options: [.sortedKeys, .prettyPrinted] + ), + encoding: .utf8 + )! + NSLog("LOG ANALYTICS: \(jsonString)") + #endif + + guard AnalyticsClientV2.shouldCollectAnalytics else { + return + } + + var request = URLRequest(url: AnalyticsClientV2.loggerUrl) + request.httpMethod = "POST" + request.stp_setFormPayload(payload.jsonEncodeNestedDicts(options: .sortedKeys)) + requestHeaders.forEach { key, value in + request.setValue(value, forHTTPHeaderField: key) + } + let task: URLSessionDataTask = urlSession.dataTask(with: request as URLRequest) + task.resume() + } +} + +extension AnalyticsClientV2Protocol { + public func makeCommonPayload() -> [String: Any] { + var payload: [String: Any] = [:] + + // Required by Analytics Event Logger + payload["client_id"] = self.clientId + payload["event_id"] = UUID().uuidString + payload["created"] = Date().timeIntervalSince1970 + + // Common payload + let version = UIDevice.current.systemVersion + if !version.isEmpty { + payload["os_version"] = version + } + payload["sdk_platform"] = "ios" + payload["sdk_version"] = StripeAPIConfiguration.STPSDKVersion + if let deviceType = STPDeviceUtils.deviceType { + payload["device_type"] = deviceType + } + payload["app_name"] = Bundle.stp_applicationName() ?? "" + payload["app_version"] = Bundle.stp_applicationVersion() ?? "" + payload["plugin_type"] = PluginDetector.shared.pluginType?.rawValue + payload["platform_info"] = [ + "install": InstallMethod.current.rawValue, + "app_bundle_id": Bundle.stp_applicationBundleId() ?? "", + ] + if let deviceId = UIDevice.current.identifierForVendor?.uuidString { + payload["device_id"] = deviceId + } + + return payload + } + + public func payload(withEventName eventName: String, parameters: [String: Any]) -> [String: Any] + { + var payload = makeCommonPayload() + payload["event_name"] = eventName + payload = payload.merging( + parameters, + uniquingKeysWith: { a, _ in + return a + } + ) + return payload + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/AnalyticsHelper.swift b/StripeCore/StripeCore/Source/Analytics/AnalyticsHelper.swift new file mode 100644 index 00000000..aa629910 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/AnalyticsHelper.swift @@ -0,0 +1,54 @@ +// +// AnalyticsHelper.swift +// StripeCore +// + +import Foundation + +@_spi(STP) public class AnalyticsHelper { + @_spi(STP) public enum TimeMeasurement { + case linkSignup + case linkPopup + } + + @_spi(STP) public static let shared = AnalyticsHelper() + + @_spi(STP) public private(set) var sessionID: String? + + private let timeProvider: () -> Date + + private var startTimes: [TimeMeasurement: Date] = [:] + + init(timeProvider: @escaping () -> Date = Date.init) { + self.timeProvider = timeProvider + } + + @_spi(STP) public func generateSessionID() { + let uuid = UUID() + // Convert the UUID to lowercase to comply with RFC 4122 and ITU-T X.667. + sessionID = uuid.uuidString.lowercased() + } + + @_spi(STP) public func startTimeMeasurement(_ measurement: TimeMeasurement) { + startTimes[measurement] = timeProvider() + } + + @_spi(STP) public func getDuration(for measurement: TimeMeasurement) -> TimeInterval? { + guard let startTime = startTimes[measurement] else { + // Return `nil` if the time measurement hasn't started. + return nil + } + + let now = timeProvider() + return now.roundedTimeIntervalSince(startTime) + } +} + +extension Date { + @_spi(STP) public func roundedTimeIntervalSince(_ date: Date) -> TimeInterval { + let duration = timeIntervalSince(date) + + // Round to 2 decimal places + return round(duration * 100) / 100 + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/Eventing/Notification+Stripe.swift b/StripeCore/StripeCore/Source/Analytics/Eventing/Notification+Stripe.swift new file mode 100644 index 00000000..a2084e4e --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/Eventing/Notification+Stripe.swift @@ -0,0 +1,85 @@ +// +// Notification+Stripe.swift +// StripeCore +// + +import Foundation + +@_spi(MobilePaymentElementAnalyticEventBeta) +public extension Notification.Name { + /// These events are intended to be used for analytics purposes ONLY. + /// + /// A notification posted by Mobile Payment Element for analytics purposes. + static let mobilePaymentElement = Notification.Name("MobilePaymentElement") +} + +@_spi(MobilePaymentElementAnalyticEventBeta) +/// The object type of the NSNotification's object. +public struct MobilePaymentElementAnalyticEvent { + + /// The name of the event + public let name: Name + + public enum Name: Equatable { + /// Sheet is presented + case presentedSheet + /// Selected a different payment method type + case selectedPaymentMethodType(SelectedPaymentMethodType) + /// Payment method form for was displayed + case displayedPaymentMethodForm(DisplayedPaymentMethodForm) + + /// User interacted with a payment method form + case startedInteractionWithPaymentMethodForm(StartedInteractionWithPaymentMethodForm) + /// All mandatory fields for the payment method form have been completed + case completedPaymentMethodForm(CompletedPaymentMethodForm) + /// User tapped on the confirm button + case tappedConfirmButton(TappedConfirmButton) + + /// User selected a saved payment method + case selectedSavedPaymentMethod(SelectedSavedPaymentMethod) + /// User removed a saved payment method + case removedSavedPaymentMethod(RemovedSavedPaymentMethod) + } + + /// Details of the .selectedPaymentMethodType event + public struct SelectedPaymentMethodType: Equatable { + /// The payment method type + public let paymentMethodType: String + } + + /// Details of the .displayedPaymentMethodForm event + public struct DisplayedPaymentMethodForm: Equatable { + /// The payment method type + public let paymentMethodType: String + } + + /// Details of the .startedInteractionWithPaymentMethodForm event + public struct StartedInteractionWithPaymentMethodForm: Equatable { + /// The payment method type + public let paymentMethodType: String + } + + /// Details of the .completedPaymentMethodForm event + public struct CompletedPaymentMethodForm: Equatable { + /// The payment method type + public let paymentMethodType: String + } + + /// Details of the .tappedConfirmButton event + public struct TappedConfirmButton: Equatable { + /// The payment method type + public let paymentMethodType: String + } + + /// Details of the .selectedSavedPaymentMethod event + public struct SelectedSavedPaymentMethod: Equatable { + /// The payment method type + public let paymentMethodType: String + } + + /// Details of the .removedSavedPaymentMethod event + public struct RemovedSavedPaymentMethod: Equatable { + /// The payment method type + public let paymentMethodType: String + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/Eventing/STPAnalyticsEventTranslator.swift b/StripeCore/StripeCore/Source/Analytics/Eventing/STPAnalyticsEventTranslator.swift new file mode 100644 index 00000000..2ab3f331 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/Eventing/STPAnalyticsEventTranslator.swift @@ -0,0 +1,89 @@ +// +// STPAnalyticsEventTranslator.swift +// StripeCore +// + +import Foundation + +struct STPAnalyticsTranslatedEvent { + let notificationName: Notification.Name + let event: MobilePaymentElementAnalyticEvent + + init(notificationName: Notification.Name = .mobilePaymentElement, + name: MobilePaymentElementAnalyticEvent.Name) { + self.notificationName = notificationName + self.event = .init(name: name) + } +} + +struct STPAnalyticsEventTranslator { + func translate(_ analyticEvent: STPAnalyticEvent, payload: [String: Any]) -> STPAnalyticsTranslatedEvent? { + guard let translatedEventName = translateEvent(analyticEvent, payload: payload) else { + return nil + } + return .init(name: translatedEventName) + } + + func translateEvent(_ analyticEvent: STPAnalyticEvent, payload: [String: Any]) -> MobilePaymentElementAnalyticEvent.Name? { + let paymentMethodType = paymentMethodType(payload) + switch analyticEvent { + + // Sheet presentation + case .mcShowCustomNewPM, .mcShowCompleteNewPM, .mcShowCustomSavedPM, .mcShowCompleteSavedPM: + return .presentedSheet + + // Tapping on a payment method type + case .paymentSheetCarouselPaymentMethodTapped: + guard let paymentMethodType else { + return nil + } + return .selectedPaymentMethodType(.init(paymentMethodType: paymentMethodType)) + + // Payment Method form showed + case .paymentSheetFormShown: + guard let paymentMethodType else { + return nil + } + return .displayedPaymentMethodForm(.init(paymentMethodType: paymentMethodType)) + + // Form Interaction + case .paymentSheetFormInteracted: + guard let paymentMethodType else { + return nil + } + return .startedInteractionWithPaymentMethodForm(.init(paymentMethodType: paymentMethodType)) + case .paymentSheetFormCompleted: + guard let paymentMethodType else { + return nil + } + return .completedPaymentMethodForm(.init(paymentMethodType: paymentMethodType)) + case .paymentSheetConfirmButtonTapped: + guard let paymentMethodType else { + return nil + } + return .tappedConfirmButton(.init(paymentMethodType: paymentMethodType)) + + // Saved Payment Methods + case .mcOptionSelectCustomSavedPM, .mcOptionSelectCompleteSavedPM: + guard let paymentMethodType else { + return nil + } + return .selectedSavedPaymentMethod(.init(paymentMethodType: paymentMethodType)) + case .mcOptionRemoveCustomSavedPM, .mcOptionRemoveCompleteSavedPM: + guard let paymentMethodType else { + return nil + } + return .removedSavedPaymentMethod(.init(paymentMethodType: paymentMethodType)) + + default: + return nil + } + } + + func paymentMethodType(_ originalPayload: [String: Any]) -> String? { + if let paymentMethodType = originalPayload["selected_lpm"] as? String { + return paymentMethodType + } + return nil + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/NetworkDetector.swift b/StripeCore/StripeCore/Source/Analytics/NetworkDetector.swift new file mode 100644 index 00000000..6e9d9113 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/NetworkDetector.swift @@ -0,0 +1,59 @@ +// +// NetworkDetector.swift +// StripeCore +// +// Created by Nick Porter on 7/5/23. +// + +#if canImport(CoreTelephony) +import CoreTelephony +#endif +import Foundation +import SystemConfiguration + +/// A class which can detect the current network type of the device +class NetworkDetector { + + static func getConnectionType() -> String? { +#if canImport(CoreTelephony) + guard let reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, "www.stripe.com") else { + return nil + } + + var flags = SCNetworkReachabilityFlags() + SCNetworkReachabilityGetFlags(reachability, &flags) + + let isReachable = flags.contains(.reachable) + let isWWAN = flags.contains(.isWWAN) + + guard isReachable else { + return nil + } + + guard isWWAN else { + return "Wi-Fi" + } + + let networkInfo = CTTelephonyNetworkInfo() + let carrierType = networkInfo.serviceCurrentRadioAccessTechnology + + guard let carrierTypeName = carrierType?.first?.value else { + return "unknown" + } + + switch carrierTypeName { + case CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyEdge, CTRadioAccessTechnologyCDMA1x: + return "2G" + case CTRadioAccessTechnologyWCDMA, CTRadioAccessTechnologyHSDPA, CTRadioAccessTechnologyHSUPA, CTRadioAccessTechnologyCDMAEVDORev0, CTRadioAccessTechnologyCDMAEVDORevA, CTRadioAccessTechnologyCDMAEVDORevB, CTRadioAccessTechnologyeHRPD: + return "3G" + case CTRadioAccessTechnologyLTE: + return "4G" + default: + return "5G" + } +#else + return "Wi-Fi" +#endif + } + +} diff --git a/StripeCore/StripeCore/Source/Analytics/PluginDetector.swift b/StripeCore/StripeCore/Source/Analytics/PluginDetector.swift new file mode 100644 index 00000000..88036034 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/PluginDetector.swift @@ -0,0 +1,47 @@ +// +// PluginDetector.swift +// StripeCore +// +// Created by Nick Porter on 10/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// A class which can detect if the host app is using a known cross-platform solution. +class PluginDetector { + + /// Shared instance of the `PluginDetector` to enable caching of the `pluginType`. + static let shared = PluginDetector() + + /// Represents all the known/tracked cross-platform solutions. + enum PluginType: String, CaseIterable { + case cordova + case flutter + case ionic + case reactNative = "react-native" + case unity + case xamarin + + /// Represents a known class contained in each cross-platform environment. + var className: String { + switch self { + case .cordova: return "CDVPlugin" + case .flutter: return "FlutterAppDelegate" + case .ionic: return "CAPPlugin" + case .reactNative: return "RCTBridge" + case .unity: return "UnityFramework" + case .xamarin: return "XamarinAssociatedObject" + } + } + } + + /// Determines if this app is running within a plugin environment. + /// + /// - Returns: returns the plugin type if found, otherwise nil. + lazy var pluginType: PluginType? = { + PluginType.allCases.first { type in + NSClassFromString(type.className) != nil + } + }() +} diff --git a/StripeCore/StripeCore/Source/Analytics/STPAnalyticEvent.swift b/StripeCore/StripeCore/Source/Analytics/STPAnalyticEvent.swift new file mode 100644 index 00000000..0c88c7bd --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/STPAnalyticEvent.swift @@ -0,0 +1,296 @@ +// +// STPAnalyticEvent.swift +// StripeCore +// +// Created by Mel Ludowise on 3/12/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Enumeration of all the analytic events logged by our SDK. +@_spi(STP) public enum STPAnalyticEvent: String { + // MARK: - Payment Creation + case tokenCreation = "stripeios.token_creation" + + // This was "stripeios.source_creation" in earlier SDKs, + // but we need to support both the old and new values forever. + case sourceCreation = "stripeios.source_creationn" + + case paymentMethodCreation = "stripeios.payment_method_creation" + case paymentMethodUpdate = "stripeios.payment_method_update" + case paymentMethodIntentCreation = "stripeios.payment_intent_confirmation" + case setupIntentConfirmationAttempt = "stripeios.setup_intent_confirmation" + + // MARK: - Payment Confirmation + case _3DS2AuthenticationRequestParamsFailed = + "stripeios.3ds2_authentication_request_params_failed" + case _3DS2AuthenticationAttempt = "stripeios.3ds2_authenticate" + case _3DS2FrictionlessFlow = "stripeios.3ds2_frictionless_flow" + case urlRedirectNextAction = "stripeios.url_redirect_next_action" + case urlRedirectNextActionCompleted = "stripeios.url_redirect_next_action_completed" + case _3DS2ChallengeFlowPresented = "stripeios.3ds2_challenge_flow_presented" + case _3DS2ChallengeFlowTimedOut = "stripeios.3ds2_challenge_flow_timed_out" + case _3DS2ChallengeFlowUserCanceled = "stripeios.3ds2_challenge_flow_canceled" + case _3DS2ChallengeFlowCompleted = "stripeios.3ds2_challenge_flow_completed" + case _3DS2ChallengeFlowErrored = "stripeios.3ds2_challenge_flow_errored" + case _3DS2RedirectUserCanceled = "stripeios.3ds2_redirect_canceled" + case paymentHandlerConfirmStarted = "stripeios.paymenthandler.confirm.started" + case paymentHandlerConfirmFinished = "stripeios.paymenthandler.confirm.finished" + case paymentHandlerHandleNextActionStarted = "stripeios.paymenthandler.handle_next_action.started" + case paymentHandlerHandleNextActionFinished = "stripeios.paymenthandler.handle_next_action.finished" + + // MARK: - Card Metadata + case cardMetadataLoadedTooSlow = "stripeios.card_metadata_loaded_too_slow" + case cardMetadataResponseFailure = "stripeios.card_metadata_load_failure" + case cardMetadataMissingRange = "stripeios.card_metadata_missing_range" + + // MARK: - Card Scanning + case cardScanSucceeded = "stripeios.cardscan_success" + case cardScanCancelled = "stripeios.cardscan_cancel" + + // MARK: - Card Element Config + case cardElementConfigLoadFailure = "stripeios.card_element_config_load_failure" + + // 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" + + // MARK: - FinancialConnections + case financialConnectionsSheetPresented = "stripeios.financialconnections.sheet.presented" + case financialConnectionsSheetClosed = "stripeios.financialconnections.sheet.closed" + case financialConnectionsSheetFailed = "stripeios.financialconnections.sheet.failed" + case financialConnectionsSheetFlowDetermined = "stripeios.financialconnections.sheet.flow_determined" + case financialConnectionsSheetInitialSynchronizeStarted = "stripeios.financialconnections.sheet.initial_synchronize.started" + case financialConnectionsSheetInitialSynchronizeCompleted = "stripeios.financialconnections.sheet.initial_synchronize.completed" + + // MARK: - PaymentSheet Init + case mcInitCustomCustomer = "mc_custom_init_customer" + case mcInitCompleteCustomer = "mc_complete_init_customer" + case mcInitCustomApplePay = "mc_custom_init_applepay" + case mcInitCompleteApplePay = "mc_complete_init_applepay" + case mcInitCustomCustomerApplePay = "mc_custom_init_customer_applepay" + case mcInitCompleteCustomerApplePay = "mc_complete_init_customer_applepay" + case mcInitCustomDefault = "mc_custom_init_default" + case mcInitCompleteDefault = "mc_complete_init_default" + + // MARK: - Embedded Payment Element init + case mcInitEmbedded = "mc_embedded_init" + + // MARK: - PaymentSheet Show + case mcShowCustomNewPM = "mc_custom_sheet_newpm_show" + case mcShowCustomSavedPM = "mc_custom_sheet_savedpm_show" + case mcShowCompleteNewPM = "mc_complete_sheet_newpm_show" + case mcShowCompleteSavedPM = "mc_complete_sheet_savedpm_show" + + // MARK: - PaymentSheet Payment + case mcPaymentCustomNewPMSuccess = "mc_custom_payment_newpm_success" + case mcPaymentCustomSavedPMSuccess = "mc_custom_payment_savedpm_success" + case mcPaymentCustomApplePaySuccess = "mc_custom_payment_applepay_success" + case mcPaymentCustomLinkSuccess = "mc_custom_payment_link_success" + + case mcPaymentCompleteNewPMSuccess = "mc_complete_payment_newpm_success" + case mcPaymentCompleteSavedPMSuccess = "mc_complete_payment_savedpm_success" + case mcPaymentCompleteApplePaySuccess = "mc_complete_payment_applepay_success" + case mcPaymentCompleteLinkSuccess = "mc_complete_payment_link_success" + + case mcPaymentCustomNewPMFailure = "mc_custom_payment_newpm_failure" + case mcPaymentCustomSavedPMFailure = "mc_custom_payment_savedpm_failure" + case mcPaymentCustomApplePayFailure = "mc_custom_payment_applepay_failure" + case mcPaymentCustomLinkFailure = "mc_custom_payment_link_failure" + + case mcPaymentCompleteNewPMFailure = "mc_complete_payment_newpm_failure" + case mcPaymentCompleteSavedPMFailure = "mc_complete_payment_savedpm_failure" + case mcPaymentCompleteApplePayFailure = "mc_complete_payment_applepay_failure" + case mcPaymentCompleteLinkFailure = "mc_complete_payment_link_failure" + + case mcPaymentEmbeddedSuccess = "mc_embedded_payment_success" + case mcPaymentEmbeddedFailure = "mc_embedded_payment_failure" + + // MARK: - PaymentSheet Option Selected + case mcOptionSelectCustomNewPM = "mc_custom_paymentoption_newpm_select" + case mcOptionSelectCustomSavedPM = "mc_custom_paymentoption_savedpm_select" + case mcOptionSelectCustomApplePay = "mc_custom_paymentoption_applepay_select" + case mcOptionSelectCustomLink = "mc_custom_paymentoption_link_select" + case mcOptionSelectCompleteNewPM = "mc_complete_paymentoption_newpm_select" + case mcOptionSelectCompleteSavedPM = "mc_complete_paymentoption_savedpm_select" + case mcOptionSelectCompleteApplePay = "mc_complete_paymentoption_applepay_select" + case mcOptionSelectCompleteLink = "mc_complete_paymentoption_link_select" + case mcOptionSelectEmbeddedSavedPM = "mc_embedded_paymentoption_savedpm_select" + + // MARK: - PaymentSheet Saved Payment Method Removed + case mcOptionRemoveCustomSavedPM = "mc_custom_paymentoption_removed" + case mcOptionRemoveCompleteSavedPM = "mc_complete_paymentoption_removed" + case mcOptionRemoveEmbeddedSavedPM = "mc_embedded_paymentoption_removed" + + // MARK: - Link Signup + case linkSignupCheckboxChecked = "link.signup.checkbox_checked" + case linkSignupFlowPresented = "link.signup.flow_presented" + case linkSignupStart = "link.signup.start" + case linkSignupComplete = "link.signup.complete" + case linkSignupFailure = "link.signup.failure" + case linkCreatePaymentDetailsFailure = "link.payment.failure.create" + case linkSharePaymentDetailsFailure = "link.payment.failure.share" + case linkSignupFailureInvalidSessionState = "link.signup.failure.invalidSessionState" + case linkSignupFailureAccountExists = "link.signup.failure.account_exists" + + // MARK: - Link Popup + case linkPopupShow = "link.popup.show" + case linkPopupSuccess = "link.popup.success" + case linkPopupCancel = "link.popup.cancel" + case linkPopupSkipped = "link.popup.skipped" + case linkPopupError = "link.popup.error" + case linkPopupLogout = "link.popup.logout" + + // MARK: - Link 2FA + case link2FAStart = "link.2fa.start" + case link2FAStartFailure = "link.2fa.start_failure" + case link2FAComplete = "link.2fa.complete" + case link2FACancel = "link.2fa.cancel" + case link2FAFailure = "link.2fa.failure" + + // MARK: - Link Misc + case linkAccountLookupComplete = "link.account_lookup.complete" + case linkAccountLookupFailure = "link.account_lookup.failure" + + // MARK: - LUXE + case luxeSerializeFailure = "luxe_serialize_failure" + case luxeSpecSerializeFailure = "luxe_spec_serialize_failure" + + case luxeImageSelectorIconDownloaded = "luxe_image_selector_icon_downloaded" + case luxeImageSelectorIconFromBundle = "luxe_image_selector_icon_from_bundle" + case luxeImageSelectorIconNotFound = "luxe_image_selector_icon_not_found" + + // MARK: - CustomerSheet initialization + case customerSheetInitWithCustomerAdapter = "cs_init_with_customer_adapter" + case customerSheetInitWithCustomerSession = "cs_init_with_customer_session" + case customerSheetLoadStarted = "cs_load_started" + case customerSheetLoadSucceeded = "cs_load_succeeded" + case customerSheetLoadFailed = "cs_load_failed" + + // MARK: - Customer Sheet + case cs_add_payment_method_screen_presented = "cs_add_payment_method_screen_presented" + case cs_select_payment_method_screen_presented = "cs_select_payment_method_screen_presented" + + case cs_select_payment_method_screen_confirmed_savedpm_success = "cs_select_payment_method_screen_confirmed_savedpm_success" + case cs_select_payment_method_screen_confirmed_savedpm_failure = "cs_select_payment_method_screen_confirmed_savedpm_failure" + + case cs_select_payment_method_screen_edit_tapped = "cs_select_payment_method_screen_edit_tapped" + case cs_select_payment_method_screen_done_tapped = "cs_select_payment_method_screen_done_tapped" + + case cs_select_payment_method_screen_removepm_success = "cs_select_payment_method_screen_removepm_success" + case cs_select_payment_method_screen_removepm_failure = "cs_select_payment_method_screen_removepm_failure" + + case cs_add_payment_method_via_setupintent_success = "cs_add_payment_method_via_setup_intent_success" + case cs_add_payment_method_via_setupintent_canceled = "cs_add_payment_method_via_setupintent_canceled" + case cs_add_payment_method_via_setupintent_failure = "cs_add_payment_method_via_setup_intent_failure" + + case cs_add_payment_method_via_createAttach_success = "cs_add_payment_method_via_createAttach_success" + case cs_add_payment_method_via_createAttach_failure = "cs_add_payment_method_via_createAttach_failure" + + // MARK: - Address Element + case addressShow = "mc_address_show" + case addressCompleted = "mc_address_completed" + + // MARK: - PaymentMethodMessagingView + case paymentMethodMessagingViewLoadSucceeded = "pmmv_load_succeeded" + case paymentMethodMessagingViewLoadFailed = "pmmv_load_failed" + case paymentMethodMessagingViewTapped = "pmmv_tapped" + + // MARK: - PaymentSheet Force Success + case paymentSheetForceSuccess = "mc_force_success" + + // MARK: - PaymentSheet initialization + case paymentSheetLoadStarted = "mc_load_started" + case paymentSheetLoadSucceeded = "mc_load_succeeded" + case paymentSheetLoadFailed = "mc_load_failed" + + // MARK: - PaymentSheet dismiss + case paymentSheetDismissed = "mc_dismiss" + + // MARK: - PaymentSheet checkout + case paymentSheetCarouselPaymentMethodTapped = "mc_carousel_payment_method_tapped" + case paymentSheetConfirmButtonTapped = "mc_confirm_button_tapped" + case paymentSheetFormShown = "mc_form_shown" + case paymentSheetFormInteracted = "mc_form_interacted" + case paymentSheetFormCompleted = "mc_form_completed" + case paymentSheetCardNumberCompleted = "mc_card_number_completed" + case paymentSheetDeferredIntentPaymentMethodMismatch = "mc_deferred_intent_payment_method_mismatch" + + // MARK: - v1/elements/session + case paymentSheetElementsSessionLoadFailed = "mc_elements_session_load_failed" + case paymentSheetElementsSessionCustomerDeserializeFailed = "mc_elements_session_customer_deserialize_failed" + case paymentSheetElementsSessionEPMLoadFailed = "mc_elements_session_epms_load_failed" + + // MARK: - PaymentSheet card brand choice + case paymentSheetDisplayCardBrandDropdownIndicator = "mc_display_cbc_dropdown" + case paymentSheetOpenCardBrandDropdown = "mc_open_cbc_dropdown" + case paymentSheetCloseCardBrandDropDown = "mc_close_cbc_dropdown" + case paymentSheetOpenCardBrandEditScreen = "mc_open_edit_screen" + case paymentSheetUpdateCardBrand = "mc_update_card" + case paymentSheetUpdateCardBrandFailed = "mc_update_card_failed" + case paymentSheetClosesEditScreen = "mc_cancel_edit_screen" + case paymentSheetDisallowedCardBrand = "mc_disallowed_card_brand" + + // MARK: - CustomerSheet card brand choice + case customerSheetDisplayCardBrandDropdownIndicator = "cs_display_cbc_dropdown" + case customerSheetOpenCardBrandDropdown = "cs_open_cbc_dropdown" + case customerSheetCloseCardBrandDropDown = "cs_close_cbc_dropdown" + case customerSheetOpenCardBrandEditScreen = "cs_open_edit_screen" + case customerSheetUpdateCardBrand = "cs_update_card" + case customerSheetUpdateCardBrandFailed = "cs_update_card_failed" + case customerSheetClosesEditScreen = "cs_cancel_edit_screen" + + // MARK: - Basic Integration + // Loading + case biLoadStarted = "bi_load_started" + case biLoadSucceeded = "bi_load_succeeded" + case biLoadFailed = "bi_load_failed" + + // Confirmation + case biPaymentCompleteNewPMSuccess = "bi_complete_payment_newpm_success" + case biPaymentCompleteSavedPMSuccess = "bi_complete_payment_savedpm_success" + case biPaymentCompleteApplePaySuccess = "bi_complete_payment_applepay_success" + case biPaymentCompleteNewPMFailure = "bi_complete_payment_newpm_failure" + case biPaymentCompleteSavedPMFailure = "bi_complete_payment_savedpm_failure" + case biPaymentCompleteApplePayFailure = "bi_complete_payment_applepay_failure" + + // UI events + case biOptionsShown = "bi_options_shown" + case biFormShown = "bi_form_shown" + case biFormInteracted = "bi_form_interacted" + case biCardNumberCompleted = "bi_card_number_completed" + case biDoneButtonTapped = "bi_done_button_tapped" + + // MARK: - STPBankAccountCollector + case bankAccountCollectorStarted = "stripeios.bankaccountcollector.started" + case bankAccountCollectorFinished = "stripeios.bankaccountcollector.finished" + + // MARK: - Unexpected errors + // These errors should _never happen_ and indicate a problem with the SDK or the Stripe backend. + case unexpectedPaymentSheetFormFactoryError = "unexpected_error.paymentsheet.formfactory" + case unexpectedStripeUICoreAddressSpecProvider = "unexpected_error.stripeuicore.addressspecprovider" + case unexpectedStripeUICoreBSBNumberProvider = "unexpected_error.stripeuicore.bsbnumberprovider" + case unexpectedApplePayError = "unexpected_error.applepay" + case unexpectedPaymentSheetError = "unexpected_error.paymentsheet" + case unexpectedCustomerSheetError = "unexpected_error.customersheet" + case unexpectedPaymentSheetConfirmationError = "unexpected_error.paymentsheet.confirmation" + case unexpectedPaymentSheetViewControllerError = "unexpected_error.paymentsheet.paymentsheetviewcontroller" + case unexpectedFlowControllerViewControllerError = "unexpected_error.paymentsheet.flowcontrollerviewcontroller" + case unexpectedPaymentHandlerError = "unexpected_error.paymenthandler" + + // MARK: - Misc. errors + case stripePaymentSheetDownloadManagerError = "stripepaymentsheet.downloadmanager.error" + + // MARK: - Refresh Endpoint + case refreshPaymentIntentStarted = "stripeios.refresh_payment_intent_started" + case refreshSetupIntentStarted = "stripeios.refresh_setup_intent_started" + case refreshPaymentIntentSuccess = "stripeios.refresh_payment_intent_success" + case refreshSetupIntentSuccess = "stripeios.refresh_setup_intent_success" + case refreshPaymentIntentFailed = "stripeios.refresh_payment_intent_failed" + case refreshSetupIntentFailed = "stripeios.refresh_setup_intent_failed" + + // MARK: - Telemetry Client + case fraudDetectionApiFailure = "fraud_detection_data_repository.api_failure" +} diff --git a/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient+Error.swift b/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient+Error.swift new file mode 100644 index 00000000..7f1e2104 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient+Error.swift @@ -0,0 +1,26 @@ +// +// STPAnalyticsClient+Error.swift +// StripeCameraCore +// +// Created by Yuki Tokuhiro on 3/18/24. +// + +import Foundation + +/// An error analytic that can be logged to our analytics system. +@_spi(STP) public struct ErrorAnalytic: Analytic { + public let event: STPAnalyticEvent + public let error: Error + public var params: [String: Any] { + var params = error.serializeForV1Analytics() + params.mergeAssertingOnOverwrites(additionalNonPIIParams) + return params + } + let additionalNonPIIParams: [String: Any] + + public init(event: STPAnalyticEvent, error: Error, additionalNonPIIParams: [String: Any] = [:]) { + self.event = event + self.error = error + self.additionalNonPIIParams = additionalNonPIIParams + } +} diff --git a/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift b/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift new file mode 100644 index 00000000..39460383 --- /dev/null +++ b/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift @@ -0,0 +1,167 @@ +// +// STPAnalyticsClient.swift +// StripeCore +// +// Created by Ben Guo on 4/22/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +@_spi(STP) public protocol STPAnalyticsProtocol { + static var stp_analyticsIdentifier: String { get } +} + +@_spi(STP) public protocol STPAnalyticsClientProtocol { + func addClass(toProductUsageIfNecessary klass: T.Type) + func log(analytic: Analytic, apiClient: STPAPIClient) +} + +/// This exists so our example/test apps can hook into when STPAnalyticsClient.sharedClient sends events. +@_spi(STP) public protocol STPAnalyticsClientDelegate: AnyObject { + func analyticsClientDidLog(analyticsClient: STPAnalyticsClient, payload: [String: Any]) +} + +@_spi(STP) public class STPAnalyticsClient: NSObject, STPAnalyticsClientProtocol { + @objc public static let sharedClient = STPAnalyticsClient() + /// When this class logs a payload in an XCTestCase, it's added to `_testLogHistory` instead of being sent over the network. + /// This is a hack - ideally, we inject a different analytics client in our tests. This is an escape hatch until we can make that (significant) refactor + public var _testLogHistory: [[String: Any]] = [] + public weak var delegate: STPAnalyticsClientDelegate? + + @objc public var productUsage: Set = Set() + private var additionalInfoSet: Set = Set() + private(set) var urlSession: URLSession = URLSession( + configuration: StripeAPIConfiguration.sharedUrlSessionConfiguration + ) + let url = URL(string: "https://q.stripe.com")! + private let analyticsEventTranslator = STPAnalyticsEventTranslator() + @objc public class func tokenType(fromParameters parameters: [AnyHashable: Any]) -> String? { + let parameterKeys = parameters.keys + + // Before SDK 23.0.0, this returned "card" for some Apple Pay payments. + if parameterKeys.contains("pk_token") { + return "apple_pay" + } + // these are currently mutually exclusive, so we can just run through and find the first match + let tokenTypes = ["account", "bank_account", "card", "pii", "cvc_update"] + if let type = tokenTypes.first(where: { parameterKeys.contains($0) }) { + return type + } + return nil + } + + public func addClass(toProductUsageIfNecessary klass: T.Type) { + objc_sync_enter(self) + _ = productUsage.insert(klass.stp_analyticsIdentifier) + objc_sync_exit(self) + } + + func addAdditionalInfo(_ info: String) { + _ = additionalInfoSet.insert(info) + } + + public func clearAdditionalInfo() { + additionalInfoSet.removeAll() + } + + public static var isSimulatorOrTest: Bool { + #if targetEnvironment(simulator) + return true + #else + return isUnitOrUITest + #endif + } + + static var isUnitOrUITest: Bool { + return NSClassFromString("XCTest") != nil || ProcessInfo.processInfo.environment["UITesting"] != nil + } + + public func additionalInfo() -> [String] { + return additionalInfoSet.sorted() + } + + /// Creates a payload dictionary for the given analytic that includes the event name, + /// common payload, additional info, and product usage dictionary. + /// + /// - Parameters: + /// - analytic: The analytic to log. + /// - apiClient: The `STPAPIClient` instance with which this payload should be associated + /// (i.e. publishable key). Defaults to `STPAPIClient.shared`. + func payload(from analytic: Analytic, apiClient: STPAPIClient = .shared) -> [String: Any] { + var payload = commonPayload(apiClient) + + payload["event"] = analytic.event.rawValue + + payload.mergeAssertingOnOverwrites(analytic.params) + return payload + } + + /// Logs an analytic with a payload dictionary that includes the event name, common payload, + /// additional info, and product usage dictionary. + /// + /// - Parameters + /// - analytic: The analytic to log. + /// - apiClient: The `STPAPIClient` instance with which this payload should be associated + /// (i.e. publishable key). Defaults to `STPAPIClient.shared`. + public func log(analytic: Analytic, apiClient: STPAPIClient = .shared) { + log(analytic: analytic, apiClient: apiClient, notificationCenter: .default) + } + + func log(analytic: Analytic, apiClient: STPAPIClient = .shared, notificationCenter: NotificationCenter = .default) { + let payload = payload(from: analytic, apiClient: apiClient) + + #if DEBUG + NSLog("LOG ANALYTICS: \(analytic.event.rawValue) - \(analytic.params.sorted { $0.0 > $1.0 })") + delegate?.analyticsClientDidLog(analyticsClient: self, payload: payload) + #endif + + if let translatedEvent = analyticsEventTranslator.translate(analytic.event, payload: payload) { + notificationCenter.post(name: translatedEvent.notificationName, + object: translatedEvent.event) + } + + // If in testing, don't log analytic, instead append payload to log history + guard !STPAnalyticsClient.isUnitOrUITest else { + _testLogHistory.append(payload) + return + } + + var request = URLRequest(url: url) + request.stp_addParameters(toURL: payload) + let task: URLSessionDataTask = urlSession.dataTask(with: request as URLRequest) + task.resume() + } +} + +// MARK: - Helpers + +extension STPAnalyticsClient { + public func commonPayload(_ apiClient: STPAPIClient) -> [String: Any] { + var payload: [String: Any] = [:] + payload["bindings_version"] = StripeAPIConfiguration.STPSDKVersion + payload["analytics_ua"] = "analytics.stripeios-1.0" + let version = UIDevice.current.systemVersion + if !version.isEmpty { + payload["os_version"] = version + } + if let deviceType = STPDeviceUtils.deviceType { + payload["device_type"] = deviceType + } + payload["app_name"] = Bundle.stp_applicationName() ?? "" + payload["app_version"] = Bundle.stp_applicationVersion() ?? "" + payload["plugin_type"] = PluginDetector.shared.pluginType?.rawValue + payload["network_type"] = NetworkDetector.getConnectionType() + payload["install"] = InstallMethod.current.rawValue + payload["publishable_key"] = apiClient.sanitizedPublishableKey ?? "unknown" + payload["session_id"] = AnalyticsHelper.shared.sessionID + if STPAnalyticsClient.isSimulatorOrTest { + payload["is_development"] = true + } + payload["locale"] = Locale.autoupdatingCurrent.identifier + payload["additional_info"] = additionalInfo() + payload["product_usage"] = productUsage.sorted() + return payload + } +} diff --git a/StripeCore/StripeCore/Source/Categories/Decimal+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/Decimal+StripeCore.swift new file mode 100644 index 00000000..3acc0257 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/Decimal+StripeCore.swift @@ -0,0 +1,15 @@ +// +// Decimal+StripeCore.swift +// StripeCore +// +// Created by Mel Ludowise on 4/14/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) extension Decimal { + public var floatValue: Float { + return (self as NSDecimalNumber).floatValue + } +} diff --git a/StripeCore/StripeCore/Source/Categories/Dictionary+Stripe.swift b/StripeCore/StripeCore/Source/Categories/Dictionary+Stripe.swift new file mode 100644 index 00000000..7e20329e --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/Dictionary+Stripe.swift @@ -0,0 +1,96 @@ +// +// Dictionary+Stripe.swift +// StripeCore +// +// Created by David Estes on 10/18/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension Dictionary { + static func stp_deepMerge(old: Any, new: Any) throws -> Any { + if let oldDictionary = old as? [String: Any], + let newDictionary = new as? [String: Any] + { + return try oldDictionary.merging(newDictionary, uniquingKeysWith: stp_deepMerge) + } + return new + } + + /// Return the dictionary, minus any fields that also exist in + /// the passed dictionary. + func subtracting(_ subtractDict: Dictionary) -> Dictionary { + var newDict = self + for (key, value) in self { + if let equatableValue = value as? AnyHashable, + let equatableSubtractValue = subtractDict[key] as? AnyHashable + { + if equatableValue == equatableSubtractValue { + newDict.removeValue(forKey: key) + continue + } + } + if let dict1 = value as? Dictionary, + let dict2 = subtractDict[key] as? Dictionary + { + let subtractedDict = dict1.subtracting(dict2) + if subtractedDict.isEmpty { + newDict.removeValue(forKey: key) + } else { + newDict[key] = subtractedDict as? Value + } + } + } + return newDict + } + + @_spi(STP) public mutating func mergeAssertingOnOverwrites(_ other: [Key: Value]) { + merge(other) { a, b in + stpAssertionFailure("Dictionary merge is overwriting a key with values: \(a) and \(b)!") + return a + } + } + + @_spi(STP) public func mergingAssertingOnOverwrites(_ other: S) -> [Key: Value] where S: Sequence, S.Element == (Key, Value) { + merging(other) { a, b in + stpAssertionFailure("Dictionary merge is overwriting a key with values: \(a) and \(b)!") + return a + } + } +} + +extension Dictionary where Value == Any { + func jsonEncodeNestedDicts(options: JSONSerialization.WritingOptions = []) -> [Key: Any] { + return compactMapValues { value in + guard let dict = value as? Dictionary else { + return value + } + + // Note: An NSInvalidArgumentException can occur when the dict can't be + // serialized instead of throwing an error, resulting in an app crash. + // Call `isValidJSONObject` to ensure it's able to serialize the dict. + guard JSONSerialization.isValidJSONObject(dict), + let data = try? JSONSerialization.data(withJSONObject: dict, options: options) + else { + assertionFailure("Dictionary could not be serialized") + return nil + } + + return String(data: data, encoding: .utf8) + } + } +} + +// From https://talk.objc.io/episodes/S01E31-mutating-untyped-dictionaries +@_spi(STP) public extension Dictionary { + /// Example usage: `dict[jsonDict: "countries"]?[jsonDict: "japan"]?["capital"] = "berlin"` + subscript(jsonDict key: Key) -> [String: Any]? { + get { + return self[key] as? [String: Any] + } + set { + self[key] = newValue as? Value + } + } +} diff --git a/StripeCore/StripeCore/Source/Categories/Enums+CustomStringConvertible.swift b/StripeCore/StripeCore/Source/Categories/Enums+CustomStringConvertible.swift new file mode 100644 index 00000000..09d0b194 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/Enums+CustomStringConvertible.swift @@ -0,0 +1,29 @@ +// +// Enums+CustomStringConvertible.swift +// Stripe +// +// Autogenerated by generate_objc_enum_string_values.rb +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +/// :nodoc: +extension STPErrorCode: CustomStringConvertible { + public var description: String { + switch self { + case .apiError: + return "apiError" + case .authenticationError: + return "authenticationError" + case .cancellationError: + return "cancellationError" + case .cardError: + return "cardError" + case .connectionError: + return "connectionError" + case .ephemeralKeyDecodingError: + return "ephemeralKeyDecodingError" + case .invalidRequestError: + return "invalidRequestError" + } + } +} diff --git a/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift new file mode 100644 index 00000000..3b064f2b --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/Locale+StripeCore.swift @@ -0,0 +1,94 @@ +// +// Locale+StripeCore.swift +// StripeCore +// +// Created by David Estes on 11/20/23. +// + +import Foundation + +@_spi(STP) public extension Locale { + /// Returns the regionCode, for visionOS compatibility + /// We can remove this once we drop iOS 16 + var stp_regionCode: String? { +#if canImport(CompositorServices) + return self.region?.identifier + #else + return self.regionCode + #endif + } + + var stp_currencyCode: String? { + #if canImport(CompositorServices) + return self.currency?.identifier + #else + return self.currencyCode + #endif + } + + var stp_languageCode: String? { +#if canImport(CompositorServices) + return self.language.languageCode?.identifier + #else + return self.languageCode + #endif + } + + static var stp_isoRegionCodes: [String] { +#if canImport(CompositorServices) + return self.Region.isoRegions.map { $0.identifier } +#else + return self.isoRegionCodes +#endif + } + + /// Returns the BCP 47(-ish) language tag representing the locale. + /// + /// The language tag is expected to be well-formed as long as the locale identifier contains a + /// valid language code. For example: + /// + /// ``` + /// let locale = Locale(identifier: "fr_CA") + /// locale.toLanguageTag() // -> "fr-CA" + /// ``` + /// + /// The following example returns `"-ES"`, even though `"und-ES"` will be the appropriate BCP 47 tag: + /// + /// ``` + /// let locale = Locale(identifier: "_ES") + /// locale.toLanguageTag() // -> "-ES" + /// ``` + /// All system iOS and macOS locales are expected to contain valid language codes. + /// + /// On iOS 16+, the device region may be different from the language region. When these are different, + /// the device region is encoded at the end. The example below corresponds to: + /// Language=English (UK) and Region=United States: + /// + /// ``` + /// let locale = Locale(identifier: "en_GB@rg=uszzzz") + /// locale.toLanguageTag() // -> "en-GB" + /// ``` + /// + func toLanguageTag() -> String { + var tag = Locale.canonicalLanguageIdentifier(from: self.identifier) + + // Drop sub-tags or extended variants like `en-US@calendar=gregorian` + // or `en-GB@rg=uszzzz` + if let unextended = tag.split(separator: "@").first { + tag = String(unextended) + } + + /* + iOS omits the language script when specifying: + language=Chinese, Traditional and region=Hong Kong (China) + + Stripe's web and backend localization will default to Simplified Chinese + (zh-Hans) if no script is specified, so insert the `Hant` script to + ensure Traditional Chinese is returned. + */ + if tag == "zh-HK" { + tag = "zh-Hant-HK" + } + return tag + } +} diff --git a/StripeCore/StripeCore/Source/Categories/NSArray+Stripe.swift b/StripeCore/StripeCore/Source/Categories/NSArray+Stripe.swift new file mode 100644 index 00000000..b21be9e8 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSArray+Stripe.swift @@ -0,0 +1,31 @@ +// +// NSArray+Stripe.swift +// StripeCore +// +// Created by Jack Flintermann on 1/19/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) extension Array { + public func stp_boundSafeObject(at index: Int) -> Element? { + if index + 1 > count || index < 0 { + return nil + } + return self[index] + } +} + +extension Array where Element == String { + public func caseInsensitiveContains(_ other: String) -> Bool { + return self.map { $0.uppercased() }.contains(other.uppercased()) + } +} + +extension Array where Element: Equatable { + @discardableResult public mutating func remove(_ element: Element) -> Element? { + guard let index = firstIndex(of: element) else { return nil } + return remove(at: index) + } +} diff --git a/StripeCore/StripeCore/Source/Categories/NSBundle+Stripe_AppName.swift b/StripeCore/StripeCore/Source/Categories/NSBundle+Stripe_AppName.swift new file mode 100644 index 00000000..0c459691 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSBundle+Stripe_AppName.swift @@ -0,0 +1,32 @@ +// +// NSBundle+Stripe_AppName.swift +// StripeCore +// +// Created by Jack Flintermann on 4/20/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension Bundle { + @_spi(STP) public class func stp_applicationName() -> String? { + return self.main.infoDictionary?[kCFBundleNameKey as String] as? String + } + + @_spi(STP) public class func stp_applicationVersion() -> String? { + return self.main.infoDictionary?["CFBundleShortVersionString"] as? String + } + + @_spi(STP) public class func stp_applicationBundleId() -> String? { + return self.main.bundleIdentifier + } + + @_spi(STP) public class func buildVersion() -> String? { + return self.main.infoDictionary?["CFBundleVersion"] as? String + } + + @_spi(STP) public class var displayName: String? { + return self.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? self.main + .object(forInfoDictionaryKey: "CFBundleName") as? String + } +} diff --git a/StripeCore/StripeCore/Source/Categories/NSCharacterSet+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/NSCharacterSet+StripeCore.swift new file mode 100644 index 00000000..c0b8e331 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSCharacterSet+StripeCore.swift @@ -0,0 +1,21 @@ +// +// NSCharacterSet+StripeCore.swift +// StripeCore +// +// Created by Brian Dorfman on 6/9/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) extension CharacterSet { + public static let stp_asciiDigit = CharacterSet(charactersIn: "0123456789") + public static let stp_asciiLetters = CharacterSet( + charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + ) + public static let stp_invertedAsciiDigit = stp_asciiDigit.inverted + public static let stp_postalCode = CharacterSet( + charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789- " + ) + public static let stp_invertedPostalCode = stp_postalCode.inverted +} diff --git a/StripeCore/StripeCore/Source/Categories/NSError+Stripe.swift b/StripeCore/StripeCore/Source/Categories/NSError+Stripe.swift new file mode 100644 index 00000000..41b32090 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSError+Stripe.swift @@ -0,0 +1,135 @@ +// +// NSError+Stripe.swift +// StripeCore +// +// Created by Brian Dorfman on 8/4/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension NSError { + @objc @_spi(STP) public class func stp_genericConnectionError() -> NSError { + let userInfo = [ + NSLocalizedDescriptionKey: self.stp_unexpectedErrorMessage(), + STPError.errorMessageKey: "There was an error connecting to Stripe.", + ] + return NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.connectionError.rawValue, + userInfo: userInfo + ) + } + + @objc @_spi(STP) public class func stp_genericFailedToParseResponseError() -> NSError { + let userInfo = [ + NSLocalizedDescriptionKey: self.stp_unexpectedErrorMessage(), + STPError.errorMessageKey: + "The response from Stripe failed to get parsed into valid JSON.", + ] + return NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.apiError.rawValue, + userInfo: userInfo + ) + } + + @objc @_spi(STP) public class func stp_ephemeralKeyDecodingError() -> NSError { + let userInfo = [ + NSLocalizedDescriptionKey: self.stp_unexpectedErrorMessage(), + STPError.errorMessageKey: + "Failed to decode the ephemeral key. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app.", + ] + return NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.ephemeralKeyDecodingError.rawValue, + userInfo: userInfo + ) + } + + @objc @_spi(STP) public class func stp_clientSecretError() -> NSError { + let userInfo = [ + NSLocalizedDescriptionKey: self.stp_unexpectedErrorMessage(), + STPError.errorMessageKey: + "The `secret` format does not match expected client secret formatting.", + ] + return NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.invalidRequestError.rawValue, + userInfo: userInfo + ) + } + + // TODO(davide): We'll want to move these into StripePayments, once it exists. + + // MARK: Strings + @objc @_spi(STP) public class func stp_cardErrorInvalidNumberUserMessage() -> String { + return STPLocalizedString( + "Your card's number is invalid", + "Error when the card number is not valid" + ) + } + + @objc @_spi(STP) public class func stp_cardInvalidCVCUserMessage() -> String { + return STPLocalizedString( + "Your card's security code is invalid", + "Error when the card's CVC is not valid" + ) + } + + @objc @_spi(STP) public class func stp_cardErrorInvalidExpMonthUserMessage() -> String { + return STPLocalizedString( + "Your card's expiration month is invalid", + "Error when the card's expiration month is not valid" + ) + } + + @objc @_spi(STP) public class func stp_cardErrorInvalidExpYearUserMessage() -> String { + return STPLocalizedString( + "Your card's expiration year is invalid", + "Error when the card's expiration year is not valid" + ) + } + + @objc @_spi(STP) public class func stp_cardErrorExpiredCardUserMessage() -> String { + return STPLocalizedString( + "Your card has expired", + "Error when the card has already expired" + ) + } + + @objc @_spi(STP) public class func stp_cardErrorDeclinedUserMessage() -> String { + return STPLocalizedString( + "Your card was declined", + "Error when the card was declined by the credit card networks" + ) + } + + @objc @_spi(STP) public class func stp_genericDeclineErrorUserMessage() -> String { + return STPLocalizedString( + "Your payment method was declined.", + "Error message when a payment method gets declined." + ) + } + + @objc @_spi(STP) public class func stp_cardErrorProcessingErrorUserMessage() -> String { + return STPLocalizedString( + "There was an error processing your card -- try again in a few seconds", + "Error when there is a problem processing the credit card" + ) + } + + @_spi(STP) public static var stp_invalidOwnerName: String { + return STPLocalizedString( + "Your name is invalid.", + "Error when customer's name is invalid" + ) + } + + @_spi(STP) public static var stp_invalidBankAccountIban: String { + return STPLocalizedString( + "The IBAN you entered is invalid.", + "An error message displayed when the customer's iban is invalid." + ) + } +} diff --git a/StripeCore/StripeCore/Source/Categories/NSError+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/NSError+StripeCore.swift new file mode 100644 index 00000000..5f3cbecf --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSError+StripeCore.swift @@ -0,0 +1,18 @@ +// +// NSError+StripeCore.swift +// StripeCore +// +// Created by Mel Ludowise on 7/7/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) extension NSError { + public class func stp_unexpectedErrorMessage() -> String { + return STPLocalizedString( + "There was an unexpected error -- try again in a few seconds", + "Unexpected error, such as a 500 from Stripe or a JSON parse error" + ) + } +} diff --git a/StripeCore/StripeCore/Source/Categories/NSMutableURLRequest+Stripe.swift b/StripeCore/StripeCore/Source/Categories/NSMutableURLRequest+Stripe.swift new file mode 100644 index 00000000..d2b30702 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSMutableURLRequest+Stripe.swift @@ -0,0 +1,53 @@ +// +// NSMutableURLRequest+Stripe.swift +// StripeCore +// +// Created by Ben Guo on 4/22/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension URLRequest { + @_spi(STP) public mutating func stp_addParameters(toURL parameters: [String: Any]) { + guard let url else { + assertionFailure() + return + } + let query = URLEncoder.queryString(from: parameters) + let urlString = url.absoluteString + (url.query != nil ? "&\(query)" : "?\(query)") + // On iOS 17, the `URL` class initializers parse the string using the stricter RFC 3986 and started returning nil for certain URL strings we create, like "https://foo.com?foo[bar]=baz". + // To preserve our old encoding behavior, we use CFURLCreateWithString instead. + // It's possible that we could instead change our encoding behavior to use RFC 3986. + // Seealso: https://jira.corp.stripe.com/browse/MOBILESDK-1335 + self.url = CFURLCreateWithString(nil, urlString as CFString, nil) as URL? + } + + @_spi(STP) public mutating func stp_setFormPayload(_ formPayload: [String: Any]) { + let formData = URLEncoder.queryString(from: formPayload).data(using: .utf8) + httpBody = formData + setValue( + String(format: "%lu", UInt(formData?.count ?? 0)), + forHTTPHeaderField: "Content-Length" + ) + setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + + #if DEBUG + if StripeAPIConfiguration.includeDebugParamsHeader { + setValue(URLEncoder.queryString(from: formPayload), forHTTPHeaderField: "X-Stripe-Mock-Request") + } + #endif + } + + @_spi(STP) public mutating func stp_setMultipartForm(_ data: Data?, boundary: String?) { + httpBody = data + setValue( + String(format: "%lu", UInt(data?.count ?? 0)), + forHTTPHeaderField: "Content-Length" + ) + setValue( + "multipart/form-data; boundary=\(boundary ?? "")", + forHTTPHeaderField: "Content-Type" + ) + } +} diff --git a/StripeCore/StripeCore/Source/Categories/NSURLComponents+Stripe.swift b/StripeCore/StripeCore/Source/Categories/NSURLComponents+Stripe.swift new file mode 100644 index 00000000..b0bb90ce --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/NSURLComponents+Stripe.swift @@ -0,0 +1,63 @@ +// +// NSURLComponents+Stripe.swift +// StripeCore +// +// Created by Brian Dorfman on 1/26/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension NSURLComponents { + /// Returns or sets self.queryItems as a dictionary where all the keys are the item + /// names and the values are the values. + /// + /// When reading, if there are duplicate + /// names, earlier ones are overwritten by later ones. + @objc var stp_queryItemsDictionary: [String: String] { + get { + guard let queryItems = queryItems else { + return [:] + } + var queryItemsDict = [String: String]() + for queryItem in queryItems { + queryItemsDict[queryItem.name] = queryItem.value + } + return queryItemsDict + } + set(stp_queryItemsDictionary) { + var queryItems: [URLQueryItem] = [] + for (key, value) in stp_queryItemsDictionary { + queryItems.append(URLQueryItem(name: key, value: value)) + } + + self.queryItems = queryItems + } + } + + /// Returns YES if the passed in url matches self in scheme, host, and path, + /// AND all the query items in self are also in the passed + /// in components (as determined by `stp_queryItemsDictionary`) + /// This is used for URL routing style matching + /// - Parameter rhsComponents: The components to match against + /// - Returns: YES if there is a match, NO otherwise. + func stp_matchesURLComponents(_ rhsComponents: NSURLComponents) -> Bool { + var matches = + (scheme?.lowercased() == rhsComponents.scheme?.lowercased()) + && (host?.lowercased() == rhsComponents.host?.lowercased()) + && (path == rhsComponents.path) + + if matches { + let rhsQueryItems = rhsComponents.stp_queryItemsDictionary + + for queryItem in queryItems ?? [] { + if !(rhsQueryItems[queryItem.name] == queryItem.value) { + matches = false + break + } + } + } + + return matches + } +} diff --git a/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift new file mode 100644 index 00000000..43097f04 --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/String+StripeCore.swift @@ -0,0 +1,36 @@ +// +// String+StripeCore.swift +// StripeCore +// +// Created by Mel Ludowise on 9/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) extension String { + public func stp_stringByRemovingCharacters(from characterSet: CharacterSet) -> String { + return String(unicodeScalars.filter { !characterSet.contains($0) }) + } + + public var isSecretKey: Bool { + return self.hasPrefix("sk_") + } + + public var nonEmpty: String? { + stringIfHasContentsElseNil(self) + } +} + +@_spi(STP) public func stringIfHasContentsElseNil( + _ string: String? +) -> // MARK: - + String? +{ + guard let string = string, + !string.isEmpty + else { + return nil + } + return string +} diff --git a/StripeCore/StripeCore/Source/Categories/UIImage+StripeCore.swift b/StripeCore/StripeCore/Source/Categories/UIImage+StripeCore.swift new file mode 100644 index 00000000..ec170ced --- /dev/null +++ b/StripeCore/StripeCore/Source/Categories/UIImage+StripeCore.swift @@ -0,0 +1,162 @@ +// +// UIImage+StripeCore.swift +// StripeCore +// +// Created by Brian Dorfman on 4/25/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// +import AVFoundation +import UIKit + +@_spi(STP) public typealias ImageDataAndSize = (imageData: Data, imageSize: CGSize) + +extension UIImage { + @_spi(STP) public static let defaultCompressionQuality: CGFloat = 0.5 + + /// Encodes the image to jpeg at the specified compression quality. + /// + /// The image will be scaled down, if needed, to ensure its size does not exceed `maxBytes`. + /// + /// - Parameters: + /// - maxBytes: The maximum size of the allowed file. If value is nil, then + /// the image will not be scaled down. + /// - compressionQuality: The compression quality to use when encoding the jpeg. + /// - Returns: A tuple containing the following properties. + /// - `imageData`: Data object of the jpeg encoded image. + /// - `imageSize`: The dimensions of the the image that was encoded. + /// This size may be smaller than the original image size if the image + /// needed to be scaled down to fit the specified `maxBytes`. + @_spi(STP) public func jpegDataAndDimensions( + maxBytes: Int? = nil, + compressionQuality: CGFloat = defaultCompressionQuality + ) -> ImageDataAndSize { + dataAndDimensions( + maxBytes: maxBytes, + compressionQuality: compressionQuality + ) { image, quality in + image.jpegData(compressionQuality: quality) + } + } + + /// Encodes the image to heic at the specified compression quality. + /// + /// The image will be scaled down, if needed, to ensure its size does not exceed `maxBytes`. + /// + /// - Parameters: + /// - maxBytes: The maximum size of the allowed file. If value is nil, then + /// the image will not be scaled down. + /// - compressionQuality: The compression quality to use when encoding the jpeg. + /// - Returns: A tuple containing the following properties. + /// - `imageData`: Data object of the jpeg encoded image. + /// - `imageSize`: The dimensions of the the image that was encoded. + /// This size may be smaller than the original image size if the image + /// needed to be scaled down to fit the specified `maxBytes`. + @_spi(STP) public func heicDataAndDimensions( + maxBytes: Int? = nil, + compressionQuality: CGFloat = defaultCompressionQuality + ) -> ImageDataAndSize { + dataAndDimensions( + maxBytes: maxBytes, + compressionQuality: compressionQuality + ) { image, quality in + image.heicData(compressionQuality: quality) + } + } + + @_spi(STP) public func resized(to scale: CGFloat) -> UIImage? { + let newImageSize = CGSize( + width: CGFloat(floor(size.width * scale)), + height: CGFloat(floor(size.height * scale)) + ) + UIGraphicsBeginImageContextWithOptions(newImageSize, false, self.scale) + + defer { + UIGraphicsEndImageContext() + } + + draw(in: CGRect(x: 0, y: 0, width: newImageSize.width, height: newImageSize.height)) + return UIGraphicsGetImageFromCurrentImageContext() + } + + private func heicData(compressionQuality: CGFloat = UIImage.defaultCompressionQuality) -> Data? + { + [self].heicData(compressionQuality: compressionQuality) + } + + private func dataAndDimensions( + maxBytes: Int? = nil, + compressionQuality: CGFloat = defaultCompressionQuality, + imageDataProvider: ((_ image: UIImage, _ quality: CGFloat) -> Data?) + ) -> ImageDataAndSize { + var imageData = imageDataProvider(self, compressionQuality) + + guard imageData != nil else { + return (imageData: Data(), imageSize: .zero) + } + + var newImageSize = self.size + + // Try something smarter first + if let maxBytes = maxBytes, + (imageData?.count ?? 0) > maxBytes + { + var scale = CGFloat(1.0) + + // Assuming jpeg file size roughly scales linearly with area of the image + // which is ~correct (although breaks down at really small file sizes) + let percentSmallerNeeded = CGFloat(maxBytes) / CGFloat((imageData?.count ?? 0)) + + // Shrink to a little bit less than we need to try to ensure we're under + // (otherwise its likely our first pass will be over the limit due to + // compression variance and floating point rounding) + scale = scale * (percentSmallerNeeded - (percentSmallerNeeded * 0.05)) + + repeat { + if let newImage = resized(to: scale) { + newImageSize = newImage.size + imageData = imageDataProvider(newImage, compressionQuality) + } + + // If the smart thing doesn't work, just start scaling down a bit on a loop until we get there + scale *= 0.7 + } while (imageData?.count ?? 0) > maxBytes + } + + return (imageData: imageData!, imageSize: newImageSize) + } +} + +extension Array where Element: UIImage { + @_spi(STP) public func heicData( + compressionQuality: CGFloat = UIImage.defaultCompressionQuality + ) -> Data? { + guard let mutableData = CFDataCreateMutable(nil, 0) else { + return nil + } + + guard + let destination = CGImageDestinationCreateWithData( + mutableData, + AVFileType.heic as CFString, + self.count, + nil + ) + else { + return nil + } + + let properties = + [kCGImageDestinationLossyCompressionQuality: compressionQuality] as CFDictionary + + for image in self { + let cgImage = image.cgImage! + CGImageDestinationAddImage(destination, cgImage, properties) + } + + if CGImageDestinationFinalize(destination) { + return mutableData as Data + } + + return nil + } +} diff --git a/StripeCore/StripeCore/Source/Coder/StripeCodable.swift b/StripeCore/StripeCore/Source/Coder/StripeCodable.swift new file mode 100644 index 00000000..a5e516a1 --- /dev/null +++ b/StripeCore/StripeCore/Source/Coder/StripeCodable.swift @@ -0,0 +1,177 @@ +// +// StripeCodable.swift +// StripeCore +// +// Created by David Estes on 7/20/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +// This contains ~most of our custom encoding/decoding logic for handling unknown fields. +// +// It isn't intended for public use, but exposing objects with this protocol +// requires that we make the protocol itself public. +// + +import Foundation + +/// A Decodable object that retains unknown fields. +/// :nodoc: +public protocol UnknownFieldsDecodable: Decodable { + /// This should not be used directly. + /// Use the `allResponseFields` accessor instead. + /// :nodoc: + var _allResponseFieldsStorage: NonEncodableParameters? { get set } +} + +/// An Encodable object that allows unknown fields to be set. +/// :nodoc: +public protocol UnknownFieldsEncodable: Encodable { + /// This should not be used directly. + /// Use the `additionalParameters` accessor instead. + /// :nodoc: + var _additionalParametersStorage: NonEncodableParameters? { get set } +} + +/// A Decodable enum that sets an "unparsable" case +/// instead of failing on values that are unknown to the SDK. +/// :nodoc: +public protocol SafeEnumDecodable: Decodable { + /// If the value is unparsable, the result will be available in + /// the `allResponseFields` of the parent object. + static var unparsable: Self { get } + + // It'd be nice to include the value of the unparsable enum + // as an associated value, but Swift can't auto-generate the Codable + // keys if we do that. +} + +/// A Codable enum that sets an "unparsable" case +/// instead of failing on values that are unknown to the SDK. +/// :nodoc: +public protocol SafeEnumCodable: Encodable, SafeEnumDecodable {} + +extension UnknownFieldsDecodable { + /// A dictionary containing all response fields from the original JSON, + /// including unknown fields. + public internal(set) var allResponseFields: [String: Any] { + get { + self._allResponseFieldsStorage?.storage ?? [:] + } + set { + if self._allResponseFieldsStorage == nil { + self._allResponseFieldsStorage = NonEncodableParameters() + } + self._allResponseFieldsStorage!.storage = newValue + } + } + + static func decodedObject(jsonData: Data) throws -> Self { + return try StripeJSONDecoder.decode(jsonData: jsonData) + } +} + +extension UnknownFieldsEncodable { + /// You can use this property to add additional fields to an API request that + /// are not explicitly defined by the object's interface. + /// + /// This can be useful when using beta features that haven't been added to the Stripe SDK yet. + /// For example, if the /v1/tokens API began to accept a beta field called "test_field", + /// you might do the following: + /// + /// ```swift + /// var cardParams = PaymentMethodParams.Card() + /// // add card values + /// cardParams.additionalParameters = ["test_field": "example_value"] + /// PaymentsAPI.shared.createToken(withParameters: cardParams completion:...) + /// ``` + public var additionalParameters: [String: Any] { + get { + self._additionalParametersStorage?.storage ?? [:] + } + set { + if self._additionalParametersStorage == nil { + self._additionalParametersStorage = NonEncodableParameters() + } + self._additionalParametersStorage!.storage = newValue + } + } +} + +extension Encodable { + func encodeJSONDictionary(includingUnknownFields: Bool = true) throws -> [String: Any] { + let encoder = StripeJSONEncoder() + return try encoder.encodeJSONDictionary( + self, + includingUnknownFields: includingUnknownFields + ) + } +} + +@_spi(STP) public enum UnknownFieldsCodableFloats: String { + case PositiveInfinity = "Inf" + case NegativeInfinity = "-Inf" + case NaN = "nan" +} + +/// A protocol that conforms to both UnknownFieldsEncodable and UnknownFieldsDecodable. +/// :nodoc: +public protocol UnknownFieldsCodable: UnknownFieldsEncodable, UnknownFieldsDecodable {} + +/// This should not be used directly. +/// Use the `additionalParameters` and `allResponseFields` accessors instead. +/// :nodoc: +public struct NonEncodableParameters { + @_spi(STP) public internal(set) var storage: [String: Any] = [:] +} + +extension NonEncodableParameters: Decodable { + /// :nodoc: + public init( + from decoder: Decoder + ) throws { + // no-op + } +} + +extension NonEncodableParameters: Encodable { + /// :nodoc: + public func encode(to encoder: Encoder) throws { + // no-op + } +} + +extension NonEncodableParameters: Equatable { + /// :nodoc: + public static func == (lhs: NonEncodableParameters, rhs: NonEncodableParameters) -> Bool { + return NSDictionary(dictionary: lhs.storage).isEqual(to: rhs.storage) + } +} + +// The default debugging behavior for structs is to print *everything*, +// which is undesirable as it could contain card numbers or other PII. +// For now, override it to just give an overview of the struct. +extension NonEncodableParameters: CustomStringConvertible, CustomDebugStringConvertible, + CustomLeafReflectable +{ + /// :nodoc: + public var customMirror: Mirror { + return Mirror(reflecting: self.description) + } + + /// :nodoc: + public var debugDescription: String { + return description + } + + /// :nodoc: + public var description: String { + return "\(storage.count) fields" + } +} + +extension StripeJSONDecoder { + static func decode(jsonData: Data) throws -> T { + let decoder = StripeJSONDecoder() + return try decoder.decode(T.self, from: jsonData) + } +} diff --git a/StripeCore/StripeCore/Source/Coder/StripeJSONDecoder.swift b/StripeCore/StripeCore/Source/Coder/StripeJSONDecoder.swift new file mode 100644 index 00000000..385520b1 --- /dev/null +++ b/StripeCore/StripeCore/Source/Coder/StripeJSONDecoder.swift @@ -0,0 +1,712 @@ +// +// StripeJSONDecoder.swift +// StripeCore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// +// This is a bridge between NSJSONSerialization and Decoder, including some Stripe-specific behavior. +// + +import Foundation + +@_spi(STP) public class StripeJSONDecoder { + @_spi(STP) public init() {} + + @_spi(STP) public var userInfo: [CodingUserInfoKey: Any] = [:] + + @_spi(STP) public var inputFormatting: JSONSerialization.ReadingOptions = [] + + @_spi(STP) public func decode(_ type: T.Type, from data: Data) throws -> T + where T: Decodable { + var inputFormatting = self.inputFormatting + // We always allow fragments. (Though we mostly only use these for tests.) + inputFormatting.insert(.fragmentsAllowed) + let jsonObject: Any + do { + jsonObject = try JSONSerialization.jsonObject(with: data, options: inputFormatting) + } catch let error { + throw DecodingError.dataCorrupted( + .init( + codingPath: [], + debugDescription: "The given data was not valid JSON.", + underlyingError: error + ) + ) + } + guard let object = jsonObject as? NSObject else { + throw DecodingError.dataCorrupted( + .init( + codingPath: [], + debugDescription: "The given data could not be decoded from JSON.", + underlyingError: nil + ) + ) + } + let decoder = _stpinternal_JSONDecoder(jsonObject: object) + userInfo[UnknownFieldsDecodableSourceStorageKey] = data + decoder.userInfo = userInfo + let value: T = try decoder.castFromNSObject() + if var sdValue = value as? UnknownFieldsDecodable { + try sdValue.applyUnknownFieldDecodingTransforms(userInfo: userInfo, codingPath: []) + return sdValue as! T + } + return value + } +} + +private class _stpinternal_JSONDecoder: Decoder, STPDecodingContainerProtocol { + var userInfo: [CodingUserInfoKey: Any] = [:] + var codingPath: [CodingKey] = [] + var jsonObject: NSObject + + init( + jsonObject: NSObject + ) { + self.jsonObject = jsonObject + } + + func castFromNSObject() throws -> T where T: Decodable { + return try castFromNSObject(codingPath: codingPath, T.self, jsonObject) + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer + where Key: CodingKey { + guard let dict = jsonObject as? NSDictionary else { + throw DecodingError.typeMismatch( + NSDictionary.self, + .init( + codingPath: codingPath, + debugDescription: "KeyedContainer is not a dictionary", + underlyingError: nil + ) + ) + } + return KeyedDecodingContainer( + STPKeyedDecodingContainer( + codingPath: codingPath, + dict: dict, + allKeys: [], + userInfo: userInfo + ) + ) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + var objectToDecode = jsonObject + + // The implementation of Codable encodes Dictionaries that have Keys that are not exactly + // `String.Type` or `Int.Type` as an `Array`. + // If we have a Dictionary that has Keys that derived from String we end up here. + // To solve this, just convert to an Array. + // see: https://github.com/apple/swift/blob/d2085d8b0ed69e40a10e555669bb6cc9b450d0b3/stdlib/public/core/Codable.swift.gyb#L1967 + // For the decoding see: https://github.com/apple/swift/blob/d2085d8b0ed69e40a10e555669bb6cc9b450d0b3/stdlib/public/core/Codable.swift.gyb#L2036 + // Interestingly the default implementation does not handle this which seems like a bug. + // See here: https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/JSONDecoder.swift + if let dict = objectToDecode as? NSDictionary { + let arrayCopy = NSMutableArray() + for (key, value) in dict { + arrayCopy.add(key) + arrayCopy.add(value) + } + + objectToDecode = arrayCopy + } + + guard let array = objectToDecode as? NSArray else { + throw DecodingError.typeMismatch( + NSArray.self, + .init( + codingPath: codingPath, + debugDescription: "UnkeyedContainer is not an array", + underlyingError: nil + ) + ) + } + return STPUnkeyedDecodingContainer(userInfo: userInfo, array: array, codingPath: codingPath) + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + return STPSingleValueDecodingContainer( + codingPath: codingPath, + userInfo: userInfo, + object: jsonObject + ) + } +} + +private protocol STPDecodingContainerProtocol { + var userInfo: [CodingUserInfoKey: Any] { + get set + } +} + +private struct STPKeyedDecodingContainer: STPDecodingContainerProtocol, + KeyedDecodingContainerProtocol +where K: CodingKey { + var codingPath: [CodingKey] + + var dict: NSDictionary + var allKeys: [K] + + var userInfo: [CodingUserInfoKey: Any] + + typealias Key = K + + func _dictionaryKey(from key: K) -> String { + let maintainExistingCase = userInfo[STPMaintainExistingCase] as? Bool ?? false + var key = key.stringValue + + if !maintainExistingCase { + key = URLEncoder.convertToSnakeCase(camelCase: key) + } + + return key + } + + func contains(_ key: K) -> Bool { + let key = _dictionaryKey(from: key) + return dict[key] != nil + } + + func _objectForKey(_ key: K) throws -> NSObject { + if !contains(key) { + throw DecodingError.keyNotFound( + key, + .init( + codingPath: codingPath, + debugDescription: "Key \(key) not found in \(codingPath)", + underlyingError: nil + ) + ) + } + + let key = _dictionaryKey(from: key) + return dict[key] as! NSObject + } + + func _decode(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { + let newPath = codingPath + [key] + let value: T = try castFromNSObject(codingPath: newPath, type, _objectForKey(key)) + if var sdValue = value as? UnknownFieldsDecodable { + try sdValue.applyUnknownFieldDecodingTransforms(userInfo: userInfo, codingPath: newPath) + return sdValue as! T + } + return value + } + + func decodeNil(forKey key: K) throws -> Bool { + return (try _objectForKey(key) is NSNull) + } + + func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { + return try _decode(type, forKey: key) + } + + func decode(_ type: String.Type, forKey key: K) throws -> String { + return try _decode(type, forKey: key) + } + + func decode(_ type: Double.Type, forKey key: K) throws -> Double { + return try _decode(type, forKey: key) + } + + func decode(_ type: Float.Type, forKey key: K) throws -> Float { + return try _decode(type, forKey: key) + } + + func decode(_ type: Int.Type, forKey key: K) throws -> Int { + return try _decode(type, forKey: key) + } + + func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { + return try _decode(type, forKey: key) + } + + func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { + return try _decode(type, forKey: key) + } + + func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { + return try _decode(type, forKey: key) + } + + func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { + return try _decode(type, forKey: key) + } + + func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { + return try _decode(type, forKey: key) + } + + func decode(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { + return try _decode(type, forKey: key) + } + + func nestedContainer( + keyedBy type: NestedKey.Type, + forKey key: K + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + assertionFailure("nestedContainer(keyedBy:forKey:) is not implemented.") + return KeyedDecodingContainer( + STPKeyedDecodingContainer( + codingPath: [], + dict: NSMutableDictionary(), + allKeys: [], + userInfo: [:] + ) + ) + } + + func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { + assertionFailure("nestedUnkeyedContainer(forKey:) is not implemented.") + return STPUnkeyedDecodingContainer( + userInfo: [:], + array: NSArray(), + codingPath: [], + currentIndex: 0 + ) + } + + func superDecoder() throws -> Decoder { + assertionFailure("superDecoder() is not implemented.") + return _stpinternal_JSONDecoder(jsonObject: NSNull()) + } + + func superDecoder(forKey key: K) throws -> Decoder { + assertionFailure("superDecoder(forKey:) is not implemented.") + return _stpinternal_JSONDecoder(jsonObject: NSNull()) + } + +} + +private struct STPUnkeyedDecodingContainer: UnkeyedDecodingContainer, STPDecodingContainerProtocol { + var userInfo: [CodingUserInfoKey: Any] + + var array: NSArray + + var codingPath: [CodingKey] + + var count: Int? { + return array.count + } + + var isAtEnd: Bool { + return currentIndex >= count ?? 0 + } + + var currentIndex: Int = 0 + + mutating func _popObject() -> NSObject { + assert(!isAtEnd, "Tried to read past the end of the container.") + let object = array[currentIndex] as! NSObject + currentIndex += 1 + return object + } + + mutating func _decode(_ type: T.Type) throws -> T where T: Decodable { + let newPath = codingPath + [STPCodingKey(intValue: currentIndex)!] + + let value: T = try castFromNSObject(codingPath: newPath, type, _popObject()) + if var sdValue = value as? UnknownFieldsDecodable { + try sdValue.applyUnknownFieldDecodingTransforms(userInfo: userInfo, codingPath: newPath) + return sdValue as! T + } + return value + } + + mutating func decodeNil() throws -> Bool { + let object = _popObject() + return (object is NSNull) + } + + mutating func decode(_ type: Bool.Type) throws -> Bool { + return try _decode(type) + } + + mutating func decode(_ type: String.Type) throws -> String { + return try _decode(type) + } + + mutating func decode(_ type: Double.Type) throws -> Double { + return try _decode(type) + } + + mutating func decode(_ type: Float.Type) throws -> Float { + return try _decode(type) + } + + mutating func decode(_ type: Int.Type) throws -> Int { + return try _decode(type) + } + + mutating func decode(_ type: Int8.Type) throws -> Int8 { + return try _decode(type) + } + + mutating func decode(_ type: Int16.Type) throws -> Int16 { + return try _decode(type) + } + + mutating func decode(_ type: Int32.Type) throws -> Int32 { + return try _decode(type) + } + + mutating func decode(_ type: Int64.Type) throws -> Int64 { + return try _decode(type) + } + + mutating func decode(_ type: UInt.Type) throws -> UInt { + return try _decode(type) + } + + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { + return try _decode(type) + } + + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { + return try _decode(type) + } + + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { + return try _decode(type) + } + + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { + return try _decode(type) + } + + mutating func decode(_ type: T.Type) throws -> T where T: Decodable { + return try _decode(type) + } + + mutating func nestedContainer( + keyedBy type: NestedKey.Type + ) throws -> KeyedDecodingContainer where NestedKey: CodingKey { + assertionFailure("nestedContainer(keyedBy:) is not implemented.") + return KeyedDecodingContainer( + STPKeyedDecodingContainer( + codingPath: [], + dict: NSMutableDictionary(), + allKeys: [], + userInfo: [:] + ) + ) + } + + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + assertionFailure("nestedUnkeyedContainer(forKey:) is not implemented.") + return STPUnkeyedDecodingContainer(userInfo: [:], array: NSArray(), codingPath: []) + } + + mutating func superDecoder() throws -> Decoder { + assertionFailure("superDecoder() is not implemented.") + return _stpinternal_JSONDecoder(jsonObject: NSNull()) + } + +} + +private struct STPSingleValueDecodingContainer: SingleValueDecodingContainer, + STPDecodingContainerProtocol +{ + var codingPath: [CodingKey] + + var userInfo: [CodingUserInfoKey: Any] + + var object: NSObject + + func _decode(_ type: T.Type) throws -> T where T: Decodable { + let value: T = try castFromNSObject(codingPath: codingPath, type, object) + if var sdValue = value as? UnknownFieldsDecodable { + try sdValue.applyUnknownFieldDecodingTransforms( + userInfo: userInfo, + codingPath: codingPath + ) + return sdValue as! T + } + return value + } + + func decodeNil() -> Bool { + return object == NSNull() + } + + func decode(_ type: Bool.Type) throws -> Bool { + return try _decode(type) + } + + func decode(_ type: String.Type) throws -> String { + return try _decode(type) + } + + func decode(_ type: Double.Type) throws -> Double { + return try _decode(type) + } + + func decode(_ type: Float.Type) throws -> Float { + return try _decode(type) + } + + func decode(_ type: Int.Type) throws -> Int { + return try _decode(type) + } + + func decode(_ type: Int8.Type) throws -> Int8 { + return try _decode(type) + } + + func decode(_ type: Int16.Type) throws -> Int16 { + return try _decode(type) + } + + func decode(_ type: Int32.Type) throws -> Int32 { + return try _decode(type) + } + + func decode(_ type: Int64.Type) throws -> Int64 { + return try _decode(type) + } + + func decode(_ type: UInt.Type) throws -> UInt { + return try _decode(type) + } + + func decode(_ type: UInt8.Type) throws -> UInt8 { + return try _decode(type) + } + + func decode(_ type: UInt16.Type) throws -> UInt16 { + return try _decode(type) + } + + func decode(_ type: UInt32.Type) throws -> UInt32 { + return try _decode(type) + } + + func decode(_ type: UInt64.Type) throws -> UInt64 { + return try _decode(type) + } + + func decode(_ type: T.Type) throws -> T where T: Decodable { + return try _decode(type) + } + +} + +// MARK: Casting logic + +// These extensions help us maintain the type information for Arrays and Dictionaries within castFromNSObject, so that +// inner calls to castFromNSObject call the templated function without erasing the underlying type. +// I'm not sure if there's a cleaner way to do this... +private protocol _STPDecodableIsArray { + static var valueType: Decodable.Type { get } +} +extension Array: _STPDecodableIsArray where Element: Decodable { + static var valueType: Decodable.Type { return Element.self } +} +private protocol _STPDecodableIsDictionary { + static var valueType: Decodable.Type { get } +} +extension Dictionary: _STPDecodableIsDictionary where Key == String, Value: Decodable { + static var valueType: Decodable.Type { return Value.self } +} +extension Decodable { + fileprivate static func _castFromNSObject( + codingPath: [CodingKey] = [], + decodingContainer: STPDecodingContainerProtocol, + object: NSObject + ) throws -> Self { + return try decodingContainer.castFromNSObject(codingPath: codingPath, Self.self, object) + } +} + +extension STPDecodingContainerProtocol { + func castFromNSObject( + codingPath: [CodingKey] = [], + _ type: T.Type, + _ object: NSObject + ) throws -> T where T: Decodable { + switch type { + case is Double.Type: + switch object as? String { + case UnknownFieldsCodableFloats.PositiveInfinity.rawValue: + return Double.infinity as! T + case UnknownFieldsCodableFloats.NegativeInfinity.rawValue: + return -Double.infinity as! T + case UnknownFieldsCodableFloats.NaN.rawValue: + return Double.nan as! T + case .none, .some: + guard let value = object as? Double, let returnValue = value as? T else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: + "Parsed JSON number <\(object)> does not fit in \(type).", + underlyingError: nil + ) + ) + } + return returnValue + } + case is Float.Type: + switch object as? String { + case UnknownFieldsCodableFloats.PositiveInfinity.rawValue: + return Float.infinity as! T + case UnknownFieldsCodableFloats.NegativeInfinity.rawValue: + return -Float.infinity as! T + case UnknownFieldsCodableFloats.NaN.rawValue: + return Float.nan as! T + case .none, .some: + guard let value = object as? Float, let returnValue = value as? T else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: + "Parsed JSON number <\(object)> does not fit in \(type).", + underlyingError: nil + ) + ) + } + return returnValue + } + case is Bool.Type, + is Int.Type, + is Int8.Type, + is Int16.Type, + is Int32.Type, + is Int64.Type, + is UInt.Type, + is UInt8.Type, + is UInt16.Type, + is UInt32.Type, + is UInt64.Type, + is String.Type: + guard let value = object as? T else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Parsed JSON number <\(object)> does not fit in \(type).", + underlyingError: nil + ) + ) + } + return value + case is Decimal.Type: + guard let decimal = (object as? NSNumber)?.decimalValue else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Could not convert <\(object)> to \(type).", + underlyingError: nil + ) + ) + } + return decimal as! T + case is URL.Type: + guard let string = object as? String, let url = URL(string: string) else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Could not convert <\(object)> to \(type).", + underlyingError: nil + ) + ) + } + return url as! T + case is Data.Type: + guard let string = object as? String, let data = Data(base64Encoded: string) else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Could not convert <\(object)> to \(type).", + underlyingError: nil + ) + ) + } + return data as! T + case is Date.Type: + guard let ti = object as? TimeInterval else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Could not convert <\(object)> to \(type).", + underlyingError: nil + ) + ) + } + return Date(timeIntervalSince1970: ti) as! T + case is _STPDecodableIsDictionary.Type: + guard let dict = object as? [String: Any] else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Could not convert <\(object)> to \(type).", + underlyingError: nil + ) + ) + } + var convertedDict: [String: Any] = [:] + for (k, v) in dict { + let dictType = T.self as! (_STPDecodableIsDictionary.Type) + convertedDict[k] = try dictType.valueType._castFromNSObject( + codingPath: codingPath, + decodingContainer: self, + object: v as! NSObject + ) + } + return convertedDict as! T + case is _STPDecodableIsArray.Type: + guard let array = object as? [Any] else { + throw DecodingError.dataCorrupted( + .init( + codingPath: codingPath, + debugDescription: "Could not convert <\(object)> to \(type).", + underlyingError: nil + ) + ) + } + var convertedArray: [Any] = [] + for i in array { + let arrayType = T.self as! (_STPDecodableIsArray.Type) + convertedArray.append( + try arrayType.valueType._castFromNSObject( + codingPath: codingPath, + decodingContainer: self, + object: i as! NSObject + ) + ) + } + return convertedArray as! T + case is SafeEnumDecodable.Type: + do { + let decoder = _stpinternal_JSONDecoder(jsonObject: object) + decoder.userInfo = userInfo + decoder.codingPath = codingPath + return try T(from: decoder) + } catch Swift.DecodingError.dataCorrupted { + let enumDecodableType = T.self as! (SafeEnumDecodable.Type) + return enumDecodableType.unparsable as! T + } + default: + let decoder = _stpinternal_JSONDecoder(jsonObject: object) + decoder.userInfo = userInfo + decoder.codingPath = codingPath + return try T(from: decoder) + } + } +} diff --git a/StripeCore/StripeCore/Source/Coder/StripeJSONEncoder.swift b/StripeCore/StripeCore/Source/Coder/StripeJSONEncoder.swift new file mode 100644 index 00000000..fd85a1d1 --- /dev/null +++ b/StripeCore/StripeCore/Source/Coder/StripeJSONEncoder.swift @@ -0,0 +1,564 @@ +// +// StripeJSONEncoder.swift +// StripeCore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// +// This is a bridge between NSJSONSerialization and Encoder, including some Stripe-specific behavior. +// + +import Foundation + +@_spi(STP) public class StripeJSONEncoder { + @_spi(STP) public var userInfo: [CodingUserInfoKey: Any] = [:] + + @_spi(STP) public var outputFormatting: JSONSerialization.WritingOptions = [] + + @_spi(STP) public func encode(_ value: T, includingUnknownFields: Bool = true) throws -> Data + where T: Encodable { + var outputFormatting = self.outputFormatting + outputFormatting.insert(.fragmentsAllowed) + + if value is UnknownFieldsEncodable { + let jsonDictionary = try self.encodeJSONDictionary( + value, + includingUnknownFields: includingUnknownFields + ) + return try JSONSerialization.data( + withJSONObject: jsonDictionary, + options: outputFormatting + ) + } else { + return try JSONSerialization.data( + withJSONObject: castToNSObject(value), + options: outputFormatting + ) + } + } + + @_spi(STP) public func encodeJSONDictionary( + _ value: T, + includingUnknownFields: Bool = true + ) throws -> [String: Any] where T: Encodable { + var outputFormatting = self.outputFormatting + outputFormatting.insert(.fragmentsAllowed) + + // Set up a dictionary on the encoder to fill with additionalAPIParameters during encoding + let dictionary = NSMutableDictionary() + userInfo[UnknownFieldsEncodableSourceStorageKey] = dictionary + userInfo[StripeIncludeUnknownFieldsKey] = includingUnknownFields + + if let seValue = value as? UnknownFieldsEncodable { + // Encode the top-level additionalAPIParameters into the userInfo + seValue.applyUnknownFieldEncodingTransforms(userInfo: userInfo, codingPath: []) + } + + // Encode the object to JSON data + let jsonData = try JSONSerialization.data( + withJSONObject: castToNSObject(value), + options: outputFormatting + ) + + // Convert the JSON data into a JSON dictionary + var jsonDictionary = + try JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any] + + // Merge in the additional parameters we collected in our encoder userInfo's NSMutableDictionary during encoding + try jsonDictionary.merge( + dictionary as! [String: Any], + uniquingKeysWith: [String: Any].stp_deepMerge + ) + + return jsonDictionary + } +} + +// Make sure StripeJSONEncoder can call castToNSObject +extension StripeJSONEncoder: StripeEncodingContainer {} + +class _stpinternal_JSONEncoder: Encoder { + var codingPath: [CodingKey] = [] + + var userInfo: [CodingUserInfoKey: Any] = [:] + + enum ContainerValue { + case dict(NSMutableDictionary) + case array(NSMutableArray) + case singleValue(NSObject) + } + + private var container: ContainerValue? + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer + where Key: CodingKey { + let dict = NSMutableDictionary() + self.container = .dict(dict) + return KeyedEncodingContainer( + STPKeyedEncodingContainer(codingPath: codingPath, dict: dict, userInfo: userInfo) + ) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + // We've been asked for an array, so initialize a top-level empty array to handle the case of an empty array. + let array = NSMutableArray() + self.container = .array(array) + return STPUnkeyedEncodingContainer(codingPath: codingPath, array: array, userInfo: userInfo) + } + + func singleValueContainer() -> SingleValueEncodingContainer { + self.container = .singleValue(NSNull()) + return STPSingleValueEncodingContainer( + codingPath: codingPath, + encodingBlock: { self.container = .singleValue($0) }, + userInfo: userInfo + ) + } + + /// Return the NSObject contained by this encoder: Either a dictionary, an array, or a single object. + var singleContainer: NSObject { + guard let container = container else { + assertionFailure("Called singleContainer on an empty decoder") + return NSNull() + } + switch container { + case .dict(let dict): + return dict + case .array(let array): + return array + case .singleValue(let singleValue): + return singleValue + } + } +} + +struct STPKeyedEncodingContainer: StripeEncodingContainer, KeyedEncodingContainerProtocol +where K: CodingKey { + var codingPath: [CodingKey] + + typealias Key = K + + var dict: NSMutableDictionary + var userInfo: [CodingUserInfoKey: Any] + + mutating private func encode(object: NSObject, forKey key: K) throws { + let maintainExistingCase = userInfo[STPMaintainExistingCase] as? Bool ?? false + + let stringKey = key.stringValue + let key = + maintainExistingCase ? stringKey : URLEncoder.convertToSnakeCase(camelCase: stringKey) + + dict[key] = object + } + + mutating func encodeNil(forKey key: K) throws { + try encode(object: NSNull(), forKey: key) + } + + mutating func encode(_ value: Bool, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: String, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Double, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Float, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Int, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Int8, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Int16, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Int32, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: Int64, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: UInt, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: UInt8, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: UInt16, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: UInt32, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: UInt64, forKey key: K) throws { + try encode(object: castToNSObject(value), forKey: key) + } + + mutating func encode(_ value: T, forKey key: K) throws where T: Encodable { + if value is NonEncodableParameters { + // Don't encode this + return + } + let newPath = codingPath + [key] + if let seValue = value as? UnknownFieldsEncodable { + seValue.applyUnknownFieldEncodingTransforms(userInfo: userInfo, codingPath: newPath) + } + try encode(object: castToNSObject(codingPath: newPath, value), forKey: key) + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: K + ) -> KeyedEncodingContainer where NestedKey: CodingKey { + // This is messy to support and we don't have any situations + // in the Stripe SDK where we want to use it. The implementation + // would probably look similar to superEncoder() above. + assertionFailure("nestedContainer(keyedBy:) is not implemented.") + return KeyedEncodingContainer( + STPKeyedEncodingContainer( + codingPath: codingPath + [key], + dict: NSMutableDictionary(), + userInfo: userInfo + ) + ) + } + + mutating func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { + // This is messy to support and we don't have any situations + // in the Stripe SDK where we want to use it. The implementation + // would probably look similar to superEncoder() above. + assertionFailure("nestedUnkeyedContainer(forKey:) is not implemented.") + return STPUnkeyedEncodingContainer( + codingPath: codingPath + [key], + array: NSMutableArray(), + userInfo: userInfo + ) + } + + mutating func superEncoder() -> Encoder { + // See above superEncoder() comment. + assertionFailure("superEncoder() is not implemented.") + return _stpinternal_JSONEncoder() + } + + mutating func superEncoder(forKey key: K) -> Encoder { + // See above superEncoder() comment. + assertionFailure("superEncoder(forKey:) is not implemented.") + return _stpinternal_JSONEncoder() + } +} + +struct STPUnkeyedEncodingContainer: UnkeyedEncodingContainer, StripeEncodingContainer { + var codingPath: [CodingKey] + + var count: Int = 0 + + var array: NSMutableArray + var userInfo: [CodingUserInfoKey: Any] + + mutating func encodeNil() throws { + array.add(NSNull()) + count += 1 + } + + mutating func encode(_ value: Bool) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: String) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Double) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Float) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Int) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Int8) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Int16) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Int32) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: Int64) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: UInt) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: UInt8) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: UInt16) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: UInt32) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: UInt64) throws { + try array.add(castToNSObject(value)) + count += 1 + } + + mutating func encode(_ value: T) throws where T: Encodable { + if value is NonEncodableParameters { + // Don't encode this + return + } + let newPath = codingPath + [STPCodingKey(intValue: count)!] + if let seValue = value as? UnknownFieldsEncodable { + seValue.applyUnknownFieldEncodingTransforms(userInfo: userInfo, codingPath: newPath) + } + try array.add(castToNSObject(codingPath: newPath, value)) + + count += 1 + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type + ) -> KeyedEncodingContainer where NestedKey: CodingKey { + // This is messy to support and we don't have any situations + // in the Stripe SDK where we want to use it. The implementation + // would probably look similar to superEncoder() below. + assertionFailure("nestedContainer(keyedBy:) is not implemented.") + return KeyedEncodingContainer( + STPKeyedEncodingContainer( + codingPath: codingPath, + dict: NSMutableDictionary(), + userInfo: userInfo + ) + ) + } + + mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + // This is messy to support and we don't have any situations + // in the Stripe SDK where we want to use it. The implementation + // would probably look similar to superEncoder() below. + assertionFailure("nestedUnkeyedContainer() is not implemented.") + return STPUnkeyedEncodingContainer( + codingPath: codingPath, + array: NSMutableArray(), + userInfo: userInfo + ) + } + + mutating func superEncoder() -> Encoder { + // Super-encoding is messy and we don't have any situations in + // the Stripe SDK where a Codable inherits from another Codable. + // If you'd like to implement this, see + // https://forums.swift.org/t/writing-encoders-and-decoders-different-question/10232/5 + // for details. + assertionFailure("superEncoder() is not implemented.") + return _stpinternal_JSONEncoder() + } + +} + +struct STPSingleValueEncodingContainer: SingleValueEncodingContainer, StripeEncodingContainer { + var codingPath: [CodingKey] + + var encodingBlock: (NSObject) -> Void + var userInfo: [CodingUserInfoKey: Any] + + mutating func encodeNil() throws { + encodingBlock(NSNull()) + } + + mutating func encode(_ value: Bool) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: String) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Double) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Float) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Int) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Int8) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Int16) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Int32) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: Int64) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: UInt) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: UInt8) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: UInt16) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: UInt32) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: UInt64) throws { + encodingBlock(try castToNSObject(value)) + } + + mutating func encode(_ value: T) throws where T: Encodable { + if value is NonEncodableParameters { + // Don't encode this + return + } + if let seValue = value as? UnknownFieldsEncodable { + seValue.applyUnknownFieldEncodingTransforms(userInfo: userInfo, codingPath: codingPath) + } + encodingBlock(try castToNSObject(value)) + } +} + +protocol StripeEncodingContainer { + var userInfo: [CodingUserInfoKey: Any] { + get set + } +} + +extension StripeEncodingContainer { + fileprivate func castToNSObject(codingPath: [CodingKey] = [], _ value: T) throws -> NSObject + where T: Encodable { + switch value { + case let n as Bool: + return n as NSObject + case let n as String: + return n as NSObject + case let n as Double: + if n == .infinity { + return UnknownFieldsCodableFloats.PositiveInfinity.rawValue as NSObject + } + if n == -.infinity { + return UnknownFieldsCodableFloats.NegativeInfinity.rawValue as NSObject + } + if n.isNaN { + return UnknownFieldsCodableFloats.NaN.rawValue as NSObject + } + return n as NSObject + case let n as Float: + if n == .infinity { + return UnknownFieldsCodableFloats.PositiveInfinity.rawValue as NSObject + } + if n == -.infinity { + return UnknownFieldsCodableFloats.NegativeInfinity.rawValue as NSObject + } + if n.isNaN { + return UnknownFieldsCodableFloats.NaN.rawValue as NSObject + } + return n as NSObject + case let n as Int: + return n as NSObject + case let n as Int8: + return n as NSObject + case let n as Int16: + return n as NSObject + case let n as Int32: + return n as NSObject + case let n as Int64: + return n as NSObject + case let n as UInt: + return n as NSObject + case let n as UInt8: + return n as NSObject + case let n as UInt16: + return n as NSObject + case let n as UInt32: + return n as NSObject + case let n as UInt64: + return n as NSObject + case let decimal as Decimal: + return NSDecimalNumber(decimal: decimal) + case let url as URL: + return url.absoluteString as NSObject + case let date as Date: + // Stripe expects an integer number of seconds since the Unix epoch + return Int(date.timeIntervalSince1970) as NSObject + case let data as Data: + // Stripe expects base64-encoded data + return data.base64EncodedString() as NSObject + case is [AnyHashable: Any]: + let encoder = _stpinternal_JSONEncoder() + encoder.userInfo = userInfo + // If this is a dictionary, don't apply transformations to the keys + encoder.userInfo[STPMaintainExistingCase] = true + encoder.codingPath = codingPath + try value.encode(to: encoder) + return encoder.singleContainer + default: + let encoder = _stpinternal_JSONEncoder() + encoder.userInfo = userInfo + encoder.codingPath = codingPath + try value.encode(to: encoder) + return encoder.singleContainer + } + } +} diff --git a/StripeCore/StripeCore/Source/Coder/StripeJSONShared.swift b/StripeCore/StripeCore/Source/Coder/StripeJSONShared.swift new file mode 100644 index 00000000..0381b42d --- /dev/null +++ b/StripeCore/StripeCore/Source/Coder/StripeJSONShared.swift @@ -0,0 +1,37 @@ +// +// StripeJSONShared.swift +// StripeCore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +// Constants +internal let STPMaintainExistingCase = CodingUserInfoKey(rawValue: "_STPMaintainExistingCase")! + +internal struct STPCodingKey: CodingKey { + init?( + stringValue: String + ) { + self.stringValue = stringValue + } + + init?( + intValue: Int + ) { + self.intValue = intValue + self.stringValue = intValue.description + } + + init( + stringValue: String, + intValue: Int? + ) { + self.intValue = intValue + self.stringValue = stringValue + } + + var stringValue: String + var intValue: Int? +} diff --git a/StripeCore/StripeCore/Source/Coder/UnknownFields.swift b/StripeCore/StripeCore/Source/Coder/UnknownFields.swift new file mode 100644 index 00000000..281bfbe4 --- /dev/null +++ b/StripeCore/StripeCore/Source/Coder/UnknownFields.swift @@ -0,0 +1,98 @@ +// +// UnknownFields.swift +// StripeCore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension UnknownFieldsEncodable { + func applyUnknownFieldEncodingTransforms( + userInfo: [CodingUserInfoKey: Any], + codingPath: [CodingKey] + ) { + if !(userInfo[StripeIncludeUnknownFieldsKey] as? Bool ?? false) { + // Don't include unknown fields. + return + } + // If we have additional parameters, add these to the parameters we're sending. + // Follow the encoder codingPath *up*, then store it in the userInfo + + // We can't modify the userInfo of the encoder directly, + // but we *can* give it a reference to an NSMutableDictionary + // and mutate that as we go. + if !self.additionalParameters.isEmpty, + let dictionary = userInfo[UnknownFieldsEncodableSourceStorageKey] + as? NSMutableDictionary + { + var mutateDictionary = dictionary + for path in codingPath { + // Make sure we're dealing with snake_case. + let snakeValue = URLEncoder.convertToSnakeCase(camelCase: path.stringValue) + // If the dictionary exists at that level, retrieve it as an NSMutableDictionary reference + if let subDictionary = mutateDictionary[snakeValue] as? NSMutableDictionary { + mutateDictionary = subDictionary + } else { + // If it does not exist, create an NSMutableDictionary at that level. + let newDictionary = NSMutableDictionary() + mutateDictionary[snakeValue] = newDictionary + mutateDictionary = newDictionary + } + } + // Merge the additionalParameters dictionary onto the existing dictionary. + mutateDictionary.addEntries(from: self.additionalParameters) + } + } +} + +extension UnknownFieldsDecodable { + mutating func applyUnknownFieldDecodingTransforms( + userInfo: [CodingUserInfoKey: Any], + codingPath: [CodingKey] + ) throws { + var object = self + + // Follow the encoder's codingPath down the userInfo JSON dictionary + if let originalJSON = userInfo[UnknownFieldsDecodableSourceStorageKey] as? Data, + var jsonDictionary = try JSONSerialization.jsonObject(with: originalJSON, options: []) + as? [String: Any] + { + for path in codingPath { + let snakeValue = URLEncoder.convertToSnakeCase(camelCase: path.stringValue) + // This will always be a dictionary. If it isn't then something is being + // decoded as an object by JSONDecoder, but a dictionary by JSONSerialization. + jsonDictionary = jsonDictionary[snakeValue] as! [String: Any] + } + // Set the allResponseFields dictionary, so that users can access unknown fields. + object.allResponseFields = jsonDictionary + + // If the wrapped value is also *encodable*, we'll want some special behavior + // so it can be re-encoded without losing the unknown fields. + // To do this, we'll: + // 1. Re-encode it (without unknown fields) to a dictionary + // 2. Subtract the "known fields" dictionay from our source dictionary + // 3. Set additionalParameters to the resulting dictionary, giving us + // a dictionary of only our missing or uninterpretable fields. + // When the object is later re-encoded, the additionalParameters will + // be re-added to the encoded JSON. + if var encodableValue = object as? UnknownFieldsEncodable { + let encodedDictionary = try encodableValue.encodeJSONDictionary( + includingUnknownFields: false + ) + encodableValue.additionalParameters = jsonDictionary.subtracting(encodedDictionary) + object = encodableValue as! Self + } + } + self = object + } +} + +let StripeIncludeUnknownFieldsKey = CodingUserInfoKey(rawValue: "_StripeIncludeUnknownFieldsKey")! + +let UnknownFieldsEncodableSourceStorageKey = CodingUserInfoKey( + rawValue: "_UnknownFieldsEncodableSourceStorageKey" +)! +let UnknownFieldsDecodableSourceStorageKey = CodingUserInfoKey( + rawValue: "_UnknownFieldsDecodableSourceStorageKey" +)! diff --git a/StripeCore/StripeCore/Source/Connections Bindings/BillingAddress.swift b/StripeCore/StripeCore/Source/Connections Bindings/BillingAddress.swift new file mode 100644 index 00000000..da8a12b2 --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/BillingAddress.swift @@ -0,0 +1,70 @@ +// +// BillingAddress.swift +// StripeCore +// +// Created by Mat Schmid on 2024-10-16. +// + +import Foundation + +@_spi(STP) public struct BillingAddress: Codable { + @_spi(STP) public let name: String? + @_spi(STP) public let line1: String? + @_spi(STP) public let line2: String? + @_spi(STP) public let city: String? + @_spi(STP) public let state: String? + @_spi(STP) public let postalCode: String? + @_spi(STP) public let countryCode: String? + + @_spi(STP) public init( + name: String? = nil, + line1: String? = nil, + line2: String? = nil, + city: String? = nil, + state: String? = nil, + postalCode: String? = nil, + countryCode: String? = nil + ) { + self.name = name + self.line1 = line1 + self.line2 = line2 + self.city = city + self.state = state + self.postalCode = postalCode + self.countryCode = countryCode + } + + @_spi(STP) public init(from billingDetails: ElementsSessionContext.BillingDetails?) { + self.init( + name: billingDetails?.name, + line1: billingDetails?.address?.line1, + line2: billingDetails?.address?.line2, + city: billingDetails?.address?.city, + state: billingDetails?.address?.state, + postalCode: billingDetails?.address?.postalCode, + countryCode: billingDetails?.address?.country + ) + } + + enum CodingKeys: String, CodingKey { + case name + case line1 = "line_1" + case line2 = "line_2" + case city = "locality" + case state = "administrative_area" + case postalCode = "postal_code" + case countryCode = "country_code" + } + + // Custom encoder to only encode non-nil & non-empty properties. + @_spi(STP) public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfNotEmpty(name, forKey: .name) + try container.encodeIfNotEmpty(line1, forKey: .line1) + try container.encodeIfNotEmpty(line2, forKey: .line2) + try container.encodeIfNotEmpty(city, forKey: .city) + try container.encodeIfNotEmpty(state, forKey: .state) + try container.encodeIfNotEmpty(postalCode, forKey: .postalCode) + try container.encodeIfNotEmpty(countryCode, forKey: .countryCode) + } +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/ElementsSessionContext.swift b/StripeCore/StripeCore/Source/Connections Bindings/ElementsSessionContext.swift new file mode 100644 index 00000000..07dfb01b --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/ElementsSessionContext.swift @@ -0,0 +1,145 @@ +// +// ElementsSessionContext.swift +// StripeCore +// +// Created by Mat Schmid on 2024-09-25. +// + +import Foundation + +/// Contains elements session context useful for the Financial Connections SDK. +@_spi(STP) public struct ElementsSessionContext { + @_spi(STP) @frozen public enum IntentID { + case payment(String) + case setup(String) + } + + /// These fields will be used to prefill the Financial Connections Link Login pane. + /// An unformatted phone number + country code will be passed to the web flow, + /// and a formatted phone number will be passed to the native flow. + @_spi(STP) public struct PrefillDetails { + @_spi(STP) public let email: String? + @_spi(STP) public let formattedPhoneNumber: String? + @_spi(STP) public let unformattedPhoneNumber: String? + @_spi(STP) public let countryCode: String? + + @_spi(STP) public init( + email: String?, + formattedPhoneNumber: String?, + unformattedPhoneNumber: String?, + countryCode: String? + ) { + self.email = email + self.formattedPhoneNumber = formattedPhoneNumber + self.unformattedPhoneNumber = unformattedPhoneNumber + self.countryCode = countryCode + } + } + + @_spi(STP) public let amount: Int? + @_spi(STP) public let currency: String? + @_spi(STP) public let prefillDetails: PrefillDetails? + @_spi(STP) public let intentId: IntentID? + @_spi(STP) public let linkMode: LinkMode? + @_spi(STP) public let billingDetails: BillingDetails? + + @_spi(STP) public var billingAddress: BillingAddress? { + BillingAddress(from: billingDetails) + } + + @_spi(STP) public init( + amount: Int?, + currency: String?, + prefillDetails: PrefillDetails?, + intentId: IntentID?, + linkMode: LinkMode?, + billingDetails: BillingDetails? + ) { + self.amount = amount + self.currency = currency + self.prefillDetails = prefillDetails + self.intentId = intentId + self.linkMode = linkMode + self.billingDetails = billingDetails + } +} + +@_spi(STP) public extension ElementsSessionContext { + /// https://docs.stripe.com/api/payment_methods/create#create_payment_method-billing_details + struct BillingDetails: Encodable { + @_spi(STP) public struct Address: Encodable { + @_spi(STP) public let city: String? + @_spi(STP) public let country: String? + @_spi(STP) public let line1: String? + @_spi(STP) public let line2: String? + @_spi(STP) public let postalCode: String? + @_spi(STP) public let state: String? + + @_spi(STP) public init?( + city: String?, + country: String?, + line1: String?, + line2: String?, + postalCode: String?, + state: String? + ) { + guard city != nil || country != nil || line1 != nil || line2 != nil || postalCode != nil || state != nil else { + return nil + } + + self.city = city + self.country = country + self.line1 = line1 + self.line2 = line2 + self.postalCode = postalCode + self.state = state + } + + enum CodingKeys: String, CodingKey { + case city + case country + case line1 + case line2 + case postalCode = "postal_code" + case state + } + + @_spi(STP) public func encode(to encoder: any Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfNotEmpty(self.city, forKey: .city) + try container.encodeIfNotEmpty(self.country, forKey: .country) + try container.encodeIfNotEmpty(self.line1, forKey: .line1) + try container.encodeIfNotEmpty(self.line2, forKey: .line2) + try container.encodeIfNotEmpty(self.postalCode, forKey: .postalCode) + try container.encodeIfNotEmpty(self.state, forKey: .state) + } + } + + @_spi(STP) public let name: String? + @_spi(STP) public let email: String? + @_spi(STP) public let phone: String? + @_spi(STP) public let address: Address? + + @_spi(STP) public init(name: String?, email: String?, phone: String?, address: Address?) { + self.name = name + self.email = email + self.phone = phone + self.address = address + } + + enum CodingKeys: CodingKey { + case name + case email + case phone + case address + } + + @_spi(STP) public func encode(to encoder: any Encoder) throws { + var container: KeyedEncodingContainer = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfNotEmpty(self.name, forKey: .name) + try container.encodeIfNotEmpty(self.email, forKey: .email) + try container.encodeIfNotEmpty(self.phone, forKey: .phone) + try container.encodeIfPresent(self.address, forKey: .address) + } + } +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsEvent.swift b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsEvent.swift new file mode 100644 index 00000000..9d54c9d7 --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsEvent.swift @@ -0,0 +1,151 @@ +// +// FinancialConnectionsEvent.swift +// StripeCore +// +// Created by Krisjanis Gaidis on 4/16/24. +// + +import Foundation + +public struct FinancialConnectionsEvent { + + public enum Name: String { + /// Invoked when the sheet successfully opens. + case open = "open" + + /// Invoked when the manual entry flow is initiated. + case manualEntryInitiated = "manual_entry_initiated" + + /// Invoked when “Agree and continue” is selected on the consent pane. + case consentAcquired = "consent_acquired" + + /// Invoked when the search bar is selected, the user inputs search terms, + /// and receives an API response. + case searchInitiated = "search_initiated" + + /// Invoked when an institution is selected, either from featured institutions or search results. + /// + /// `institutionName` will be available in metadata as a `String`. + case institutionSelected = "institution_selected" + + /// Invoked when the authorization is successfully completed. + case institutionAuthorized = "institution_authorized" + + /// Invoked when accounts are selected and “confirm” is selected. + case accountsSelected = "accounts_selected" + + /// Invoked when the flow is completed and selected accounts are correctly + /// connected to the payment instrument. + /// + /// `manualEntry` will be available in metadata as a `Bool`. + case success = "success" + + /// Invoked when an error is encountered. Refer to error codes for more details. + /// + /// `errorCode` will be available in metadata as `ErrorCode`. + case error = "error" + + /// Invoked when the flow is cancelled, typically by the user pressing the "X" button. + case cancel = "cancel" + + /// Invoked when the modal is launched in an external browser. After this event, no other events + /// will be sent until the completion of the browser session with either 'success', 'cancel', or 'error'. + case flowLaunchedInBrowser = "flow_launched_in_browser" + } + + public struct Metadata { + + /// Dictionary containing metadata key-value pairs. + /// + /// For instance, `errorCode` could be a key `String` (`"error_code"`) + /// mapped to a corresponding error code value `String` (`"unexpected_error"`). + public let dictionary: [String: Any] + + private static let manualEntryKey = "manual_entry" + + /// A Boolean value that indicates if the user completed the process through the manual entry flow. + /// + /// This property is included as part of the `success` event. + public var manualEntry: Bool? { + return dictionary[Self.manualEntryKey] as? Bool + } + + private static let institutionNameKey = "institution_name" + + /// A String value containing the name of the institution that the user selected. + /// + /// Appears as part of the `institutionSelected` event. + public var institutionName: String? { + return dictionary[Self.institutionNameKey] as? String + } + + private static let errorCodeKey = "error_code" + + /// An `ErrorCode` value representing the type of error that occurred. + /// + /// Appears as part of the `error` event. + public var errorCode: ErrorCode? { + guard let errorCodeRawValue = dictionary[Self.errorCodeKey] as? String else { + return nil + } + return ErrorCode(rawValue: errorCodeRawValue) + } + + @_spi(STP) public init( + institutionName: String? = nil, + manualEntry: Bool? = nil, + errorCode: ErrorCode? = nil + ) { + var dictionary: [String: Any] = [:] + dictionary[Self.institutionNameKey] = institutionName + dictionary[Self.manualEntryKey] = manualEntry + dictionary[Self.errorCodeKey] = errorCode?.rawValue + self.dictionary = dictionary + } + } + + public enum ErrorCode: String { + + /// The system could not retrieve account numbers for selected accounts. + case accountNumbersUnavailable = "account_numbers_unavailable" + + /// The system could not retrieve accounts for the selected institution. + case accountsUnavailable = "accounts_unavailable" + + /// For payment flows, no debitable account was available at the selected institution. + case noDebitableAccount = "no_debitable_account" + + /// Authorization with the selected institution has failed. + case authorizationFailed = "authorization_failed" + + /// The selected institution is down for expected maintenance. + case institutionUnavailablePlanned = "institution_unavailable_planned" + + /// The selected institution is unexpectedly down. + case institutionUnavailableUnplanned = "institution_unavailable_unplanned" + + /// A timeout occurred while communicating with our partner or downstream institutions. + case institutionTimeout = "institution_timeout" + + /// An unexpected error occurred, either in an API call or on the client-side. + case unexpectedError = "unexpected_error" + + /// The client secret that powers the session has expired. + case sessionExpired = "session_expired" + + /// The hCaptcha challenge failed. + case failedBotDetection = "failed_bot_detection" + } + + /// The event's name. Represents the type of event that has occurred + /// during the financial connection process. + public let name: Name + + /// Event-associated metadata. Provides further detail related to the occurred event. + public let metadata: Metadata + + @_spi(STP) public init(name: Name, metadata: Metadata = Metadata()) { + self.name = name + self.metadata = metadata + } +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsLinkedBank.swift b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsLinkedBank.swift new file mode 100644 index 00000000..b71bf4ae --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsLinkedBank.swift @@ -0,0 +1,33 @@ +// +// FinancialConnectionsLinkedBank.swift +// StripeCore +// +// Created by Krisjanis Gaidis on 4/16/24. +// + +import Foundation + +@_spi(STP) public struct FinancialConnectionsLinkedBank: Equatable { + public let sessionId: String + public let accountId: String + public let displayName: String? + public let bankName: String? + public let last4: String? + public let instantlyVerified: Bool + + public init( + sessionId: String, + accountId: String, + displayName: String?, + bankName: String?, + last4: String?, + instantlyVerified: Bool + ) { + self.sessionId = sessionId + self.accountId = accountId + self.displayName = displayName + self.bankName = bankName + self.last4 = last4 + self.instantlyVerified = instantlyVerified + } +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsSDKInterface.swift b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsSDKInterface.swift new file mode 100644 index 00000000..1bdbb1f1 --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsSDKInterface.swift @@ -0,0 +1,22 @@ +// +// ConnectionsSDKInterface.swift +// StripeCore +// +// Created by Vardges Avetisyan on 2/24/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public protocol FinancialConnectionsSDKInterface { + init() + func presentFinancialConnectionsSheet( + apiClient: STPAPIClient, + clientSecret: String, + returnURL: String?, + elementsSessionContext: ElementsSessionContext?, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + from presentingViewController: UIViewController, + completion: @escaping (FinancialConnectionsSDKResult) -> Void + ) +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsSDKResult.swift b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsSDKResult.swift new file mode 100644 index 00000000..c906205c --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/FinancialConnectionsSDKResult.swift @@ -0,0 +1,19 @@ +// +// FinancialConnectionsSDKResult.swift +// StripeCore +// +// Created by Krisjanis Gaidis on 4/16/24. +// + +import Foundation + +@_spi(STP) @frozen public enum FinancialConnectionsSDKResult { + case completed(Completed) + case cancelled + case failed(error: Error) + + @_spi(STP) public enum Completed { + case financialConnections(FinancialConnectionsLinkedBank) + case instantDebits(InstantDebitsLinkedBank) + } +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/InstantDebitsLinkedBank.swift b/StripeCore/StripeCore/Source/Connections Bindings/InstantDebitsLinkedBank.swift new file mode 100644 index 00000000..e879b801 --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/InstantDebitsLinkedBank.swift @@ -0,0 +1,27 @@ +// +// InstantDebitsLinkedBank.swift +// StripeCore +// +// Created by Krisjanis Gaidis on 4/16/24. +// + +import Foundation + +@_spi(STP) public struct InstantDebitsLinkedBank: Equatable { + public let paymentMethod: LinkBankPaymentMethod + public let bankName: String? + public let last4: String? + public let linkMode: LinkMode? + + public init( + paymentMethod: LinkBankPaymentMethod, + bankName: String?, + last4: String?, + linkMode: LinkMode? + ) { + self.paymentMethod = paymentMethod + self.bankName = bankName + self.last4 = last4 + self.linkMode = linkMode + } +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/LinkBankPaymentMethod.swift b/StripeCore/StripeCore/Source/Connections Bindings/LinkBankPaymentMethod.swift new file mode 100644 index 00000000..164f05fd --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/LinkBankPaymentMethod.swift @@ -0,0 +1,16 @@ +// +// LinkBankPaymentMethod.swift +// StripeCore +// +// Created by Till Hellmund on 10/11/24. +// + +import Foundation + +/// This struct represents the encoded `PaymentMethod` that we receive during the Instant Debits flow. +/// We don't decode it into a proper struct to prevent said struct (which would live in StripeCore) from getting +/// out-of-sync with `STPPaymentMethod`, which this payment method will eventually be decoded into. +@_spi(STP) public struct LinkBankPaymentMethod: UnknownFieldsDecodable, Equatable { + public var _allResponseFieldsStorage: NonEncodableParameters? + public var id: String +} diff --git a/StripeCore/StripeCore/Source/Connections Bindings/LinkMode.swift b/StripeCore/StripeCore/Source/Connections Bindings/LinkMode.swift new file mode 100644 index 00000000..a4e567e1 --- /dev/null +++ b/StripeCore/StripeCore/Source/Connections Bindings/LinkMode.swift @@ -0,0 +1,22 @@ +// +// LinkMode.swift +// StripeCore +// +// Created by Mat Schmid on 2024-09-24. +// + +import Foundation + +@_spi(STP) public enum LinkMode: String { + case linkPaymentMethod = "LINK_PAYMENT_METHOD" + case passthrough = "PASSTHROUGH" + case linkCardBrand = "LINK_CARD_BRAND" + + @_spi(STP) public var isPantherPayment: Bool { + self == .linkCardBrand + } + + @_spi(STP) public var expectedPaymentMethodType: String { + isPantherPayment ? "card" : "bank_account" + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/Async.swift b/StripeCore/StripeCore/Source/Helpers/Async.swift new file mode 100644 index 00000000..596fd82a --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/Async.swift @@ -0,0 +1,167 @@ +// +// Async.swift +// StripeCore +// +// Created by Yuki Tokuhiro on 9/12/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// +// Futures and Promises. Delete this when the SDK is iOS13+ and use the Combine framework instead. +// +// Taken from https://github.com/JohnSundell/SwiftBySundell/blob/master/Blog/Under-the-hood-of-Futures-and-Promises.swift +// +// MIT License +// +// Copyright (c) 2017 John Sundell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +@_spi(STP) public class Future { + public typealias Result = Swift.Result + + fileprivate var result: Result? { + // Observe whenever a result is assigned, and report it: + didSet { + propertyAccessQueue.async { [self] in + result.map(report) + } + } + } + private var callbacks = [(Result) -> Void]() + // Since our methods can be called on different threads and our methods access our properties, we need to protect access to prevent race conditions. + let propertyAccessQueue = DispatchQueue(label: "FutureQueue", qos: .userInitiated) + + public func observe( + on queue: DispatchQueue = .main, + using callback: @escaping (Result) -> Void + ) { + let wrappedCallback: (Result) -> Void = { r in + queue.async { + callback(r) + } + } + + propertyAccessQueue.async { [self] in + // If a result has already been set, call the callback directly: + if let result { + return wrappedCallback(result) + } + + callbacks.append(wrappedCallback) + } + } + + private func report(result: Result) { + propertyAccessQueue.async { [self] in + callbacks.forEach { $0(result) } + callbacks = [] + } + } + + public func chained( + on queue: DispatchQueue = .main, + using closure: @escaping (Value) throws -> Future + ) -> Future { + // We'll start by constructing a "wrapper" promise that will be + // returned from this method: + let promise = Promise() + + // Observe the current future: + observe(on: queue) { result in + switch result { + case .success(let value): + do { + // Attempt to construct a new future using the value + // returned from the first one: + let future = try closure(value) + + // Observe the "nested" future, and once it + // completes, resolve/reject the "wrapper" future: + future.observe(on: queue) { result in + switch result { + case .success(let value): + promise.resolve(with: value) + case .failure(let error): + promise.reject(with: error) + } + } + } catch { + promise.reject(with: error) + } + case .failure(let error): + promise.reject(with: error) + } + } + + return promise + } + + public func transformed( + on queue: DispatchQueue = .main, + with closure: @escaping (Value) throws -> T + ) -> Future { + chained(on: queue) { value in + try Promise(value: closure(value)) + } + } +} + +@_spi(STP) public class Promise: Future { + public override init() { + super.init() + } + + public convenience init( + value: Value + ) { + self.init() + + // If the value was already known at the time the promise + // was constructed, we can report it directly: + result = .success(value) + } + + public convenience init( + error: Error + ) { + self.init() + result = .failure(error) + } + + public func resolve(with value: Value) { + result = .success(value) + } + + public func reject(with error: Error) { + result = .failure(error) + } + + public func fullfill(with result: Result) { + self.result = result + } + + public func fulfill(with block: () throws -> Value) { + do { + self.result = .success(try block()) + } catch { + self.result = .failure(error) + } + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/BundleLocatorProtocol.swift b/StripeCore/StripeCore/Source/Helpers/BundleLocatorProtocol.swift new file mode 100644 index 00000000..03c147ae --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/BundleLocatorProtocol.swift @@ -0,0 +1,75 @@ +// +// BundleLocatorProtocol.swift +// StripeCore +// +// Created by Brian Dorfman on 8/31/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public protocol BundleLocatorProtocol { + /// A final class that is internal to the bundle implementing this protocol. + /// + /// - Note: The class must be `final` to ensure that it can't be subclassed, + /// which may change the result of `bundleForClass`. + static var internalClass: AnyClass { get } + + /// Name of the bundle. + static var bundleName: String { get } + + /// Cached result from `computeResourcesBundle()` so it doesn't need to be recomputed. + static var resourcesBundle: Bundle { get } + + #if SWIFT_PACKAGE + /// SPM Bundle, if available. + /// + /// Implementation should be should be `Bundle.module`. + static var spmResourcesBundle: Bundle { get } + #endif +} + +extension BundleLocatorProtocol { + /// Computes the bundle to fetch resources from. + /// + /// - Note: This should never be called directly. Instead, call `resourcesBundle`. + /// - Description: + /// Places to check: + /// 1. Swift Package Manager bundle. + /// 2. Stripe.bundle (for manual static installations and framework-less Cocoapods). + /// 3. Stripe.framework/Stripe.bundle (for framework-based Cocoapods). + /// 4. Stripe.framework (for Carthage, manual dynamic installations). + /// 5. main bundle (for people dragging all our files into their project). + public static func computeResourcesBundle() -> Bundle { + var ourBundle: Bundle? + + #if SWIFT_PACKAGE + ourBundle = spmResourcesBundle + #endif + + if ourBundle == nil { + ourBundle = Bundle(path: "\(bundleName).bundle") + } + + if ourBundle == nil { + // This might be the same as the previous check if not using a dynamic framework + if let path = Bundle(for: internalClass).path( + forResource: bundleName, + ofType: "bundle" + ) { + ourBundle = Bundle(path: path) + } + } + + if ourBundle == nil { + // This will be the same as mainBundle if not using a dynamic framework + ourBundle = Bundle(for: internalClass) + } + + if let ourBundle = ourBundle { + return ourBundle + } else { + return Bundle.main + } + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/FileDownloader.swift b/StripeCore/StripeCore/Source/Helpers/FileDownloader.swift new file mode 100644 index 00000000..1dc7ddee --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/FileDownloader.swift @@ -0,0 +1,75 @@ +// +// FileDownloader.swift +// StripeCore +// +// Created by Mel Ludowise on 2/1/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Downloads files using a downloadTask. +@_spi(STP) public final class FileDownloader { + let urlSession: URLSession + + /// Initializes the `FileDownloader`. + /// + /// - Parameter urlSession: The session to use to download files with + public init( + urlSession: URLSession + ) { + self.urlSession = urlSession + } + + /// Downloads a file from the specified URL and returns a promise that will + /// resolve to the temporary local file location where the file was downloaded to. + /// + /// The temporary file will be deleted by the file system immediately after the + /// promise is observed. If the promise must not be observed on another + /// DispatchQueue or the file will be deleted before it can be observed. + /// + /// - Parameter remoteURL: The URL to download the file from. + public func downloadFileTemporarily(from remoteURL: URL) -> Future { + let promise = Promise() + + let request = URLRequest(url: remoteURL) + + let downloadTask = urlSession.downloadTask(with: request) { url, _, error in + + if let error = error { + return promise.reject(with: error) + } + + guard let url = url else { + return promise.reject(with: NSError.stp_genericConnectionError()) + } + + promise.resolve(with: url) + } + downloadTask.resume() + + return promise + } + + /// Downloads a file from the specified URL and returns a promise that will + /// resolve to the data contents of the file. + /// + /// - Parameters: + /// - remoteURL: The URL to download the file from + /// - fileReadingOptions: Options for reading the file after it's been downloaded locally. + public func downloadFile( + from remoteURL: URL, + fileReadingOptions: Data.ReadingOptions = [] + ) -> Future { + return downloadFileTemporarily(from: remoteURL).chained { fileURL in + let promise = Promise() + promise.fulfill { + return try Data( + contentsOf: fileURL, + options: fileReadingOptions + ) + } + return promise + } + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/InstallMethod.swift b/StripeCore/StripeCore/Source/Helpers/InstallMethod.swift new file mode 100644 index 00000000..5660e0ee --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/InstallMethod.swift @@ -0,0 +1,27 @@ +// +// InstallMethod.swift +// StripeCore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +enum InstallMethod: String { + case cocoapods = "C" + case spm = "S" + case binary = "B" // Built via export_builds.sh + case xcode = "X" // Directly built via Xcode or xcodebuild + + static let current: InstallMethod = { + #if COCOAPODS + return .cocoapods + #elseif SWIFT_PACKAGE + return .spm + #elseif STRIPE_BUILD_PACKAGE + return .binary + #else + return .xcode + #endif + }() +} diff --git a/StripeCore/StripeCore/Source/Helpers/KeyedEncodingContainer+Extensions.swift b/StripeCore/StripeCore/Source/Helpers/KeyedEncodingContainer+Extensions.swift new file mode 100644 index 00000000..7e889ee9 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/KeyedEncodingContainer+Extensions.swift @@ -0,0 +1,15 @@ +// +// KeyedEncodingContainer+Extensions.swift +// StripeCore +// +// Created by Mat Schmid on 2024-10-29. +// + +import Foundation + +@_spi(STP) public extension KeyedEncodingContainer { + mutating func encodeIfNotEmpty(_ value: String?, forKey key: K) throws { + guard let value, !value.isEmpty else { return } + try encode(value, forKey: key) + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift b/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift new file mode 100644 index 00000000..7a2d110a --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift @@ -0,0 +1,53 @@ +// +// PaymentsSDKVariant.swift +// StripeCore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public class PaymentsSDKVariant { + @_spi(STP) public static let variant: String = { + if NSClassFromString("STP_Internal_PaymentSheetViewController") != nil { + // This is the PaymentSheet SDK + return "paymentsheet" + } + if NSClassFromString("STPPaymentCardTextField") != nil { + // This is the Payments UI SDK + return "payments-ui" + } + if NSClassFromString("STPCardValidator") != nil { + // This is the API-only Payments SDK + return "payments-api" + } + if NSClassFromString("STPApplePayContext") != nil { + // This is only the Apple Pay SDK + return "applepay" + } + // This is a cryptid + return "unknown" + }() + + @_spi(STP) public static var ocrTypeString: String { + // "STPCardScanner" is STPCardScanner.stp_analyticsIdentifier, but STPCardScanner only exists in Stripe.framework. + if STPAnalyticsClient.sharedClient.productUsage.contains( + "STPCardScanner" + ) + || STPAnalyticsClient.sharedClient.productUsage.contains( + "STPCardScanner_legacy" + ) + { + return "stripe" + } + return "none" + } + + @_spi(STP) public static var paymentUserAgent: String { + var paymentUserAgent = "stripe-ios/\(STPAPIClient.STPSDKVersion)" + let variant = "variant.\(variant)" + let components = [paymentUserAgent, variant] + STPAnalyticsClient.sharedClient.productUsage + paymentUserAgent = components.joined(separator: "; ") + return paymentUserAgent + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/Result+Extensions.swift b/StripeCore/StripeCore/Source/Helpers/Result+Extensions.swift new file mode 100644 index 00000000..44e6c82b --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/Result+Extensions.swift @@ -0,0 +1,24 @@ +// +// Result+Extensions.swift +// StripeCore +// +// Created by Mat Schmid on 2024-07-09. +// + +import Foundation + +@_spi(STP) public extension Result { + /// Whether or not the result is a success. + var success: Bool { + switch self { + case .success: true + case .failure: false + } + } + + /// Returns the error if the result is a failure. + var error: Error? { + guard case .failure(let error) = self else { return nil } + return error + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/STPAssert.swift b/StripeCore/StripeCore/Source/Helpers/STPAssert.swift new file mode 100644 index 00000000..da9c0016 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/STPAssert.swift @@ -0,0 +1,45 @@ +// +// STPAssert.swift +// StripeCore +// +// Created by Yuki Tokuhiro on 3/25/24. +// + +import Foundation + +#if ENABLE_STPASSERTIONFAILURE +/// A very barebones way to test stpasserts in XCTest. +@_spi(STP) public class STPAssertTestUtil { + /// If set to `true` in an XCTest, the next assertion that fires populates `_testExpectAssertMessage` instead of crashing and resets this flag to `false`. + public static var shouldSuppressNextSTPAlert: Bool = false + /// The message of the assertion that fired when `_testExpectAssert` was `true`. + public static var lastAssertMessage: String = "" +} +#endif + +/// A wrapper that only calls `assertionFailure` when the `ENABLE_STPASSERTIONFAILURE` compiler flag is set. +/// Use this for assertions that should not trigger in merchant apps. +@inlinable @_spi(STP) public func stpAssertionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) { + #if ENABLE_STPASSERTIONFAILURE + if NSClassFromString("XCTest") != nil && STPAssertTestUtil.shouldSuppressNextSTPAlert { + STPAssertTestUtil.shouldSuppressNextSTPAlert = false + STPAssertTestUtil.lastAssertMessage = message() + return + } + assertionFailure(message(), file: file, line: line) + #else + print("⚠️ STPAssertionFailure: \(message()) in \(file) on line \(line)") + #endif +} + +/// A wrapper that only calls `assert` when the `ENABLE_STPASSERTIONFAILURE` compiler flag is set. +/// Use this for assertions that should not trigger in merchant apps. +@inlinable @_spi(STP) public func stpAssert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) { + #if ENABLE_STPASSERTIONFAILURE + assert(condition(), message(), file: file, line: line) + #else + if !condition() { + print("⚠️ STPAssertionFailure: \(message()) in \(file) on line \(line)") + } + #endif +} diff --git a/StripeCore/StripeCore/Source/Helpers/STPDeviceUtils.swift b/StripeCore/StripeCore/Source/Helpers/STPDeviceUtils.swift new file mode 100644 index 00000000..5bd87007 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/STPDeviceUtils.swift @@ -0,0 +1,25 @@ +// +// STPDeviceUtils.swift +// StripeCore +// +// Created by Mel Ludowise on 3/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +struct STPDeviceUtils { + static var deviceType: String? { + var systemInfo: utsname = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let deviceType = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + guard !deviceType.isEmpty else { + return nil + } + return deviceType + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/STPDispatchFunctions.swift b/StripeCore/StripeCore/Source/Helpers/STPDispatchFunctions.swift new file mode 100644 index 00000000..a0c116e9 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/STPDispatchFunctions.swift @@ -0,0 +1,17 @@ +// +// STPDispatchFunctions.swift +// StripeCore +// +// Created by Brian Dorfman on 10/24/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public func stpDispatchToMainThreadIfNecessary(_ block: @escaping () -> Void) { + if Thread.isMainThread { + block() + } else { + DispatchQueue.main.async(execute: block) + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/STPError.swift b/StripeCore/StripeCore/Source/Helpers/STPError.swift new file mode 100644 index 00000000..28c162d7 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/STPError.swift @@ -0,0 +1,315 @@ +// +// STPError.swift +// StripeCore +// +// Created by Saikat Chakrabarti on 11/4/12. +// Copyright © 2012 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Possible error code values for NSErrors with the `StripeDomain` domain. +@objc public enum STPErrorCode: Int { + /// Trouble connecting to Stripe. + @objc(STPConnectionError) case connectionError = 40 + /// Your request had invalid parameters. + @objc(STPInvalidRequestError) case invalidRequestError = 50 + /// No valid publishable API key provided. + @objc(STPAuthenticationError) case authenticationError = 51 + /// General-purpose API error. + @objc(STPAPIError) case apiError = 60 + /// Something was wrong with the given card details. + @objc(STPCardError) case cardError = 70 + /// The operation was cancelled. + @objc(STPCancellationError) case cancellationError = 80 + /// The ephemeral key could not be decoded. Make sure your backend is sending + /// the unmodified JSON of the ephemeral key to your app. + /// https://stripe.com/docs/mobile/ios/basic#prepare-your-api + @objc(STPEphemeralKeyDecodingError) case ephemeralKeyDecodingError = 1000 +} + +// MARK: - STPError + +// swift-format-ignore: DontRepeatTypeInStaticProperties +/// Top-level class for Stripe error constants. +@objc public class STPError: NSObject { + // MARK: userInfo keys + /// All Stripe iOS errors will be under this domain. + @objc public static let stripeDomain = "com.stripe.lib" + + // swift-format-ignore: DontRepeatTypeInStaticProperties + /// The error domain for errors in `STPPaymentHandler`. + @objc public static let STPPaymentHandlerErrorDomain = "STPPaymentHandlerErrorDomain" + + /// A human-readable message providing more details about the error. + /// For card errors, these messages can be shown to your users. + /// - seealso: https://stripe.com/docs/api/errors#errors-message + @objc public static let errorMessageKey = "com.stripe.lib:ErrorMessageKey" + /// An SDK-supplied "hint" that is intended to help you, the developer, fix the error. + @objc public static let hintKey = "com.stripe.lib:hintKey" + /// What went wrong with your STPCard (e.g., STPInvalidCVC). + /// + /// See below for full list). + @objc public static let cardErrorCodeKey = "com.stripe.lib:CardErrorCodeKey" + /// Which parameter on the STPCard had an error (e.g., "cvc"). + /// + /// Useful for marking up the right UI element. + @objc public static let errorParameterKey = "com.stripe.lib:ErrorParameterKey" + /// The error code returned by the Stripe API. + /// + /// - seealso: https://stripe.com/docs/api#errors-code + /// - seealso: https://stripe.com/docs/error-codes + @objc public static let stripeErrorCodeKey = "com.stripe.lib:StripeErrorCodeKey" + /// The error type returned by the Stripe API. + /// + /// - seealso: https://stripe.com/docs/api#errors-type + @objc public static let stripeErrorTypeKey = "com.stripe.lib:StripeErrorTypeKey" + /// If the value of `userInfo[stripeErrorCodeKey]` is `STPError.cardDeclined`, + /// the value for this key contains the decline code. + /// + /// - seealso: https://stripe.com/docs/declines/codes + @objc public static let stripeDeclineCodeKey = "com.stripe.lib:DeclineCodeKey" + + /// The Stripe API request ID, if available. Looks like `req_123`. + @_spi(STP) public static let stripeRequestIDKey = "com.stripe.lib:StripeRequestIDKey" +} + +extension NSError { + @_spi(STP) public class Utils { + private static let apiErrorCodeToMessage: [String: String] = [ + "incorrect_number": NSError.stp_cardErrorInvalidNumberUserMessage(), + "invalid_number": NSError.stp_cardErrorInvalidNumberUserMessage(), + "invalid_expiry_month": NSError.stp_cardErrorInvalidExpMonthUserMessage(), + "invalid_expiry_year": NSError.stp_cardErrorInvalidExpYearUserMessage(), + "invalid_cvc": NSError.stp_cardInvalidCVCUserMessage(), + "expired_card": NSError.stp_cardErrorExpiredCardUserMessage(), + "incorrect_cvc": NSError.stp_cardInvalidCVCUserMessage(), + "card_declined": NSError.stp_cardErrorDeclinedUserMessage(), + "processing_error": NSError.stp_cardErrorProcessingErrorUserMessage(), + "invalid_owner_name": NSError.stp_invalidOwnerName, + "invalid_bank_account_iban": NSError.stp_invalidBankAccountIban, + "generic_decline": NSError.stp_genericDeclineErrorUserMessage(), + ] + + private static let apiErrorCodeToCardErrorCode: [String: STPCardErrorCode] = [ + "incorrect_number": .incorrectNumber, + "invalid_number": .invalidNumber, + "invalid_expiry_month": .invalidExpMonth, + "invalid_expiry_year": .invalidExpYear, + "invalid_cvc": .invalidCVC, + "expired_card": .expiredCard, + "incorrect_cvc": .invalidCVC, + "card_declined": .cardDeclined, + "processing_error": .processingError, + "incorrect_zip": .incorrectZip, + ] + + private init() {} + + @_spi(STP) public static func localizedMessage( + fromAPIErrorCode errorCode: String, + declineCode: String? = nil + ) -> String? { + return + (apiErrorCodeToMessage[errorCode] + ?? declineCode.flatMap { apiErrorCodeToMessage[$0] }) + } + + @_spi(STP) public static func cardErrorCode( + fromAPIErrorCode errorCode: String + ) -> STPCardErrorCode? { + return apiErrorCodeToCardErrorCode[errorCode] + } + } +} + +/// NSError extensions for creating error objects from Stripe API responses. +extension NSError { + @_spi(STP) public static func stp_error(from modernStripeError: StripeError) -> NSError? { + switch modernStripeError { + case .apiError(let stripeAPIError): + return stp_error(fromStripeResponse: ["error": stripeAPIError.allResponseFields]) + case .invalidRequest: + return NSError( + domain: STPError.stripeDomain, + code: STPErrorCode.invalidRequestError.rawValue, + userInfo: nil + ) + } + } + + @_spi(STP) public static func stp_error( + errorType: String?, + stripeErrorCode: String?, + stripeErrorMessage: String?, + errorParam: String?, + declineCode: Any?, + httpResponse: HTTPURLResponse? + ) -> NSError? { + var code = 0 + + var userInfo: [AnyHashable: Any] = [ + NSLocalizedDescriptionKey: self.stp_unexpectedErrorMessage(), + ] + userInfo[STPError.stripeErrorCodeKey] = stripeErrorCode ?? "" + userInfo[STPError.stripeErrorTypeKey] = errorType ?? "" + if let errorParam = errorParam { + userInfo[STPError.errorParameterKey] = URLEncoder.convertToCamelCase( + snakeCase: errorParam + ) + } + if let stripeErrorMessage = stripeErrorMessage { + userInfo[STPError.errorMessageKey] = stripeErrorMessage + userInfo[STPError.hintKey] = ServerErrorMapper.mobileErrorMessage( + from: stripeErrorMessage, + httpResponse: httpResponse + ) + } else { + userInfo[STPError.errorMessageKey] = + "Could not interpret the error response that was returned from Stripe." + } + if errorType == "api_error" { + code = STPErrorCode.apiError.rawValue + } else { + if errorType == "invalid_request_error" { + switch httpResponse?.statusCode { + case 401: + code = STPErrorCode.authenticationError.rawValue + default: + code = STPErrorCode.invalidRequestError.rawValue + } + } else if errorType == "card_error" { + code = STPErrorCode.cardError.rawValue + // see https://stripe.com/docs/api/errors#errors-message + userInfo[NSLocalizedDescriptionKey] = stripeErrorMessage + } else { + code = STPErrorCode.apiError.rawValue + } + + if let stripeErrorCode = stripeErrorCode, !stripeErrorCode.isEmpty { + if let cardErrorCode = Utils.cardErrorCode(fromAPIErrorCode: stripeErrorCode) { + if cardErrorCode == STPCardErrorCode.cardDeclined, + let decline_code = declineCode + { + userInfo[STPError.stripeDeclineCodeKey] = decline_code + } + userInfo[STPError.cardErrorCodeKey] = cardErrorCode.rawValue + } + + // If the server didn't send an error message, use a local one. + if stripeErrorMessage == nil { + let localizedMessage = Utils.localizedMessage( + fromAPIErrorCode: stripeErrorCode, + declineCode: declineCode as? String + ) + + if let localizedMessage = localizedMessage { + userInfo[NSLocalizedDescriptionKey] = localizedMessage + } + } + } + } + + // Add the Stripe request id if it exists + if let requestId = httpResponse?.value(forHTTPHeaderField: "request-id") { + userInfo[STPError.stripeRequestIDKey] = requestId + } + + return NSError( + domain: STPError.stripeDomain, + code: code, + userInfo: userInfo as? [String: Any] + ) + } + + @_spi(STP) public static func stp_error( + fromStripeResponse jsonDictionary: [AnyHashable: Any]?, + httpResponse: HTTPURLResponse? + ) -> NSError? { + // TODO: Refactor. A lot of this can be replaced by a lookup/decision table. Check Android implementation for cues. + guard let dict = (jsonDictionary as NSDictionary?), + let errorDictionary = dict["error"] as? NSDictionary + else { + return nil + } + let errorType = errorDictionary["type"] as? String + let errorParam = errorDictionary["param"] as? String + let stripeErrorMessage = errorDictionary["message"] as? String + let stripeErrorCode = errorDictionary["code"] as? String + let declineCode = errorDictionary["decline_code"] + + return stp_error( + errorType: errorType, + stripeErrorCode: stripeErrorCode, + stripeErrorMessage: stripeErrorMessage, + errorParam: errorParam, + declineCode: declineCode, + httpResponse: httpResponse + ) + } + + /// Creates an NSError object from a given Stripe API json response. + /// - Parameter jsonDictionary: The root dictionary from the JSON response. + /// - Returns: An NSError object with the error information from the JSON response, + /// or nil if there was no error information included in the JSON dictionary. + @objc(stp_errorFromStripeResponse:) public static func stp_error( + fromStripeResponse jsonDictionary: [AnyHashable: Any]? + ) + -> NSError? + { + stp_error(fromStripeResponse: jsonDictionary, httpResponse: nil) + } +} + +// MARK: STPCardErrorCodeKeys - + +/// Possible string values you may receive when there was an error tokenizing a card. +/// +/// These values will come back in the error `userInfo` dictionary +/// under the `STPCardErrorCodeKey` key. +public enum STPCardErrorCode: String { + /// The card number is not a valid credit card number. + case invalidNumber = "com.stripe.lib:InvalidNumber" + /// The card has an invalid expiration month. + case invalidExpMonth = "com.stripe.lib:InvalidExpiryMonth" + /// The card has an invalid expiration year. + case invalidExpYear = "com.stripe.lib:InvalidExpiryYear" + /// The card has an invalid CVC. + case invalidCVC = "com.stripe.lib:InvalidCVC" + /// The card number is incorrect. + case incorrectNumber = "com.stripe.lib:IncorrectNumber" + /// The card is expired. + case expiredCard = "com.stripe.lib:ExpiredCard" + /// The card was declined. + case cardDeclined = "com.stripe.lib:CardDeclined" + /// The card has an incorrect CVC. + case incorrectCVC = "com.stripe.lib:IncorrectCVC" + /// An error occured while processing this card. + case processingError = "com.stripe.lib:ProcessingError" + /// The postal code is incorrect. + case incorrectZip = "com.stripe.lib:IncorrectZip" +} + +// swift-format-ignore: DontRepeatTypeInStaticProperties +@objc extension STPError { + /// The card number is not a valid credit card number. + public static let invalidNumber = STPCardErrorCode.invalidNumber.rawValue + /// The card has an invalid expiration month. + public static let invalidExpMonth = STPCardErrorCode.invalidExpMonth.rawValue + /// The card has an invalid expiration year. + public static let invalidExpYear = STPCardErrorCode.invalidExpYear.rawValue + /// The card has an invalid CVC. + public static let invalidCVC = STPCardErrorCode.invalidCVC.rawValue + /// The card number is incorrect. + public static let incorrectNumber = STPCardErrorCode.incorrectNumber.rawValue + /// The card is expired. + public static let expiredCard = STPCardErrorCode.expiredCard.rawValue + /// The card was declined. + public static let cardDeclined = STPCardErrorCode.cardDeclined.rawValue + /// An error occured while processing this card. + public static let processingError = STPCardErrorCode.processingError.rawValue + /// The card has an incorrect CVC. + public static let incorrectCVC = STPCardErrorCode.incorrectCVC.rawValue + /// The postal code is incorrect. + public static let incorrectZip = STPCardErrorCode.incorrectZip.rawValue +} diff --git a/StripeCore/StripeCore/Source/Helpers/STPNumericStringValidator.swift b/StripeCore/StripeCore/Source/Helpers/STPNumericStringValidator.swift new file mode 100644 index 00000000..6f84c71c --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/STPNumericStringValidator.swift @@ -0,0 +1,31 @@ +// +// STPNumericStringValidator.swift +// StripeCore +// +// Created by Cameron Sabol on 3/6/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public enum STPTextValidationState: Int { + case empty + case incomplete + case complete + case invalid +} + +@_spi(STP) open class STPNumericStringValidator: NSObject { + /// Whether or not the target string contains only numeric characters. + @_spi(STP) public class func isStringNumeric(_ string: String) -> Bool { + return + (string as NSString).rangeOfCharacter(from: CharacterSet.stp_invertedAsciiDigit) + .location + == NSNotFound + } + + /// Returns a copy of the passed string with all non-numeric characters removed. + @_spi(STP) public class func sanitizedNumericString(for string: String) -> String { + return string.stp_stringByRemovingCharacters(from: CharacterSet.stp_invertedAsciiDigit) + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/STPURLCallbackHandler.swift b/StripeCore/StripeCore/Source/Helpers/STPURLCallbackHandler.swift new file mode 100644 index 00000000..221a64e7 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/STPURLCallbackHandler.swift @@ -0,0 +1,92 @@ +// +// STPURLCallbackHandler.swift +// StripeCore +// +// Created by Brian Dorfman on 10/6/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) @objc public protocol STPURLCallbackListener: NSObjectProtocol { + func handleURLCallback(_ url: URL) -> Bool +} + +@_spi(STP) public class STPURLCallbackHandler: NSObject { + @_spi(STP) public static var sharedHandler: STPURLCallbackHandler = STPURLCallbackHandler() + + @objc @_spi(STP) public class func shared() -> STPURLCallbackHandler { + return sharedHandler + } + + @objc @discardableResult @_spi(STP) public func handleURLCallback(_ url: URL) -> Bool { + guard + let components = NSURLComponents( + url: url, + resolvingAgainstBaseURL: false + ) + else { + return false + } + + var resultsOrred = false + + for callback in callbacks { + if let listener = callback.listener { + if callback.urlComponents.stp_matchesURLComponents(components) { + resultsOrred = resultsOrred || listener.handleURLCallback(url) + } + } + } + + return resultsOrred + } + + @objc(registerListener:forURL:) @_spi(STP) public func register( + _ listener: STPURLCallbackListener, + for url: URL + ) { + + guard + let urlComponents = NSURLComponents( + url: url, + resolvingAgainstBaseURL: false + ) + else { + return + } + let callback = STPURLCallback(urlComponents: urlComponents, listener: listener) + var callbacksCopy = callbacks + callbacksCopy.append(callback) + callbacks = callbacksCopy + } + + @objc @_spi(STP) public func unregisterListener(_ listener: STPURLCallbackListener) { + var callbacksToRemove: [AnyHashable] = [] + + for callback in callbacks { + if listener.isEqual(callback.listener) { + callbacksToRemove.append(callback) + } + } + var callbacksCopy = callbacks + callbacksCopy = callbacksCopy.filter({ !callbacksToRemove.contains($0) }) + callbacks = callbacksCopy + } + + private var callbacks: [STPURLCallback] = [] +} + +class STPURLCallback: NSObject { + init( + urlComponents: NSURLComponents, + listener: STPURLCallbackListener + ) { + self.urlComponents = urlComponents + self.listener = listener + super.init() + } + + var urlComponents: NSURLComponents + weak var listener: STPURLCallbackListener? +} diff --git a/StripeCore/StripeCore/Source/Helpers/ServerErrorMapper.swift b/StripeCore/StripeCore/Source/Helpers/ServerErrorMapper.swift new file mode 100644 index 00000000..b608326e --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/ServerErrorMapper.swift @@ -0,0 +1,95 @@ +// +// ServerErrorMapper.swift +// StripeCore +// +// Created by Nick Porter on 9/13/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +private enum ServerErrorPrefixes: String, CaseIterable { + case missingPublishableKey = "You did not provide an API key." + case invalidApiKey = "Invalid API Key provided" + case mismatchPublishableKey = + "The client_secret provided does not match the client_secret associated with" + case noSuchPaymentIntent = "No such payment_intent" + case noSuchSetupIntent = "No such setup_intent" + + /// Maps the corresponding `ServerErrorMapper` to a `MobileErrorMessage`. + /// + /// - Parameters: + /// - serverErrorMessage: the raw server error message from the server. + /// - httpResponse: the http response for the error. + /// - Returns: a `MobileErrorMessage` that maps to this `ServerErrorPrefixes`. + func mobileError( + from serverErrorMessage: String, + httpResponse: HTTPURLResponse + ) -> MobileErrorMessage { + switch self { + case .missingPublishableKey: + return MobileErrorMessage.missingPublishableKey + case .invalidApiKey: + if httpResponse.url?.absoluteString.hasPrefix( + "https://api.stripe.com/v1/payment_methods?customer=" + ) ?? false { + // User didn't set ephemeral key correctly + return MobileErrorMessage.invalidCustomerEphKey + } else { + // User didn't set publishable key correctly + return MobileErrorMessage.missingPublishableKey + } + case .mismatchPublishableKey: + return MobileErrorMessage.mismatchPublishableKey + case .noSuchPaymentIntent: + return MobileErrorMessage.noSuchPaymentIntent + case .noSuchSetupIntent: + return MobileErrorMessage.noSuchSetupIntent + } + } +} + +/// List of mobile friendly error messages for common upstream server errors. +private enum MobileErrorMessage: String { + case missingPublishableKey = + "No valid API key provided. Set `STPAPIClient.shared.publishableKey` to your publishable key, which you can find here: https://stripe.com/docs/keys" + + case invalidCustomerEphKey = + "Invalid customer ephemeral key secret. You can find more information at https://stripe.com/docs/payments/accept-a-payment?platform=ios#add-server-endpoint" + + case mismatchPublishableKey = + "The publishable key provided does not match the publishable key associated with the PaymentIntent/SetupIntent. This is most likley caused by using a different publishable key in `STPAPIClient.shared.publishableKey` than what your server is using." + + case noSuchPaymentIntent = + "No matching PaymentIntent could be found. Ensure you are creating a PaymentIntent server side and using the same publishable key on both client and server. You can find more information at https://stripe.com/docs/api/payment_intents/create" + + case noSuchSetupIntent = + "No matching SetupIntent could be found. Ensure you are creating a SetupIntent server side and using the same publishable key on both client and server. You can find more information at https://stripe.com/docs/api/setup_intents/create" +} + +/// Maps known server error message to mobile friendly versions. +struct ServerErrorMapper { + + /// Maps common server error messages to a mobile friendly equivalent if known, + /// otherwise defaults to the server error message. + /// + /// - Parameters: + /// - serverErrorMessage: the error message returned from the server. + /// - httpResponse: the http response for this error. + /// - Returns: a mobile friendly error message if known, + /// otherwise defaults the error message from the server. + static func mobileErrorMessage( + from serverErrorMessage: String, + httpResponse: HTTPURLResponse? + ) -> String? { + guard let httpResponse = httpResponse else { + return nil + } + + let serverError = ServerErrorPrefixes.allCases.first(where: { + serverErrorMessage.hasPrefix($0.rawValue) + }) + return serverError?.mobileError(from: serverErrorMessage, httpResponse: httpResponse) + .rawValue + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/StripeCoreBundleLocator.swift b/StripeCore/StripeCore/Source/Helpers/StripeCoreBundleLocator.swift new file mode 100644 index 00000000..8a94aff7 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/StripeCoreBundleLocator.swift @@ -0,0 +1,18 @@ +// +// StripeCoreBundleLocator.swift +// StripeCore +// +// Created by Mel Ludowise on 7/6/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +final class StripeCoreBundleLocator: BundleLocatorProtocol { + static let internalClass: AnyClass = StripeCoreBundleLocator.self + static let bundleName = "StripeCoreBundle" + #if SWIFT_PACKAGE + static let spmResourcesBundle = Bundle.module + #endif + static let resourcesBundle = StripeCoreBundleLocator.computeResourcesBundle() +} diff --git a/StripeCore/StripeCore/Source/Helpers/URLEncoder.swift b/StripeCore/StripeCore/Source/Helpers/URLEncoder.swift new file mode 100644 index 00000000..de33cab3 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/URLEncoder.swift @@ -0,0 +1,147 @@ +// +// URLEncoder.swift +// StripeCore +// +// Created by Mel Ludowise on 5/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public final class URLEncoder { + public class func string(byURLEncoding string: String) -> String { + return escape(string) + } + + public class func convertToCamelCase(snakeCase input: String) -> String { + let parts: [String] = input.components(separatedBy: "_") + var camelCaseParam = "" + for (idx, part) in parts.enumerated() { + camelCaseParam += idx == 0 ? part : part.capitalized + } + + return camelCaseParam + } + + public class func convertToSnakeCase(camelCase input: String) -> String { + var newString = input + + while let range = newString.rangeOfCharacter(from: .uppercaseLetters) { + let character = newString[range] + newString = newString.replacingCharacters(in: range, with: character.lowercased()) + newString.insert("_", at: range.lowerBound) + } + + return newString + } + + @objc(queryStringFromParameters:) + public class func queryString(from parameters: [String: Any]) -> String { + return query(parameters) + } +} + +// MARK: - +// The code below is adapted from https://github.com/Alamofire/Alamofire +struct Key { + enum Part { + case normal(String) + case dontEscape(String) + } + let parts: [Part] +} + +/// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively. +/// +/// - Parameters: +/// - key: Key of the query component. +/// - value: Value of the query component. +/// +/// - Returns: The percent-escaped, URL encoded query string components. +private func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { + func unwrap(_ any: T) -> Any { + let mirror = Mirror(reflecting: any) + guard mirror.displayStyle == .optional, let first = mirror.children.first else { + return any + } + return first.value + } + + var components: [(String, String)] = [] + switch value { + case let dictionary as [String: Any]: + for nestedKey in dictionary.keys.sorted() { + let value = dictionary[nestedKey]! + let escapedNestedKey = escape(nestedKey) + components += queryComponents(fromKey: "\(key)[\(escapedNestedKey)]", value: value) + } + case let array as [Any]: + for (index, value) in array.enumerated() { + components += queryComponents(fromKey: "\(key)[\(index)]", value: value) + } + case let number as NSNumber: + if number.isBool { + components.append((key, escape(number.boolValue ? "true" : "false"))) + } else { + components.append((key, escape("\(number)"))) + } + case let bool as Bool: + components.append((key, escape(bool ? "true" : "false"))) + case let set as Set: + for value in Array(set) { + components += queryComponents(fromKey: "\(key)", value: value) + } + default: + let unwrappedValue = unwrap(value) + components.append((key, escape("\(unwrappedValue)"))) + } + return components +} + +/// Creates a percent-escaped string following RFC 3986 for a query string key or value. +/// +/// - Parameter string: `String` to be percent-escaped. +/// +/// - Returns: The percent-escaped `String`. +private func escape(_ string: String) -> String { + string.addingPercentEncoding(withAllowedCharacters: URLQueryAllowed) ?? string +} + +private func query(_ parameters: [String: Any]) -> String { + var components: [(String, String)] = [] + + for key in parameters.keys.sorted(by: <) { + let value = parameters[key]! + components += queryComponents(fromKey: escape(key), value: value) + } + return components.map { "\($0)=\($1)" }.joined(separator: "&") +} + +/// Creates a CharacterSet from RFC 3986 allowed characters. +/// +/// RFC 3986 states that the following characters are "reserved" characters. +/// +/// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" +/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" +/// +/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow +/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" +/// should be percent-escaped in the query string. +private let URLQueryAllowed: CharacterSet = { + // does not include "?" or "/" due to RFC 3986 - Section 3.4. + let generalDelimitersToEncode = ":#[]@" + let subDelimitersToEncode = "!$&'()*+,;=" + let encodableDelimiters = CharacterSet( + charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)" + ) + + return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters) +}() + +extension NSNumber { + fileprivate var isBool: Bool { + // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of + // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22). + String(cString: objCType) == "c" + } +} diff --git a/StripeCore/StripeCore/Source/Helpers/URLSession+Retry.swift b/StripeCore/StripeCore/Source/Helpers/URLSession+Retry.swift new file mode 100644 index 00000000..5750a299 --- /dev/null +++ b/StripeCore/StripeCore/Source/Helpers/URLSession+Retry.swift @@ -0,0 +1,42 @@ +// +// URLSession+Retry.swift +// StripeCore +// +// Created by David Estes on 3/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension URLSession { + @_spi(STP) public func stp_performDataTask( + with request: URLRequest, + completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void, + retryCount: Int = StripeAPI.maxRetries + ) { + let task = dataTask(with: request) { (data, response, error) in + if let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 429, + retryCount > 0 + { + // Add some backoff time with a little bit of jitter: + let delayTime = TimeInterval( + pow(Double(1 + StripeAPI.maxRetries - retryCount), Double(2)) + + .random(in: 0..<0.5) + ) + + let fireDate = Date() + delayTime + self.delegateQueue.schedule(after: .init(fireDate)) { + self.stp_performDataTask( + with: request, + completionHandler: completionHandler, + retryCount: retryCount - 1 + ) + } + } else { + completionHandler(data, response, error) + } + } + task.resume() + } +} diff --git a/StripeCore/StripeCore/Source/Localization/STPLocalizationUtils.swift b/StripeCore/StripeCore/Source/Localization/STPLocalizationUtils.swift new file mode 100644 index 00000000..d3ee8ba6 --- /dev/null +++ b/StripeCore/StripeCore/Source/Localization/STPLocalizationUtils.swift @@ -0,0 +1,97 @@ +// +// STPLocalizationUtils.swift +// StripeCore +// +// Created by Brian Dorfman on 8/11/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public final class STPLocalizationUtils { + /// Acts like NSLocalizedString but tries to find the string in the Stripe + /// bundle first if possible. + /// + /// If the main app has a localization that we do not support, we want to switch + /// to pulling strings from the main bundle instead of our own bundle so that + /// users can add translations for our strings without having to fork the sdk. + /// At launch, NSBundles' store what language(s) the user requests that they + /// actually have translations for in `preferredLocalizations`. + /// We compare our framework's resource bundle to the main app's bundle, and + /// if their language choice doesn't match up we switch to pulling strings + /// from the main bundle instead. + /// This also prevents language mismatches. E.g. the user lists portuguese and + /// then spanish as their preferred languages. The main app supports both so all its + /// strings are in pt, but we support spanish so our bundle marks es as our + /// preferred language and our strings are in es. + /// If the main bundle doesn't have the correct string, we'll always fall back to + /// using the Stripe bundle so we don't inadvertently show an untranslated string. + static func localizedStripeStringUseMainBundle( + bundleLocator: BundleLocatorProtocol.Type + ) -> Bool { + if bundleLocator.resourcesBundle.preferredLocalizations.first + != Bundle.main.preferredLocalizations.first + { + return true + } + return false + } + + static let UnknownString = "STPSTRINGNOTFOUND" + + public class func localizedStripeString( + forKey key: String, + bundleLocator: BundleLocatorProtocol.Type + ) -> String { + if languageOverride != nil { + return testing_localizedStripeString(forKey: key, bundleLocator: bundleLocator) + } + if localizedStripeStringUseMainBundle(bundleLocator: bundleLocator) { + // Per https://developer.apple.com/documentation/foundation/bundle/1417694-localizedstring, + // iOS will give us an empty string if a string isn't found for the specified key. + // Work around this by specifying an unknown sentinel string as the value. If we get that value back, + // we know that the string wasn't present in the bundle. + let userTranslation = Bundle.main.localizedString( + forKey: key, + value: UnknownString, + table: nil + ) + if userTranslation != UnknownString { + return userTranslation + } + } + + return bundleLocator.resourcesBundle.localizedString( + forKey: key, + value: nil, + table: nil + ) + } + + // MARK: - Testing + static var languageOverride: String? + static func overrideLanguage(to string: String?) { + STPLocalizationUtils.languageOverride = string + } + static func testing_localizedStripeString( + forKey key: String, + bundleLocator: BundleLocatorProtocol.Type + ) -> String { + var bundle = bundleLocator.resourcesBundle + + if let languageOverride = languageOverride { + + let lprojPath = bundle.path(forResource: languageOverride, ofType: "lproj") + if let lprojPath = lprojPath { + bundle = Bundle(path: lprojPath)! + } + } + return bundle.localizedString(forKey: key, value: nil, table: nil) + } +} + +/// Use to explicitly ignore static analyzer warning: +/// "User-facing text should use localized string macro". +@inline(__always) @_spi(STP) public func STPNonLocalizedString(_ string: String) -> String { + return string +} diff --git a/StripeCore/StripeCore/Source/Localization/STPLocalizedString.swift b/StripeCore/StripeCore/Source/Localization/STPLocalizedString.swift new file mode 100644 index 00000000..7e44fcfb --- /dev/null +++ b/StripeCore/StripeCore/Source/Localization/STPLocalizedString.swift @@ -0,0 +1,14 @@ +// +// STPLocalizedString.swift +// StripeCore +// +// Created by Mel Ludowise on 7/6/20. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@inline(__always) func STPLocalizedString(_ key: String, _ comment: String?) -> String { + return STPLocalizationUtils.localizedStripeString( + forKey: key, + bundleLocator: StripeCoreBundleLocator.self + ) +} diff --git a/StripeCore/StripeCore/Source/Localization/String+Localized.swift b/StripeCore/StripeCore/Source/Localization/String+Localized.swift new file mode 100644 index 00000000..1b292e42 --- /dev/null +++ b/StripeCore/StripeCore/Source/Localization/String+Localized.swift @@ -0,0 +1,51 @@ +// +// String+Localized.swift +// StripeCore +// +// Created by Mel Ludowise on 8/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) extension String { + public enum Localized { + public static var close: String { + return STPLocalizedString("Close", "Text for close button") + } + + public static var tryAgain: String { + return STPLocalizedString("Try again", "Text for a retry button") + } + + public static var scan_card_title_capitalization: String { + STPLocalizedString("Scan Card", "Text for button to scan a credit card") + } + + public static var scan_card: String { + STPLocalizedString("Scan card", "Button title to open camera to scan credit/debit card") + } + + public static var scan_card_privacy_link_text: String { + // THIS STRING SHOULD NOT BE MODIFIED + STPLocalizedString( + "We use Stripe to verify your card details. Stripe may use and store your data according its privacy policy. Learn more", + "Informational text informing the user that Stripe is used to process data and a link to Stripe's privacy policy" + ) + } + + public static func scanCardExpectedPrivacyLinkText() -> NSAttributedString? { + let stringData = Data(String.Localized.scan_card_privacy_link_text.utf8) + let stringOptions: [NSAttributedString.DocumentReadingOptionKey: Any] = [ + .documentType: NSAttributedString.DocumentType.html, + .characterEncoding: String.Encoding.utf8.rawValue, + ] + + return try? NSAttributedString( + data: stringData, + options: stringOptions, + documentAttributes: nil + ) + } + } +} diff --git a/StripeCore/StripeCore/Source/Telemetry/FraudDetectionData.swift b/StripeCore/StripeCore/Source/Telemetry/FraudDetectionData.swift new file mode 100644 index 00000000..23e6fb91 --- /dev/null +++ b/StripeCore/StripeCore/Source/Telemetry/FraudDetectionData.swift @@ -0,0 +1,72 @@ +// +// FraudDetectionData.swift +// StripeCore +// +// Created by Yuki Tokuhiro on 5/20/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +private let SIDLifetime: TimeInterval = 30 * 60 // 30 minutes + +/// Contains encoded values returned from m.stripe.com. +/// +/// - Note: See `STPTelemetryClient`. +/// - Note: See `StripeAPI.advancedFraudSignalsEnabled`. +@_spi(STP) public final class FraudDetectionData: Codable { + @_spi(STP) public static let shared: FraudDetectionData = + // Load initial value from UserDefaults + UserDefaults.standard.fraudDetectionData ?? FraudDetectionData() + + @_spi(STP) public var muid: String? + @_spi(STP) public var guid: String? + @_spi(STP) public var sid: String? + + /// The approximate time that the sid was generated from m.stripe.com + /// Intended to be used to expire the sid after `SIDLifetime` seconds + /// - Note: This class is a dumb container; users must set this value appropriately. + var sidCreationDate: Date? + + init( + sid: String? = nil, + muid: String? = nil, + guid: String? = nil, + sidCreationDate: Date? = nil + ) { + self.sid = sid + self.muid = muid + self.guid = guid + self.sidCreationDate = sidCreationDate + } + + func resetSIDIfExpired() { + guard let sidCreationDate = sidCreationDate else { + return + } + let thirtyMinutesAgo = Date(timeIntervalSinceNow: -SIDLifetime) + if sidCreationDate < thirtyMinutesAgo { + sid = nil + } + } + + deinit { + // Write latest value to disk + UserDefaults.standard.fraudDetectionData = self + } + + func reset() { + self.sid = nil + self.muid = nil + self.guid = nil + self.sidCreationDate = nil + } +} + +extension FraudDetectionData: Equatable { + @_spi(STP) public static func == (lhs: FraudDetectionData, rhs: FraudDetectionData) -> Bool { + return + lhs.muid == rhs.muid && lhs.sid == rhs.sid && lhs.guid == rhs.guid + && lhs.sidCreationDate == rhs.sidCreationDate + } +} diff --git a/StripeCore/StripeCore/Source/Telemetry/STPTelemetryClient.swift b/StripeCore/StripeCore/Source/Telemetry/STPTelemetryClient.swift new file mode 100644 index 00000000..595e2815 --- /dev/null +++ b/StripeCore/StripeCore/Source/Telemetry/STPTelemetryClient.swift @@ -0,0 +1,229 @@ +// +// STPTelemetryClient.swift +// StripeCore +// +// Created by Ben Guo on 4/18/17. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +private let TelemetryURL = URL(string: "https://m.stripe.com/6")! + +@_spi(STP) public final class STPTelemetryClient: NSObject { + @_spi(STP) public static var shared: STPTelemetryClient = STPTelemetryClient( + sessionConfiguration: StripeAPIConfiguration.sharedUrlSessionConfiguration + ) + + @_spi(STP) public func addTelemetryFields(toParams params: inout [String: Any]) { + params["muid"] = fraudDetectionData.muid + params["guid"] = fraudDetectionData.guid + fraudDetectionData.resetSIDIfExpired() + params["sid"] = fraudDetectionData.sid + } + + @_spi(STP) public func paramsByAddingTelemetryFields( + toParams params: [String: Any] + ) -> [String: Any] { + var mutableParams = params + mutableParams["muid"] = fraudDetectionData.muid + mutableParams["guid"] = fraudDetectionData.guid + fraudDetectionData.resetSIDIfExpired() + mutableParams["sid"] = fraudDetectionData.sid + return mutableParams + } + + /// Sends a payload of telemetry to the Stripe telemetry service. + /// + /// - Parameters: + /// - forceSend: ⚠️ Always send the request. Only pass this for testing purposes. + /// - completion: Called with the result of the telemetry network request. + @_spi(STP) public func sendTelemetryData( + forceSend: Bool = false, + completion: ((Result<[String: Any], Error>) -> Void)? = nil + ) { + let wrappedCompletion: ((Result<[String: Any], Error>) -> Void) = { result in + if case .failure(let error) = result { + let errorAnalytic = ErrorAnalytic(event: .fraudDetectionApiFailure, error: error) + STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) + } + completion?(result) + } + + guard forceSend || STPTelemetryClient.shouldSendTelemetry() else { + wrappedCompletion(.failure(NSError.stp_genericConnectionError())) + return + } + sendTelemetryRequest(jsonPayload: payload, completion: wrappedCompletion) + } + + @_spi(STP) public func updateFraudDetectionIfNecessary( + completion: @escaping ((Result) -> Void) + ) { + fraudDetectionData.resetSIDIfExpired() + if fraudDetectionData.muid == nil || fraudDetectionData.sid == nil { + sendTelemetryRequest( + jsonPayload: [ + "muid": fraudDetectionData.muid ?? "", + "guid": fraudDetectionData.guid ?? "", + "sid": fraudDetectionData.sid ?? "", + ]) { result in + switch result { + case .failure(let error): + completion(.failure(error)) + case .success: + completion(.success(self.fraudDetectionData)) + } + } + } else { + completion(.success(fraudDetectionData)) + } + } + + private let urlSession: URLSession + + @_spi(STP) public class func shouldSendTelemetry() -> Bool { + #if targetEnvironment(simulator) + return false + #else + return StripeAPI.advancedFraudSignalsEnabled && NSClassFromString("XCTest") == nil + #endif + } + + @_spi(STP) public init( + sessionConfiguration config: URLSessionConfiguration + ) { + urlSession = URLSession(configuration: config) + super.init() + } + + private var language = Locale.autoupdatingCurrent.identifier + private lazy var fraudDetectionData = { + return FraudDetectionData.shared + }() + lazy private var platform = [deviceModel, osVersion].joined(separator: " ") + + private var deviceModel: String = { + var systemInfo = utsname() + uname(&systemInfo) + let model = withUnsafePointer(to: &systemInfo.machine) { + $0.withMemoryRebound( + to: CChar.self, + capacity: 1 + ) { ptr in + String.init(validatingUTF8: ptr) + } + } + return model ?? "Unknown" + }() + + private var osVersion = UIDevice.current.systemVersion + + private var screenSize: String { + #if canImport(CompositorServices) + return "visionOS" + #else + let screen = UIScreen.main + let screenRect = screen.bounds + let width = screenRect.size.width + let height = screenRect.size.height + let scale = screen.scale + return String(format: "%.0fw_%.0fh_%.0fr", width, height, scale) + #endif + } + + private var timeZoneOffset: String { + let timeZone = NSTimeZone.local as NSTimeZone + let hoursFromGMT = Double(timeZone.secondsFromGMT) / (60 * 60) + return String(format: "%.0f", hoursFromGMT) + } + + private func encodeValue(_ value: String?) -> [AnyHashable: Any]? { + if let value = value { + return [ + "v": value, + ] + } + return nil + } + + private var payload: [String: Any] { + var payload: [String: Any] = [:] + var data: [String: Any] = [:] + if let encode = encodeValue(language) { + data["c"] = encode + } + if let encode = encodeValue(platform) { + data["d"] = encode + } + if let encode = encodeValue(screenSize) { + data["f"] = encode + } + if let encode = encodeValue(timeZoneOffset) { + data["g"] = encode + } + payload["a"] = data + + // Don't pass expired SIDs to m.stripe.com + fraudDetectionData.resetSIDIfExpired() + + let otherData: [String: Any] = [ + "d": fraudDetectionData.muid ?? "", + "e": fraudDetectionData.sid ?? "", + "k": Bundle.stp_applicationName() ?? "", + "l": Bundle.stp_applicationVersion() ?? "", + "m": NSNumber(value: StripeAPI.deviceSupportsApplePay()), + "o": osVersion, + "s": deviceModel, + ] + payload["b"] = otherData + payload["tag"] = STPAPIClient.STPSDKVersion + payload["src"] = "ios-sdk" + payload["v2"] = NSNumber(value: 1) + return payload + } + + private func sendTelemetryRequest( + jsonPayload: [String: Any], + completion: ((Result<[String: Any], Error>) -> Void)? = nil + ) { + var request = URLRequest(url: TelemetryURL) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + let data = try? JSONSerialization.data( + withJSONObject: jsonPayload, + options: [] + ) + request.httpBody = data + let task = urlSession.dataTask(with: request as URLRequest) { (data, response, error) in + guard + error == nil, + let response = response as? HTTPURLResponse, + response.statusCode == 200, + let data = data, + let responseDict = try? JSONSerialization.jsonObject(with: data, options: []) + as? [String: Any] + else { + completion?(.failure(error ?? NSError.stp_genericFailedToParseResponseError())) + return + } + + // Update fraudDetectionData + if let muid = responseDict["muid"] as? String { + self.fraudDetectionData.muid = muid + } + if let guid = responseDict["guid"] as? String { + self.fraudDetectionData.guid = guid + } + if self.fraudDetectionData.sid == nil, + let sid = responseDict["sid"] as? String + { + self.fraudDetectionData.sid = sid + self.fraudDetectionData.sidCreationDate = Date() + } + completion?(.success(responseDict)) + } + task.resume() + } +} diff --git a/StripeCore/StripeCore/Source/Telemetry/UserDefaults+PaymentsCore.swift b/StripeCore/StripeCore/Source/Telemetry/UserDefaults+PaymentsCore.swift new file mode 100644 index 00000000..98abd45d --- /dev/null +++ b/StripeCore/StripeCore/Source/Telemetry/UserDefaults+PaymentsCore.swift @@ -0,0 +1,42 @@ +// +// UserDefaults+PaymentsCore.swift +// StripeCore +// +// Created by David Estes on 11/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension UserDefaults { + /// Canonical list of all UserDefaults keys the SDK uses. + enum StripePaymentsCoreKeys: String { + /// The key for a dictionary FraudDetectionData dictionary. + case fraudDetectionData = "com.stripe.lib:FraudDetectionDataKey" + } + + var fraudDetectionData: FraudDetectionData? { + get { + let key = StripePaymentsCoreKeys.fraudDetectionData.rawValue + guard let data = data(forKey: key) else { + return nil + } + do { + return try JSONDecoder().decode(FraudDetectionData.self, from: data) + } catch let e { + assertionFailure("\(e)") + return nil + } + } + set { + let key = StripePaymentsCoreKeys.fraudDetectionData.rawValue + do { + let data = try JSONEncoder().encode(newValue) + setValue(data, forKey: key) + } catch let e { + assertionFailure("\(e)") + return + } + } + } +} diff --git a/StripeCore/StripeCore/Source/UI/UIActivityIndicatorView+Stripe.swift b/StripeCore/StripeCore/Source/UI/UIActivityIndicatorView+Stripe.swift new file mode 100644 index 00000000..afc18ef5 --- /dev/null +++ b/StripeCore/StripeCore/Source/UI/UIActivityIndicatorView+Stripe.swift @@ -0,0 +1,35 @@ +// +// UIActivityIndicatorView+Stripe.swift +// StripeCore +// +// Created by Mel Ludowise on 3/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) extension UIActivityIndicatorView { + #if DEBUG + /// Disables animation for `stp_startAnimatingAndShow`. + /// + /// This should be disabled in snapshot tests. + public static var stp_isAnimationEnabled = true + #endif + + /// This method should be used in place of `hidesWhenStopped` and `startAnimating()` + /// so we can ensure consistency in snapshot tests. + public func stp_startAnimatingAndShow() { + isHidden = false + #if DEBUG + guard UIActivityIndicatorView.stp_isAnimationEnabled else { return } + #endif + startAnimating() + } + + /// This method should be used in place of and `hidesWhenStopped` and `stopAnimating()` + /// so we can ensure consistency in snapshot tests. + public func stp_stopAnimatingAndHide() { + isHidden = true + stopAnimating() + } +} diff --git a/StripeCore/StripeCore/Source/UI/UIFont+Stripe.swift b/StripeCore/StripeCore/Source/UI/UIFont+Stripe.swift new file mode 100644 index 00000000..5775138f --- /dev/null +++ b/StripeCore/StripeCore/Source/UI/UIFont+Stripe.swift @@ -0,0 +1,88 @@ +// +// UIFont+Stripe.swift +// StripeCore +// +// Created by Yuki Tokuhiro on 11/11/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +@_spi(STP) extension UIFont { + /// The default size category used to compute font size prior to scaling it. + /// + /// - seealso: + /// https://developer.apple.com/documentation/uikit/uifont/scaling_fonts_automatically + private static let defaultSizeCategory: UIContentSizeCategory = .large + + public static func preferredFont( + forTextStyle style: TextStyle, + weight: Weight, + maximumPointSize: CGFloat? = nil + ) -> UIFont { + let metrics = UIFontMetrics(forTextStyle: style) + let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) + let font = UIFont.systemFont(ofSize: desc.pointSize, weight: weight) + + if let maximumPointSize = maximumPointSize { + return metrics.scaledFont(for: font, maximumPointSize: maximumPointSize) + } + return metrics.scaledFont(for: font) + } + + /// Creates a copy of this `UIFont` with a point size matching the specified style and weight. + /// + /// - Parameters: + /// - style: The style used to determine the font's size. + /// - weight: The weight to apply to the font. + public func withPreferredSize( + forTextStyle style: TextStyle, + weight: Weight? = nil + ) -> UIFont { + // Determine the font size for the system default font for this style + // at the default font scale, apply the size to this font, then return a + // scaled font using UIFontMetrics. + // + // Note: We must scale the font in this way rather than directly using the + // font size for the current scale, or UILabel won't adjust the font size + // if the size category dynamically changes. + + // Get font descriptor for the font system default font with this style + // using the unscaled size category + let systemDefaultFontDescriptor = UIFontDescriptor.preferredFontDescriptor( + withTextStyle: style, + compatibleWith: UITraitCollection( + preferredContentSizeCategory: UIFont.defaultSizeCategory + ) + ) + + // If no weight was specified, use the weight associated with the system + // default font for this TextStyle + var useWeight = weight + if weight == nil, + let traits = systemDefaultFontDescriptor.fontAttributes[.traits] + as? [UIFontDescriptor.TraitKey: Any], + let systemDefaultWeight = traits[.weight] as? Weight + { + useWeight = systemDefaultWeight + } + + // Create a descriptor that set's the font to the specified weight + let descriptor = fontDescriptor.addingAttributes([ + .traits: [ + UIFontDescriptor.TraitKey.weight: useWeight, + ], + ]) + + // Get the point size used by the system font for this style + let pointSize = systemDefaultFontDescriptor.pointSize + + // Apply the weight and size to the font + let font = UIFont(descriptor: descriptor, size: pointSize) + + // Scale the font for the current size category + let metrics = UIFontMetrics(forTextStyle: style) + return metrics.scaledFont(for: font) + } +} diff --git a/StripeCore/StripeCore/StripeCore.h b/StripeCore/StripeCore/StripeCore.h new file mode 100644 index 00000000..55d5d203 --- /dev/null +++ b/StripeCore/StripeCore/StripeCore.h @@ -0,0 +1,18 @@ +// +// StripeCore.h +// StripeCore +// +// Created by Mel Ludowise on 6/24/21. +// + +#import + +//! Project version number for StripeCore. +FOUNDATION_EXPORT double StripeCoreVersionNumber; + +//! Project version string for StripeCore. +FOUNDATION_EXPORT const unsigned char StripeCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeCore/StripeCoreTestUtils/APIStubbedTestCase.swift b/StripeCore/StripeCoreTestUtils/APIStubbedTestCase.swift new file mode 100644 index 00000000..f56ffb61 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/APIStubbedTestCase.swift @@ -0,0 +1,54 @@ +// +// APIStubbedTestCase.swift +// StripeCoreTestUtils +// +// Created by David Estes on 9/24/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift +import XCTest + +@testable@_spi(STP) import StripeCore + +/// A test case offering a custom STPAPIClient with manual JSON stubbing. +/// To automatically record requests, try STPNetworkStubbingTestCase instead. +open class APIStubbedTestCase: XCTestCase { + open override func setUp() { + super.setUp() + APIStubbedTestCase.stubAllOutgoingRequests() + } + public override func tearDown() { + super.tearDown() + HTTPStubs.removeAllStubs() + } + + public func stubbedAPIClient(configuration: URLSessionConfiguration? = nil) -> STPAPIClient { + return APIStubbedTestCase.stubbedAPIClient(configuration: configuration) + } + + static public func stubAllOutgoingRequests() { + // Stubs are evaluated in the reverse order that they are added, so if the network is hit and no other stub is matched, raise an exception + stub(condition: { _ in + return true + }) { request in + XCTFail("Attempted to hit the live network at \(request.url?.path ?? "")") + return HTTPStubsResponse() + } + } + + static public func stubbedAPIClient(configuration: URLSessionConfiguration? = nil) -> STPAPIClient { + let apiClient = STPAPIClient() + let config = configuration ?? APIStubbedTestCase.stubbedURLSessionConfig() + apiClient.urlSession = URLSession(configuration: config) + return apiClient + } + + static public func stubbedURLSessionConfig() -> URLSessionConfiguration { + let urlSessionConfig = URLSessionConfiguration.default + HTTPStubs.setEnabled(true, for: urlSessionConfig) + return urlSessionConfig + } +} diff --git a/StripeCore/StripeCoreTestUtils/Categories/STPAnalyticsClient+StripeCoreTestingUtils.swift b/StripeCore/StripeCoreTestUtils/Categories/STPAnalyticsClient+StripeCoreTestingUtils.swift new file mode 100644 index 00000000..b4717074 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Categories/STPAnalyticsClient+StripeCoreTestingUtils.swift @@ -0,0 +1,18 @@ +// +// STPAnalyticsClient+StripeCoreTestingUtils.swift +// StripeCoreTestUtils +// +// Created by Yuki Tokuhiro on 11/4/23. +// + +import Foundation +@_spi(STP) @testable import StripeCore + +@_spi(STP) public class STPTestingAnalyticsClient: STPAnalyticsClient { + public var events = [Analytic]() + + public override func log(analytic: Analytic, apiClient: STPAPIClient = .shared) { + events.append(analytic) + super.log(analytic: analytic) + } +} diff --git a/StripeCore/StripeCoreTestUtils/Categories/UIImage+StripeCoreTestingUtils.swift b/StripeCore/StripeCoreTestUtils/Categories/UIImage+StripeCoreTestingUtils.swift new file mode 100644 index 00000000..3dbbcdbe --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Categories/UIImage+StripeCoreTestingUtils.swift @@ -0,0 +1,30 @@ +// +// UIImage+StripeCoreTestingUtils.swift +// StripeCoreTestUtils +// +// Created by Ramon Torres on 11/9/21. +// + +import UIKit + +extension UIImage { + + /// Returns a 24x24 icon for testing purposes. + /// - Returns: Plus sign icon. + public class func mockIcon() -> UIImage { + let renderer = UIGraphicsImageRenderer(size: CGSize(width: 24, height: 24)) + + let icon = renderer.image { context in + context.cgContext.move(to: CGPoint(x: 12, y: 4)) + context.cgContext.addLine(to: CGPoint(x: 12, y: 20)) + context.cgContext.move(to: CGPoint(x: 4, y: 12)) + context.cgContext.addLine(to: CGPoint(x: 20, y: 12)) + context.cgContext.setLineWidth(2) + context.cgContext.setLineCap(.round) + context.cgContext.strokePath() + } + + return icon.withRenderingMode(.alwaysTemplate) + } + +} diff --git a/StripeCore/StripeCoreTestUtils/Categories/UIView+StripeCoreTestingUtils.swift b/StripeCore/StripeCoreTestUtils/Categories/UIView+StripeCoreTestingUtils.swift new file mode 100644 index 00000000..b7c5e574 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Categories/UIView+StripeCoreTestingUtils.swift @@ -0,0 +1,24 @@ +// +// UIView+StripeCoreTestingUtils.swift +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 10/4/21. +// + +import UIKit + +extension UIView { + /// Constrains the view to the given width and autosizes its height. + /// + /// - Parameter width: Resizes the view to this width + public func autosizeHeight(width: CGFloat) { + translatesAutoresizingMaskIntoConstraints = false + widthAnchor.constraint(equalToConstant: width).isActive = true + setNeedsLayout() + layoutIfNeeded() + frame = .init( + origin: .zero, + size: systemLayoutSizeFitting(CGSize(width: width, height: UIView.noIntrinsicMetric)) + ) + } +} diff --git a/StripeCore/StripeCoreTestUtils/Info.plist b/StripeCore/StripeCoreTestUtils/Info.plist new file mode 100644 index 00000000..9bcb2444 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeCore/StripeCoreTestUtils/KeyPathExpectation.swift b/StripeCore/StripeCoreTestUtils/KeyPathExpectation.swift new file mode 100644 index 00000000..9aa26de7 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/KeyPathExpectation.swift @@ -0,0 +1,70 @@ +// +// KeyPathExpectation.swift +// StripeCoreTestUtils +// +// Created by Ramon Torres on 1/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +public class KeyPathExpectation: XCTNSPredicateExpectation { + + public convenience init( + object: Object, + keyPath: KeyPath, + equalsToValue value: Value, + description: String? = nil + ) { + let description = + description + ?? "Expect predicate `\(keyPath)` == \(value) for \(String(describing: object))" + + self.init( + object: object, + keyPath: keyPath, + evaluatedWith: { $0 == value }, + description: description + ) + } + + public convenience init( + object: Object, + keyPath: KeyPath, + notEqualsToValue value: Value, + description: String? = nil + ) { + let description = + description + ?? "Expect predicate `\(keyPath)` != \(value) for \(String(describing: object))" + + self.init( + object: object, + keyPath: keyPath, + evaluatedWith: { $0 != value }, + description: description + ) + } + + init( + object: Object, + keyPath: KeyPath, + evaluatedWith block: @escaping (Value) -> Bool, + description: String? = nil + ) { + let predicate = NSPredicate { object, _ in + guard let unwrappedObject = object as? Object else { + return false + } + + return block(unwrappedObject[keyPath: keyPath]) + } + + super.init(predicate: predicate, object: object) + + expectationDescription = + description + ?? "Expect `\(keyPath)` to return `true` when evaluated with block." + } + +} diff --git a/StripeCore/StripeCoreTestUtils/Mock Files/File_IdentityDocument.json b/StripeCore/StripeCoreTestUtils/Mock Files/File_IdentityDocument.json new file mode 100644 index 00000000..bae03c32 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Mock Files/File_IdentityDocument.json @@ -0,0 +1,7 @@ +{ + "created": 1636833390, + "id": "file_id", + "purpose": "identity_document", + "size": 100, + "type": "jpg" +} diff --git a/StripeCore/StripeCoreTestUtils/Mocks/MockAnalyticsClient.swift b/StripeCore/StripeCoreTestUtils/Mocks/MockAnalyticsClient.swift new file mode 100644 index 00000000..9ef02234 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Mocks/MockAnalyticsClient.swift @@ -0,0 +1,31 @@ +// +// MockAnalyticsClient.swift +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 3/12/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore + +@_spi(STP) public final class MockAnalyticsClient: STPAnalyticsClientProtocol { + + public private(set) var productUsage: Set = [] + public private(set) var loggedAnalytics: [Analytic] = [] + + public init() {} + + public func addClass(toProductUsageIfNecessary klass: T.Type) where T: STPAnalyticsProtocol { + productUsage.insert(klass.stp_analyticsIdentifier) + } + + public func log(analytic: Analytic, apiClient: STPAPIClient = .shared) { + loggedAnalytics.append(analytic) + } + + /// Clears `loggedAnalytics` and `productUsage`. + public func reset() { + productUsage = [] + loggedAnalytics = [] + } +} diff --git a/StripeCore/StripeCoreTestUtils/Mocks/MockAnalyticsClientV2.swift b/StripeCore/StripeCoreTestUtils/Mocks/MockAnalyticsClientV2.swift new file mode 100644 index 00000000..0d03aeeb --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Mocks/MockAnalyticsClientV2.swift @@ -0,0 +1,25 @@ +// +// MockAnalyticsClientV2.swift +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 6/7/22. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public final class MockAnalyticsClientV2: AnalyticsClientV2Protocol { + public let clientId: String = "MockAnalyticsClient" + + public private(set) var loggedAnalyticsPayloads: [[String: Any]] = [] + + public func loggedAnalyticPayloads(withEventName eventName: String) -> [[String: Any]] { + return loggedAnalyticsPayloads.filter { ($0["event_name"] as? String) == eventName } + } + + public init() {} + + public func log(eventName: String, parameters: [String: Any]) { + loggedAnalyticsPayloads.append(payload(withEventName: eventName, parameters: parameters)) + } +} diff --git a/StripeCore/StripeCoreTestUtils/Mocks/MockData.swift b/StripeCore/StripeCoreTestUtils/Mocks/MockData.swift new file mode 100644 index 00000000..1dea8e79 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/Mocks/MockData.swift @@ -0,0 +1,50 @@ +// +// MockData.swift +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 10/27/21. +// + +import Foundation + +@testable@_spi(STP) import StripeCore + +/// Protocol for easily opening JSON mock files. +public protocol MockData: RawRepresentable where RawValue == String { + associatedtype ResponseType: Decodable + var bundle: Bundle { get } +} + +extension MockData { + public var url: URL { + return bundle.url(forResource: rawValue, withExtension: "json")! + } + + public func data() throws -> Data { + return try Data(contentsOf: url) + } + + public func make() throws -> ResponseType { + let result: Result = STPAPIClient.decodeResponse( + data: try data(), + error: nil, + response: nil + ) + switch result { + case .success(let response): + return response + case .failure(let error): + throw error + } + } +} + +// Dummy class to determine this bundle +private class ClassForBundle {} + +@_spi(STP) public enum FileMock: String, MockData { + public typealias ResponseType = StripeFile + public var bundle: Bundle { return Bundle(for: ClassForBundle.self) } + + case identityDocument = "File_IdentityDocument" +} diff --git a/StripeCore/StripeCoreTestUtils/STPSnapshotTestCase.swift b/StripeCore/StripeCoreTestUtils/STPSnapshotTestCase.swift new file mode 100644 index 00000000..06aacca4 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/STPSnapshotTestCase.swift @@ -0,0 +1,73 @@ +// +// STPSnapshotTestCase.swift +// StripeCoreTestUtils +// +// Created by David Estes on 4/13/22. +// + +#if !canImport(CompositorServices) +import Foundation +import iOSSnapshotTestCase + +let TEST_DEVICE_MODEL = "iPhone13,1" // iPhone 12 mini +let TEST_DEVICE_OS_VERSION = "16.4" + +open class STPSnapshotTestCase: FBSnapshotTestCase { + + open override func setUp() { + super.setUp() + let deviceModel = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"]! + recordMode = ProcessInfo.processInfo.environment["STP_RECORD_SNAPSHOTS"] != nil + if deviceModel != TEST_DEVICE_MODEL || UIDevice.current.systemVersion != TEST_DEVICE_OS_VERSION { + continueAfterFailure = false + XCTFail("You must run snapshot tests on \(TEST_DEVICE_MODEL) running \(TEST_DEVICE_OS_VERSION). You are running these tests on a \(deviceModel) on \(UIDevice.current.systemVersion).") + } + } + + // Calls FBSnapshotVerifyView with a default 2% per-pixel color differentiation, as M1 and Intel machines render shadows differently. + public func STPSnapshotVerifyView( + _ view: UIView, + identifier: String? = nil, + suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), + perPixelTolerance: CGFloat = 0.02, + overallTolerance: CGFloat = 0, + autoSizingHeightForWidth: CGFloat? = nil, + file: StaticString = #file, + line: UInt = #line + ) { + if let autoSizingHeightForWidth { + view.autosizeHeight(width: autoSizingHeightForWidth) + } + if view.hasAmbiguousLayout { + XCTFail("Snapshot test failed: \(view.debugDescription) has ambiguous layout. \nHorizontal: \(view.constraintsAffectingLayout(for: .horizontal)) \nVertical: \(view.constraintsAffectingLayout(for: .vertical))", file: file, line: line) + } + FBSnapshotVerifyView( + view, + identifier: identifier, + suffixes: suffixes, + perPixelTolerance: perPixelTolerance, + overallTolerance: overallTolerance, + file: file, + line: line + ) + } + +} +#else +import XCTest +// No-op on visionOS for now, snapshot tests not supported +open class STPSnapshotTestCase: XCTestCase { + public func STPSnapshotVerifyView( + _ view: UIView, + identifier: String? = nil, + suffixes: NSOrderedSet = NSOrderedSet(), + perPixelTolerance: CGFloat = 0.02, + overallTolerance: CGFloat = 0, + autoSizingHeightForWidth: CGFloat? = nil, + file: StaticString = #file, + line: UInt = #line + ) { + // Do nothing! + } +} +#endif diff --git a/StripeCore/StripeCoreTestUtils/StripeCoreTestUtils.h b/StripeCore/StripeCoreTestUtils/StripeCoreTestUtils.h new file mode 100644 index 00000000..4a9c8d94 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/StripeCoreTestUtils.h @@ -0,0 +1,18 @@ +// +// StripeCoreTestUtils.h +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 7/1/21. +// + +#import + +//! Project version number for StripeCoreTestUtils. +FOUNDATION_EXPORT double StripeCoreTestUtilsVersionNumber; + +//! Project version string for StripeCoreTestUtils. +FOUNDATION_EXPORT const unsigned char StripeCoreTestUtilsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeCore/StripeCoreTestUtils/TestConstants.swift b/StripeCore/StripeCoreTestUtils/TestConstants.swift new file mode 100644 index 00000000..90a80923 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/TestConstants.swift @@ -0,0 +1,16 @@ +// +// TestConstants.swift +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 10/26/21. +// + +import Foundation + +public let STPTestingNetworkRequestTimeout: TimeInterval = 8 + +@objc(TestConstants) +public class _objc_Constants: NSObject { + @objc(STPTestingNetworkRequestTimeout) + public static let _objc_STPTestingNetworkRequestTimeout = STPTestingNetworkRequestTimeout +} diff --git a/StripeCore/StripeCoreTestUtils/URLRequest+StripeTest.swift b/StripeCore/StripeCoreTestUtils/URLRequest+StripeTest.swift new file mode 100644 index 00000000..5cb22cbe --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/URLRequest+StripeTest.swift @@ -0,0 +1,50 @@ +// +// URLRequest+StripeTest.swift +// StripeiOS Tests +// +// Created by David Estes on 9/24/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension URLRequest { + /// A Data representation of the response's body, + /// fetched from the body Data or body InputStream. + public var httpBodyOrBodyStream: Data? { + if let httpBody = httpBody { + return httpBody + } + if let httpBodyStream = httpBodyStream { + let maxLength = 1024 + var data = Data() + var buffer = Data(count: maxLength) + httpBodyStream.open() + buffer.withUnsafeMutableBytes { bufferPtr in + let bufferTypedPtr = bufferPtr.bindMemory(to: UInt8.self) + while httpBodyStream.hasBytesAvailable { + let length = httpBodyStream.read( + bufferTypedPtr.baseAddress!, + maxLength: maxLength + ) + if length == 0 { + break + } else { + data.append(bufferTypedPtr.baseAddress!, count: length) + } + } + } + return data + } + return nil + } + + // Query items sent as part of this URLRequest + public var queryItems: [URLQueryItem]? { + let body = String(data: httpBodyOrBodyStream!, encoding: .utf8)! + // Create a combined URLComponents with the URL params from the body + var urlComponents = URLComponents(url: url!, resolvingAgainstBaseURL: false) + urlComponents?.query = body + return urlComponents?.queryItems + } +} diff --git a/StripeCore/StripeCoreTestUtils/XCTestCase+Stripe.swift b/StripeCore/StripeCoreTestUtils/XCTestCase+Stripe.swift new file mode 100644 index 00000000..9953ad92 --- /dev/null +++ b/StripeCore/StripeCoreTestUtils/XCTestCase+Stripe.swift @@ -0,0 +1,72 @@ +// +// XCTestCase+Stripe.swift +// StripeCoreTestUtils +// +// Created by Mel Ludowise on 11/3/21. +// + +import XCTest + +extension XCTestCase { + + public func expectation( + for object: Object, + keyPath: KeyPath, + equalsToValue value: Value, + description: String? = nil + ) -> KeyPathExpectation { + return KeyPathExpectation( + object: object, + keyPath: keyPath, + equalsToValue: value, + description: description + ) + } + + public func expectation( + for object: Object, + keyPath: KeyPath, + notEqualsToValue value: Value, + description: String? = nil + ) -> KeyPathExpectation { + return KeyPathExpectation( + object: object, + keyPath: keyPath, + notEqualsToValue: value, + description: description + ) + } + + public func notNullExpectation( + for object: Object, + keyPath: KeyPath, + description: String? = nil + ) -> KeyPathExpectation { + let description = + description ?? "Expect predicate `\(keyPath)` != nil for \(String(describing: object))" + + return KeyPathExpectation( + object: object, + keyPath: keyPath, + evaluatedWith: { $0 != nil }, + description: description + ) + } + + public func wait(seconds: TimeInterval) { + let e = expectation(description: "Wait for \(seconds) seconds") + DispatchQueue.main.asyncAfter(deadline: .now() + seconds) { + e.fulfill() + } + waitForExpectations(timeout: seconds) + } +} + +public func XCTAssertIs( + _ item: Any, + _ t: T.Type, + file: StaticString = #filePath, + line: UInt = #line +) { + XCTAssert(item is T, "\(type(of: item)) is not type \(T.self)", file: file, line: line) +} diff --git a/StripeCore/StripeCoreTests/API Bindings/STPAPIClient+EmptyResponseTest.swift b/StripeCore/StripeCoreTests/API Bindings/STPAPIClient+EmptyResponseTest.swift new file mode 100644 index 00000000..f755f95b --- /dev/null +++ b/StripeCore/StripeCoreTests/API Bindings/STPAPIClient+EmptyResponseTest.swift @@ -0,0 +1,81 @@ +// +// STPAPIClient+EmptyResponseTest.swift +// StripeCoreTests +// +// Created by Jaime Park on 1/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import XCTest + +class STPAPIClient_EmptyResponseTest: XCTestCase { + /// Response is an error; Error is nil. + /// + /// Should result in a failure.` + func testEmptyResponse_WithErrorResponse() throws { + let response = [ + "error": [ + "type": "api_error", + "message": "some message", + ], + ] + + let responseData = try JSONSerialization.data(withJSONObject: response, options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: HTTPURLResponse( + url: URL(string: "https://www.stripe.com")!, + statusCode: 400, + httpVersion: nil, + headerFields: nil + ) + ) + + switch result { + case .success: + XCTFail("The request should not have succeeded") + case .failure(let error): + if let stripeError = error as? StripeError, case .apiError(let apiError) = stripeError { + XCTAssert(apiError.statusCode == 400, "expected status code to be set") + } else { + XCTFail("The error should have been an `.apiError`") + } + } + } + + /// Response is an empty response; Error is not nil. + /// + /// Should result in a failure. + func testEmptyResponse_WithError() throws { + let responseData = try JSONSerialization.data(withJSONObject: [:], options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: NSError.stp_genericConnectionError(), + response: nil + ) + + guard case .failure = result else { + XCTFail("The request should not have succeeded") + return + } + } + + /// Response is an empty response; Error is nil. + /// + /// Should result in a success. + func testEmptyResponse_NoError() throws { + let responseData = try JSONSerialization.data(withJSONObject: [:], options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: nil + ) + + guard case .success = result else { + XCTFail("The request should have succeeded") + return + } + } +} diff --git a/StripeCore/StripeCoreTests/API Bindings/STPAPIClient+ErrorResponseTest.swift b/StripeCore/StripeCoreTests/API Bindings/STPAPIClient+ErrorResponseTest.swift new file mode 100644 index 00000000..7869815f --- /dev/null +++ b/StripeCore/StripeCoreTests/API Bindings/STPAPIClient+ErrorResponseTest.swift @@ -0,0 +1,143 @@ +// +// STPAPIClient+ErrorResponseTest.swift +// StripeCoreTests +// +// Created by Eduardo Urias on 9/30/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import XCTest + +class STPAPIClient_ErrorResponseTest: XCTestCase { + /// Response is an error, error code is a known error code, and no message is present, + /// `localizedDescription` is filled by a default error message. + func testResponse_WithKnownErrorCode_noMessage() throws { + let response = [ + "error": [ + "type": "card_error", + "code": "incorrect_number", + ], + ] + + let responseData = try JSONSerialization.data(withJSONObject: response, options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: nil + ) + + switch result { + case .failure(let error): + XCTAssertEqual( + error.localizedDescription, + NSError.stp_cardErrorInvalidNumberUserMessage() + ) + case .success: + XCTFail("The request should not have succeeded") + } + } + + /// Response is an error, error code is a known error code, and message is present, + /// `localizedDescription` is filled with the provided message. + func testResponse_WithKnownErrorCode_messagePresent() throws { + let response = [ + "error": [ + "type": "card_error", + "message": "some message", + "code": "incorrect_number", + ], + ] + + let responseData = try JSONSerialization.data(withJSONObject: response, options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: nil + ) + + switch result { + case .failure(let error): + XCTAssertEqual( + error.localizedDescription, + "some message" + ) + case .success: + XCTFail("The request should not have succeeded") + } + } + + /// Response is an error, error code is not present. + func testResponse_WithNoErrorCode() throws { + let response = [ + "error": [ + "type": "card_error", + "message": "some message", + ], + ] + + let responseData = try JSONSerialization.data(withJSONObject: response, options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: nil + ) + + switch result { + case .failure(let error): + XCTAssertEqual(error.localizedDescription, "some message") + case .success: + XCTFail("The request should not have succeeded") + } + } + + /// Response is an error, error code is empty. + func testResponse_WithEmptyErrorCode() throws { + let response = [ + "error": [ + "type": "card_error", + "message": "some message", + "code": "", + ], + ] + + let responseData = try JSONSerialization.data(withJSONObject: response, options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: nil + ) + + switch result { + case .failure(let error): + XCTAssertEqual(error.localizedDescription, "some message") + case .success: + XCTFail("The request should not have succeeded") + } + } + + /// Response is an error, error code is invalid. + func testResponse_WithInvalidErrorCode() throws { + let response = [ + "error": [ + "type": "card_error", + "message": "some message", + "code": "garbage", + ], + ] + + let responseData = try JSONSerialization.data(withJSONObject: response, options: []) + let result: Result = STPAPIClient.decodeResponse( + data: responseData, + error: nil, + response: nil + ) + + switch result { + case .failure(let error): + XCTAssertEqual(error.localizedDescription, "some message") + case .success: + XCTFail("The request should not have succeeded") + } + } +} diff --git a/StripeCore/StripeCoreTests/API Bindings/StripeCodableTest.swift b/StripeCore/StripeCoreTests/API Bindings/StripeCodableTest.swift new file mode 100644 index 00000000..b03b035f --- /dev/null +++ b/StripeCore/StripeCoreTests/API Bindings/StripeCodableTest.swift @@ -0,0 +1,212 @@ +// +// StripeCodableTest.swift +// StripeCoreTests +// +// Created by David Estes on 8/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import OHHTTPStubs +import OHHTTPStubsSwift +@_spi(STP)@testable import StripeCore +import StripeCoreTestUtils +import XCTest + +struct TestCodable: UnknownFieldsCodable { + struct Nested: UnknownFieldsCodable { + struct DeeplyNested: UnknownFieldsCodable { + var deeplyNestedProperty: String + + var _additionalParametersStorage: NonEncodableParameters? + var _allResponseFieldsStorage: NonEncodableParameters? + } + + var nestedProperty: String + + var deeplyNested: DeeplyNested? + + var _additionalParametersStorage: NonEncodableParameters? + var _allResponseFieldsStorage: NonEncodableParameters? + } + + var topProperty: String + + var arrayProperty: [Nested]? + + var nested: Nested? + + var testEnum: TestEnum? + var testEnums: [TestEnum]? + + var testEnumDict: [String: TestEnum]? + + enum TestEnum: String, SafeEnumCodable { + case hello + case hey + case unparsable + } + + var _additionalParametersStorage: NonEncodableParameters? + var _allResponseFieldsStorage: NonEncodableParameters? +} + +struct TestNonOptionalEnumCodable: UnknownFieldsCodable { + var testEnum: TestEnum + var testEnums: [TestEnum] + + enum TestEnum: String, SafeEnumCodable { + case hello + case hey + case unparsable + } + + var _additionalParametersStorage: NonEncodableParameters? + var _allResponseFieldsStorage: NonEncodableParameters? +} + +class StripeAPIRequestTest: APIStubbedTestCase { + func codableTest( + codable: T, + completion: @escaping ([String: Any], Result) -> Void + ) { + let e = expectation(description: "Request completed") + let encodedDict = try! codable.encodeJSONDictionary() + let encodedData = try? JSONSerialization.data(withJSONObject: encodedDict, options: []) + + let apiClient = stubbedAPIClient() + stub { _ in + return true + } response: { _ in + return HTTPStubsResponse(data: encodedData!, statusCode: 200, headers: nil) + } + + apiClient.post(resource: "anything", object: codable) { (result: Result) in + completion(encodedDict, result) + e.fulfill() + } + waitForExpectations(timeout: STPTestingNetworkRequestTimeout, handler: nil) + } + + func testValidEnum() { + var codable = TestCodable(topProperty: "hello1") + codable.additionalParameters = ["test_enum": "hey", "test_enums": ["hello", "hey"]] + codableTest(codable: codable) { _, result in + let resultObject = try! result.get() + XCTAssertEqual(resultObject.testEnum, .hey) + XCTAssertEqual(resultObject.testEnums, [.hello, .hey]) + } + } + + func testNonOptionalValidEnum() { + let codable = TestNonOptionalEnumCodable(testEnum: .hey, testEnums: [.hello, .hey]) + codableTest(codable: codable) { _, result in + let resultObject = try! result.get() + XCTAssertEqual(resultObject.testEnum, .hey) + XCTAssertEqual(resultObject.testEnums, [.hello, .hey]) + } + } + + func testUnknownEnum() { + var codable = TestCodable(topProperty: "hello1") + codable.additionalParameters = [ + "test_enum": "hellooo", "test_enums": ["hello", "helloooo"], + "test_enum_dict": ["item1": "hello", "item2": "this_will_not_parse"], + ] + codableTest(codable: codable) { _, result in + let resultObject = try! result.get() + XCTAssertEqual(resultObject.testEnum, .unparsable) + XCTAssertEqual(resultObject.testEnumDict!["item1"], .hello) + XCTAssertEqual(resultObject.testEnumDict!["item2"], .unparsable) + } + } + + func testEmptyEnum() { + let codable = TestCodable(topProperty: "hello1") + codableTest(codable: codable) { _, result in + let resultObject = try! result.get() + XCTAssertNil(resultObject.testEnum) + } + } + + func testUnpopulatedFieldsAreNil() { + let codable = TestCodable(topProperty: "hello1") + codableTest(codable: codable) { _, result in + let resultObject = try! result.get() + // wrappedValues will sometimes be populated but empty. We want them to be nil. + XCTAssertNil(resultObject.nested) + XCTAssertNil(resultObject.nested?.deeplyNested) + } + } + + func testArrays() { + var codable = TestCodable(topProperty: "hello1") + codable.arrayProperty = [ + TestCodable.Nested(nestedProperty: "hi"), TestCodable.Nested(nestedProperty: "there"), + ] + codableTest(codable: codable) { _, result in + let resultObject = try! result.get() + XCTAssert(resultObject.arrayProperty![0].nestedProperty == "hi") + } + } + + func testRoundtripKnownFields() { + var codable = TestCodable(topProperty: "hello1") + codable.topProperty = "hello1" + codable.nested = TestCodable.Nested(nestedProperty: "hello2") + codable.nested!.deeplyNested = TestCodable.Nested.DeeplyNested( + deeplyNestedProperty: "hello3" + ) + codableTest(codable: codable) { codableDict, result in + let resultObject = try! result.get() + XCTAssertEqual( + resultObject.nested!.deeplyNested!.deeplyNestedProperty, + codable.nested!.deeplyNested!.deeplyNestedProperty + ) + let newDictionary = try! resultObject.encodeJSONDictionary() as NSDictionary + XCTAssert(newDictionary.isEqual(to: codableDict)) + } + } + + func testRoundtripUnknownFields() { + var codable = TestCodable(topProperty: "hello1") + codable.topProperty = "hello1" + codable.nested = TestCodable.Nested(nestedProperty: "hello2") + codable.nested!.deeplyNested = TestCodable.Nested.DeeplyNested( + deeplyNestedProperty: "hello3" + ) + codable.nested?.additionalParameters = [ + "nested_property": "a_different_thing", + "deeply_nested": [ + "hello": "world", + "deepest": [ + "deep": + ["wow": "very deep"], + ], + ], + ] + codable.additionalParameters = ["boop": "beep"] + + codableTest(codable: codable) { codableDict, result in + let resultObject = try! result.get() + XCTAssertEqual( + resultObject.nested!.deeplyNested!.deeplyNestedProperty, + codable.nested!.deeplyNested!.deeplyNestedProperty + ) + let newDictionary = try! resultObject.encodeJSONDictionary() as NSDictionary + XCTAssert(newDictionary.isEqual(to: codableDict)) + XCTAssertEqual( + resultObject.nested!.nestedProperty, + "a_different_thing" + ) + XCTAssertEqual( + resultObject.allResponseFields["boop"] as! String, + "beep" + ) + XCTAssertEqual( + resultObject.nested!.deeplyNested!.allResponseFields["hello"] as! String, + "world" + ) + } + } +} diff --git a/StripeCore/StripeCoreTests/Analytics/AnalyticLoggableErrorTest.swift b/StripeCore/StripeCoreTests/Analytics/AnalyticLoggableErrorTest.swift new file mode 100644 index 00000000..51fa0b2c --- /dev/null +++ b/StripeCore/StripeCoreTests/Analytics/AnalyticLoggableErrorTest.swift @@ -0,0 +1,174 @@ +// +// AnalyticLoggableErrorTest.swift +// StripeCoreTests +// +// Created by Yuki Tokuhiro on 3/20/24. +// +// + +import Foundation +@testable@_spi(STP) import StripeCore +import XCTest + +class AnalyticLoggableErrorTest: XCTestCase { + enum BasicSwiftError: Error { + case foo + case bar(pii: String) + } + + enum BasicSwiftErrorWithDebugDescription: Error, CustomDebugStringConvertible { + case someErrorCase + case someOtherCase(pii: String) + + var debugDescription: String { + switch self { + case .someErrorCase: + "Some error occurred." + case .someOtherCase(let pii): + "Some other error occured with this PII: \(pii)" + } + } + } + + func testSerializeForV1Logging() { + // Stripe API Error + let stripeAPIErrorJSON = [ + "error": [ + "type": "card_error", + "message": "Your card number is incorrect.", + "code": "incorrect_number", + ], + ] + let stripeAPIErrorHTTPResponse = HTTPURLResponse(url: URL(string: "https://api.stripe.com/v1/some_endpoint")!, statusCode: 402, httpVersion: nil, headerFields: ["request-id": "req_123"]) + let stripeAPIError = NSError.stp_error(fromStripeResponse: stripeAPIErrorJSON, httpResponse: stripeAPIErrorHTTPResponse)! + XCTAssertEqual( + stripeAPIError.serializeForV1Analytics() as? [String: String], + [ + "error_type": "card_error", + "error_code": "incorrect_number", + "request_id": "req_123", + ] + ) + + // StripeCore.StripeError.stripeAPIError - same as above, but different type + let stripeAPIErrorJSONData = try! JSONSerialization.data( + withJSONObject: stripeAPIErrorJSON, + options: [.prettyPrinted] + ) + let stripeCoreStripeError = STPAPIClient.decodeStripeErrorResponse(data: stripeAPIErrorJSONData, response: stripeAPIErrorHTTPResponse)! + XCTAssertEqual( + stripeCoreStripeError.serializeForV1Analytics() as? [String: String], + [ + "error_type": "card_error", + "error_code": "incorrect_number", + "request_id": "req_123", + ] + ) + + // Decoding Error from an iOS library + let decodingError = DecodingError.keyNotFound(STPCodingKey(intValue: 0)!, .init(codingPath: [], debugDescription: "PII")) + XCTAssertEqual( + decodingError.serializeForV1Analytics() as? [String: String], + [ + "error_type": "Swift.DecodingError", + "error_code": "keyNotFound", + ] + ) + + // Swift Error + let swiftError = BasicSwiftError.foo + XCTAssertEqual( + swiftError.serializeForV1Analytics() as? [String: String], + [ + "error_type": "StripeCoreTests.AnalyticLoggableErrorTest.BasicSwiftError", + "error_code": "foo", + ] + ) + + // Swift Error with associated value - shouldn't include associated value + let swiftErrorWithPIIInAssociatedValue = BasicSwiftError.bar(pii: "pii") + XCTAssertEqual( + swiftErrorWithPIIInAssociatedValue.serializeForV1Analytics() as? [String: String], + [ + "error_type": "StripeCoreTests.AnalyticLoggableErrorTest.BasicSwiftError", + "error_code": "bar", + ] + ) + + // Swift Error with debug description - ok to use debug description for case w/o associated value + let swiftErrorWithDebugDescription = BasicSwiftErrorWithDebugDescription.someErrorCase + XCTAssertEqual( + swiftErrorWithDebugDescription.serializeForV1Analytics() as? [String: String], + [ + "error_type": "StripeCoreTests.AnalyticLoggableErrorTest.BasicSwiftErrorWithDebugDescription", + "error_code": "Some error occurred.", + ] + ) + + // Swift Error with associated value and debug description - should use case name + let swiftErrorWithPIIInAssociatedValueAndDebugDescription = BasicSwiftErrorWithDebugDescription.someOtherCase(pii: "pii") + XCTAssertEqual( + swiftErrorWithPIIInAssociatedValueAndDebugDescription.serializeForV1Analytics() as? [String: String], + [ + "error_type": "StripeCoreTests.AnalyticLoggableErrorTest.BasicSwiftErrorWithDebugDescription", + "error_code": "someOtherCase", + ] + ) + + // NSError + let nsURLError = NSError( + domain: NSURLErrorDomain, + code: NSURLErrorNotConnectedToInternet, + userInfo: nil + ) + XCTAssertEqual( + nsURLError.serializeForV1Analytics() as? [String: String], + [ + "error_type": "NSURLErrorDomain", + "error_code": "-1009", + ] + ) + } + + func testAnalyticLoggableError() { + // Implementing `AnalyticLoggableError`... + enum MyError: Error, AnalyticLoggableError, CustomDebugStringConvertible { + case invalidClientSecret(clientSecret: String) + var debugDescription: String { + switch self { + case .invalidClientSecret(clientSecret: let clientSecret): + return "Invalid client secret provided starting with \(clientSecret.prefix(5))" + } + } + + // ...and overriding everything... + var analyticsErrorType: String { + return "overriden error type" + } + var analyticsErrorCode: String { + return "overridden error code" + } + + // ...and using additionalNonPIIErrorDetails... + var additionalNonPIIErrorDetails: [String: Any] { + switch self { + case .invalidClientSecret(clientSecret: let clientSecret): + return [ + "client_secret_snippet": String(clientSecret.prefix(5)), + ] + } + } + } + + // ...should use all the custom values... + let analyticsClient = STPAnalyticsClient() + analyticsClient.log(analytic: ErrorAnalytic(event: ._3DS2ChallengeFlowErrored, error: MyError.invalidClientSecret(clientSecret: "cs_12345"))) + + let log = analyticsClient._testLogHistory.first! + XCTAssertEqual(log["error_type"] as? String, "overriden error type") + XCTAssertEqual(log["error_code"] as? String, "overridden error code") + let errorDetails = log["error_details"] as? [String: Any] + XCTAssertEqual(errorDetails?["client_secret_snippet"] as? String, "cs_12") + XCTAssertEqual(errorDetails?.keys.count, 1) + } +} diff --git a/StripeCore/StripeCoreTests/Analytics/AnalyticsClientV2Test.swift b/StripeCore/StripeCoreTests/Analytics/AnalyticsClientV2Test.swift new file mode 100644 index 00000000..1a652ee5 --- /dev/null +++ b/StripeCore/StripeCoreTests/Analytics/AnalyticsClientV2Test.swift @@ -0,0 +1,95 @@ +// +// AnalyticsClientV2Test.swift +// StripeCoreTests +// +// Created by Mel Ludowise on 6/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +@testable@_spi(STP) import StripeCore + +final class AnalyticsClientV2Test: XCTestCase { + let client = AnalyticsClientV2( + clientId: "test_client_id", + origin: "test_origin" + ) + + func testShouldCollectAnalytics_alwaysFalseInTest() { + XCTAssertFalse(AnalyticsClientV2.shouldCollectAnalytics) + } + + func testRequestHeaders() { + let headers = client.requestHeaders + XCTAssertEqual( + headers["user-agent"]?.starts(with: "Stripe/v1 ios/"), + true, + String(describing: headers["user-agent"]) + ) + XCTAssertEqual(headers["origin"], "test_origin") + } + + func testSerializeError() { + let payload = AnalyticsClientV2.serialize( + error: NSError(domain: "my_domain", code: 125, userInfo: ["foo": "bar"]), + filePath: "/some/file/path/my_device/MyClass.swift", + line: 786 + ) + + XCTAssertEqual(payload.count, 4) + XCTAssertEqual(payload["domain"] as? String, "my_domain") + XCTAssertEqual(payload["code"] as? Int, 125) + XCTAssertEqual(payload["file"] as? String, "MyClass.swift") + XCTAssertEqual(payload["line"] as? UInt, 786) + } + + func testCommonPayload() { + let commonPayload = client.makeCommonPayload() + + XCTAssertEqual(commonPayload["client_id"] as? String, "test_client_id") + XCTAssertNotNil(commonPayload["event_id"] as? String) + XCTAssertNotNil(commonPayload["created"] as? Double) + XCTAssertNotNil(commonPayload["os_version"] as? String) + XCTAssertEqual(commonPayload["sdk_platform"] as? String, "ios") + XCTAssertNotNil(commonPayload["sdk_version"] as? String) + XCTAssertNotNil(commonPayload["device_type"] as? String) + XCTAssertNotNil(commonPayload["app_name"] as? String) + XCTAssertNotNil(commonPayload["app_version"] as? String) + + // Verify this is a valid UUID + XCTAssertNotNil((commonPayload["device_id"] as? String).map(UUID.init)) + + let platformInfo = commonPayload["platform_info"] as? [String: Any] + XCTAssertNotNil(platformInfo?["install"] as? String) + XCTAssertNotNil(platformInfo?["app_bundle_id"] as? String) + + } + + func testPayloadFromAnalytic() { + + let payload = client.payload( + withEventName: "foo", + parameters: [ + "custom_property": "test_property", + "event_metadata": [ + "string_property": "test_string", + "int_property": 156, + ], + ] + ) + + // Ensure some common properties are present + XCTAssertNotNil(payload["client_id"] as? String) + XCTAssertNotNil(payload["event_id"] as? String) + + // Ensure encoded analytic is merged + XCTAssertEqual(payload["event_name"] as? String, "foo") + XCTAssertEqual(payload["custom_property"] as? String, "test_property") + + let metadata = payload["event_metadata"] as? [String: Any] + XCTAssertEqual(metadata?["string_property"] as? String, "test_string") + XCTAssertEqual(metadata?["int_property"] as? Int, 156) + } +} diff --git a/StripeCore/StripeCoreTests/Analytics/Error_SerializeForLoggingTest.swift b/StripeCore/StripeCoreTests/Analytics/Error_SerializeForLoggingTest.swift new file mode 100644 index 00000000..48e30cde --- /dev/null +++ b/StripeCore/StripeCoreTests/Analytics/Error_SerializeForLoggingTest.swift @@ -0,0 +1,67 @@ +// +// Error_SerializeForLoggingTest.swift +// StripeCoreTests +// +// Created by Nick Porter on 9/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +@testable@_spi(STP) import StripeCore + +class Error_SerializeForLoggingTest: XCTestCase { + + struct CustomLoggableError: Error, AnalyticLoggableErrorV2 { + func analyticLoggableSerializeForLogging() -> [String: Any] { + return [ + "foo": "value", + ] + } + } + + enum StringError: String, AnalyticLoggableStringErrorV2 { + case foo + } + + func testNSErrorSerializedForLogging() throws { + let error = NSError( + domain: "test-domain", + code: 1, + userInfo: ["description": "test-description"] + ) + + let serializedError = error.serializeForV2Logging() + + XCTAssertEqual(serializedError.count, 2) + XCTAssertEqual("test-domain", serializedError["domain"] as? String) + XCTAssertEqual(serializedError["code"] as? Int, 1) + } + + /// Tests that casting an the error to `Error` still uses custom + /// serialization as opposed to the NSError default behavior. + func testAnalyticLoggableSerializedForLogging() { + let error: Error = CustomLoggableError() + + let serializedError = error.serializeForV2Logging() + + XCTAssertEqual(serializedError.count, 1) + XCTAssertEqual(serializedError["foo"] as? String, "value") + } + + func testStringErrorSerializeForLogging() { + let error: Error = StringError.foo + + let serializedError = error.serializeForV2Logging() + + print(serializedError) + + XCTAssertEqual(serializedError.count, 2) + XCTAssertEqual(serializedError["type"] as? String, "foo") + XCTAssertEqual( + serializedError["domain"] as? String, + "StripeCoreTests.Error_SerializeForLoggingTest.StringError" + ) + } +} diff --git a/StripeCore/StripeCoreTests/Analytics/STPAnalyticsClientTest.swift b/StripeCore/StripeCoreTests/Analytics/STPAnalyticsClientTest.swift new file mode 100644 index 00000000..2129e40c --- /dev/null +++ b/StripeCore/StripeCoreTests/Analytics/STPAnalyticsClientTest.swift @@ -0,0 +1,262 @@ +// +// STPAnalyticsClientTest.swift +// StripeCoreTests +// +// Created by Yuki Tokuhiro on 12/15/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +@testable@_spi(STP) @_spi(MobilePaymentElementAnalyticEventBeta) import StripeCore + +class STPAnalyticsClientTest: XCTestCase { + + func testIsUnitOrUITest_alwaysTrueInTest() { + XCTAssertTrue(STPAnalyticsClient.isUnitOrUITest) + } + + func testShouldRedactLiveKeyFromLog() { + let analyticsClient = STPAnalyticsClient() + + let payload = analyticsClient.commonPayload(STPAPIClient(publishableKey: "sk_live_foo")) + + XCTAssertEqual("[REDACTED_LIVE_KEY]", payload["publishable_key"] as? String) + } + + func testShouldRedactUserKeyFromLog() { + let analyticsClient = STPAnalyticsClient() + + let payload = analyticsClient.commonPayload(STPAPIClient(publishableKey: "uk_live_foo")) + + XCTAssertEqual("[REDACTED_LIVE_KEY]", payload["publishable_key"] as? String) + } + + func testShouldNotRedactLiveKeyFromLog() { + let analyticsClient = STPAnalyticsClient() + + let payload = analyticsClient.commonPayload(STPAPIClient(publishableKey: "pk_foo")) + + XCTAssertEqual("pk_foo", payload["publishable_key"] as? String) + } + + func testLogShouldRespectAPIClient() { + STPAPIClient.shared.publishableKey = "pk_shared" + let apiClient = STPAPIClient(publishableKey: "pk_not_shared") + let analyticsClient = STPAnalyticsClient() + // ...logging an arbitrary analytic and passing apiClient... + analyticsClient.log(analytic: GenericAnalytic.init(event: .addressShow, params: [:]), apiClient: apiClient) + // ...should use the passed in apiClient publishable key and not the shared apiClient + let payload = analyticsClient._testLogHistory.first! + XCTAssertEqual("pk_not_shared", payload["publishable_key"] as? String) + } + func testmcShowCustomNewPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .presentedSheet = eventName else { + XCTFail("Failed to convert eventName") + return + } + e.fulfill() + } + _testLogEvent(event: .mcShowCustomNewPM, + params: [:], observer: observer) + wait(for: [e], timeout: 1) + } + + func testmcShowCompleteNewPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .presentedSheet = eventName else { + XCTFail("Failed to convert eventName") + return + } + e.fulfill() + } + _testLogEvent(event: .mcShowCompleteNewPM, + params: [:], observer: observer) + wait(for: [e], timeout: 1) + } + func testmcShowCustomSavedPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .presentedSheet = eventName else { + XCTFail("Failed to convert eventName") + return + } + e.fulfill() + } + _testLogEvent(event: .mcShowCustomSavedPM, + params: [:], observer: observer) + wait(for: [e], timeout: 1) + } + func testmcShowCompleteSavedPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .presentedSheet = eventName else { + XCTFail("Failed to convert eventName") + return + } + e.fulfill() + } + _testLogEvent(event: .mcShowCompleteSavedPM, + params: [:], observer: observer) + wait(for: [e], timeout: 1) + } + func testPaymentSheetCarouselPaymentMethodTapped() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .selectedPaymentMethodType(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .paymentSheetCarouselPaymentMethodTapped, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + + func testPaymentSheetFormShown() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .displayedPaymentMethodForm(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .paymentSheetFormShown, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + + func testPaymentSheetFormInteracted() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .startedInteractionWithPaymentMethodForm(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .paymentSheetFormInteracted, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func testPaymentSheetFormCompleted() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .completedPaymentMethodForm(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .paymentSheetFormCompleted, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func testPaymentSheetConfirmButtonTapped() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .tappedConfirmButton(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .paymentSheetConfirmButtonTapped, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func testMcOptionSelectCustomSavedPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .selectedSavedPaymentMethod(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .mcOptionSelectCustomSavedPM, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func testMcOptionSelectCompleteSavedPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .selectedSavedPaymentMethod(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .mcOptionSelectCompleteSavedPM, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func testMcOptionRemoveCustomSavedPM() { + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .removedSavedPaymentMethod(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .mcOptionRemoveCustomSavedPM, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func testMcOptionRemoveCompleteSavedPM(){ + let e = expectation(description: "") + let observer = TestObserver { eventName in + guard case .removedSavedPaymentMethod(let data) = eventName else { + XCTFail("Failed to convert eventName") + return + } + XCTAssertEqual(data.paymentMethodType, "card") + e.fulfill() + } + _testLogEvent(event: .mcOptionRemoveCompleteSavedPM, + params: ["selected_lpm": "card"], observer: observer) + wait(for: [e], timeout: 1) + } + func _testLogEvent(event: STPAnalyticEvent, params: [String: Any], observer: TestObserver) { + let notificationCenter = NotificationCenter() + let analyticsClient = STPAnalyticsClient() + + notificationCenter.addObserver(observer, + selector: #selector(TestObserver.mobilePaymentElementNotification(notification:)), + name: .mobilePaymentElement, object: nil) + let genericAnalytic = GenericAnalytic(event: event, params: params) + analyticsClient.log(analytic: genericAnalytic, notificationCenter: notificationCenter) + } + +} + +class TestObserver { + let onNotificationCallback: (MobilePaymentElementAnalyticEvent.Name) -> Void + + init(_ callback: @escaping (MobilePaymentElementAnalyticEvent.Name) -> Void) { + onNotificationCallback = callback + } + @objc + func mobilePaymentElementNotification(notification: NSNotification) { + guard let event = notification.object as? MobilePaymentElementAnalyticEvent else { + XCTFail("Failed to convert to MobilePaymentElementAnalyticEvent") + return + } + onNotificationCallback(event.name) + } +} diff --git a/StripeCore/StripeCoreTests/Analytics/STPAnalyticsEventTranslatorTest.swift b/StripeCore/StripeCoreTests/Analytics/STPAnalyticsEventTranslatorTest.swift new file mode 100644 index 00000000..6505d848 --- /dev/null +++ b/StripeCore/StripeCoreTests/Analytics/STPAnalyticsEventTranslatorTest.swift @@ -0,0 +1,63 @@ +// +// STPAnalyticsEventTranslatorTest.swift +// StripeCoreTests +// + +import Foundation +import XCTest + +@testable @_spi(STP) @_spi(MobilePaymentElementAnalyticEventBeta) import StripeCore + +class STPAnalyticsTranslatedEventTest: XCTestCase { + let payloadWithLPM: [String: Any] = ["selected_lpm": "card"] + let payloadWithoutLPM: [String: Any] = ["test_data": "data"] + + func testSheetPresentation() { + _testTranslationMapping(event: .mcShowCustomNewPM, payload: payloadWithoutLPM, translatedEventName: .presentedSheet) + _testTranslationMapping(event: .mcShowCompleteNewPM, payload: payloadWithoutLPM, translatedEventName: .presentedSheet) + _testTranslationMapping(event: .mcShowCustomSavedPM, payload: payloadWithoutLPM, translatedEventName: .presentedSheet) + _testTranslationMapping(event: .mcShowCompleteSavedPM, payload: payloadWithoutLPM, translatedEventName: .presentedSheet) + } + func testTapPaymentMethodType() { + _testTranslationMapping(event: .paymentSheetCarouselPaymentMethodTapped, payload: payloadWithLPM, + translatedEventName: .selectedPaymentMethodType(.init(paymentMethodType: "card"))) + } + func testFormInteractions() { + _testTranslationMapping(event: .paymentSheetFormShown, payload: payloadWithLPM, + translatedEventName: .displayedPaymentMethodForm(.init(paymentMethodType: "card"))) + _testTranslationMapping(event: .paymentSheetFormInteracted, payload: payloadWithLPM, + translatedEventName: .startedInteractionWithPaymentMethodForm(.init(paymentMethodType: "card"))) + _testTranslationMapping(event: .paymentSheetFormCompleted, payload: payloadWithLPM, + translatedEventName: .completedPaymentMethodForm(.init(paymentMethodType: "card"))) + _testTranslationMapping(event: .paymentSheetConfirmButtonTapped, payload: payloadWithLPM, + translatedEventName: .tappedConfirmButton(.init(paymentMethodType: "card"))) + } + func testSavedPaymentMethods() { + _testTranslationMapping(event: .mcOptionSelectCustomSavedPM, payload: payloadWithLPM, + translatedEventName: .selectedSavedPaymentMethod(.init(paymentMethodType: "card"))) + _testTranslationMapping(event: .mcOptionSelectCompleteSavedPM, payload: payloadWithLPM, + translatedEventName: .selectedSavedPaymentMethod(.init(paymentMethodType: "card"))) + _testTranslationMapping(event: .mcOptionRemoveCustomSavedPM, payload: payloadWithLPM, + translatedEventName: .removedSavedPaymentMethod(.init(paymentMethodType: "card"))) + _testTranslationMapping(event: .mcOptionRemoveCompleteSavedPM, payload: payloadWithLPM, + translatedEventName: .removedSavedPaymentMethod(.init(paymentMethodType: "card"))) + } + func testAnalyticNotTranslated() { + let translator = STPAnalyticsEventTranslator() + + let result = translator.translate(.paymentSheetLoadStarted, payload: [:]) + + XCTAssertNil(result) + } + func _testTranslationMapping(event: STPAnalyticEvent, payload: [String: Any], translatedEventName: MobilePaymentElementAnalyticEvent.Name) { + let translator = STPAnalyticsEventTranslator() + + guard let result = translator.translate(event, payload: payload) else { + XCTFail("There is no mapping for event: \"\(event)\". See: STPAnalyticsEventTranslator") + return + } + + XCTAssertEqual(result.notificationName, Notification.Name.mobilePaymentElement) + XCTAssertEqual(result.event.name, translatedEventName) + } +} diff --git a/StripeCore/StripeCoreTests/Categories/Dictionary+StripeTests.swift b/StripeCore/StripeCoreTests/Categories/Dictionary+StripeTests.swift new file mode 100644 index 00000000..a9e18d37 --- /dev/null +++ b/StripeCore/StripeCoreTests/Categories/Dictionary+StripeTests.swift @@ -0,0 +1,37 @@ +// +// Dictionary+StripeTests.swift +// StripeCoreTests +// +// Created by Mel Ludowise on 6/16/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP)@testable import StripeCore +import XCTest + +final class Dictionary_StripeTests: XCTestCase { + func testJsonEncodeNestedDicts() { + let input: [String: Any] = [ + "string": "some_string", + "int": 0, + "float": Float(0.1), + "nested_string_dict": [ + "string": "some_other_string", + "int": 1, + "float": Float(0.5), + ], + ] + + let output = input.jsonEncodeNestedDicts(options: .sortedKeys) + + XCTAssertEqual(output.count, 4) + XCTAssertEqual(output["string"] as? String, "some_string") + XCTAssertEqual(output["int"] as? Int, 0) + XCTAssertEqual(output["float"] as? Float, 0.1) + XCTAssertEqual( + output["nested_string_dict"] as? String, + "{\"float\":0.5,\"int\":1,\"string\":\"some_other_string\"}" + ) + } +} diff --git a/StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift b/StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift new file mode 100644 index 00000000..8e8d855d --- /dev/null +++ b/StripeCore/StripeCoreTests/Categories/Locale+StripeTests.swift @@ -0,0 +1,74 @@ +// +// Locale+StripeTests.swift +// StripeCore +// +// Created by Mel Ludowise on 10/11/24. +// + +@_spi(STP) import StripeCore +import XCTest + +class Locale_StripeTests: XCTestCase { + func testLanguageTag() { + // Language=English, region not specified + XCTAssertEqual(Locale(identifier: "en").toLanguageTag(), "en") + XCTAssertEqual(Locale(identifier: "en_GB").toLanguageTag(), "en-GB") + + // Language=English, region=US, calendar=Japanese + XCTAssertEqual(Locale(identifier: "en_US@calendar=japanese").toLanguageTag(), "en-US") + + // Chinese languages, region not specified + XCTAssertEqual(Locale(identifier: "zh").toLanguageTag(), "zh") + XCTAssertEqual(Locale(identifier: "zh-Hans").toLanguageTag(), "zh-Hans") + XCTAssertEqual(Locale(identifier: "zh-Hant").toLanguageTag(), "zh-Hant") + + // Language=Simplified Chinese, region=China mainland + XCTAssertEqual(Locale(identifier: "zh_CN").toLanguageTag(), "zh-Hans") + XCTAssertEqual(Locale(identifier: "zh-Hans_CN").toLanguageTag(), "zh-Hans") + + // Language=Simplified Chinese, region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh-Hans_TW").toLanguageTag(), "zh-Hans-TW") + + // Language=Simplified Chinese, region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh-Hans_HK").toLanguageTag(), "zh-Hans-HK") + + // Language=Traditional Chinese, region=China Mainland + XCTAssertEqual(Locale(identifier: "zh-Hant_CN").toLanguageTag(), "zh-Hant-CN") + + // Language=Traditional Chinese, region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh_TW").toLanguageTag(), "zh-Hant") + XCTAssertEqual(Locale(identifier: "zh-Hant_TW").toLanguageTag(), "zh-Hant") + + // Language=Traditional Chinese, region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh_HK").toLanguageTag(), "zh-Hant-HK") + XCTAssertEqual(Locale(identifier: "zh-Hant_HK").toLanguageTag(), "zh-Hant-HK") + } + + /// On iOS 16+, the device region may be different from the language region + /// The `@rg={region-code}zzzz` indicates the device region when it's different from the language region + func testLanguageTag_languageRegionDifferentFromDevice() { + // Language=English (UK), region=United States + XCTAssertEqual(Locale(identifier: "en_GB@rg=uszzzz").toLanguageTag(), "en-GB") + + // Language=Portuguese (Brazil), region=Portugal + XCTAssertEqual(Locale(identifier: "pt_BR@rg=ptzzzz").toLanguageTag(), "pt-BR") + + // Language=Simplified Chinese, region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh-Hans@rg=hkzzzz").toLanguageTag(), "zh-Hans") + + // Language=Simplified Chinese, region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh-Hans@rg=twzzzz").toLanguageTag(), "zh-Hans") + + // Language=Traditional Chinese (Taiwan), region=China mainland + XCTAssertEqual(Locale(identifier: "zh-Hant_TW@rg=cnzzzz").toLanguageTag(), "zh-Hant") + + // Language=Traditional Chinese (Taiwan), region=Hong Kong (China) + XCTAssertEqual(Locale(identifier: "zh-Hant_TW@rg=hkzzzz").toLanguageTag(), "zh-Hant") + + // Language=Traditional Chinese (Hong Kong), region=Taiwan (China) + XCTAssertEqual(Locale(identifier: "zh-Hant_HK@rg=twzzzz").toLanguageTag(), "zh-Hant-HK") + + // Language=Traditional Chinese (Hong Kong), region=China mainland + XCTAssertEqual(Locale(identifier: "zh-Hant_HK@rg=cnzzzz").toLanguageTag(), "zh-Hant-HK") + } +} diff --git a/StripeCore/StripeCoreTests/Categories/NSArray+StripeCoreTest.swift b/StripeCore/StripeCoreTests/Categories/NSArray+StripeCoreTest.swift new file mode 100644 index 00000000..3523541d --- /dev/null +++ b/StripeCore/StripeCoreTests/Categories/NSArray+StripeCoreTest.swift @@ -0,0 +1,27 @@ +// +// NSArray+StripeCoreTest.swift +// StripeCoreTests +// +// Created by Jack Flintermann on 1/19/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import XCTest + +class Array_StripeCoreTest: XCTestCase { + func test_boundSafeObjectAtIndex_emptyArray() { + let test: [Any] = [] + XCTAssertNil(test.stp_boundSafeObject(at: 5)) + } + + func test_boundSafeObjectAtIndex_tooHighIndex() { + let test = [NSNumber(value: 1), NSNumber(value: 2), NSNumber(value: 3)] + XCTAssertNil(test.stp_boundSafeObject(at: 5)) + } + + func test_boundSafeObjectAtIndex_withinBoundsIndex() { + let test = [NSNumber(value: 1), NSNumber(value: 2), NSNumber(value: 3)] + XCTAssertEqual(test.stp_boundSafeObject(at: 1), NSNumber(value: 2)) + } +} diff --git a/StripeCore/StripeCoreTests/Categories/NSMutableURLRequest+StripeTest.swift b/StripeCore/StripeCoreTests/Categories/NSMutableURLRequest+StripeTest.swift new file mode 100644 index 00000000..f0211105 --- /dev/null +++ b/StripeCore/StripeCoreTests/Categories/NSMutableURLRequest+StripeTest.swift @@ -0,0 +1,68 @@ +// +// NSMutableURLRequest+StripeTest.swift +// StripeCoreTests +// +// Created by Ben Guo on 4/22/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import XCTest + +class NSMutableURLRequest_StripeTest: XCTestCase { + func testAddParametersToURL_noQuery() { + var request: URLRequest? + if let url = URL(string: "https://example.com") { + request = URLRequest(url: url) + } + request?.stp_addParameters(toURL: [ + "foo": "bar", + ]) + + XCTAssertEqual(request?.url?.absoluteString, "https://example.com?foo=bar") + } + + func testAddParametersToURL_hasQuery() { + var request: URLRequest? + if let url = URL(string: "https://example.com?a=b") { + request = URLRequest(url: url) + } + request?.stp_addParameters(toURL: [ + "foo": "bar", + ]) + + XCTAssertEqual(request?.url?.absoluteString, "https://example.com?a=b&foo=bar") + } + + func testAddParametersToURL_encodesSpecialCharacters() { + let baseURL = URL(string: "https://example.com")! + struct TestCase { + let parameters: [String: Any] + let expectedURL: String + } + // These test cases expected values were generated using SDK v23.22.0 pre-iOS 17. + // They don't comply with RFC 3986, but they are correct/expected in the sense that they've worked in practice when used w/ the Stripe API. + let testcases: [TestCase] = [ + .init(parameters: ["~!@W#$%^&*()-=+[]{}/?\\": "~!@W#$%^&*()-=+[]{}/?\\"], expectedURL: "https://example.com?~%21%40W%23%24%25%5E%26%2A%28%29-%3D%2B%5B%5D%7B%7D/?%5C=~%21%40W%23%24%25%5E%26%2A%28%29-%3D%2B%5B%5D%7B%7D/?%5C"), + .init(parameters: ["name": "John Doe"], expectedURL: "https://example.com?name=John%20Doe"), + .init(parameters: ["bool_flag": true], expectedURL: "https://example.com?bool_flag=true"), + .init(parameters: ["return_url": "https://foo.com?src=bar"], expectedURL: "https://example.com?return_url=https%3A//foo.com?src%3Dbar"), + .init(parameters: [ + "mpe_config": [ + "nested_list": ["A", "B", "C"], + "appearance_config": [ + "toggle": true, + ], + "name": "John Doe", + "return_url": "https://foo.com?src=bar", + ], + ], expectedURL: "https://example.com?mpe_config%5Bappearance_config%5D%5Btoggle%5D=true&mpe_config%5Bname%5D=John%20Doe&mpe_config%5Bnested_list%5D%5B0%5D=A&mpe_config%5Bnested_list%5D%5B1%5D=B&mpe_config%5Bnested_list%5D%5B2%5D=C&mpe_config%5Breturn_url%5D=https%3A//foo.com?src%3Dbar"), + .init(parameters: ["locale": "en-US"], expectedURL: "https://example.com?locale=en-US"), + ] + for testcase in testcases { + var request = URLRequest(url: baseURL) + request.stp_addParameters(toURL: testcase.parameters) + XCTAssertEqual(request.url?.absoluteString, testcase.expectedURL) + } + } +} diff --git a/StripeCore/StripeCoreTests/Categories/UIImage+StripeCoreTests.swift b/StripeCore/StripeCoreTests/Categories/UIImage+StripeCoreTests.swift new file mode 100644 index 00000000..86d31bbf --- /dev/null +++ b/StripeCore/StripeCoreTests/Categories/UIImage+StripeCoreTests.swift @@ -0,0 +1,85 @@ +// +// UIImage+StripeCoreTests.swift +// StripeCoreTests +// +// Created by Brian Dorfman on 4/25/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import XCTest + +// swift-format-ignore +@_spi(STP) @testable import StripeCore + +class UIImage_StripeTests: XCTestCase { + static let testJpegImageResizingKBiggerSize = 50000 + static let testJpegImageResizingKSmallerSize = 6000 + // Don't make this too low or test becomes somewhat meaningless, as jpegs can only get so small + static let testJpegImageResizingKMuchSmallerSize = 5000 + + func testJpegImageResizing() { + // Strategy is to grab an image from our bundle and pass to the resizer + // with maximums both larger and smaller than it already is + // then make sure we get what we expect + + guard + let testImage = UIImage( + named: "test_image", + in: Bundle(for: UIImage_StripeTests.self), + compatibleWith: nil + ) + else { + return XCTFail("Could not load test image") + } + + // Verify that before being passed to resizer it is within the + // correct size range for our tests to be meaningful + var data = testImage.jpegData(compressionQuality: 0.5)! + + XCTAssertLessThan(data.count, UIImage_StripeTests.testJpegImageResizingKBiggerSize) + XCTAssertGreaterThan(data.count, UIImage_StripeTests.testJpegImageResizingKSmallerSize) + XCTAssertGreaterThan(data.count, UIImage_StripeTests.testJpegImageResizingKMuchSmallerSize) + + // This is the size the data would be without scaling it less than maxBytes + let baselineSize = data.count + + // Test passing in a maxBytes larger than original image + data = + testImage.jpegDataAndDimensions( + maxBytes: UIImage_StripeTests.testJpegImageResizingKBiggerSize + ).imageData + var resultingImage = UIImage(data: data, scale: testImage.scale)! + XCTAssertLessThan(data.count, UIImage_StripeTests.testJpegImageResizingKBiggerSize) + // Image shouldn't have been shrunk at all + XCTAssertEqual(resultingImage.size, testImage.size) + + // Test passing in a maxBytes a bit smaller than the original image + data = + testImage.jpegDataAndDimensions( + maxBytes: UIImage_StripeTests.testJpegImageResizingKSmallerSize + ).imageData + resultingImage = UIImage(data: data, scale: testImage.scale)! + XCTAssertNotNil(data) + XCTAssertLessThan(data.count, UIImage_StripeTests.testJpegImageResizingKSmallerSize) + XCTAssertLessThan(resultingImage.size.width, testImage.size.width) + XCTAssertLessThan(resultingImage.size.height, testImage.size.height) + + // Test passing in a maxBytes a lot smaller than the original image + data = + testImage.jpegDataAndDimensions( + maxBytes: UIImage_StripeTests.testJpegImageResizingKMuchSmallerSize + ).imageData + resultingImage = UIImage(data: data, scale: testImage.scale)! + XCTAssertNotNil(data) + XCTAssertLessThan(data.count, UIImage_StripeTests.testJpegImageResizingKMuchSmallerSize) + XCTAssertLessThan(resultingImage.size.width, testImage.size.width) + XCTAssertLessThan(resultingImage.size.height, testImage.size.height) + + // Test passing in nil maxBytes + data = testImage.jpegDataAndDimensions(maxBytes: nil).imageData + resultingImage = UIImage(data: data, scale: testImage.scale)! + XCTAssertNotNil(data) + XCTAssertEqual(data.count, baselineSize) + XCTAssertEqual(resultingImage.size, testImage.size) + } +} diff --git a/StripeCore/StripeCoreTests/External/TestJSONEncoder.swift b/StripeCore/StripeCoreTests/External/TestJSONEncoder.swift new file mode 100644 index 00000000..74d13345 --- /dev/null +++ b/StripeCore/StripeCoreTests/External/TestJSONEncoder.swift @@ -0,0 +1,1756 @@ +// Modifications copyright (c) 2022 Stripe, Inc. +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// + +import Foundation +import XCTest + +@testable@_spi(STP) import StripeCore + +struct TopLevelObjectWrapper: Codable, Equatable { + var value: T + + static func == (lhs: TopLevelObjectWrapper, rhs: TopLevelObjectWrapper) -> Bool { + return lhs.value == rhs.value + } + + init( + _ value: T + ) { + self.value = value + } +} + +class TestJSONEncoder: XCTestCase { + + // MARK: - Encoding Top-Level fragments + // JSON fragments are only supported by JSONDecoder in iOS 13 or later + + func test_encodingTopLevelFragments() { + + func _testFragment(value: T, fragment: String) { + let data: Data + let payload: String + + do { + data = try StripeJSONEncoder().encode(value) + payload = try XCTUnwrap(String.init(decoding: data, as: UTF8.self)) + XCTAssertEqual(fragment, payload) + } catch { + XCTFail("Failed to encode \(T.self) to JSON: \(error)") + return + } + do { + let decodedValue = try StripeJSONDecoder().decode(T.self, from: data) + XCTAssertEqual(value, decodedValue) + } catch { + XCTFail("Failed to decode \(payload) to \(T.self): \(error)") + } + } + _testFragment(value: 2, fragment: "2") + _testFragment(value: false, fragment: "false") + _testFragment(value: true, fragment: "true") + _testFragment(value: Float(1), fragment: "1") + _testFragment(value: Double(2), fragment: "2") + _testFragment( + value: Decimal(Double(Float.leastNormalMagnitude)), + fragment: "0.000000000000000000000000000000000000011754943508222875648" + ) + _testFragment(value: "test", fragment: "\"test\"") + let v: Int? = nil + _testFragment(value: v, fragment: "null") + } + + // MARK: - Encoding Top-Level Empty Types + func test_encodingTopLevelEmptyStruct() { + let empty = EmptyStruct() + _testRoundTrip(of: empty, expectedJSON: _jsonEmptyDictionary) + } + + func test_encodingTopLevelEmptyClass() { + let empty = EmptyClass() + _testRoundTrip(of: empty, expectedJSON: _jsonEmptyDictionary) + } + + // MARK: - Encoding Top-Level Single-Value Types + // JSON fragments are only supported by JSONDecoder in iOS 13 or later + + func test_encodingTopLevelSingleValueEnum() { + _testRoundTrip(of: Switch.off) + _testRoundTrip(of: Switch.on) + + _testRoundTrip(of: TopLevelArrayWrapper(Switch.off)) + _testRoundTrip(of: TopLevelArrayWrapper(Switch.on)) + } + + // JSON fragments are only supported by JSONDecoder in iOS 13 or later + + func test_encodingTopLevelSingleValueStruct() { + _testRoundTrip(of: Timestamp(3_141_592_653)) + _testRoundTrip(of: TopLevelArrayWrapper(Timestamp(3_141_592_653))) + } + + // JSON fragments are only supported by JSONDecoder in iOS 13 or later + + func test_encodingTopLevelSingleValueClass() { + _testRoundTrip(of: Counter()) + _testRoundTrip(of: TopLevelArrayWrapper(Counter())) + } + + // MARK: - Encoding Top-Level Structured Types + func test_encodingTopLevelStructuredStruct() { + // Address is a struct type with multiple fields. + let address = Address.testValue + _testRoundTrip(of: address) + } + + func test_encodingTopLevelStructuredClass() { + // Person is a class with multiple fields. + let expectedJSON = "{\"name\":\"Johnny Appleseed\",\"email\":\"appleseed@apple.com\"}".data( + using: .utf8 + )! + let person = Person.testValue + _testRoundTrip(of: person, expectedJSON: expectedJSON) + } + + func test_encodingTopLevelStructuredSingleStruct() { + // Numbers is a struct which encodes as an array through a single value container. + let numbers = Numbers.testValue + _testRoundTrip(of: numbers) + } + + func test_encodingTopLevelStructuredSingleClass() { + // Mapping is a class which encodes as a dictionary through a single value container. + let mapping = Mapping.testValue + _testRoundTrip(of: mapping) + } + + func test_encodingTopLevelDeepStructuredType() { + // Company is a type with fields which are Codable themselves. + let company = Company.testValue + _testRoundTrip(of: company) + } + + // MARK: - Output Formatting Tests + func test_encodingOutputFormattingDefault() { + let expectedJSON = "{\"name\":\"Johnny Appleseed\",\"email\":\"appleseed@apple.com\"}".data( + using: .utf8 + )! + let person = Person.testValue + _testRoundTrip(of: person, expectedJSON: expectedJSON) + } + + func test_encodingOutputFormattingPrettyPrinted() throws { + let expectedJSON = + "{\n \"name\" : \"Johnny Appleseed\",\n \"email\" : \"appleseed@apple.com\"\n}".data( + using: .utf8 + )! + let person = Person.testValue + _testRoundTrip(of: person, expectedJSON: expectedJSON, outputFormatting: [.prettyPrinted]) + + let encoder = StripeJSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + + let emptyArray: [Int] = [] + let arrayOutput = try encoder.encode(emptyArray) + XCTAssertEqual(String.init(decoding: arrayOutput, as: UTF8.self), "[\n\n]") + + let emptyDictionary: [String: Int] = [:] + let dictionaryOutput = try encoder.encode(emptyDictionary) + XCTAssertEqual(String.init(decoding: dictionaryOutput, as: UTF8.self), "{\n\n}") + + struct DataType: Encodable { + let array = [1, 2, 3] + let dictionary: [String: Int] = [:] + let emptyArray: [Int] = [] + let secondArray: [Int] = [4, 5, 6] + let secondDictionary: [String: Int] = ["one": 1, "two": 2, "three": 3] + let singleElement: [Int] = [1] + let subArray: [String: [Int]] = ["array": []] + let subDictionary: [String: [String: Int]] = ["dictionary": [:]] + } + + let dataOutput = try encoder.encode([DataType(), DataType()]) + XCTAssertEqual( + String.init(decoding: dataOutput, as: UTF8.self), + """ + [ + { + "array" : [ + 1, + 2, + 3 + ], + "dictionary" : { + + }, + "empty_array" : [ + + ], + "second_array" : [ + 4, + 5, + 6 + ], + "second_dictionary" : { + "one" : 1, + "three" : 3, + "two" : 2 + }, + "single_element" : [ + 1 + ], + "sub_array" : { + "array" : [ + + ] + }, + "sub_dictionary" : { + "dictionary" : { + + } + } + }, + { + "array" : [ + 1, + 2, + 3 + ], + "dictionary" : { + + }, + "empty_array" : [ + + ], + "second_array" : [ + 4, + 5, + 6 + ], + "second_dictionary" : { + "one" : 1, + "three" : 3, + "two" : 2 + }, + "single_element" : [ + 1 + ], + "sub_array" : { + "array" : [ + + ] + }, + "sub_dictionary" : { + "dictionary" : { + + } + } + } + ] + """ + ) + } + + func test_encodingOutputFormattingSortedKeys() { + let expectedJSON = "{\"email\":\"appleseed@apple.com\",\"name\":\"Johnny Appleseed\"}".data( + using: .utf8 + )! + let person = Person.testValue + #if os(macOS) || DARWIN_COMPATIBILITY_TESTS + if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) { + _testRoundTrip( + of: person, + expectedJSON: expectedJSON, + outputFormatting: [.sortedKeys] + ) + } + #else + _testRoundTrip(of: person, expectedJSON: expectedJSON, outputFormatting: [.sortedKeys]) + #endif + } + + func test_encodingOutputFormattingPrettyPrintedSortedKeys() { + let expectedJSON = + "{\n \"email\" : \"appleseed@apple.com\",\n \"name\" : \"Johnny Appleseed\"\n}".data( + using: .utf8 + )! + let person = Person.testValue + #if os(macOS) || DARWIN_COMPATIBILITY_TESTS + if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) { + _testRoundTrip( + of: person, + expectedJSON: expectedJSON, + outputFormatting: [.prettyPrinted, .sortedKeys] + ) + } + #else + _testRoundTrip( + of: person, + expectedJSON: expectedJSON, + outputFormatting: [.prettyPrinted, .sortedKeys] + ) + #endif + } + + // MARK: - Date Strategy Tests + func test_encodingDate() { + // We intentionally drop precision to seconds, so round the date to the nearest second. + let date = Date(timeIntervalSince1970: Date().timeIntervalSince1970.rounded()) + // We can't encode a top-level Date, so it'll be wrapped in an array. + _testRoundTrip(of: TopLevelArrayWrapper(date)) + } + + func test_encodingDateSecondsSince1970() { + // Cannot encode an arbitrary number of seconds since we've lost precision since 1970. + let seconds = 1000.0 + let expectedJSON = "[1000]".data(using: .utf8)! + + // We can't encode a top-level Date, so it'll be wrapped in an array. + _testRoundTrip( + of: TopLevelArrayWrapper(Date(timeIntervalSince1970: seconds)), + expectedJSON: expectedJSON, + dateEncodingStrategy: .secondsSince1970, + dateDecodingStrategy: .secondsSince1970 + ) + } + + // MARK: - Data Strategy Tests + func test_encodingBase64Data() { + let data = Data([0xDE, 0xAD, 0xBE, 0xEF]) + + // We can't encode a top-level Data, so it'll be wrapped in an array. + let expectedJSON = "[\"3q2+7w==\"]".data(using: .utf8)! + _testRoundTrip(of: TopLevelArrayWrapper(data), expectedJSON: expectedJSON) + } + + // MARK: - Non-Conforming Floating Point Strategy Tests + func test_encodingNonConformingFloatStrings() { + let encodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .convertToString( + positiveInfinity: "Inf", + negativeInfinity: "-Inf", + nan: "nan" + ) + let decodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .convertFromString( + positiveInfinity: "Inf", + negativeInfinity: "-Inf", + nan: "nan" + ) + + _testRoundTrip( + of: TopLevelArrayWrapper(Float.infinity), + expectedJSON: "[\"Inf\"]".data(using: .utf8)!, + nonConformingFloatEncodingStrategy: encodingStrategy, + nonConformingFloatDecodingStrategy: decodingStrategy + ) + _testRoundTrip( + of: TopLevelArrayWrapper(-Float.infinity), + expectedJSON: "[\"-Inf\"]".data(using: .utf8)!, + nonConformingFloatEncodingStrategy: encodingStrategy, + nonConformingFloatDecodingStrategy: decodingStrategy + ) + + // Since Float.nan != Float.nan, we have to use a placeholder that'll encode NaN but actually round-trip. + _testRoundTrip( + of: TopLevelArrayWrapper(FloatNaNPlaceholder()), + expectedJSON: "[\"nan\"]".data(using: .utf8)!, + nonConformingFloatEncodingStrategy: encodingStrategy, + nonConformingFloatDecodingStrategy: decodingStrategy + ) + + _testRoundTrip( + of: TopLevelArrayWrapper(Double.infinity), + expectedJSON: "[\"Inf\"]".data(using: .utf8)!, + nonConformingFloatEncodingStrategy: encodingStrategy, + nonConformingFloatDecodingStrategy: decodingStrategy + ) + _testRoundTrip( + of: TopLevelArrayWrapper(-Double.infinity), + expectedJSON: "[\"-Inf\"]".data(using: .utf8)!, + nonConformingFloatEncodingStrategy: encodingStrategy, + nonConformingFloatDecodingStrategy: decodingStrategy + ) + + // Since Double.nan != Double.nan, we have to use a placeholder that'll encode NaN but actually round-trip. + _testRoundTrip( + of: TopLevelArrayWrapper(DoubleNaNPlaceholder()), + expectedJSON: "[\"nan\"]".data(using: .utf8)!, + nonConformingFloatEncodingStrategy: encodingStrategy, + nonConformingFloatDecodingStrategy: decodingStrategy + ) + } + + // MARK: - Encoder Features + // Nested containers are not supported, see StripeJSONEncoder for details. + // func test_nestedContainerCodingPaths() { + // let encoder = StripeJSONEncoder() + // do { + // let _ = try encoder.encode(NestedContainersTestType()) + // } catch { + // XCTFail("Caught error during encoding nested container types: \(error)") + // } + // } + + // Superencoding isn't supported, see StripeJSONEncoder for details. + // func test_superEncoderCodingPaths() { + // let encoder = StripeJSONEncoder() + // do { + // let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) + // } catch { + // XCTFail("Caught error during encoding nested container types: \(error)") + // } + // } + + // MARK: - Test encoding and decoding of built-in Codable types + func test_codingOfBool() { + test_codingOf(value: Bool(true), toAndFrom: "true") + test_codingOf(value: Bool(false), toAndFrom: "false") + + // Check that a Bool false or true isn't converted to 0 or 1 + struct Foo: Decodable { + var intValue: Int? + var int8Value: Int8? + var int16Value: Int16? + var int32Value: Int32? + var int64Value: Int64? + var uintValue: UInt? + var uint8Value: UInt8? + var uint16Value: UInt16? + var uint32Value: UInt32? + var uint64Value: UInt64? + var floatValue: Float? + var doubleValue: Double? + var decimalValue: Decimal? + let boolValue: Bool + } + + func testValue(_ valueName: String) { + do { + let jsonData = "{ \"\(valueName)\": false }".data(using: .utf8)! + _ = try StripeJSONDecoder().decode(Foo.self, from: jsonData) + XCTFail("Decoded 'false' as non Bool for \(valueName)") + } catch {} + do { + let jsonData = "{ \"\(valueName)\": true }".data(using: .utf8)! + _ = try StripeJSONDecoder().decode(Foo.self, from: jsonData) + XCTFail("Decoded 'true' as non Bool for \(valueName)") + } catch {} + } + + testValue("intValue") + testValue("int8Value") + testValue("int16Value") + testValue("int32Value") + testValue("int64Value") + testValue("uintValue") + testValue("uint8Value") + testValue("uint16Value") + testValue("uint32Value") + testValue("uint64Value") + testValue("floatValue") + testValue("doubleValue") + testValue("decimalValue") + let falseJsonData = "{ \"bool_value\": false }".data(using: .utf8)! + if let falseFoo = try? StripeJSONDecoder().decode(Foo.self, from: falseJsonData) { + XCTAssertFalse(falseFoo.boolValue) + } else { + XCTFail("Could not decode 'false' as a Bool") + } + + let trueJsonData = "{ \"bool_value\": true }".data(using: .utf8)! + if let trueFoo = try? StripeJSONDecoder().decode(Foo.self, from: trueJsonData) { + XCTAssertTrue(trueFoo.boolValue) + } else { + XCTFail("Could not decode 'true' as a Bool") + } + } + + func test_codingOfNil() { + let x: Int? = nil + test_codingOf(value: x, toAndFrom: "null") + } + + func test_codingOfInt8() { + test_codingOf(value: Int8(-42), toAndFrom: "-42") + } + + func test_codingOfUInt8() { + test_codingOf(value: UInt8(42), toAndFrom: "42") + } + + func test_codingOfInt16() { + test_codingOf(value: Int16(-30042), toAndFrom: "-30042") + } + + func test_codingOfUInt16() { + test_codingOf(value: UInt16(30042), toAndFrom: "30042") + } + + func test_codingOfInt32() { + test_codingOf(value: Int32(-2_000_000_042), toAndFrom: "-2000000042") + } + + func test_codingOfUInt32() { + test_codingOf(value: UInt32(2_000_000_042), toAndFrom: "2000000042") + } + + func test_codingOfInt64() { + #if !arch(arm) + test_codingOf( + value: Int64(-9_000_000_000_000_000_042), + toAndFrom: "-9000000000000000042" + ) + #endif + } + + func test_codingOfUInt64() { + #if !arch(arm) + test_codingOf( + value: UInt64(9_000_000_000_000_000_042), + toAndFrom: "9000000000000000042" + ) + #endif + } + + func test_codingOfInt() { + let intSize = MemoryLayout.size + switch intSize { + case 4: // 32-bit + test_codingOf(value: Int(-2_000_000_042), toAndFrom: "-2000000042") + case 8: // 64-bit + #if arch(arm) + break + #else + test_codingOf( + value: Int(-9_000_000_000_000_000_042), + toAndFrom: "-9000000000000000042" + ) + #endif + default: + XCTFail("Unexpected UInt size: \(intSize)") + } + } + + func test_codingOfUInt() { + let uintSize = MemoryLayout.size + switch uintSize { + case 4: // 32-bit + test_codingOf(value: UInt(2_000_000_042), toAndFrom: "2000000042") + case 8: // 64-bit + #if arch(arm) + break + #else + test_codingOf( + value: UInt(9_000_000_000_000_000_042), + toAndFrom: "9000000000000000042" + ) + #endif + default: + XCTFail("Unexpected UInt size: \(uintSize)") + } + } + + func test_codingOfFloat() { + test_codingOf(value: Float(1.5), toAndFrom: "1.5") + + // Check value too large fails to decode. + XCTAssertThrowsError( + try StripeJSONDecoder().decode(Float.self, from: "1e100".data(using: .utf8)!) + ) + } + + func test_codingOfDouble() { + test_codingOf(value: Double(1.5), toAndFrom: "1.5") + + // Check value too large fails to decode. + XCTAssertThrowsError( + try StripeJSONDecoder().decode(Double.self, from: "100e323".data(using: .utf8)!) + ) + } + + func test_codingOfDecimal() { + test_codingOf(value: Decimal.pi, toAndFrom: "3.14159265358979323846264338327950288419") + + // Check value too large fails to decode. + // TODO(davide): This doesn't pass on Darwin Foundation, and I'm not sure if it's necessary here + // XCTAssertThrowsError(try JSONDecoder().decode(Decimal.self, from: "100e200".data(using: .utf8)!)) + } + + func test_codingOfString() { + test_codingOf(value: "Hello, world!", toAndFrom: "\"Hello, world!\"") + } + + func test_codingOfURL() { + test_codingOf(value: URL(string: "https://swift.org")!, toAndFrom: "\"https://swift.org\"") + } + + // UInt and Int + func test_codingOfUIntMinMax() { + + struct MyValue: Encodable { + let int64Min = Int64.min + let int64Max = Int64.max + let uint64Min = UInt64.min + let uint64Max = UInt64.max + } + + func compareJSON(_ s1: String, _ s2: String) { + let ss1 = s1.trimmingCharacters(in: CharacterSet(charactersIn: "{}")).split( + separator: Character(",") + ).sorted() + let ss2 = s2.trimmingCharacters(in: CharacterSet(charactersIn: "{}")).split( + separator: Character(",") + ).sorted() + XCTAssertEqual(ss1, ss2) + } + + do { + let encoder = StripeJSONEncoder() + let myValue = MyValue() + let result = try encoder.encode(myValue) + let r = String(data: result, encoding: .utf8) ?? "nil" + compareJSON( + r, + "{\"uint64_min\":0,\"uint64_max\":18446744073709551615,\"int64_min\":-9223372036854775808,\"int64_max\":9223372036854775807}" + ) + } catch { + XCTFail(String(describing: error)) + } + } + + func test_numericLimits() { + struct DataStruct: Codable { + let int8Value: Int8? + let uint8Value: UInt8? + let int16Value: Int16? + let uint16Value: UInt16? + let int32Value: Int32? + let uint32Value: UInt32? + let int64Value: Int64? + let intValue: Int? + let uintValue: UInt? + let uint64Value: UInt64? + let floatValue: Float? + let doubleValue: Double? + let decimalValue: Decimal? + } + + func decode(_ type: String, _ value: String) throws { + var key = type.lowercased() + key.append("_value") + _ = try StripeJSONDecoder().decode( + DataStruct.self, + from: "{ \"\(key)\": \(value) }".data(using: .utf8)! + ) + } + + func testGoodValue(_ type: String, _ value: String) { + do { + try decode(type, value) + } catch { + XCTFail("Unexpected error: \(error) for parsing \(value) to \(type)") + } + } + + func testErrorThrown(_ type: String, _ value: String, errorMessage: String) { + do { + try decode(type, value) + XCTFail("Decode of \(value) to \(type) should not succeed") + } catch DecodingError.dataCorrupted(let context) { + XCTAssertEqual(context.debugDescription, errorMessage) + } catch { + XCTAssertEqual(String(describing: error), errorMessage) + } + } + + var goodValues = [ + ("Int8", "0"), ("Int8", "1"), ("Int8", "-1"), ("Int8", "-128"), ("Int8", "127"), + ("UInt8", "0"), ("UInt8", "1"), ("UInt8", "255"), ("UInt8", "-0"), + + ("Int16", "0"), ("Int16", "1"), ("Int16", "-1"), ("Int16", "-32768"), + ("Int16", "32767"), + ("UInt16", "0"), ("UInt16", "1"), ("UInt16", "65535"), ("UInt16", "34.0"), + + ("Int32", "0"), ("Int32", "1"), ("Int32", "-1"), ("Int32", "-2147483648"), + ("Int32", "2147483647"), + ("UInt32", "0"), ("UInt32", "1"), ("UInt32", "4294967295"), + + ("Int64", "0"), ("Int64", "1"), ("Int64", "-1"), ("Int64", "-9223372036854775808"), + ("Int64", "9223372036854775807"), + ("UInt64", "0"), ("UInt64", "1"), ("UInt64", "18446744073709551615"), + + ("Double", "0"), ("Double", "1"), ("Double", "-1"), + ("Double", "2.2250738585072014e-308"), ("Double", "1.7976931348623157e+308"), + ("Double", "5e-324"), ("Double", "3.141592653589793"), + + ("Decimal", "1.2"), ("Decimal", "3.14159265358979323846264338327950288419"), + ( + "Decimal", + "3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ), + ( + "Decimal", + "-3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ), + ] + + if Int.max == Int64.max { + goodValues += [ + ("Int", "0"), ("Int", "1"), ("Int", "-1"), ("Int", "-9223372036854775808"), + ("Int", "9223372036854775807"), + ("UInt", "0"), ("UInt", "1"), ("UInt", "18446744073709551615"), + ] + } else { + goodValues += [ + ("Int", "0"), ("Int", "1"), ("Int", "-1"), ("Int", "-2147483648"), + ("Int", "2147483647"), + ("UInt", "0"), ("UInt", "1"), ("UInt", "4294967295"), + ] + } + + let badValues = [ + ("Int8", "-129"), ("Int8", "128"), ("Int8", "1.2"), + ("UInt8", "-1"), ("UInt8", "256"), + + ("Int16", "-32769"), ("Int16", "32768"), + ("UInt16", "-1"), ("UInt16", "65536"), + + ("Int32", "-2147483649"), ("Int32", "2147483648"), + ("UInt32", "-1"), ("UInt32", "4294967296"), + + ("Int64", "9223372036854775808"), ("Int64", "9223372036854775808"), + ("Int64", "-100000000000000000000"), + ("UInt64", "-1"), ("UInt64", "18446744073709600000"), + ("Int64", "10000000000000000000000000000000000000"), + ] + + for value in goodValues { + testGoodValue(value.0, value.1) + } + + for (type, value) in badValues { + testErrorThrown( + type, + value, + errorMessage: "Parsed JSON number <\(value)> does not fit in \(type)." + ) + } + + // Invalid JSON number formats + testErrorThrown( + "Int8", + "0000000000000000000000000000001", + errorMessage: "The given data was not valid JSON." + ) + testErrorThrown("Double", "-.1", errorMessage: "The given data was not valid JSON.") + testErrorThrown("Int32", "+1", errorMessage: "The given data was not valid JSON.") + testErrorThrown("Int", ".012", errorMessage: "The given data was not valid JSON.") + } + + func test_snake_case_encoding() throws { + struct MyTestData: Codable, Equatable { + let thisIsAString: String + let thisIsABool: Bool + let thisIsAnInt: Int + let thisIsAnInt8: Int8 + let thisIsAnInt16: Int16 + let thisIsAnInt32: Int32 + let thisIsAnInt64: Int64 + let thisIsAUint: UInt + let thisIsAUint8: UInt8 + let thisIsAUint16: UInt16 + let thisIsAUint32: UInt32 + let thisIsAUint64: UInt64 + let thisIsAFloat: Float + let thisIsADouble: Double + let thisIsADate: Date + let thisIsAnArray: [Int] + let thisIsADictionary: [String: Bool] + } + + let data = MyTestData( + thisIsAString: "Hello", + thisIsABool: true, + thisIsAnInt: 1, + thisIsAnInt8: 2, + thisIsAnInt16: 3, + thisIsAnInt32: 4, + thisIsAnInt64: 5, + thisIsAUint: 6, + thisIsAUint8: 7, + thisIsAUint16: 8, + thisIsAUint32: 9, + thisIsAUint64: 10, + thisIsAFloat: 11, + thisIsADouble: 12, + thisIsADate: Date.init(timeIntervalSince1970: 0), + thisIsAnArray: [1, 2, 3], + thisIsADictionary: ["trueValue": true, "falseValue": false] + ) + + let encoder = StripeJSONEncoder() + let encodedData = try encoder.encode(data) + guard let jsonObject = try JSONSerialization.jsonObject(with: encodedData) as? [String: Any] + else { + XCTFail("Cant decode json object") + return + } + XCTAssertEqual(jsonObject["this_is_a_string"] as? String, "Hello") + XCTAssertEqual(jsonObject["this_is_a_bool"] as? Bool, true) + XCTAssertEqual(jsonObject["this_is_an_int"] as? Int, 1) + XCTAssertEqual(jsonObject["this_is_an_int8"] as? Int8, 2) + XCTAssertEqual(jsonObject["this_is_an_int16"] as? Int16, 3) + XCTAssertEqual(jsonObject["this_is_an_int32"] as? Int32, 4) + XCTAssertEqual(jsonObject["this_is_an_int64"] as? Int64, 5) + XCTAssertEqual(jsonObject["this_is_a_uint"] as? UInt, 6) + XCTAssertEqual(jsonObject["this_is_a_uint8"] as? UInt8, 7) + XCTAssertEqual(jsonObject["this_is_a_uint16"] as? UInt16, 8) + XCTAssertEqual(jsonObject["this_is_a_uint32"] as? UInt32, 9) + XCTAssertEqual(jsonObject["this_is_a_uint64"] as? UInt64, 10) + XCTAssertEqual(jsonObject["this_is_a_float"] as? Float, 11) + XCTAssertEqual(jsonObject["this_is_a_double"] as? Double, 12) + XCTAssertEqual(jsonObject["this_is_a_date"] as? Int, 0) + XCTAssertEqual(jsonObject["this_is_an_array"] as? [Int], [1, 2, 3]) + XCTAssertEqual( + jsonObject["this_is_a_dictionary"] as? [String: Bool], + ["trueValue": true, "falseValue": false] + ) + + let decoder = StripeJSONDecoder() + let decodedData = try decoder.decode(MyTestData.self, from: encodedData) + XCTAssertEqual(data, decodedData) + } + + func test_dictionary_snake_case_decoding() throws { + let decoder = StripeJSONDecoder() + let snakeCaseJSONData = """ + { + "snake_case_key": { + "nested_dictionary": 1 + } + } + """.data(using: .utf8)! + let decodedDictionary = try decoder.decode( + [String: [String: Int]].self, + from: snakeCaseJSONData + ) + let expectedDictionary = ["snake_case_key": ["nested_dictionary": 1]] + XCTAssertEqual(decodedDictionary, expectedDictionary) + } + + func test_dictionary_snake_case_encoding() throws { + let encoder = StripeJSONEncoder() + let camelCaseDictionary = ["camelCaseKey": ["nested_dictionary": 1]] + let encodedData = try encoder.encode(camelCaseDictionary) + guard + let jsonObject = try JSONSerialization.jsonObject(with: encodedData) + as? [String: [String: Int]] + else { + XCTFail("Cant decode json object") + return + } + XCTAssertEqual(jsonObject, camelCaseDictionary) + } + + func test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip() throws { + struct Something: Codable { + struct Key: Codable, Hashable { + var x: String + } + + var dict: [Key: String] + + enum CodingKeys: String, CodingKey { + case dict + } + + init( + from decoder: Decoder + ) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.dict = try container.decode([Key: String].self, forKey: .dict) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(dict, forKey: .dict) + } + + init( + dict: [Key: String] + ) { + self.dict = dict + } + } + + let toEncode = Something(dict: [:]) + let data = try StripeJSONEncoder().encode(toEncode) + let result = try StripeJSONDecoder().decode(Something.self, from: data) + XCTAssertEqual(result.dict.count, 0) + } + + func testIncorrectArrayType() throws { + struct PaymentMethod: Decodable { + let type: String + } + + let json = """ + { + "type": "card" + } + """ + + let decoder = StripeJSONDecoder() + do { + _ = try decoder.decode(Array.self, from: json.data(using: .utf8)!) + } catch DecodingError.dataCorrupted(let context) { + XCTAssert(context.debugDescription.hasPrefix("Could not convert")) + } + } + + // MARK: - Helper Functions + private var _jsonEmptyDictionary: Data { + return "{}".data(using: .utf8)! + } + + private func _testEncodeFailure(of value: T) { + do { + _ = try StripeJSONEncoder().encode(value) + XCTFail("Encode of top-level \(T.self) was expected to fail.") + } catch {} + } + + private func _testRoundTrip( + of value: T, + expectedJSON json: Data? = nil, + outputFormatting: JSONSerialization.WritingOptions = [], + dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, + dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, + dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .base64, + dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .base64, + nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .throw, + nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .throw + ) where T: Codable, T: Equatable { + var payload: Data! = nil + do { + let encoder = StripeJSONEncoder() + encoder.outputFormatting = outputFormatting + payload = try encoder.encode(value) + } catch { + XCTFail("Failed to encode \(T.self) to JSON: \(error)") + } + + if let expectedJSON = json { + // We do not compare expectedJSON to payload directly, because they might have values like + // {"name": "Bob", "age": 22} + // and + // {"age": 22, "name": "Bob"} + // which if compared as Data would not be equal, but the contained JSON values are equal. + // So we wrap them in a JSON type, which compares data as if it were a json. + + let expectedJSONObject: JSON + let payloadJSONObject: JSON + + do { + expectedJSONObject = try JSON(data: expectedJSON) + } catch { + XCTFail("Invalid JSON data passed as expectedJSON: \(error)") + return + } + + do { + payloadJSONObject = try JSON(data: payload) + } catch { + XCTFail("Produced data is not a valid JSON: \(error)") + return + } + + XCTAssertEqual( + expectedJSONObject, + payloadJSONObject, + "Produced JSON not identical to expected JSON." + ) + } + + do { + let decoder = StripeJSONDecoder() + let decoded = try decoder.decode(T.self, from: payload) + XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.") + } catch { + XCTFail("Failed to decode \(T.self) from JSON: \(error)") + } + } + + func test_codingOf(value: T, toAndFrom stringValue: String) { + _testRoundTrip( + of: TopLevelObjectWrapper(value), + expectedJSON: "{\"value\":\(stringValue)}".data(using: .utf8)! + ) + + _testRoundTrip( + of: TopLevelArrayWrapper(value), + expectedJSON: "[\(stringValue)]".data(using: .utf8)! + ) + } + + enum Format: String, SafeEnumCodable { + case format1 + case format2 + case format3 + case unparsable + } + + struct FormatContainer: Decodable, Equatable { + private let container: [Format: String?] + + init( + _ container: [Format: String?] + ) { + self.container = container + } + + static public func == (lhs: FormatContainer, rhs: FormatContainer) -> Bool { + return NSDictionary(dictionary: lhs.container as [AnyHashable: Any]).isEqual( + rhs.container + ) + } + } + + /// This method tests a dictionary that has keys of a custom type + /// (types that are not exactly `String.Type` or `Int.Type`. + func test_encodingCustomKeysForDictionary() { + let formats = FormatContainer([ + .format1: "The first format", + .format2: "The second format", + .format3: "The third format", + ]) + + do { + let encodedData = + "{\"container\":[\"format3\",\"The third format\",\"format2\",\"The second format\",\"format1\",\"The first format\"]}" + .data(using: .utf8) + let decodedResult: FormatContainer = try StripeJSONDecoder.decode( + jsonData: encodedData! + ) + + XCTAssertEqual( + formats, + decodedResult, + "\(FormatContainer.self) did not round-trip to an equal value." + ) + } catch { + XCTFail(String(describing: error)) + } + } +} + +// MARK: - Helper Global Functions +func expectEqualPaths(_ lhs: [CodingKey?], _ rhs: [CodingKey?], _ prefix: String) { + if lhs.count != rhs.count { + XCTFail( + "\(prefix) [CodingKey?].count mismatch: \(lhs.count) != \(rhs.count). \(lhs) != \(rhs)" + ) + return + } + + for (k1, k2) in zip(lhs, rhs) { + switch (k1, k2) { + case (nil, nil): continue + case (let _k1?, nil): + XCTFail("\(prefix) CodingKey mismatch: \(type(of: _k1)) != nil") + return + case (nil, let _k2?): + XCTFail("\(prefix) CodingKey mismatch: nil != \(type(of: _k2))") + return + default: break + } + + let key1 = k1! + let key2 = k2! + + switch (key1.intValue, key2.intValue) { + case (nil, nil): break + case (let i1?, nil): + XCTFail("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") + return + case (nil, let i2?): + XCTFail("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") + return + case (let i1?, let i2?): + guard i1 == i2 else { + XCTFail( + "\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))" + ) + return + } + } + + XCTAssertEqual( + key1.stringValue, + key2.stringValue, + "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')" + ) + } +} + +// MARK: - Test Types +// FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. + +// MARK: - Empty Types +private struct EmptyStruct: Codable, Equatable { + static func == (_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool { + return true + } +} + +private class EmptyClass: Codable, Equatable { + static func == (_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool { + return true + } +} + +// MARK: - Single-Value Types +/// A simple on-off switch type that encodes as a single Bool value. +private enum Switch: Codable { + case off + case on + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + switch try container.decode(Bool.self) { + case false: self = .off + case true: self = .on + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .off: try container.encode(false) + case .on: try container.encode(true) + } + } +} + +/// A simple timestamp type that encodes as a single Double value. +private struct Timestamp: Codable, Equatable { + let value: Double + + init( + _ value: Double + ) { + self.value = value + } + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + value = try container.decode(Double.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.value) + } + + static func == (_ lhs: Timestamp, _ rhs: Timestamp) -> Bool { + return lhs.value == rhs.value + } +} + +/// A simple referential counter type that encodes as a single Int value. +private final class Counter: Codable, Equatable { + var count: Int = 0 + + init() {} + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + count = try container.decode(Int.self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.count) + } + + static func == (_ lhs: Counter, _ rhs: Counter) -> Bool { + return lhs === rhs || lhs.count == rhs.count + } +} + +// MARK: - Structured Types +/// A simple address type that encodes as a dictionary of values. +private struct Address: Codable, Equatable { + let street: String + let city: String + let state: String + let zipCode: Int + let country: String + + static func == (_ lhs: Address, _ rhs: Address) -> Bool { + return lhs.street == rhs.street && lhs.city == rhs.city && lhs.state == rhs.state + && lhs.zipCode == rhs.zipCode && lhs.country == rhs.country + } + + static var testValue: Address { + return Address( + street: "1 Infinite Loop", + city: "Cupertino", + state: "CA", + zipCode: 95014, + country: "United States" + ) + } +} + +/// A simple person class that encodes as a dictionary of values. +private class Person: Codable, Equatable { + let name: String + let email: String + + // FIXME: This property is present only in order to test the expected result of Codable synthesis in the compiler. + // We want to test against expected encoded output (to ensure this generates an encodeIfPresent call), but we need an output format for that. + // Once we have a VerifyingEncoder for compiler unit tests, we should move this test there. + let website: URL? + + init( + name: String, + email: String, + website: URL? = nil + ) { + self.name = name + self.email = email + self.website = website + } + + static func == (_ lhs: Person, _ rhs: Person) -> Bool { + return lhs.name == rhs.name && lhs.email == rhs.email && lhs.website == rhs.website + } + + static var testValue: Person { + return Person(name: "Johnny Appleseed", email: "appleseed@apple.com") + } +} + +/// A simple company struct which encodes as a dictionary of nested values. +private struct Company: Codable, Equatable { + let address: Address + var employees: [Person] + + static func == (_ lhs: Company, _ rhs: Company) -> Bool { + return lhs.address == rhs.address && lhs.employees == rhs.employees + } + + static var testValue: Company { + return Company(address: Address.testValue, employees: [Person.testValue]) + } +} + +// MARK: - Helper Types + +/// A key type which can take on any string or integer value. +/// +/// This needs to mirror `_JSONKey`. +private struct _TestKey: CodingKey { + var stringValue: String + var intValue: Int? + + init?( + stringValue: String + ) { + self.stringValue = stringValue + self.intValue = nil + } + + init?( + intValue: Int + ) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + init( + index: Int + ) { + self.stringValue = "Index \(index)" + self.intValue = index + } +} + +/// Wraps a type T so that it can be encoded at the top level of a payload. +private struct TopLevelArrayWrapper: Codable, Equatable where T: Codable, T: Equatable { + let value: T + + init( + _ value: T + ) { + self.value = value + } + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(value) + } + + init( + from decoder: Decoder + ) throws { + var container = try decoder.unkeyedContainer() + value = try container.decode(T.self) + assert(container.isAtEnd) + } + + static func == (_ lhs: TopLevelArrayWrapper, _ rhs: TopLevelArrayWrapper) -> Bool { + return lhs.value == rhs.value + } +} + +private struct FloatNaNPlaceholder: Codable, Equatable { + init() {} + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(Float.nan) + } + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + let float = try container.decode(Float.self) + if !float.isNaN { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Couldn't decode NaN." + ) + ) + } + } + + static func == (_ lhs: FloatNaNPlaceholder, _ rhs: FloatNaNPlaceholder) -> Bool { + return true + } +} + +private struct DoubleNaNPlaceholder: Codable, Equatable { + init() {} + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(Double.nan) + } + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + let double = try container.decode(Double.self) + if !double.isNaN { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Couldn't decode NaN." + ) + ) + } + } + + static func == (_ lhs: DoubleNaNPlaceholder, _ rhs: DoubleNaNPlaceholder) -> Bool { + return true + } +} + +/// A type which encodes as an array directly through a single value container. +struct Numbers: Codable, Equatable { + let values = [4, 8, 15, 16, 23, 42] + + init() {} + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + let decodedValues = try container.decode([Int].self) + guard decodedValues == values else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: + "The Numbers are wrong! decoded \(decodedValues) but expected \(values)!" + ) + ) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(values) + } + + static func == (_ lhs: Numbers, _ rhs: Numbers) -> Bool { + return lhs.values == rhs.values + } + + static var testValue: Numbers { + return Numbers() + } +} + +/// A type which encodes as a dictionary directly through a single value container. +private final class Mapping: Codable, Equatable { + let values: [String: URL] + + init( + values: [String: URL] + ) { + self.values = values + } + + init( + from decoder: Decoder + ) throws { + let container = try decoder.singleValueContainer() + values = try container.decode([String: URL].self) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(values) + } + + static func == (_ lhs: Mapping, _ rhs: Mapping) -> Bool { + return lhs === rhs || lhs.values == rhs.values + } + + static var testValue: Mapping { + return Mapping(values: [ + "Apple": URL(string: "http://apple.com")!, + "localhost": URL(string: "http://127.0.0.1")!, + ]) + } +} + +struct NestedContainersTestType: Encodable { + let testSuperEncoder: Bool + + init( + testSuperEncoder: Bool = false + ) { + self.testSuperEncoder = testSuperEncoder + } + + enum TopLevelCodingKeys: Int, CodingKey { + case a + case b + case c + } + + enum IntermediateCodingKeys: Int, CodingKey { + case one + case two + } + + func encode(to encoder: Encoder) throws { + if self.testSuperEncoder { + var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) + expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") + expectEqualPaths( + topLevelContainer.codingPath, + [], + "New first-level keyed container has non-empty codingPath." + ) + + let superEncoder = topLevelContainer.superEncoder(forKey: .a) + expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") + expectEqualPaths( + topLevelContainer.codingPath, + [], + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + superEncoder.codingPath, + [TopLevelCodingKeys.a], + "New superEncoder had unexpected codingPath." + ) + _testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a]) + } else { + _testNestedContainers(in: encoder, baseCodingPath: []) + } + } + + func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey?]) { + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "New encoder has non-empty codingPath." + ) + + // codingPath should not change upon fetching a non-nested container. + var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath, + "New first-level keyed container has non-empty codingPath." + ) + + // Nested Keyed Container + do { + // Nested container for key should have a new key pushed on. + var secondLevelContainer = firstLevelContainer.nestedContainer( + keyedBy: IntermediateCodingKeys.self, + forKey: .a + ) + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath, + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + secondLevelContainer.codingPath, + baseCodingPath + [TopLevelCodingKeys.a], + "New second-level keyed container had unexpected codingPath." + ) + + // Inserting a keyed container should not change existing coding paths. + let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer( + keyedBy: IntermediateCodingKeys.self, + forKey: .one + ) + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath, + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + secondLevelContainer.codingPath, + baseCodingPath + [TopLevelCodingKeys.a], + "Second-level keyed container's codingPath changed." + ) + expectEqualPaths( + thirdLevelContainerKeyed.codingPath, + baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], + "New third-level keyed container had unexpected codingPath." + ) + + // Inserting an unkeyed container should not change existing coding paths. + let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer( + forKey: .two + ) + expectEqualPaths( + encoder.codingPath, + baseCodingPath + [], + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath + [], + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + secondLevelContainer.codingPath, + baseCodingPath + [TopLevelCodingKeys.a], + "Second-level keyed container's codingPath changed." + ) + expectEqualPaths( + thirdLevelContainerUnkeyed.codingPath, + baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], + "New third-level unkeyed container had unexpected codingPath." + ) + } + + // Nested Unkeyed Container + do { + // Nested container for key should have a new key pushed on. + var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b) + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath, + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + secondLevelContainer.codingPath, + baseCodingPath + [TopLevelCodingKeys.b], + "New second-level keyed container had unexpected codingPath." + ) + + // Appending a keyed container should not change existing coding paths. + let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer( + keyedBy: IntermediateCodingKeys.self + ) + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath, + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + secondLevelContainer.codingPath, + baseCodingPath + [TopLevelCodingKeys.b], + "Second-level unkeyed container's codingPath changed." + ) + expectEqualPaths( + thirdLevelContainerKeyed.codingPath, + baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], + "New third-level keyed container had unexpected codingPath." + ) + + // Appending an unkeyed container should not change existing coding paths. + let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer() + expectEqualPaths( + encoder.codingPath, + baseCodingPath, + "Top-level Encoder's codingPath changed." + ) + expectEqualPaths( + firstLevelContainer.codingPath, + baseCodingPath, + "First-level keyed container's codingPath changed." + ) + expectEqualPaths( + secondLevelContainer.codingPath, + baseCodingPath + [TopLevelCodingKeys.b], + "Second-level unkeyed container's codingPath changed." + ) + expectEqualPaths( + thirdLevelContainerUnkeyed.codingPath, + baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], + "New third-level unkeyed container had unexpected codingPath." + ) + } + } +} + +// MARK: - Helpers + +private struct JSON: Equatable { + private var jsonObject: Any + + fileprivate init( + data: Data + ) throws { + self.jsonObject = try JSONSerialization.jsonObject(with: data, options: []) + } + + static func == (lhs: JSON, rhs: JSON) -> Bool { + switch (lhs.jsonObject, rhs.jsonObject) { + case let (lhs, rhs) as ([AnyHashable: Any], [AnyHashable: Any]): + return NSDictionary(dictionary: lhs) == NSDictionary(dictionary: rhs) + case let (lhs, rhs) as ([Any], [Any]): + return NSArray(array: lhs) == NSArray(array: rhs) + default: + return false + } + } +} + +// MARK: - Run Tests + +extension TestJSONEncoder { + static var allTests: [(String, (TestJSONEncoder) -> () throws -> Void)] { + return [ + ("test_encodingTopLevelFragments", test_encodingTopLevelFragments), + ("test_encodingTopLevelEmptyStruct", test_encodingTopLevelEmptyStruct), + ("test_encodingTopLevelEmptyClass", test_encodingTopLevelEmptyClass), + ("test_encodingTopLevelSingleValueEnum", test_encodingTopLevelSingleValueEnum), + ("test_encodingTopLevelSingleValueStruct", test_encodingTopLevelSingleValueStruct), + ("test_encodingTopLevelSingleValueClass", test_encodingTopLevelSingleValueClass), + ("test_encodingTopLevelStructuredStruct", test_encodingTopLevelStructuredStruct), + ("test_encodingTopLevelStructuredClass", test_encodingTopLevelStructuredClass), + ( + "test_encodingTopLevelStructuredSingleStruct", + test_encodingTopLevelStructuredSingleStruct + ), + ( + "test_encodingTopLevelStructuredSingleClass", + test_encodingTopLevelStructuredSingleClass + ), + ( + "test_encodingTopLevelDeepStructuredType", + test_encodingTopLevelDeepStructuredType + ), + ("test_encodingOutputFormattingDefault", test_encodingOutputFormattingDefault), + ( + "test_encodingOutputFormattingPrettyPrinted", + test_encodingOutputFormattingPrettyPrinted + ), + ( + "test_encodingOutputFormattingSortedKeys", + test_encodingOutputFormattingSortedKeys + ), + ( + "test_encodingOutputFormattingPrettyPrintedSortedKeys", + test_encodingOutputFormattingPrettyPrintedSortedKeys + ), + ("test_encodingDate", test_encodingDate), + ("test_encodingDateSecondsSince1970", test_encodingDateSecondsSince1970), + ("test_encodingBase64Data", test_encodingBase64Data), + ("test_encodingNonConformingFloatStrings", test_encodingNonConformingFloatStrings), + // ("test_nestedContainerCodingPaths", test_nestedContainerCodingPaths), + // ("test_superEncoderCodingPaths", test_superEncoderCodingPaths), + ("test_codingOfBool", test_codingOfBool), + ("test_codingOfNil", test_codingOfNil), + ("test_codingOfInt8", test_codingOfInt8), + ("test_codingOfUInt8", test_codingOfUInt8), + ("test_codingOfInt16", test_codingOfInt16), + ("test_codingOfUInt16", test_codingOfUInt16), + ("test_codingOfInt32", test_codingOfInt32), + ("test_codingOfUInt32", test_codingOfUInt32), + ("test_codingOfInt64", test_codingOfInt64), + ("test_codingOfUInt64", test_codingOfUInt64), + ("test_codingOfInt", test_codingOfInt), + ("test_codingOfUInt", test_codingOfUInt), + ("test_codingOfFloat", test_codingOfFloat), + ("test_codingOfDouble", test_codingOfDouble), + ("test_codingOfDecimal", test_codingOfDecimal), + ("test_codingOfString", test_codingOfString), + ("test_codingOfURL", test_codingOfURL), + ("test_codingOfUIntMinMax", test_codingOfUIntMinMax), + ("test_numericLimits", test_numericLimits), + ("test_snake_case_encoding", test_snake_case_encoding), + ("test_dictionary_snake_case_decoding", test_dictionary_snake_case_decoding), + ("test_dictionary_snake_case_encoding", test_dictionary_snake_case_encoding), + ( + "test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip", + test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip + ), + ] + } +} diff --git a/StripeCore/StripeCoreTests/Helpers/AsyncTests.swift b/StripeCore/StripeCoreTests/Helpers/AsyncTests.swift new file mode 100644 index 00000000..51b369b7 --- /dev/null +++ b/StripeCore/StripeCoreTests/Helpers/AsyncTests.swift @@ -0,0 +1,155 @@ +// +// AsyncTests.swift +// StripeCoreTests +// +// Created by Mat Schmid on 2024-09-24. +// + +@_spi(STP) @testable import StripeCore +import XCTest + +class AsyncTests: XCTestCase { + + func testFutureObserveWithImmediateResult() { + let promise = Promise(value: 42) + let expectation = XCTestExpectation(description: "Observe immediate result") + + promise.observe { result in + XCTAssertEqual(result.successValue, 42) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } + + func testFutureObserveWithDelayedResult() { + let promise = Promise() + let expectation = XCTestExpectation(description: "Observe delayed result") + + promise.observe { result in + XCTAssertEqual(result.successValue, 42) + expectation.fulfill() + } + + promise.resolve(with: 42) + wait(for: [expectation], timeout: 1.0) + } + + func testFutureChainedSuccess() { + let promise = Promise(value: 42) + let chainedFuture = promise.chained { value in + return Promise(value: value * 2) + } + + let expectation = XCTestExpectation(description: "Chained success") + + chainedFuture.observe { result in + XCTAssertEqual(result.successValue, 84) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } + + func testFutureChainedFailure() { + let promise = Promise() + let chainedFuture: Future = promise.chained { _ in + return Promise(error: NSError(domain: "test", code: 0, userInfo: nil)) + } + + let expectation = XCTestExpectation(description: "Chained failure") + + chainedFuture.observe { result in + XCTAssertNotNil(result.failureValue) + expectation.fulfill() + } + + promise.reject(with: NSError(domain: "test", code: 0, userInfo: nil)) + wait(for: [expectation], timeout: 1.0) + } + + func testFutureTransformed() { + let promise = Promise(value: 42) + let transformedFuture = promise.transformed { value in + return value * 2 + } + + let expectation = XCTestExpectation(description: "Transformed success") + + transformedFuture.observe { result in + XCTAssertEqual(result.successValue, 84) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 1.0) + } + + func testFutureTransformedFailure() { + let promise = Promise() + let transformedFuture = promise.transformed { value in + return value * 2 + } + + let expectation = XCTestExpectation(description: "Transformed failure") + + transformedFuture.observe { result in + XCTAssertNotNil(result.failureValue) + expectation.fulfill() + } + + promise.reject(with: NSError(domain: "test", code: 0, userInfo: nil)) + wait(for: [expectation], timeout: 1.0) + } + + func testPromiseResolved() { + let promise = Promise() + let expectation = XCTestExpectation(description: "Promise resolved") + + promise.observe { result in + XCTAssertEqual(result.successValue, 42) + expectation.fulfill() + } + + promise.resolve(with: 42) + wait(for: [expectation], timeout: 1.0) + } + + func testPromiseRejected() { + let promise = Promise() + let expectation = XCTestExpectation(description: "Promise rejected") + + promise.observe { result in + XCTAssertNotNil(result.failureValue) + expectation.fulfill() + } + + promise.reject(with: NSError(domain: "test", code: 0, userInfo: nil)) + wait(for: [expectation], timeout: 1.0) + } + + func testPromiseFullfill() { + let promise = Promise() + let expectation = XCTestExpectation(description: "Promise fullfill") + + promise.observe { result in + XCTAssertEqual(result.successValue, 42) + expectation.fulfill() + } + + promise.fullfill(with: .success(42)) + wait(for: [expectation], timeout: 1.0) + } +} + +private extension Result { + var successValue: Success? { + try? get() + } + + var failureValue: Error? { + guard case .failure(let error) = self else { + return nil + } + return error + } +} diff --git a/StripeCore/StripeCoreTests/Helpers/URLEncoderTest.swift b/StripeCore/StripeCoreTests/Helpers/URLEncoderTest.swift new file mode 100644 index 00000000..345a3e73 --- /dev/null +++ b/StripeCore/StripeCoreTests/Helpers/URLEncoderTest.swift @@ -0,0 +1,61 @@ +// +// URLEncoderTest.swift +// StripeCoreTests +// +// Created by Mel Ludowise on 5/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import XCTest + +final class URLEncoderTest: XCTestCase { + func testStringByReplacingSnakeCaseWithCamelCase() { + let camelCase = URLEncoder.convertToCamelCase(snakeCase: "test_1_2_34_test") + XCTAssertEqual("test1234Test", camelCase) + } + + func testStringByReplacingCamelCaseWithSnakeCase() { + let snakeCase = URLEncoder.convertToSnakeCase(camelCase: "test1234Test") + XCTAssertEqual("test1234_test", snakeCase) + let snakeCase2 = URLEncoder.convertToSnakeCase(camelCase: "testUrlTest") + XCTAssertEqual("test_url_test", snakeCase2) + } + + func testQueryStringWithBadFields() { + let params = [ + "foo]": "bar", + "baz": "qux[", + "woo;": ";hoo", + ] + let result = URLEncoder.queryString(from: params) + XCTAssertEqual(result, "baz=qux%5B&foo%5D=bar&woo%3B=%3Bhoo") + } + + func testQueryStringFromParameters() { + let params = + [ + "foo": "bar", + "baz": [ + "qux": NSNumber(value: 1), + ], + ] as [String: AnyHashable] + let result = URLEncoder.queryString(from: params) + XCTAssertEqual(result, "baz[qux]=1&foo=bar") + } + + func testPushProvisioningQueryStringFromParameters() { + let params = [ + "ios": [ + "certificates": ["cert1", "cert2"], + "nonce": "123mynonce", + "nonce_signature": "sig", + ], + ] + let result = URLEncoder.queryString(from: params) + XCTAssertEqual( + result, + "ios[certificates][0]=cert1&ios[certificates][1]=cert2&ios[nonce]=123mynonce&ios[nonce_signature]=sig" + ) + } +} diff --git a/StripeCore/StripeCoreTests/Info.plist b/StripeCore/StripeCoreTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripeCore/StripeCoreTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripeCore/StripeCoreTests/Mock Files/test_image.png b/StripeCore/StripeCoreTests/Mock Files/test_image.png new file mode 100644 index 00000000..60ed2123 Binary files /dev/null and b/StripeCore/StripeCoreTests/Mock Files/test_image.png differ diff --git a/StripeFinancialConnections/README.md b/StripeFinancialConnections/README.md new file mode 100644 index 00000000..9a30c890 --- /dev/null +++ b/StripeFinancialConnections/README.md @@ -0,0 +1,50 @@ +# Stripe Financial Connections iOS SDK + +Stripe Financial Connections iOS SDK lets your users securely share their financial data by linking their external financial accounts to your business in your iOS app. + +## Table of contents + + +* [Features](#features) +* [Requirements](#requirements) +* [Getting started](#getting-started) + * [Integration](#integration) + * [Example](#example) +* [Manual linking](#manual-linking) + + + +## Features + +**Prebuilt UI**: We provide [`FinancialConnectionsSheet`](https://stripe.dev/stripe-ios/stripe-financialconnections/Classes/FinancialConnectionsSheet.html), a prebuilt UI that combines all the steps required for your users to linking their external financial accounts to your business. + +Data retrieved through Financial Connections can help you unlock a variety of use cases, including: + +- Tokenized account and routing numbers let you instantly verify bank accounts for ACH Direct Debit payments. +- Real-time balance data helps you avoid fees from insufficient funds failures before initiating a bank-based payment or wallet transfer. +- Account ownership information, such as the name and address of the bank accountholder, helps you mitigate fraud when onboarding a customer or merchant. +- Transactions data that you can use to help users track expenses, handle bills, manage their finances, and take control of their financial well-being. +- Transactions and balance data helps you speed up underwriting and improve access to credit and other financial services. + + + +## Requirements + +The Stripe Financial Connections iOS SDK is compatible with apps targeting iOS 12.0 or above. + +## Getting started + +### Integration + +Get started with Stripe Financial Connections [📚 iOS integration guide](https://stripe.com/docs/financial-connections/other-data-powered-products?platform=ios) and [example project](../Example/FinancialConnections%20Example), or [📘 browse the SDK reference](https://stripe.dev/stripe-ios/stripe-financialconnections/index.html) for fine-grained documentation of all the classes and methods in the SDK. + +### Example + +[Financial Connections Example](../Example/FinancialConnections%20Example) – This example demonstrates how to let your user link their external financial accounts. + +## Manual linking + +If you link the Stripe Financial Connections library manually, use a version from our [releases](https://github.com/stripe/stripe-ios/releases) page and make sure to embed all of the following frameworks: +- `StripeFinancialConnections.xcframework` +- `StripeCore.xcframework` +- `StripeUICore.xcframework` diff --git a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj new file mode 100644 index 00000000..d4d00ff1 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj @@ -0,0 +1,1662 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 01C820ECDBFC041A741A5499 /* FlowRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1407DD9E95ADFE143FA046E4 /* FlowRouter.swift */; }; + 0375F8C6D79947C992C32362 /* NetworkingOTPDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0430E0E195DD128FA2D5F86 /* NetworkingOTPDataSource.swift */; }; + 06445472B3008395FCA92FEC /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C650E82A4195A7566AA54298 /* StripeCore.framework */; }; + 07712610C7D2F484AAB96982 /* FinancialConnectionsInstitution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE8BCF5A9FF2B9392A755EA /* FinancialConnectionsInstitution.swift */; }; + 07A86CEB6B4F6BEB524EFE37 /* ManualEntryValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F897F5AA6A684D0A370EA7BC /* ManualEntryValidator.swift */; }; + 07BFA34C5643A79E1E35A159 /* FinancialConnectionsSession_only_accounts.json in Resources */ = {isa = PBXBuildFile; fileRef = 4AFBF95DAE0783010A17EB58 /* FinancialConnectionsSession_only_accounts.json */; }; + 0AF88791C01102CDCC31F419 /* AccountFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A71EBB1B98CD285DD17D5F /* AccountFetcherTests.swift */; }; + 0D56BD448019185656DF9310 /* FinancialConnectionsCustomManualEntryRequiredError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2C7AE6509C80B6F30662AA /* FinancialConnectionsCustomManualEntryRequiredError.swift */; }; + 11782289208971CCAA1037A5 /* NetworkingLinkStepUpVerificationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 925B7F2CBACCB2346CD0CDFC /* NetworkingLinkStepUpVerificationDataSource.swift */; }; + 11FB97AC840FEB5B5BF85BF9 /* FinancialConnectionsAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2EAD7059FF8358E674774A /* FinancialConnectionsAPIClient.swift */; }; + 15EC9F36187C341800164428 /* FinancialConnectionsAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBBF5CEE2C9030B2D374BC76 /* FinancialConnectionsAnalyticsClient.swift */; }; + 163E387D567068E4A64A4C13 /* AccountPickerSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72DEDE8871A732D603E96E2B /* AccountPickerSelectionView.swift */; }; + 166ACB3BF53BDB4443E276E3 /* LinkAccountPickerFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A680CB323B9139F838643EC1 /* LinkAccountPickerFooterView.swift */; }; + 16F2968DC3B2FC4558821970 /* chevron_down@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = EC561AF0993C02AD68472D11 /* chevron_down@3x.png */; }; + 1889ECB24D40EF331974C288 /* AccountPickerNoAccountEligibleErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 912E3AA36B68492A69019AEA /* AccountPickerNoAccountEligibleErrorView.swift */; }; + 19D1548A5A4034D349DB0947 /* ManualEntryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8301F7BA1FF90D131AE96E10 /* ManualEntryViewController.swift */; }; + 1C043C73281C0856D2C979C6 /* FinancialConnectionsSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACEF0BAF1A5BBA3061C15A09 /* FinancialConnectionsSession.swift */; }; + 1E0C39EB65B8CB04F218D0BD /* NetworkingLinkLoginWarmupDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091D43608583C7BE5E444C2C /* NetworkingLinkLoginWarmupDataSource.swift */; }; + 22426A37E01AE759BF93C422 /* AttributedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE64D5A17913CF4AAD855A9A /* AttributedLabel.swift */; }; + 2343C58289259920DD81620D /* ManualEntryErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88FD9148F64D2AA8989D361 /* ManualEntryErrorView.swift */; }; + 23DBA4240ED1727C47937A6B /* AccountPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97CE72E38D41E86E0A1FAE9F /* AccountPickerViewController.swift */; }; + 2671241DE661B675E575C0AB /* NetworkingSaveToLinkVerificationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3BDFEB5860F73D4CD90907A /* NetworkingSaveToLinkVerificationDataSource.swift */; }; + 2AA0942F22A323B33CA6B7CA /* PartnerAuthDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC8B933341AF86D9AFF5979 /* PartnerAuthDataSource.swift */; }; + 2CE89100448F26DDA831F455 /* NativeFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F856808B78F4F1975959805 /* NativeFlowController.swift */; }; + 2D14461B27B3DEE2CC19B090 /* FinancialConnectionsAccountFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC1BC95816DAD5AE9680662 /* FinancialConnectionsAccountFetcher.swift */; }; + 2FADCA33DEC08E6551D94811 /* AttributedTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97948A848C99ACD1A498F841 /* AttributedTextView.swift */; }; + 313F5F7F2B0BE5D100BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F7E2B0BE5D100BD98A9 /* Docs.docc */; }; + 31AD3BE92B0C2F000080C800 /* ScreenNativeScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD3BE82B0C2F000080C800 /* ScreenNativeScale.swift */; }; + 31CDFC342BA8E61F00B3DD91 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 31CDFC332BA8E61F00B3DD91 /* PrivacyInfo.xcprivacy */; }; + 333B9C3E3349F5369FBA7C32 /* NetworkingLinkVerificationDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DDB75C69FE9322C745943B3 /* NetworkingLinkVerificationDataSource.swift */; }; + 33FA1684CE79F21271D14F23 /* HitTestStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00D518C15FF3FAED7C193C2 /* HitTestStackView.swift */; }; + 3446145FCA3278D51A9D4B80 /* AttachLinkedPaymentAccountDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518F1F230FD4DF68E683C728 /* AttachLinkedPaymentAccountDataSource.swift */; }; + 34E12CB27B60F6A53D030765 /* FinancialConnectionsFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83E0A15A666F20DA97F128EA /* FinancialConnectionsFont.swift */; }; + 368DFF9D68F1F8D6A4353961 /* AuthFlowHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B31B3A45DD8AAFD6F08820 /* AuthFlowHelpers.swift */; }; + 39E5D4531961150E9CB3262F /* EmptyFinancialConnectionsAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A7B146AA6BF44921A249DB8 /* EmptyFinancialConnectionsAPIClient.swift */; }; + 3AC5CA5F5529B55026342A54 /* NetworkingLinkSignupDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D688616FDC435586025D2023 /* NetworkingLinkSignupDataSource.swift */; }; + 3AE1C7A78FB5B220F5200F49 /* search@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = EF0111A8932418631FFA1663 /* search@3x.png */; }; + 3BF4BBF7E722B961E037286C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94869BACB486153419B30DE5 /* XCTest.framework */; }; + 3BFED24B6DF835A0F2FB4939 /* TerminalErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83A2749140B4E129CEF39C4 /* TerminalErrorViewController.swift */; }; + 3ECA346F75060BD954376EBF /* StripeFinancialConnectionsBundleLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0F907D5B0AC58BE7A454BA /* StripeFinancialConnectionsBundleLocator.swift */; }; + 3F835D5A1C797C1C9BCF05D0 /* NetworkingLinkStepUpVerificationBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F9D6F0CC79B7949D037DE66 /* NetworkingLinkStepUpVerificationBodyView.swift */; }; + 3FE4DEFAD6FF77B8D9EE68D3 /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05B47C10B77812660F7B01A /* String+Localized.swift */; }; + 40FF444E6CF20E9DA7D90448 /* NetworkingOTPView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A3C86CF29F533F87C7DFD6 /* NetworkingOTPView.swift */; }; + 432463EBF562CDDC6D3DC252 /* BankAccountToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D890BD770F4E33D23ABA37EA /* BankAccountToken.swift */; }; + 44203505ED2F64D07632566B /* LinkAccountPickerBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DD5619FDD6C8D85FC352F99 /* LinkAccountPickerBodyView.swift */; }; + 444884F264D13FF654EA7471 /* PaneLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34452D5FDC1ED566A13427FE /* PaneLayoutView.swift */; }; + 460C7685096AA6C693309647 /* FinancialConnectionsAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C837C27C2577391B91FF0E5 /* FinancialConnectionsAuthSession.swift */; }; + 465AE8A58AD2183E1E2042FE /* ConsentDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81304AD5BE5CCA10D1A866E0 /* ConsentDataSource.swift */; }; + 486E50E6CB90208AB98C031E /* UIImageView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3AC48492FAB61E5B66D94 /* UIImageView+Extensions.swift */; }; + 490B00112C93582900B1A489 /* FinancialConnectionsAccountPickerPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490B00102C93582900B1A489 /* FinancialConnectionsAccountPickerPane.swift */; }; + 492039952CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */; }; + 492651662C24C9E7001DDBCA /* TestModeAutofillBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */; }; + 492651682C25C0C2001DDBCA /* info@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 492651672C25C0C2001DDBCA /* info@3x.png */; }; + 494D62072C45B9B700106519 /* link_logo@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 494D62062C45B9B700106519 /* link_logo@3x.png */; }; + 495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */; }; + 496A6AE72C29E0BB00D34F8E /* testmode@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 496A6AE62C29E0BB00D34F8E /* testmode@3x.png */; }; + 497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497142BB2C514B08000DFA64 /* FlowRouterTests.swift */; }; + 49A0B5862C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */; }; + 49AC518C2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */; }; + 49C911372C597EAF00589E0D /* LinkLoginDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C911332C597EAF00589E0D /* LinkLoginDataSource.swift */; }; + 49C911392C597EAF00589E0D /* LinkLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C911352C597EAF00589E0D /* LinkLoginViewController.swift */; }; + 49C9113B2C59932300589E0D /* LinkSignupFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C9113A2C59932300589E0D /* LinkSignupFormView.swift */; }; + 49F047532C63B430006BAD3E /* StripeSchemeAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F047522C63B430006BAD3E /* StripeSchemeAddress.swift */; }; + 49F047552C63C2E5006BAD3E /* FinancialConnectionsPaymentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F047542C63C2E5006BAD3E /* FinancialConnectionsPaymentDetails.swift */; }; + 4A0D015C978BD79BBFE6CE57 /* ManualEntryDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4C39F5F9AF440B13F51A81 /* ManualEntryDataSource.swift */; }; + 4A537AE0C50CAFF3889EFE28 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7E41313B709F87B549D85F /* UIViewController+Extensions.swift */; }; + 4DC8EB63806434ABF4C9CC43 /* add@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 782A419DCF59BE6AB6439D04 /* add@3x.png */; }; + 54B51EA1F75B9607D7C29B08 /* NetworkingLinkSignupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CDEF702710EEA29BA3DC653 /* NetworkingLinkSignupViewController.swift */; }; + 645D6FF67167263F9A1C2BB0 /* bullet@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 07BCA9D23511A3494C82B632 /* bullet@3x.png */; }; + 648FA50974B14CC861B08ECB /* APIPollingHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF54EDA6123C7E4E78D9D56B /* APIPollingHelper.swift */; }; + 6744CB1B182C5F7220B0B804 /* AuthFlowHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFC0D3ED86914DC4216CCCA /* AuthFlowHelpersTests.swift */; }; + 691619AE9A989548ABA36535 /* HitTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F669BB8F3DA862C425897705 /* HitTestView.swift */; }; + 6944E131D351784058C7D734 /* FinancialConnectionsPaymentMethodType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 191760EFAA9154C1F168E1D2 /* FinancialConnectionsPaymentMethodType.swift */; }; + 6A13B9822B48BD6C00FFA327 /* AccountPickerRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A13B9812B48BD6C00FFA327 /* AccountPickerRowView.swift */; }; + 6A13B9842B48BF4300FFA327 /* AccountPickerRowLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A13B9832B48BF4300FFA327 /* AccountPickerRowLabelView.swift */; }; + 6A13B9862B4CD04100FFA327 /* RetrieveAccountsLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A13B9852B4CD04100FFA327 /* RetrieveAccountsLoadingView.swift */; }; + 6A13B9FA2B4E182A00FFA327 /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A13B9F92B4E182A00FFA327 /* SpinnerView.swift */; }; + 6A13B9FC2B58545F00FFA327 /* person@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6A13B9FB2B58545F00FFA327 /* person@3x.png */; }; + 6A1D42FF2B1686FC005A1EB0 /* InstitutionTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1D42FE2B1686FC005A1EB0 /* InstitutionTableView.swift */; }; + 6A1D43012B1687AB005A1EB0 /* InstitutionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1D43002B1687AB005A1EB0 /* InstitutionTableViewCell.swift */; }; + 6A1D43032B16B3DC005A1EB0 /* InstitutionCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1D43022B16B3DC005A1EB0 /* InstitutionCellView.swift */; }; + 6A1D43052B17AD76005A1EB0 /* InstitutionTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1D43042B17AD76005A1EB0 /* InstitutionTableFooterView.swift */; }; + 6A1D43072B17AE37005A1EB0 /* RoundedIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1D43062B17AE37005A1EB0 /* RoundedIconView.swift */; }; + 6A1D43092B17CB04005A1EB0 /* InstitutionNoResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1D43082B17CB04005A1EB0 /* InstitutionNoResultsView.swift */; }; + 6A26A3AB2B991A4D00215510 /* FeedbackGeneratorAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A26A3AA2B991A4D00215510 /* FeedbackGeneratorAdapter.swift */; }; + 6A3739142C40558900D1F765 /* GenericInfoBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3739132C40558900D1F765 /* GenericInfoBodyView.swift */; }; + 6A3739162C4060BD00D1F765 /* AutoResizableUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3739152C4060BD00D1F765 /* AutoResizableUIView.swift */; }; + 6A384A842C24DD720044AB99 /* FinancialConnectionsGenericInfoScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A384A832C24DD720044AB99 /* FinancialConnectionsGenericInfoScreen.swift */; }; + 6A3DA1F52C34A37F005C3F6E /* GenericInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3DA1F42C34A37F005C3F6E /* GenericInfoViewController.swift */; }; + 6A3DA1F72C34B254005C3F6E /* GenericInfoFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3DA1F62C34B254005C3F6E /* GenericInfoFooterView.swift */; }; + 6A43202A2B7E8C5400A67A70 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4320292B7E8C5400A67A70 /* Constants.swift */; }; + 6A6F989C2C4F1BF00035C03D /* CreatePaneParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A6F989B2C4F1BF00035C03D /* CreatePaneParameters.swift */; }; + 6A732C9A2B61C51C00828CB1 /* ShimmeringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732C992B61C51C00828CB1 /* ShimmeringView.swift */; }; + 6A732C9C2B61C56A00828CB1 /* InstitutionTableLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732C9B2B61C56A00828CB1 /* InstitutionTableLoadingView.swift */; }; + 6A732C9E2B64787E00828CB1 /* LinkAccountPickerLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732C9D2B64787E00828CB1 /* LinkAccountPickerLoadingView.swift */; }; + 6A732CA02B6871EA00828CB1 /* panel_arrow_right@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6A732C9F2B6871E900828CB1 /* panel_arrow_right@3x.png */; }; + 6A732CA22B69821300828CB1 /* RoundedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732CA12B69821300828CB1 /* RoundedTextField.swift */; }; + 6A732CA42B69871F00828CB1 /* EmailTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732CA32B69871F00828CB1 /* EmailTextField.swift */; }; + 6A732CA62B69A46D00828CB1 /* PhoneTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732CA52B69A46D00828CB1 /* PhoneTextField.swift */; }; + 6A732CA82B69C34F00828CB1 /* PhoneCountryCodeSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732CA72B69C34F00828CB1 /* PhoneCountryCodeSelectorView.swift */; }; + 6A732CAA2B69CCDD00828CB1 /* PhoneCountryCodePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A732CA92B69CCDD00828CB1 /* PhoneCountryCodePickerView.swift */; }; + 6A78140D2B30F15400168992 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A78140C2B30F15400168992 /* SheetViewController.swift */; }; + 6A7814112B32462100168992 /* CloseConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7814102B32462100168992 /* CloseConfirmationViewController.swift */; }; + 6A7814182B361C5000168992 /* PaneLayoutView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7814172B361C5000168992 /* PaneLayoutView+Extensions.swift */; }; + 6A78141A2B45D53700168992 /* DataAccessNoticeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7814192B45D53700168992 /* DataAccessNoticeViewController.swift */; }; + 6A78141C2B462D5D00168992 /* LegalDetailsNoticeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A78141B2B462D5D00168992 /* LegalDetailsNoticeViewController.swift */; }; + 6ABFE5522B72BE630037437C /* PrepaneViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABFE5512B72BE630037437C /* PrepaneViews.swift */; }; + 6ABFE5552B74479A0037437C /* ErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABFE5542B74479A0037437C /* ErrorViewController.swift */; }; + 6ABFE5572B7449390037437C /* ErrorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABFE5562B7449390037437C /* ErrorDataSource.swift */; }; + 6ABFE5592B7451710037437C /* TerminalErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ABFE5582B7451710037437C /* TerminalErrorView.swift */; }; + 6BC6DB482984F9288944FE25 /* NetworkingSaveToLinkVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D3CAB53EC9D33831C5A48B /* NetworkingSaveToLinkVerificationViewController.swift */; }; + 6D018BB3C1253ED4C1674E0B /* ManualEntryFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C03F4DDC67B50C5E1993F6 /* ManualEntryFormView.swift */; }; + 6D29E55F6A3864ED52799169 /* InstitutionPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B3BF292FD82A198752A82EB /* InstitutionPickerViewController.swift */; }; + 6E6E30D01D4E9629DB07E97B /* FinancialConnectionsNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA6A57067FB5EF86FEBD5B3 /* FinancialConnectionsNavigationController.swift */; }; + 6FE9F171CF9A5D0EDB2035AA /* FinancialConnectionsNetworkingLinkSignup.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF7CE16FE4D5E8B889BF5D1E /* FinancialConnectionsNetworkingLinkSignup.swift */; }; + 700B745FEF43088D9E34C0E4 /* AccountPickerHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ED33E6BADC0893C3F6B22D2 /* AccountPickerHelpersTests.swift */; }; + 707C265C4179A8FEC98913FE /* ConsentBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA941D5F7178E89DE70076F /* ConsentBodyView.swift */; }; + 716E12A9AC0B790F14FB72C6 /* AccountNumberRetrievalErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6224E799E667DF223757D493 /* AccountNumberRetrievalErrorView.swift */; }; + 72BB9389206F10DE9B18E542 /* LinkAccountPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1848547B588045C776236B3B /* LinkAccountPickerViewController.swift */; }; + 7386E1F9256B23CE29BF996D /* FinancialConnectionsInstitutionSearchResultResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 921686D9A3076749E1A9E549 /* FinancialConnectionsInstitutionSearchResultResource.swift */; }; + 74CC216C8A71AD357B8AA544 /* NativeFlowDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780BC432329228B042DA97D8 /* NativeFlowDataManager.swift */; }; + 755140DEEE50DCD6E939E528 /* check@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B77DE6D7A86CC847977396A /* check@3x.png */; }; + 76FB143918C5463B587091BB /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2D765AC793D89D26B74FC4 /* STPLocalizedString.swift */; }; + 779C729BB49FD4B99DCD517B /* MarkdownBoldAttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7D318EE807701AB3FCA17D /* MarkdownBoldAttributedStringTests.swift */; }; + 77C7F9A1DD0461FA2B1B4328 /* FinancialConnectionsPartnerAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452989E2D269784006EFD18C /* FinancialConnectionsPartnerAccount.swift */; }; + 77D3B375B9DBF80BA209BC99 /* FinancialConnectionsSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05C2C5CDAA55CE700662040 /* FinancialConnectionsSessionTests.swift */; }; + 7AE7474B7AFF416B6072721C /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CF67A1F497E6CC73029CF0 /* StripeCore+Import.swift */; }; + 7DEC399FFE0BAAAB2026E684 /* PaymentAccount+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A72263B4842E5E30FF91AD /* PaymentAccount+Extensions.swift */; }; + 82FD3CEE526DE8B6519F666E /* FinancialConnectionsSessionFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A872C2B500306F775622F904 /* FinancialConnectionsSessionFetcher.swift */; }; + 846D1D7429B9E414744DEC99 /* FinancialConnectionsSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E7725EF317C3BD62ADF845 /* FinancialConnectionsSheetTests.swift */; }; + 864C5159C62C562C655B53F7 /* StripeFinancialConnections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E37D8CE9CD73443A9AAF2AE8 /* StripeFinancialConnections.framework */; }; + 87198EFD873751CA4E4B5005 /* FinancialConnectionsOAuthPrepane.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC5894A5EB74F6157C7DE95 /* FinancialConnectionsOAuthPrepane.swift */; }; + 87E22AF1E35FB63C20AEE9DF /* warning_triangle@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5D555DB0657A602274596428 /* warning_triangle@3x.png */; }; + 8927328EE28A0C94B5AB69DB /* ConsentLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0281E0221BDE01D0845DC0F9 /* ConsentLogoView.swift */; }; + 8DC6C2A239456994091BF3EE /* NetworkingLinkSignupFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72A74B9F667A3BF48253045E /* NetworkingLinkSignupFooterView.swift */; }; + 91A3583A0BDE0F8F0C4AD3E2 /* InstitutionIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939D10B20D3ECA7BF7021BF8 /* InstitutionIconView.swift */; }; + 933F9DFE970FAB4715369086 /* HostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C4ECB724BD75320A999C42 /* HostController.swift */; }; + 95B2A73AC5DA9FA64017B3CB /* NetworkingLinkSignupBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40ECB2B008FC082B4D38D2FE /* NetworkingLinkSignupBodyView.swift */; }; + 97032B101B54E6A98178FD73 /* stripe_logo@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = B4F56BF50DBF4A353D2526A6 /* stripe_logo@3x.png */; }; + 971E6F5E78BC3265CD80D0C6 /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6652EBE38C47B36962AD370A /* StripeUICore.framework */; }; + 97C528CE821C6A55D58F68A4 /* ConsentFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E58AE51252DA4597DC82988 /* ConsentFooterView.swift */; }; + 99F41681B77ECB0090F34E31 /* SFSafariViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D38B3C816EAB38AD242B064 /* SFSafariViewController+Extensions.swift */; }; + 9AF6EC34D666BEB3C1397092 /* BulletPointLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 591E8073D2AD30115ABDB60F /* BulletPointLabelView.swift */; }; + 9B2CAE99344C26D524EDCF26 /* ModalPresentationWrapperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587CD174831344F15ADB538D /* ModalPresentationWrapperViewController.swift */; }; + 9CE29EA549C4BFA447AB82E0 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C650E82A4195A7566AA54298 /* StripeCore.framework */; }; + 9E0044ABEC04E2A8C50E3658 /* FinancialConnectionsSessionManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 429F985168AE9F9D700AE37B /* FinancialConnectionsSessionManifest.swift */; }; + A10B5A3E5E8AE8767CF09C15 /* AccountPickerSelectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283469CD0298E3AFCFDAF10F /* AccountPickerSelectionListView.swift */; }; + A156FACA60231988F247F6F4 /* FinancialConnectionsSession_only_both_missing.json in Resources */ = {isa = PBXBuildFile; fileRef = 1DF07A1AAD6B39033F0B86FD /* FinancialConnectionsSession_only_both_missing.json */; }; + A34AB3AC6D071605CABFFC9B /* UIViewController+KeyboardAvoiding.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB3C49A180D1697B03C79A59 /* UIViewController+KeyboardAvoiding.swift */; }; + A573468B2800DABF384CAB43 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BCC4356AE3295B4A2F4A28 /* Image.swift */; }; + A79D6A26EE9FF96D24F4AC5C /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 267B3586136203186882F5CE /* NSAttributedString+Extensions.swift */; }; + A9F9E63FD6B72F5552A8A850 /* ResetFlowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA81F9910A88A7DEB0F0BFD /* ResetFlowViewController.swift */; }; + AA80602323C28AFAC391358D /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E813BE6B34901E4E050FFE13 /* TimeInterval+Extensions.swift */; }; + AB5AFAC3C70D6195075DE5AE /* FinancialConnectionsBulletPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3FD6A7D1638E42AA00C88C4 /* FinancialConnectionsBulletPoint.swift */; }; + AB7C9A26484953762FFBB4A5 /* FinancialConnectionsWebFlowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BD07CE6F99D7FAE83FC5CCC /* FinancialConnectionsWebFlowViewController.swift */; }; + ABB28C3F6604C2BA2FCA079D /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F3A660CB2E9651947FE6D0A /* String+Extensions.swift */; }; + ACD21F21C6E42706A882A1AE /* FinancialConnectionsSession_only_la.json in Resources */ = {isa = PBXBuildFile; fileRef = AA01BC4016BF8788633CCAD9 /* FinancialConnectionsSession_only_la.json */; }; + AD5B496425E2993C87F0B770 /* PrepaneImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54F545087F12B09FF416991 /* PrepaneImageView.swift */; }; + B271AAF41C9FE6AE392B88D3 /* FinancialConnectionsMixedOAuthParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3BF4CD26CEAE792AC2A7313 /* FinancialConnectionsMixedOAuthParams.swift */; }; + B2970FE2753A4D79E428BA73 /* SuccessDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C15A30C40F34CE330F89C41 /* SuccessDataSource.swift */; }; + B45B8DC3DAACDD5F04B1B1BE /* SuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3F67BE6E46ED018EB8C3FD /* SuccessViewController.swift */; }; + B5EEF34D158C08A1745FA150 /* ContinueStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA55452373FD735983F3690B /* ContinueStateView.swift */; }; + B9A24A47454134F2B869C969 /* FinancialConnectionsConsent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FD9739F1AA7CBA76DD3E1E2 /* FinancialConnectionsConsent.swift */; }; + BCEA321423DF0E7674C2544C /* APIVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D95E5F34BDEE0237F52DA0A /* APIVersion.swift */; }; + BD3C87E03EB44F7D1C11664C /* spinner@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 57B289E803B7A53B000D7919 /* spinner@3x.png */; }; + BF5F964E1CA6312755D4161E /* SessionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965814B0C5F3D13158E610E3 /* SessionFetcherTests.swift */; }; + BFF222008EEEDC3FACE342D9 /* AccountPickerFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ACAB1B6DB88D74F5ECC1C6D /* AccountPickerFooterView.swift */; }; + C0831318A33A32BF2EAB641A /* AccountPickerHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1362591D12A04CA663A69A47 /* AccountPickerHelpers.swift */; }; + C128C1681E46F0F12EB4EB9F /* NetworkingLinkStepUpVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE6D662AB9854F3BDB90D8FD /* NetworkingLinkStepUpVerificationViewController.swift */; }; + C19996D0AC7E046DA87B6B32 /* ManualEntryValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C7FDF59D906EA5C6B7A514 /* ManualEntryValidatorTests.swift */; }; + C1A079E8E76A02EBCB2588DA /* AccountPickerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D715516C6703A780913E66EB /* AccountPickerDataSource.swift */; }; + C258E0D849083BCC8A9B5068 /* HostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0863AF9E2F9BD7C026FE59E /* HostViewController.swift */; }; + C3338FA5019EC8E99E2BA62F /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064D0E3A3AC71FAA60B54FC5 /* Helpers.swift */; }; + C38BEDD99477C83C91B105DD /* AccountPickerAccountLoadErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463549CECD379484842033E3 /* AccountPickerAccountLoadErrorView.swift */; }; + C39214EA5995D85B847406BE /* SuccessFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD828AB80DE41DE11D38AF5C /* SuccessFooterView.swift */; }; + C55F79F4B85E1EB8730B02C6 /* LinkAccountPickerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 524D3116FDA5A3AD68075AA4 /* LinkAccountPickerDataSource.swift */; }; + C59DBA5A86A3331113D6ED7E /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60B7CFC14964440E8AA670A9 /* LoadingView.swift */; }; + C5FEC806A31021B7D119A73C /* NetworkingLinkVerificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD861E4EB8BA294545B7651 /* NetworkingLinkVerificationViewController.swift */; }; + C61D5957D3276991795F7D16 /* FinancialConnectionsSheetAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6038978C79785C18257CD74 /* FinancialConnectionsSheetAnalytics.swift */; }; + C6B99A1C34886D3B5E1AF1A2 /* InstitutionSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DA1C1B311E06C1165C6F6A2 /* InstitutionSearchBar.swift */; }; + C747113C75AC92643B283CBD /* close@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F1C7A2FE53419CB29CBB6C08 /* close@3x.png */; }; + C7D2763ACCE2CC71E788E18F /* FinancialConnectionsLegalDetailsNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07526E95D85120F6492E78AE /* FinancialConnectionsLegalDetailsNotice.swift */; }; + C906FC4DE38F16032B787607 /* ResetFlowDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1C78684DD0B2D168C86229 /* ResetFlowDataSource.swift */; }; + CB734C25A19D38A87876FB2B /* FinancialConnectionsAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73AB8A480620B5C3567F453C /* FinancialConnectionsAnalyticsTest.swift */; }; + CBEAB081DD7353928F485071 /* APIPollingHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710183EE587F6FDA077FC150 /* APIPollingHelperTests.swift */; }; + CBF7BE2271D309F2B1E794CC /* FinancialConnectionsDataAccessNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32ED8A7E94822F14AD94A698 /* FinancialConnectionsDataAccessNotice.swift */; }; + CF47070B2A4CA27FEE9AE5FA /* generic_error@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6A764CF4DB5B5F6F488132A8 /* generic_error@3x.png */; }; + D0C1EF46A418A8F8774B7418 /* FinancialConnectionsSession_both_accounts_la.json in Resources */ = {isa = PBXBuildFile; fileRef = F6CF7F1005B57D566E139DE3 /* FinancialConnectionsSession_both_accounts_la.json */; }; + D0C6D94867FA04B1BF80D56D /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9AB787FE87EDD702B1BBF09 /* StripeCoreTestUtils.framework */; }; + D10FB0DAC5E452D4569CEA14 /* back_arrow@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C4DA5D0EB4ED760B3F9818C5 /* back_arrow@3x.png */; }; + D3AB52D5AE87FE51642C50C1 /* ExperimentHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C2E9D1B7C962C46F7E0002A /* ExperimentHelper.swift */; }; + D50E771043434AD80EA28628 /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6652EBE38C47B36962AD370A /* StripeUICore.framework */; }; + D926228B6C7601AE4C806C93 /* FinancialConnectionsPaymentAccountResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF9E2D4C9D7684B02AD6037A /* FinancialConnectionsPaymentAccountResource.swift */; }; + D936C8A9F6E018DB144A5B0A /* FinancialConnectionsSynchronize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4667E3861CDEC3A41B757714 /* FinancialConnectionsSynchronize.swift */; }; + D949AE695F3288F84258BACD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF6810BAADB14ACB95216C2B /* Localizable.strings */; }; + D9D84D6FF624CF4363D87CEB /* InstitutionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ED1CF95D441821773EA68EE /* InstitutionDataSource.swift */; }; + D9F35B3B31CA2E52055D6B1D /* StringExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF731140836AE438C7F4F6AB /* StringExtensionsTests.swift */; }; + DAA51ABB496551074DBA1A20 /* FinancialConnectionsNetworkedAccountsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3979E84D319D3ED1C3273D74 /* FinancialConnectionsNetworkedAccountsResponse.swift */; }; + DC4DFC847378AC9E9112B443 /* AuthenticationSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596A401ABA089532A5006584 /* AuthenticationSessionManager.swift */; }; + E3F62D2F9C344A1178030E8E /* AttachLinkedPaymentAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA62F9B77C6A1D1B12F02CF5 /* AttachLinkedPaymentAccountViewController.swift */; }; + E4D00DB842047E595DD85BEF /* CheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97EE2BCD7B861ACA49DB56CD /* CheckboxView.swift */; }; + E637387728FA1597B1B51E5D /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EA7801B85973F10F65DDB6 /* UIImage+Extensions.swift */; }; + E760C94B619A8934D1D5E1D0 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC23DAC91962D0D8A713D37 /* UIColor+Extensions.swift */; }; + E85DCFCA61299EF27B3201CF /* FinancialConnectionsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D8397DB43DEC09BDF66E8A /* FinancialConnectionsSheet.swift */; }; + E9866D5CA186A242BBEA69E1 /* ConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AAB10AEE7A8EC5C9C53FFA /* ConsentViewController.swift */; }; + EABA08E892B087D89C97AE4F /* FinancialConnectionsEvent+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B23D2BD3CD4071A40D9AE9 /* FinancialConnectionsEvent+Extensions.swift */; }; + EC74B719F0FA1A977EF4708C /* FinancialConnectionsAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359BF8ACFB35A16EBD96C4F0 /* FinancialConnectionsAccount.swift */; }; + ED818E10F37230678B9B73CC /* SoftLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90A06A97D714450321A5D76D /* SoftLinkTests.swift */; }; + F0397F4E1D6A91416897F45E /* brandicon_default@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7413C9D80DF7190CA6FB82EE /* brandicon_default@3x.png */; }; + F03F840B9E896F1B09742191 /* PartnerAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25BDE722D8A827955C3182E8 /* PartnerAuthViewController.swift */; }; + F0495231F4C70E054149C03A /* LinkAccountPickerNewAccountRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFF070262170A321F5622CCF /* LinkAccountPickerNewAccountRowView.swift */; }; + F0FB346A0F86C3561CD3C048 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F27D0FACAE9D4F4D15A73 /* UITableView+Extensions.swift */; }; + F10147CF75C2A09D66CB5C14 /* cancel_circle@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = A4CC00446B086B2987114099 /* cancel_circle@3x.png */; }; + F22DE4B785D51B318A1A3D08 /* FinancialConnectionsSheetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0737DE86515E172909366F /* FinancialConnectionsSheetError.swift */; }; + F65E8D16DE691EB6C99C4521 /* Button+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4A84F0646AD673029CB6FC /* Button+Extensions.swift */; }; + F67624595BD2CD7B6793BFDA /* FinancialConnectionsImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C93F7139E9BFB044902962D0 /* FinancialConnectionsImage.swift */; }; + F7C10A1AB247D0F6E111DE36 /* NetworkingLinkLoginWarmupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 252DA1FE0574822605438AB4 /* NetworkingLinkLoginWarmupViewController.swift */; }; + FBF513C7F73002FA30CC7C21 /* ConsumerSessionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C32F1447E47221DC0B7095 /* ConsumerSessionModels.swift */; }; + FCC5A360E0064887DB28F5C6 /* StripeFinancialConnections.h in Headers */ = {isa = PBXBuildFile; fileRef = F96A3BA5CCB8DCCFA3126974 /* StripeFinancialConnections.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FE268512851E63E4E111DECD /* FinancialConnectionsSDKImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 248D51F7AADE404E49957DDA /* FinancialConnectionsSDKImplementation.swift */; }; + FFD76E78070ECBB283D43D5E /* NetworkingLinkLoginWarmupBodyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13983D90462EB946B2A178C6 /* NetworkingLinkLoginWarmupBodyView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 99584176FCBCA6DC9B8E22E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3D00B888AF0B02587576A83F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 44C90013B7C82C80A2F69956; + remoteInfo = StripeFinancialConnections; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4E8F557BB03B30AB6BCE5DAC /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 6EAD6F45EDAE7B645FDC823B /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0281E0221BDE01D0845DC0F9 /* ConsentLogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentLogoView.swift; sourceTree = ""; }; + 064D0E3A3AC71FAA60B54FC5 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; + 07526E95D85120F6492E78AE /* FinancialConnectionsLegalDetailsNotice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLegalDetailsNotice.swift; sourceTree = ""; }; + 07BCA9D23511A3494C82B632 /* bullet@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "bullet@3x.png"; sourceTree = ""; }; + 08C32F1447E47221DC0B7095 /* ConsumerSessionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsumerSessionModels.swift; sourceTree = ""; }; + 091D43608583C7BE5E444C2C /* NetworkingLinkLoginWarmupDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkLoginWarmupDataSource.swift; sourceTree = ""; }; + 0DA7868C9DD47582244B47C8 /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 0DD5619FDD6C8D85FC352F99 /* LinkAccountPickerBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountPickerBodyView.swift; sourceTree = ""; }; + 0F4FC108D8C162EEE1EEA97E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 106427315CD279EAAD7D1B74 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + 1362591D12A04CA663A69A47 /* AccountPickerHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerHelpers.swift; sourceTree = ""; }; + 13983D90462EB946B2A178C6 /* NetworkingLinkLoginWarmupBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkLoginWarmupBodyView.swift; sourceTree = ""; }; + 13AAB10AEE7A8EC5C9C53FFA /* ConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentViewController.swift; sourceTree = ""; }; + 1407DD9E95ADFE143FA046E4 /* FlowRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowRouter.swift; sourceTree = ""; }; + 14CED33665ED3D8EE8D5D7B7 /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + 1848547B588045C776236B3B /* LinkAccountPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountPickerViewController.swift; sourceTree = ""; }; + 191760EFAA9154C1F168E1D2 /* FinancialConnectionsPaymentMethodType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsPaymentMethodType.swift; sourceTree = ""; }; + 1CD19E0601599AE89976DB4D /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + 1CE32B7E492EFD8143F687F2 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + 1CFE14532C10471EC61BB05A /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + 1D38B3C816EAB38AD242B064 /* SFSafariViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SFSafariViewController+Extensions.swift"; sourceTree = ""; }; + 1DF07A1AAD6B39033F0B86FD /* FinancialConnectionsSession_only_both_missing.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FinancialConnectionsSession_only_both_missing.json; sourceTree = ""; }; + 1E58AE51252DA4597DC82988 /* ConsentFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentFooterView.swift; sourceTree = ""; }; + 1E80DD2D042B327D9756E083 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 1F2C7AE6509C80B6F30662AA /* FinancialConnectionsCustomManualEntryRequiredError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsCustomManualEntryRequiredError.swift; sourceTree = ""; }; + 1F9D6F0CC79B7949D037DE66 /* NetworkingLinkStepUpVerificationBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkStepUpVerificationBodyView.swift; sourceTree = ""; }; + 20E7725EF317C3BD62ADF845 /* FinancialConnectionsSheetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSheetTests.swift; sourceTree = ""; }; + 24701CABF53C21DD7BCF3E48 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + 248D51F7AADE404E49957DDA /* FinancialConnectionsSDKImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSDKImplementation.swift; sourceTree = ""; }; + 24D4A72B4CCA677F45C29A5C /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + 252DA1FE0574822605438AB4 /* NetworkingLinkLoginWarmupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkLoginWarmupViewController.swift; sourceTree = ""; }; + 25BDE722D8A827955C3182E8 /* PartnerAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerAuthViewController.swift; sourceTree = ""; }; + 267B3586136203186882F5CE /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = ""; }; + 283469CD0298E3AFCFDAF10F /* AccountPickerSelectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerSelectionListView.swift; sourceTree = ""; }; + 2C0F907D5B0AC58BE7A454BA /* StripeFinancialConnectionsBundleLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeFinancialConnectionsBundleLocator.swift; sourceTree = ""; }; + 2C10E841FF9EBFEA8C2E30AF /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 2D95E5F34BDEE0237F52DA0A /* APIVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIVersion.swift; sourceTree = ""; }; + 313F5F7E2B0BE5D100BD98A9 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; + 314462DF7856349FF9775598 /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + 31AD3BE82B0C2F000080C800 /* ScreenNativeScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenNativeScale.swift; sourceTree = ""; }; + 31CDFC332BA8E61F00B3DD91 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 32ED8A7E94822F14AD94A698 /* FinancialConnectionsDataAccessNotice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsDataAccessNotice.swift; sourceTree = ""; }; + 33B1E2861FA7CA86FF79236C /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + 34452D5FDC1ED566A13427FE /* PaneLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaneLayoutView.swift; path = StripeFinancialConnections/Source/Native/Shared/PaneLayoutView.swift; sourceTree = SOURCE_ROOT; }; + 359BF8ACFB35A16EBD96C4F0 /* FinancialConnectionsAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAccount.swift; sourceTree = ""; }; + 3979E84D319D3ED1C3273D74 /* FinancialConnectionsNetworkedAccountsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsNetworkedAccountsResponse.swift; sourceTree = ""; }; + 3BD07CE6F99D7FAE83FC5CCC /* FinancialConnectionsWebFlowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsWebFlowViewController.swift; sourceTree = ""; }; + 3ED33E6BADC0893C3F6B22D2 /* AccountPickerHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerHelpersTests.swift; sourceTree = ""; }; + 3FD9739F1AA7CBA76DD3E1E2 /* FinancialConnectionsConsent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsConsent.swift; sourceTree = ""; }; + 40ECB2B008FC082B4D38D2FE /* NetworkingLinkSignupBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkSignupBodyView.swift; sourceTree = ""; }; + 429F985168AE9F9D700AE37B /* FinancialConnectionsSessionManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSessionManifest.swift; sourceTree = ""; }; + 452989E2D269784006EFD18C /* FinancialConnectionsPartnerAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsPartnerAccount.swift; sourceTree = ""; }; + 463549CECD379484842033E3 /* AccountPickerAccountLoadErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerAccountLoadErrorView.swift; sourceTree = ""; }; + 4667E3861CDEC3A41B757714 /* FinancialConnectionsSynchronize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSynchronize.swift; sourceTree = ""; }; + 490B00102C93582900B1A489 /* FinancialConnectionsAccountPickerPane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAccountPickerPane.swift; sourceTree = ""; }; + 492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsWebFlowTests.swift; sourceTree = ""; }; + 492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestModeAutofillBannerView.swift; sourceTree = ""; }; + 492651672C25C0C2001DDBCA /* info@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "info@3x.png"; sourceTree = ""; }; + 494D62062C45B9B700106519 /* link_logo@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "link_logo@3x.png"; sourceTree = ""; }; + 495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsTheme.swift; sourceTree = ""; }; + 496A6AE62C29E0BB00D34F8E /* testmode@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testmode@3x.png"; sourceTree = ""; }; + 497142BB2C514B08000DFA64 /* FlowRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowRouterTests.swift; sourceTree = ""; }; + 49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClientTests.swift; sourceTree = ""; }; + 49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLinkLoginPane.swift; sourceTree = ""; }; + 49C911332C597EAF00589E0D /* LinkLoginDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkLoginDataSource.swift; sourceTree = ""; }; + 49C911352C597EAF00589E0D /* LinkLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkLoginViewController.swift; sourceTree = ""; }; + 49C9113A2C59932300589E0D /* LinkSignupFormView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LinkSignupFormView.swift; path = StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/LinkSignupFormView.swift; sourceTree = SOURCE_ROOT; }; + 49F047522C63B430006BAD3E /* StripeSchemeAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeSchemeAddress.swift; sourceTree = ""; }; + 49F047542C63C2E5006BAD3E /* FinancialConnectionsPaymentDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsPaymentDetails.swift; sourceTree = ""; }; + 4A7B146AA6BF44921A249DB8 /* EmptyFinancialConnectionsAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyFinancialConnectionsAPIClient.swift; sourceTree = ""; }; + 4AFBF95DAE0783010A17EB58 /* FinancialConnectionsSession_only_accounts.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FinancialConnectionsSession_only_accounts.json; sourceTree = ""; }; + 4BFCD9C339634B71FC8F85E9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 4C15A30C40F34CE330F89C41 /* SuccessDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessDataSource.swift; sourceTree = ""; }; + 4DA1C1B311E06C1165C6F6A2 /* InstitutionSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionSearchBar.swift; sourceTree = ""; }; + 4E2EAD7059FF8358E674774A /* FinancialConnectionsAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClient.swift; sourceTree = ""; }; + 4E4A84F0646AD673029CB6FC /* Button+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Button+Extensions.swift"; sourceTree = ""; }; + 4E7D318EE807701AB3FCA17D /* MarkdownBoldAttributedStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownBoldAttributedStringTests.swift; sourceTree = ""; }; + 50B4E948868910ADA557F50D /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + 518F1F230FD4DF68E683C728 /* AttachLinkedPaymentAccountDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachLinkedPaymentAccountDataSource.swift; sourceTree = ""; }; + 51D8397DB43DEC09BDF66E8A /* FinancialConnectionsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSheet.swift; sourceTree = ""; }; + 51EEC3A9E3BC863ED054B1DC /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 524D3116FDA5A3AD68075AA4 /* LinkAccountPickerDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountPickerDataSource.swift; sourceTree = ""; }; + 54CF67A1F497E6CC73029CF0 /* StripeCore+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeCore+Import.swift"; sourceTree = ""; }; + 57B289E803B7A53B000D7919 /* spinner@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "spinner@3x.png"; sourceTree = ""; }; + 587CD174831344F15ADB538D /* ModalPresentationWrapperViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationWrapperViewController.swift; sourceTree = ""; }; + 591E8073D2AD30115ABDB60F /* BulletPointLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BulletPointLabelView.swift; sourceTree = ""; }; + 596A401ABA089532A5006584 /* AuthenticationSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSessionManager.swift; sourceTree = ""; }; + 5AC5D8EE52FE5D305F78E3A0 /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + 5B65388786D25271A87D34CE /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 5B77DE6D7A86CC847977396A /* check@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "check@3x.png"; sourceTree = ""; }; + 5C0737DE86515E172909366F /* FinancialConnectionsSheetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSheetError.swift; sourceTree = ""; }; + 5C09425306344278C7B55089 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 5C837C27C2577391B91FF0E5 /* FinancialConnectionsAuthSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAuthSession.swift; sourceTree = ""; }; + 5D1C78684DD0B2D168C86229 /* ResetFlowDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetFlowDataSource.swift; sourceTree = ""; }; + 5D555DB0657A602274596428 /* warning_triangle@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "warning_triangle@3x.png"; sourceTree = ""; }; + 5E48DB3155C1546B196DF97B /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + 5F856808B78F4F1975959805 /* NativeFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFlowController.swift; sourceTree = ""; }; + 60B7CFC14964440E8AA670A9 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; + 6224E799E667DF223757D493 /* AccountNumberRetrievalErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNumberRetrievalErrorView.swift; sourceTree = ""; }; + 65BCC4356AE3295B4A2F4A28 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; + 6652EBE38C47B36962AD370A /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 66D2857E68EA69AC6F658BEA /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + 66D3CAB53EC9D33831C5A48B /* NetworkingSaveToLinkVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingSaveToLinkVerificationViewController.swift; sourceTree = ""; }; + 6A13B9812B48BD6C00FFA327 /* AccountPickerRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerRowView.swift; sourceTree = ""; }; + 6A13B9832B48BF4300FFA327 /* AccountPickerRowLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerRowLabelView.swift; sourceTree = ""; }; + 6A13B9852B4CD04100FFA327 /* RetrieveAccountsLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveAccountsLoadingView.swift; sourceTree = ""; }; + 6A13B9F92B4E182A00FFA327 /* SpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; + 6A13B9FB2B58545F00FFA327 /* person@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "person@3x.png"; sourceTree = ""; }; + 6A1D42FE2B1686FC005A1EB0 /* InstitutionTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionTableView.swift; sourceTree = ""; }; + 6A1D43002B1687AB005A1EB0 /* InstitutionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionTableViewCell.swift; sourceTree = ""; }; + 6A1D43022B16B3DC005A1EB0 /* InstitutionCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionCellView.swift; sourceTree = ""; }; + 6A1D43042B17AD76005A1EB0 /* InstitutionTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionTableFooterView.swift; sourceTree = ""; }; + 6A1D43062B17AE37005A1EB0 /* RoundedIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedIconView.swift; sourceTree = ""; }; + 6A1D43082B17CB04005A1EB0 /* InstitutionNoResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionNoResultsView.swift; sourceTree = ""; }; + 6A26A3AA2B991A4D00215510 /* FeedbackGeneratorAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackGeneratorAdapter.swift; sourceTree = ""; }; + 6A3739132C40558900D1F765 /* GenericInfoBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericInfoBodyView.swift; sourceTree = ""; }; + 6A3739152C4060BD00D1F765 /* AutoResizableUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoResizableUIView.swift; sourceTree = ""; }; + 6A384A832C24DD720044AB99 /* FinancialConnectionsGenericInfoScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsGenericInfoScreen.swift; sourceTree = ""; }; + 6A3DA1F42C34A37F005C3F6E /* GenericInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericInfoViewController.swift; sourceTree = ""; }; + 6A3DA1F62C34B254005C3F6E /* GenericInfoFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericInfoFooterView.swift; sourceTree = ""; }; + 6A4320292B7E8C5400A67A70 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 6A6F989B2C4F1BF00035C03D /* CreatePaneParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePaneParameters.swift; sourceTree = ""; }; + 6A732C992B61C51C00828CB1 /* ShimmeringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmeringView.swift; sourceTree = ""; }; + 6A732C9B2B61C56A00828CB1 /* InstitutionTableLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionTableLoadingView.swift; sourceTree = ""; }; + 6A732C9D2B64787E00828CB1 /* LinkAccountPickerLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountPickerLoadingView.swift; sourceTree = ""; }; + 6A732C9F2B6871E900828CB1 /* panel_arrow_right@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "panel_arrow_right@3x.png"; sourceTree = ""; }; + 6A732CA12B69821300828CB1 /* RoundedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedTextField.swift; sourceTree = ""; }; + 6A732CA32B69871F00828CB1 /* EmailTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailTextField.swift; sourceTree = ""; }; + 6A732CA52B69A46D00828CB1 /* PhoneTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneTextField.swift; sourceTree = ""; }; + 6A732CA72B69C34F00828CB1 /* PhoneCountryCodeSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCountryCodeSelectorView.swift; sourceTree = ""; }; + 6A732CA92B69CCDD00828CB1 /* PhoneCountryCodePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCountryCodePickerView.swift; sourceTree = ""; }; + 6A764CF4DB5B5F6F488132A8 /* generic_error@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "generic_error@3x.png"; sourceTree = ""; }; + 6A78140C2B30F15400168992 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = ""; }; + 6A7814102B32462100168992 /* CloseConfirmationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseConfirmationViewController.swift; sourceTree = ""; }; + 6A7814172B361C5000168992 /* PaneLayoutView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaneLayoutView+Extensions.swift"; sourceTree = ""; }; + 6A7814192B45D53700168992 /* DataAccessNoticeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataAccessNoticeViewController.swift; sourceTree = ""; }; + 6A78141B2B462D5D00168992 /* LegalDetailsNoticeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalDetailsNoticeViewController.swift; sourceTree = ""; }; + 6ABFE5512B72BE630037437C /* PrepaneViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepaneViews.swift; sourceTree = ""; }; + 6ABFE5542B74479A0037437C /* ErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewController.swift; sourceTree = ""; }; + 6ABFE5562B7449390037437C /* ErrorDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDataSource.swift; sourceTree = ""; }; + 6ABFE5582B7451710037437C /* TerminalErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalErrorView.swift; sourceTree = ""; }; + 6B70A0C4DBFE46805549CF8B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 6C81D547F6BAD96C62E1E4D3 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + 6CDEF702710EEA29BA3DC653 /* NetworkingLinkSignupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkSignupViewController.swift; sourceTree = ""; }; + 6DA6A57067FB5EF86FEBD5B3 /* FinancialConnectionsNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsNavigationController.swift; sourceTree = ""; }; + 6DDB75C69FE9322C745943B3 /* NetworkingLinkVerificationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkVerificationDataSource.swift; sourceTree = ""; }; + 6E2D765AC793D89D26B74FC4 /* STPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = ""; }; + 6FA941D5F7178E89DE70076F /* ConsentBodyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentBodyView.swift; sourceTree = ""; }; + 710183EE587F6FDA077FC150 /* APIPollingHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIPollingHelperTests.swift; sourceTree = ""; }; + 72A74B9F667A3BF48253045E /* NetworkingLinkSignupFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkSignupFooterView.swift; sourceTree = ""; }; + 72DEDE8871A732D603E96E2B /* AccountPickerSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerSelectionView.swift; sourceTree = ""; }; + 73AB8A480620B5C3567F453C /* FinancialConnectionsAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAnalyticsTest.swift; sourceTree = ""; }; + 73C03F4DDC67B50C5E1993F6 /* ManualEntryFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryFormView.swift; sourceTree = ""; }; + 7413C9D80DF7190CA6FB82EE /* brandicon_default@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "brandicon_default@3x.png"; sourceTree = ""; }; + 742D94AC4B2D17F8282A6788 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + 75B23D2BD3CD4071A40D9AE9 /* FinancialConnectionsEvent+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FinancialConnectionsEvent+Extensions.swift"; sourceTree = ""; }; + 77A71EBB1B98CD285DD17D5F /* AccountFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFetcherTests.swift; sourceTree = ""; }; + 780BC432329228B042DA97D8 /* NativeFlowDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFlowDataManager.swift; sourceTree = ""; }; + 782A419DCF59BE6AB6439D04 /* add@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "add@3x.png"; sourceTree = ""; }; + 7AFC0D3ED86914DC4216CCCA /* AuthFlowHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFlowHelpersTests.swift; sourceTree = ""; }; + 7C2E9D1B7C962C46F7E0002A /* ExperimentHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentHelper.swift; sourceTree = ""; }; + 7C402C24A15DC6167E2C593F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7F3A660CB2E9651947FE6D0A /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; + 81304AD5BE5CCA10D1A866E0 /* ConsentDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentDataSource.swift; sourceTree = ""; }; + 8301F7BA1FF90D131AE96E10 /* ManualEntryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryViewController.swift; sourceTree = ""; }; + 83E0A15A666F20DA97F128EA /* FinancialConnectionsFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsFont.swift; sourceTree = ""; }; + 846D9EF58B02C69F9629AE79 /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + 88F7731972F5FB12FD4FA48B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 8B3BF292FD82A198752A82EB /* InstitutionPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionPickerViewController.swift; sourceTree = ""; }; + 8FE8BCF5A9FF2B9392A755EA /* FinancialConnectionsInstitution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsInstitution.swift; sourceTree = ""; }; + 90A06A97D714450321A5D76D /* SoftLinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLinkTests.swift; sourceTree = ""; }; + 912E3AA36B68492A69019AEA /* AccountPickerNoAccountEligibleErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerNoAccountEligibleErrorView.swift; sourceTree = ""; }; + 921686D9A3076749E1A9E549 /* FinancialConnectionsInstitutionSearchResultResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsInstitutionSearchResultResource.swift; sourceTree = ""; }; + 925B7F2CBACCB2346CD0CDFC /* NetworkingLinkStepUpVerificationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkStepUpVerificationDataSource.swift; sourceTree = ""; }; + 9312AAE1BFF1D9BBEA44E8AA /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 939D10B20D3ECA7BF7021BF8 /* InstitutionIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionIconView.swift; sourceTree = ""; }; + 93C4ECB724BD75320A999C42 /* HostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostController.swift; sourceTree = ""; }; + 94869BACB486153419B30DE5 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 965814B0C5F3D13158E610E3 /* SessionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionFetcherTests.swift; sourceTree = ""; }; + 97948A848C99ACD1A498F841 /* AttributedTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedTextView.swift; sourceTree = ""; }; + 97CE72E38D41E86E0A1FAE9F /* AccountPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerViewController.swift; sourceTree = ""; }; + 97EE2BCD7B861ACA49DB56CD /* CheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxView.swift; sourceTree = ""; }; + 9ACAB1B6DB88D74F5ECC1C6D /* AccountPickerFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerFooterView.swift; sourceTree = ""; }; + 9EA0AA05BC9FC60A06AC1B5E /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 9ED1CF95D441821773EA68EE /* InstitutionDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstitutionDataSource.swift; sourceTree = ""; }; + A00D518C15FF3FAED7C193C2 /* HitTestStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestStackView.swift; sourceTree = ""; }; + A0863AF9E2F9BD7C026FE59E /* HostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostViewController.swift; sourceTree = ""; }; + A37D7E687494FAE048945144 /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + A3A2815DF2EE9447CE7A3826 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + A4CC00446B086B2987114099 /* cancel_circle@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cancel_circle@3x.png"; sourceTree = ""; }; + A54F545087F12B09FF416991 /* PrepaneImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrepaneImageView.swift; sourceTree = ""; }; + A6038978C79785C18257CD74 /* FinancialConnectionsSheetAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSheetAnalytics.swift; sourceTree = ""; }; + A680CB323B9139F838643EC1 /* LinkAccountPickerFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountPickerFooterView.swift; sourceTree = ""; }; + A872C2B500306F775622F904 /* FinancialConnectionsSessionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSessionFetcher.swift; sourceTree = ""; }; + A9B31B3A45DD8AAFD6F08820 /* AuthFlowHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFlowHelpers.swift; sourceTree = ""; }; + AA01BC4016BF8788633CCAD9 /* FinancialConnectionsSession_only_la.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FinancialConnectionsSession_only_la.json; sourceTree = ""; }; + AC7FED22D9EAC568EA6B35EB /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + ACEF0BAF1A5BBA3061C15A09 /* FinancialConnectionsSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSession.swift; sourceTree = ""; }; + AFF070262170A321F5622CCF /* LinkAccountPickerNewAccountRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkAccountPickerNewAccountRowView.swift; sourceTree = ""; }; + B2A72263B4842E5E30FF91AD /* PaymentAccount+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentAccount+Extensions.swift"; sourceTree = ""; }; + B2B74140FCD8F5871F42C881 /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + B3FD6A7D1638E42AA00C88C4 /* FinancialConnectionsBulletPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsBulletPoint.swift; sourceTree = ""; }; + B4F56BF50DBF4A353D2526A6 /* stripe_logo@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stripe_logo@3x.png"; sourceTree = ""; }; + B52F27D0FACAE9D4F4D15A73 /* UITableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; + B5FFA1B806BC6AD3500B0567 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + B83A2749140B4E129CEF39C4 /* TerminalErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalErrorViewController.swift; sourceTree = ""; }; + BC4D2368AC577A5233DEC72C /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + BD4C39F5F9AF440B13F51A81 /* ManualEntryDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryDataSource.swift; sourceTree = ""; }; + BF7E41313B709F87B549D85F /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = ""; }; + C0430E0E195DD128FA2D5F86 /* NetworkingOTPDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingOTPDataSource.swift; sourceTree = ""; }; + C0467CE507A92557C72885DF /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + C3BDFEB5860F73D4CD90907A /* NetworkingSaveToLinkVerificationDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingSaveToLinkVerificationDataSource.swift; sourceTree = ""; }; + C4DA5D0EB4ED760B3F9818C5 /* back_arrow@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "back_arrow@3x.png"; sourceTree = ""; }; + C650E82A4195A7566AA54298 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C88FD9148F64D2AA8989D361 /* ManualEntryErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryErrorView.swift; sourceTree = ""; }; + C8AFA09E86048B4325C36CC8 /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + C93F7139E9BFB044902962D0 /* FinancialConnectionsImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsImage.swift; sourceTree = ""; }; + CA2DA47ECE153F888FA675CE /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + CB3C49A180D1697B03C79A59 /* UIViewController+KeyboardAvoiding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+KeyboardAvoiding.swift"; sourceTree = ""; }; + CDD861E4EB8BA294545B7651 /* NetworkingLinkVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkVerificationViewController.swift; sourceTree = ""; }; + CE10909F3FC7D60E13B65226 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + CEC1BC95816DAD5AE9680662 /* FinancialConnectionsAccountFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAccountFetcher.swift; sourceTree = ""; }; + CF731140836AE438C7F4F6AB /* StringExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionsTests.swift; sourceTree = ""; }; + CF7CE16FE4D5E8B889BF5D1E /* FinancialConnectionsNetworkingLinkSignup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsNetworkingLinkSignup.swift; sourceTree = ""; }; + CF80A9614EB3ADA9E81397F8 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + CFEEBA73EBBCE02A50B2DB7A /* StripeFinancialConnectionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeFinancialConnectionsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D2C62B6AA6891A4214E0754E /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + D2EA7801B85973F10F65DDB6 /* UIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; + D3BF4CD26CEAE792AC2A7313 /* FinancialConnectionsMixedOAuthParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsMixedOAuthParams.swift; sourceTree = ""; }; + D688616FDC435586025D2023 /* NetworkingLinkSignupDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkSignupDataSource.swift; sourceTree = ""; }; + D715516C6703A780913E66EB /* AccountPickerDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountPickerDataSource.swift; sourceTree = ""; }; + D890BD770F4E33D23ABA37EA /* BankAccountToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankAccountToken.swift; sourceTree = ""; }; + D8A3C86CF29F533F87C7DFD6 /* NetworkingOTPView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingOTPView.swift; sourceTree = ""; }; + DBBF5CEE2C9030B2D374BC76 /* FinancialConnectionsAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAnalyticsClient.swift; sourceTree = ""; }; + DCC5894A5EB74F6157C7DE95 /* FinancialConnectionsOAuthPrepane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsOAuthPrepane.swift; sourceTree = ""; }; + DD828AB80DE41DE11D38AF5C /* SuccessFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessFooterView.swift; sourceTree = ""; }; + DD9E2537472B2ED4AA3ED6A2 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + DDC3AC48492FAB61E5B66D94 /* UIImageView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Extensions.swift"; sourceTree = ""; }; + DFA81F9910A88A7DEB0F0BFD /* ResetFlowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetFlowViewController.swift; sourceTree = ""; }; + E05B47C10B77812660F7B01A /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + E05C2C5CDAA55CE700662040 /* FinancialConnectionsSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSessionTests.swift; sourceTree = ""; }; + E37D8CE9CD73443A9AAF2AE8 /* StripeFinancialConnections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeFinancialConnections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E813BE6B34901E4E050FFE13 /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = ""; }; + E898E7D173685669E31FC58F /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + E90CF6AD88E530CE63D57269 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + EA55452373FD735983F3690B /* ContinueStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueStateView.swift; sourceTree = ""; }; + EAC8B933341AF86D9AFF5979 /* PartnerAuthDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerAuthDataSource.swift; sourceTree = ""; }; + EB3F67BE6E46ED018EB8C3FD /* SuccessViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessViewController.swift; sourceTree = ""; }; + EC561AF0993C02AD68472D11 /* chevron_down@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "chevron_down@3x.png"; sourceTree = ""; }; + EE64D5A17913CF4AAD855A9A /* AttributedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedLabel.swift; sourceTree = ""; }; + EE6D662AB9854F3BDB90D8FD /* NetworkingLinkStepUpVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkStepUpVerificationViewController.swift; sourceTree = ""; }; + EF0111A8932418631FFA1663 /* search@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "search@3x.png"; sourceTree = ""; }; + EF9E2D4C9D7684B02AD6037A /* FinancialConnectionsPaymentAccountResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsPaymentAccountResource.swift; sourceTree = ""; }; + EFB09DF9C9434032F387E081 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + F1C7A2FE53419CB29CBB6C08 /* close@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "close@3x.png"; sourceTree = ""; }; + F25B2AB87C9548245C28D14C /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + F2B7ECC6F6A4DA1F5F376467 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + F2BA0F04A5A7D3B1DBF34AEE /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; + F669BB8F3DA862C425897705 /* HitTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestView.swift; sourceTree = ""; }; + F6CF7F1005B57D566E139DE3 /* FinancialConnectionsSession_both_accounts_la.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = FinancialConnectionsSession_both_accounts_la.json; sourceTree = ""; }; + F897F5AA6A684D0A370EA7BC /* ManualEntryValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryValidator.swift; sourceTree = ""; }; + F8C7FDF59D906EA5C6B7A514 /* ManualEntryValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualEntryValidatorTests.swift; sourceTree = ""; }; + F96A3BA5CCB8DCCFA3126974 /* StripeFinancialConnections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeFinancialConnections.h; sourceTree = ""; }; + F9A847D2AAA7271F507DC9F3 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + F9AB787FE87EDD702B1BBF09 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FA62F9B77C6A1D1B12F02CF5 /* AttachLinkedPaymentAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachLinkedPaymentAccountViewController.swift; sourceTree = ""; }; + FBC23DAC91962D0D8A713D37 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; + FF54EDA6123C7E4E78D9D56B /* APIPollingHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIPollingHelper.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13EF25670CFA2AE22BD37D31 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3BF4BBF7E722B961E037286C /* XCTest.framework in Frameworks */, + 9CE29EA549C4BFA447AB82E0 /* StripeCore.framework in Frameworks */, + D0C6D94867FA04B1BF80D56D /* StripeCoreTestUtils.framework in Frameworks */, + 864C5159C62C562C655B53F7 /* StripeFinancialConnections.framework in Frameworks */, + 971E6F5E78BC3265CD80D0C6 /* StripeUICore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 618850E963EE4CF3E0EFE1FD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 06445472B3008395FCA92FEC /* StripeCore.framework in Frameworks */, + D50E771043434AD80EA28628 /* StripeUICore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0436E20F0A78D3A243CF7761 /* Images */ = { + isa = PBXGroup; + children = ( + 782A419DCF59BE6AB6439D04 /* add@3x.png */, + C4DA5D0EB4ED760B3F9818C5 /* back_arrow@3x.png */, + 7413C9D80DF7190CA6FB82EE /* brandicon_default@3x.png */, + 07BCA9D23511A3494C82B632 /* bullet@3x.png */, + A4CC00446B086B2987114099 /* cancel_circle@3x.png */, + 5B77DE6D7A86CC847977396A /* check@3x.png */, + EC561AF0993C02AD68472D11 /* chevron_down@3x.png */, + F1C7A2FE53419CB29CBB6C08 /* close@3x.png */, + 6A764CF4DB5B5F6F488132A8 /* generic_error@3x.png */, + 492651672C25C0C2001DDBCA /* info@3x.png */, + 494D62062C45B9B700106519 /* link_logo@3x.png */, + 6A732C9F2B6871E900828CB1 /* panel_arrow_right@3x.png */, + 6A13B9FB2B58545F00FFA327 /* person@3x.png */, + EF0111A8932418631FFA1663 /* search@3x.png */, + 57B289E803B7A53B000D7919 /* spinner@3x.png */, + B4F56BF50DBF4A353D2526A6 /* stripe_logo@3x.png */, + 496A6AE62C29E0BB00D34F8E /* testmode@3x.png */, + 5D555DB0657A602274596428 /* warning_triangle@3x.png */, + ); + path = Images; + sourceTree = ""; + }; + 132A42D1B9A52681405D214A /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 2C10E841FF9EBFEA8C2E30AF /* Project-Debug.xcconfig */, + B5FFA1B806BC6AD3500B0567 /* Project-Release.xcconfig */, + CA2DA47ECE153F888FA675CE /* StripeiOS Tests-Debug.xcconfig */, + 14CED33665ED3D8EE8D5D7B7 /* StripeiOS Tests-Release.xcconfig */, + 314462DF7856349FF9775598 /* StripeiOS-Debug.xcconfig */, + 1CD19E0601599AE89976DB4D /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 24D2905794F8E635EEDEC0D8 /* NetworkingLinkVerification */ = { + isa = PBXGroup; + children = ( + 6DDB75C69FE9322C745943B3 /* NetworkingLinkVerificationDataSource.swift */, + CDD861E4EB8BA294545B7651 /* NetworkingLinkVerificationViewController.swift */, + ); + path = NetworkingLinkVerification; + sourceTree = ""; + }; + 266BB00CA59B6EBADFAD798F /* NetworkingLinkStepUpVerification */ = { + isa = PBXGroup; + children = ( + 1F9D6F0CC79B7949D037DE66 /* NetworkingLinkStepUpVerificationBodyView.swift */, + 925B7F2CBACCB2346CD0CDFC /* NetworkingLinkStepUpVerificationDataSource.swift */, + EE6D662AB9854F3BDB90D8FD /* NetworkingLinkStepUpVerificationViewController.swift */, + ); + path = NetworkingLinkStepUpVerification; + sourceTree = ""; + }; + 298B4CECCC54664B4997B9D7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 94869BACB486153419B30DE5 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 32249762D11692D5B34BBF38 /* ConsumerSession */ = { + isa = PBXGroup; + children = ( + 08C32F1447E47221DC0B7095 /* ConsumerSessionModels.swift */, + ); + path = ConsumerSession; + sourceTree = ""; + }; + 328390D72E3911449BB9FD0B /* Analytics */ = { + isa = PBXGroup; + children = ( + DBBF5CEE2C9030B2D374BC76 /* FinancialConnectionsAnalyticsClient.swift */, + A6038978C79785C18257CD74 /* FinancialConnectionsSheetAnalytics.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 35190A546A00D11AB281556E /* API Bindings */ = { + isa = PBXGroup; + children = ( + 637114D9B91F9206B6F6709B /* Models */, + FF54EDA6123C7E4E78D9D56B /* APIPollingHelper.swift */, + 2D95E5F34BDEE0237F52DA0A /* APIVersion.swift */, + 4E2EAD7059FF8358E674774A /* FinancialConnectionsAPIClient.swift */, + ); + path = "API Bindings"; + sourceTree = ""; + }; + 3ADD22C8436381E94908DA82 /* Success */ = { + isa = PBXGroup; + children = ( + 4C15A30C40F34CE330F89C41 /* SuccessDataSource.swift */, + DD828AB80DE41DE11D38AF5C /* SuccessFooterView.swift */, + EB3F67BE6E46ED018EB8C3FD /* SuccessViewController.swift */, + ); + path = Success; + sourceTree = ""; + }; + 45F5EA9A9A1DEBC1EC05937F /* FinancialConnectionsSDK */ = { + isa = PBXGroup; + children = ( + 248D51F7AADE404E49957DDA /* FinancialConnectionsSDKImplementation.swift */, + ); + path = FinancialConnectionsSDK; + sourceTree = ""; + }; + 49C911362C597EAF00589E0D /* LinkLogin */ = { + isa = PBXGroup; + children = ( + 49C911332C597EAF00589E0D /* LinkLoginDataSource.swift */, + 49C911352C597EAF00589E0D /* LinkLoginViewController.swift */, + ); + path = LinkLogin; + sourceTree = ""; + }; + 53C3F536D69FCBC77C3E7D5F /* Common */ = { + isa = PBXGroup; + children = ( + 7C2E9D1B7C962C46F7E0002A /* ExperimentHelper.swift */, + 1F2C7AE6509C80B6F30662AA /* FinancialConnectionsCustomManualEntryRequiredError.swift */, + 6DA6A57067FB5EF86FEBD5B3 /* FinancialConnectionsNavigationController.swift */, + 1407DD9E95ADFE143FA046E4 /* FlowRouter.swift */, + 93C4ECB724BD75320A999C42 /* HostController.swift */, + A0863AF9E2F9BD7C026FE59E /* HostViewController.swift */, + 60B7CFC14964440E8AA670A9 /* LoadingView.swift */, + 587CD174831344F15ADB538D /* ModalPresentationWrapperViewController.swift */, + 492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */, + ); + path = Common; + sourceTree = ""; + }; + 541D32C4B0F1635A97F4FE11 /* Web */ = { + isa = PBXGroup; + children = ( + 596A401ABA089532A5006584 /* AuthenticationSessionManager.swift */, + EA55452373FD735983F3690B /* ContinueStateView.swift */, + CEC1BC95816DAD5AE9680662 /* FinancialConnectionsAccountFetcher.swift */, + A872C2B500306F775622F904 /* FinancialConnectionsSessionFetcher.swift */, + 3BD07CE6F99D7FAE83FC5CCC /* FinancialConnectionsWebFlowViewController.swift */, + ); + path = Web; + sourceTree = ""; + }; + 5A844A24E4E9F4B7E3802DA9 /* AccountPicker */ = { + isa = PBXGroup; + children = ( + 463549CECD379484842033E3 /* AccountPickerAccountLoadErrorView.swift */, + D715516C6703A780913E66EB /* AccountPickerDataSource.swift */, + 9ACAB1B6DB88D74F5ECC1C6D /* AccountPickerFooterView.swift */, + 1362591D12A04CA663A69A47 /* AccountPickerHelpers.swift */, + 912E3AA36B68492A69019AEA /* AccountPickerNoAccountEligibleErrorView.swift */, + 283469CD0298E3AFCFDAF10F /* AccountPickerSelectionListView.swift */, + 72DEDE8871A732D603E96E2B /* AccountPickerSelectionView.swift */, + 97CE72E38D41E86E0A1FAE9F /* AccountPickerViewController.swift */, + 97EE2BCD7B861ACA49DB56CD /* CheckboxView.swift */, + 6A13B9852B4CD04100FFA327 /* RetrieveAccountsLoadingView.swift */, + ); + path = AccountPicker; + sourceTree = ""; + }; + 605E7A0FA65A081265FA6F54 /* InstitutionPicker */ = { + isa = PBXGroup; + children = ( + 9ED1CF95D441821773EA68EE /* InstitutionDataSource.swift */, + 8B3BF292FD82A198752A82EB /* InstitutionPickerViewController.swift */, + 6A1D42FE2B1686FC005A1EB0 /* InstitutionTableView.swift */, + 6A1D43002B1687AB005A1EB0 /* InstitutionTableViewCell.swift */, + 6A1D43022B16B3DC005A1EB0 /* InstitutionCellView.swift */, + 4DA1C1B311E06C1165C6F6A2 /* InstitutionSearchBar.swift */, + 6A1D43042B17AD76005A1EB0 /* InstitutionTableFooterView.swift */, + 6A1D43082B17CB04005A1EB0 /* InstitutionNoResultsView.swift */, + 6A732C9B2B61C56A00828CB1 /* InstitutionTableLoadingView.swift */, + ); + path = InstitutionPicker; + sourceTree = ""; + }; + 637114D9B91F9206B6F6709B /* Models */ = { + isa = PBXGroup; + children = ( + 32249762D11692D5B34BBF38 /* ConsumerSession */, + D890BD770F4E33D23ABA37EA /* BankAccountToken.swift */, + 359BF8ACFB35A16EBD96C4F0 /* FinancialConnectionsAccount.swift */, + 5C837C27C2577391B91FF0E5 /* FinancialConnectionsAuthSession.swift */, + B3FD6A7D1638E42AA00C88C4 /* FinancialConnectionsBulletPoint.swift */, + 3FD9739F1AA7CBA76DD3E1E2 /* FinancialConnectionsConsent.swift */, + 32ED8A7E94822F14AD94A698 /* FinancialConnectionsDataAccessNotice.swift */, + 490B00102C93582900B1A489 /* FinancialConnectionsAccountPickerPane.swift */, + C93F7139E9BFB044902962D0 /* FinancialConnectionsImage.swift */, + 8FE8BCF5A9FF2B9392A755EA /* FinancialConnectionsInstitution.swift */, + 921686D9A3076749E1A9E549 /* FinancialConnectionsInstitutionSearchResultResource.swift */, + 07526E95D85120F6492E78AE /* FinancialConnectionsLegalDetailsNotice.swift */, + D3BF4CD26CEAE792AC2A7313 /* FinancialConnectionsMixedOAuthParams.swift */, + 3979E84D319D3ED1C3273D74 /* FinancialConnectionsNetworkedAccountsResponse.swift */, + CF7CE16FE4D5E8B889BF5D1E /* FinancialConnectionsNetworkingLinkSignup.swift */, + DCC5894A5EB74F6157C7DE95 /* FinancialConnectionsOAuthPrepane.swift */, + 452989E2D269784006EFD18C /* FinancialConnectionsPartnerAccount.swift */, + EF9E2D4C9D7684B02AD6037A /* FinancialConnectionsPaymentAccountResource.swift */, + 191760EFAA9154C1F168E1D2 /* FinancialConnectionsPaymentMethodType.swift */, + ACEF0BAF1A5BBA3061C15A09 /* FinancialConnectionsSession.swift */, + 429F985168AE9F9D700AE37B /* FinancialConnectionsSessionManifest.swift */, + 4667E3861CDEC3A41B757714 /* FinancialConnectionsSynchronize.swift */, + 495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */, + 49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */, + 6A384A832C24DD720044AB99 /* FinancialConnectionsGenericInfoScreen.swift */, + 49F047542C63C2E5006BAD3E /* FinancialConnectionsPaymentDetails.swift */, + ); + path = Models; + sourceTree = ""; + }; + 65C5C08A61EC14B62EA32D0B /* Resources */ = { + isa = PBXGroup; + children = ( + 0436E20F0A78D3A243CF7761 /* Images */, + F2CF7FBAD6C8773D6E954126 /* Localizations */, + ); + path = Resources; + sourceTree = ""; + }; + 67E2F19935317C74CE2CEB8D /* LinkAccountPicker */ = { + isa = PBXGroup; + children = ( + 0DD5619FDD6C8D85FC352F99 /* LinkAccountPickerBodyView.swift */, + 524D3116FDA5A3AD68075AA4 /* LinkAccountPickerDataSource.swift */, + A680CB323B9139F838643EC1 /* LinkAccountPickerFooterView.swift */, + 6A732C9D2B64787E00828CB1 /* LinkAccountPickerLoadingView.swift */, + AFF070262170A321F5622CCF /* LinkAccountPickerNewAccountRowView.swift */, + 1848547B588045C776236B3B /* LinkAccountPickerViewController.swift */, + ); + path = LinkAccountPicker; + sourceTree = ""; + }; + 6A3DA1F32C34A32D005C3F6E /* GenericInfoScreen */ = { + isa = PBXGroup; + children = ( + 6A3DA1F42C34A37F005C3F6E /* GenericInfoViewController.swift */, + 6A3739132C40558900D1F765 /* GenericInfoBodyView.swift */, + 6A3DA1F62C34B254005C3F6E /* GenericInfoFooterView.swift */, + ); + path = GenericInfoScreen; + sourceTree = ""; + }; + 6A7814162B361C1900168992 /* PaneLayoutView */ = { + isa = PBXGroup; + children = ( + 34452D5FDC1ED566A13427FE /* PaneLayoutView.swift */, + 6A7814172B361C5000168992 /* PaneLayoutView+Extensions.swift */, + ); + path = PaneLayoutView; + sourceTree = ""; + }; + 6ABFE5532B7446C70037437C /* Error */ = { + isa = PBXGroup; + children = ( + 6ABFE5542B74479A0037437C /* ErrorViewController.swift */, + 6ABFE5562B7449390037437C /* ErrorDataSource.swift */, + ); + path = Error; + sourceTree = ""; + }; + 7879CBA341D7E807714A831B = { + isa = PBXGroup; + children = ( + 7F765C8B9BCB2FA4A3334063 /* Project */, + 298B4CECCC54664B4997B9D7 /* Frameworks */, + 820BF9CF057CF92872BC3C15 /* Products */, + ); + sourceTree = ""; + }; + 78A335C396F4FDF349C7BD59 /* Source */ = { + isa = PBXGroup; + children = ( + 328390D72E3911449BB9FD0B /* Analytics */, + 35190A546A00D11AB281556E /* API Bindings */, + 53C3F536D69FCBC77C3E7D5F /* Common */, + 45F5EA9A9A1DEBC1EC05937F /* FinancialConnectionsSDK */, + DEC2827C937247DAA010F3D2 /* Helpers */, + BF0B937496C645C3ED6E265E /* Native */, + 541D32C4B0F1635A97F4FE11 /* Web */, + 51D8397DB43DEC09BDF66E8A /* FinancialConnectionsSheet.swift */, + 5C0737DE86515E172909366F /* FinancialConnectionsSheetError.swift */, + 54CF67A1F497E6CC73029CF0 /* StripeCore+Import.swift */, + ); + path = Source; + sourceTree = ""; + }; + 7BFDCD3B61A38A4BA3466780 /* AttachLinkedPaymentAccount */ = { + isa = PBXGroup; + children = ( + 6224E799E667DF223757D493 /* AccountNumberRetrievalErrorView.swift */, + 518F1F230FD4DF68E683C728 /* AttachLinkedPaymentAccountDataSource.swift */, + FA62F9B77C6A1D1B12F02CF5 /* AttachLinkedPaymentAccountViewController.swift */, + ); + path = AttachLinkedPaymentAccount; + sourceTree = ""; + }; + 7E5623CA300ADBCBE8B33E69 /* NetworkingOTPView */ = { + isa = PBXGroup; + children = ( + C0430E0E195DD128FA2D5F86 /* NetworkingOTPDataSource.swift */, + D8A3C86CF29F533F87C7DFD6 /* NetworkingOTPView.swift */, + ); + path = NetworkingOTPView; + sourceTree = ""; + }; + 7F765C8B9BCB2FA4A3334063 /* Project */ = { + isa = PBXGroup; + children = ( + 132A42D1B9A52681405D214A /* BuildConfigurations */, + B099AE2E516197735F31B3D9 /* StripeFinancialConnections */, + EA8A954B6F8275294AD1D76F /* StripeFinancialConnectionsTests */, + ); + name = Project; + sourceTree = ""; + }; + 820BF9CF057CF92872BC3C15 /* Products */ = { + isa = PBXGroup; + children = ( + C650E82A4195A7566AA54298 /* StripeCore.framework */, + F9AB787FE87EDD702B1BBF09 /* StripeCoreTestUtils.framework */, + E37D8CE9CD73443A9AAF2AE8 /* StripeFinancialConnections.framework */, + CFEEBA73EBBCE02A50B2DB7A /* StripeFinancialConnectionsTests.xctest */, + 6652EBE38C47B36962AD370A /* StripeUICore.framework */, + ); + name = Products; + sourceTree = ""; + }; + 92A9D27306891BBCBC4DBF49 /* ManualEntry */ = { + isa = PBXGroup; + children = ( + BD4C39F5F9AF440B13F51A81 /* ManualEntryDataSource.swift */, + C88FD9148F64D2AA8989D361 /* ManualEntryErrorView.swift */, + 73C03F4DDC67B50C5E1993F6 /* ManualEntryFormView.swift */, + F897F5AA6A684D0A370EA7BC /* ManualEntryValidator.swift */, + 8301F7BA1FF90D131AE96E10 /* ManualEntryViewController.swift */, + ); + path = ManualEntry; + sourceTree = ""; + }; + 9D434FE45EB09749E475FED6 /* MockData */ = { + isa = PBXGroup; + children = ( + F6CF7F1005B57D566E139DE3 /* FinancialConnectionsSession_both_accounts_la.json */, + 4AFBF95DAE0783010A17EB58 /* FinancialConnectionsSession_only_accounts.json */, + 1DF07A1AAD6B39033F0B86FD /* FinancialConnectionsSession_only_both_missing.json */, + AA01BC4016BF8788633CCAD9 /* FinancialConnectionsSession_only_la.json */, + ); + path = MockData; + sourceTree = ""; + }; + A7767444AAEB7AB6E887A54D /* PartnerAuth */ = { + isa = PBXGroup; + children = ( + EAC8B933341AF86D9AFF5979 /* PartnerAuthDataSource.swift */, + 25BDE722D8A827955C3182E8 /* PartnerAuthViewController.swift */, + A54F545087F12B09FF416991 /* PrepaneImageView.swift */, + 6ABFE5512B72BE630037437C /* PrepaneViews.swift */, + ); + path = PartnerAuth; + sourceTree = ""; + }; + B099AE2E516197735F31B3D9 /* StripeFinancialConnections */ = { + isa = PBXGroup; + children = ( + 313F5F7E2B0BE5D100BD98A9 /* Docs.docc */, + 65C5C08A61EC14B62EA32D0B /* Resources */, + 78A335C396F4FDF349C7BD59 /* Source */, + 1E80DD2D042B327D9756E083 /* Info.plist */, + F96A3BA5CCB8DCCFA3126974 /* StripeFinancialConnections.h */, + 31CDFC332BA8E61F00B3DD91 /* PrivacyInfo.xcprivacy */, + ); + path = StripeFinancialConnections; + sourceTree = ""; + }; + B5A3DB0705F83912097C39C9 /* NetworkingLinkLoginWarmup */ = { + isa = PBXGroup; + children = ( + 13983D90462EB946B2A178C6 /* NetworkingLinkLoginWarmupBodyView.swift */, + 091D43608583C7BE5E444C2C /* NetworkingLinkLoginWarmupDataSource.swift */, + 252DA1FE0574822605438AB4 /* NetworkingLinkLoginWarmupViewController.swift */, + ); + path = NetworkingLinkLoginWarmup; + sourceTree = ""; + }; + B71852C5A10D21D52A586721 /* NetworkingSaveToLinkVerification */ = { + isa = PBXGroup; + children = ( + C3BDFEB5860F73D4CD90907A /* NetworkingSaveToLinkVerificationDataSource.swift */, + 66D3CAB53EC9D33831C5A48B /* NetworkingSaveToLinkVerificationViewController.swift */, + ); + path = NetworkingSaveToLinkVerification; + sourceTree = ""; + }; + BD42562786EC2FC703C1B28E /* TerminalError */ = { + isa = PBXGroup; + children = ( + B83A2749140B4E129CEF39C4 /* TerminalErrorViewController.swift */, + 6ABFE5582B7451710037437C /* TerminalErrorView.swift */, + ); + path = TerminalError; + sourceTree = ""; + }; + BF0B937496C645C3ED6E265E /* Native */ = { + isa = PBXGroup; + children = ( + 49C911362C597EAF00589E0D /* LinkLogin */, + 6ABFE5532B7446C70037437C /* Error */, + 5A844A24E4E9F4B7E3802DA9 /* AccountPicker */, + 7BFDCD3B61A38A4BA3466780 /* AttachLinkedPaymentAccount */, + EB6632ED2E696DA6381326C0 /* Consent */, + 605E7A0FA65A081265FA6F54 /* InstitutionPicker */, + 67E2F19935317C74CE2CEB8D /* LinkAccountPicker */, + 92A9D27306891BBCBC4DBF49 /* ManualEntry */, + B5A3DB0705F83912097C39C9 /* NetworkingLinkLoginWarmup */, + F4F0179DDFE793873C22E918 /* NetworkingLinkSignupPane */, + 266BB00CA59B6EBADFAD798F /* NetworkingLinkStepUpVerification */, + 24D2905794F8E635EEDEC0D8 /* NetworkingLinkVerification */, + B71852C5A10D21D52A586721 /* NetworkingSaveToLinkVerification */, + A7767444AAEB7AB6E887A54D /* PartnerAuth */, + C76F2B3F6D5AB3E54CE1C206 /* ResetFlow */, + C0C2FC181FFA71CFAA6F3148 /* Shared */, + 3ADD22C8436381E94908DA82 /* Success */, + BD42562786EC2FC703C1B28E /* TerminalError */, + 5F856808B78F4F1975959805 /* NativeFlowController.swift */, + 780BC432329228B042DA97D8 /* NativeFlowDataManager.swift */, + ); + path = Native; + sourceTree = ""; + }; + C0C2FC181FFA71CFAA6F3148 /* Shared */ = { + isa = PBXGroup; + children = ( + 6A3DA1F32C34A32D005C3F6E /* GenericInfoScreen */, + 6A7814162B361C1900168992 /* PaneLayoutView */, + 7E5623CA300ADBCBE8B33E69 /* NetworkingOTPView */, + EE64D5A17913CF4AAD855A9A /* AttributedLabel.swift */, + 97948A848C99ACD1A498F841 /* AttributedTextView.swift */, + A9B31B3A45DD8AAFD6F08820 /* AuthFlowHelpers.swift */, + 6A4320292B7E8C5400A67A70 /* Constants.swift */, + 591E8073D2AD30115ABDB60F /* BulletPointLabelView.swift */, + 4E4A84F0646AD673029CB6FC /* Button+Extensions.swift */, + 6A7814102B32462100168992 /* CloseConfirmationViewController.swift */, + 6A26A3AA2B991A4D00215510 /* FeedbackGeneratorAdapter.swift */, + A00D518C15FF3FAED7C193C2 /* HitTestStackView.swift */, + F669BB8F3DA862C425897705 /* HitTestView.swift */, + 939D10B20D3ECA7BF7021BF8 /* InstitutionIconView.swift */, + 6A1D43062B17AE37005A1EB0 /* RoundedIconView.swift */, + 6A732CA12B69821300828CB1 /* RoundedTextField.swift */, + 1D38B3C816EAB38AD242B064 /* SFSafariViewController+Extensions.swift */, + 6A78140C2B30F15400168992 /* SheetViewController.swift */, + E813BE6B34901E4E050FFE13 /* TimeInterval+Extensions.swift */, + D2EA7801B85973F10F65DDB6 /* UIImage+Extensions.swift */, + DDC3AC48492FAB61E5B66D94 /* UIImageView+Extensions.swift */, + B52F27D0FACAE9D4F4D15A73 /* UITableView+Extensions.swift */, + CB3C49A180D1697B03C79A59 /* UIViewController+KeyboardAvoiding.swift */, + 6A7814192B45D53700168992 /* DataAccessNoticeViewController.swift */, + 6A78141B2B462D5D00168992 /* LegalDetailsNoticeViewController.swift */, + 6A13B9812B48BD6C00FFA327 /* AccountPickerRowView.swift */, + 6A13B9832B48BF4300FFA327 /* AccountPickerRowLabelView.swift */, + 6A13B9F92B4E182A00FFA327 /* SpinnerView.swift */, + 6A732C992B61C51C00828CB1 /* ShimmeringView.swift */, + 49C9113A2C59932300589E0D /* LinkSignupFormView.swift */, + 6A3739152C4060BD00D1F765 /* AutoResizableUIView.swift */, + 6A6F989B2C4F1BF00035C03D /* CreatePaneParameters.swift */, + 49F047522C63B430006BAD3E /* StripeSchemeAddress.swift */, + ); + path = Shared; + sourceTree = ""; + }; + C76F2B3F6D5AB3E54CE1C206 /* ResetFlow */ = { + isa = PBXGroup; + children = ( + 5D1C78684DD0B2D168C86229 /* ResetFlowDataSource.swift */, + DFA81F9910A88A7DEB0F0BFD /* ResetFlowViewController.swift */, + ); + path = ResetFlow; + sourceTree = ""; + }; + DEC2827C937247DAA010F3D2 /* Helpers */ = { + isa = PBXGroup; + children = ( + 75B23D2BD3CD4071A40D9AE9 /* FinancialConnectionsEvent+Extensions.swift */, + 83E0A15A666F20DA97F128EA /* FinancialConnectionsFont.swift */, + 064D0E3A3AC71FAA60B54FC5 /* Helpers.swift */, + 31AD3BE82B0C2F000080C800 /* ScreenNativeScale.swift */, + 65BCC4356AE3295B4A2F4A28 /* Image.swift */, + 267B3586136203186882F5CE /* NSAttributedString+Extensions.swift */, + B2A72263B4842E5E30FF91AD /* PaymentAccount+Extensions.swift */, + 6E2D765AC793D89D26B74FC4 /* STPLocalizedString.swift */, + 7F3A660CB2E9651947FE6D0A /* String+Extensions.swift */, + E05B47C10B77812660F7B01A /* String+Localized.swift */, + 2C0F907D5B0AC58BE7A454BA /* StripeFinancialConnectionsBundleLocator.swift */, + FBC23DAC91962D0D8A713D37 /* UIColor+Extensions.swift */, + BF7E41313B709F87B549D85F /* UIViewController+Extensions.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + EA8A954B6F8275294AD1D76F /* StripeFinancialConnectionsTests */ = { + isa = PBXGroup; + children = ( + 9D434FE45EB09749E475FED6 /* MockData */, + 77A71EBB1B98CD285DD17D5F /* AccountFetcherTests.swift */, + 3ED33E6BADC0893C3F6B22D2 /* AccountPickerHelpersTests.swift */, + 710183EE587F6FDA077FC150 /* APIPollingHelperTests.swift */, + 7AFC0D3ED86914DC4216CCCA /* AuthFlowHelpersTests.swift */, + 4A7B146AA6BF44921A249DB8 /* EmptyFinancialConnectionsAPIClient.swift */, + 73AB8A480620B5C3567F453C /* FinancialConnectionsAnalyticsTest.swift */, + E05C2C5CDAA55CE700662040 /* FinancialConnectionsSessionTests.swift */, + 20E7725EF317C3BD62ADF845 /* FinancialConnectionsSheetTests.swift */, + 49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */, + 4BFCD9C339634B71FC8F85E9 /* Info.plist */, + F8C7FDF59D906EA5C6B7A514 /* ManualEntryValidatorTests.swift */, + 4E7D318EE807701AB3FCA17D /* MarkdownBoldAttributedStringTests.swift */, + 965814B0C5F3D13158E610E3 /* SessionFetcherTests.swift */, + 90A06A97D714450321A5D76D /* SoftLinkTests.swift */, + CF731140836AE438C7F4F6AB /* StringExtensionsTests.swift */, + 497142BB2C514B08000DFA64 /* FlowRouterTests.swift */, + 492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */, + ); + path = StripeFinancialConnectionsTests; + sourceTree = ""; + }; + EB6632ED2E696DA6381326C0 /* Consent */ = { + isa = PBXGroup; + children = ( + 6FA941D5F7178E89DE70076F /* ConsentBodyView.swift */, + 81304AD5BE5CCA10D1A866E0 /* ConsentDataSource.swift */, + 1E58AE51252DA4597DC82988 /* ConsentFooterView.swift */, + 0281E0221BDE01D0845DC0F9 /* ConsentLogoView.swift */, + 13AAB10AEE7A8EC5C9C53FFA /* ConsentViewController.swift */, + ); + path = Consent; + sourceTree = ""; + }; + F2CF7FBAD6C8773D6E954126 /* Localizations */ = { + isa = PBXGroup; + children = ( + BF6810BAADB14ACB95216C2B /* Localizable.strings */, + ); + path = Localizations; + sourceTree = ""; + }; + F4F0179DDFE793873C22E918 /* NetworkingLinkSignupPane */ = { + isa = PBXGroup; + children = ( + 40ECB2B008FC082B4D38D2FE /* NetworkingLinkSignupBodyView.swift */, + D688616FDC435586025D2023 /* NetworkingLinkSignupDataSource.swift */, + 72A74B9F667A3BF48253045E /* NetworkingLinkSignupFooterView.swift */, + 6CDEF702710EEA29BA3DC653 /* NetworkingLinkSignupViewController.swift */, + 6A732CA32B69871F00828CB1 /* EmailTextField.swift */, + 6A732CA52B69A46D00828CB1 /* PhoneTextField.swift */, + 6A732CA72B69C34F00828CB1 /* PhoneCountryCodeSelectorView.swift */, + 6A732CA92B69CCDD00828CB1 /* PhoneCountryCodePickerView.swift */, + ); + path = NetworkingLinkSignupPane; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + BD02401A40A42372CFD642EE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + FCC5A360E0064887DB28F5C6 /* StripeFinancialConnections.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 44C90013B7C82C80A2F69956 /* StripeFinancialConnections */ = { + isa = PBXNativeTarget; + buildConfigurationList = DE1BF3F953C39B1173504C4A /* Build configuration list for PBXNativeTarget "StripeFinancialConnections" */; + buildPhases = ( + BD02401A40A42372CFD642EE /* Headers */, + 019D2A9648EC14A85E873494 /* Sources */, + CC61EC7C016C47747A2D7AB0 /* Resources */, + 6EAD6F45EDAE7B645FDC823B /* Embed Frameworks */, + 618850E963EE4CF3E0EFE1FD /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeFinancialConnections; + productName = StripeFinancialConnections; + productReference = E37D8CE9CD73443A9AAF2AE8 /* StripeFinancialConnections.framework */; + productType = "com.apple.product-type.framework"; + }; + DF72D31B68363878FC1604CF /* StripeFinancialConnectionsTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8C428C73E0383F9203731DCB /* Build configuration list for PBXNativeTarget "StripeFinancialConnectionsTests" */; + buildPhases = ( + 0EF6A0BEC1B066774A0D985E /* Sources */, + 704BBAB23732F215CEEAD39C /* Resources */, + 4E8F557BB03B30AB6BCE5DAC /* Embed Frameworks */, + 13EF25670CFA2AE22BD37D31 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 3257A97C053FFC03C4806278 /* PBXTargetDependency */, + ); + name = StripeFinancialConnectionsTests; + productName = StripeFinancialConnectionsTests; + productReference = CFEEBA73EBBCE02A50B2DB7A /* StripeFinancialConnectionsTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3D00B888AF0B02587576A83F /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = F7F4B17FFDBC5691F1A51423 /* Build configuration list for PBXProject "StripeFinancialConnections" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = 7879CBA341D7E807714A831B; + productRefGroup = 820BF9CF057CF92872BC3C15 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 44C90013B7C82C80A2F69956 /* StripeFinancialConnections */, + DF72D31B68363878FC1604CF /* StripeFinancialConnectionsTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 704BBAB23732F215CEEAD39C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D0C1EF46A418A8F8774B7418 /* FinancialConnectionsSession_both_accounts_la.json in Resources */, + 07BFA34C5643A79E1E35A159 /* FinancialConnectionsSession_only_accounts.json in Resources */, + A156FACA60231988F247F6F4 /* FinancialConnectionsSession_only_both_missing.json in Resources */, + ACD21F21C6E42706A882A1AE /* FinancialConnectionsSession_only_la.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CC61EC7C016C47747A2D7AB0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4DC8EB63806434ABF4C9CC43 /* add@3x.png in Resources */, + D10FB0DAC5E452D4569CEA14 /* back_arrow@3x.png in Resources */, + F0397F4E1D6A91416897F45E /* brandicon_default@3x.png in Resources */, + 645D6FF67167263F9A1C2BB0 /* bullet@3x.png in Resources */, + F10147CF75C2A09D66CB5C14 /* cancel_circle@3x.png in Resources */, + 755140DEEE50DCD6E939E528 /* check@3x.png in Resources */, + 6A732CA02B6871EA00828CB1 /* panel_arrow_right@3x.png in Resources */, + 16F2968DC3B2FC4558821970 /* chevron_down@3x.png in Resources */, + 31CDFC342BA8E61F00B3DD91 /* PrivacyInfo.xcprivacy in Resources */, + 492651682C25C0C2001DDBCA /* info@3x.png in Resources */, + 6A13B9FC2B58545F00FFA327 /* person@3x.png in Resources */, + C747113C75AC92643B283CBD /* close@3x.png in Resources */, + 494D62072C45B9B700106519 /* link_logo@3x.png in Resources */, + CF47070B2A4CA27FEE9AE5FA /* generic_error@3x.png in Resources */, + 496A6AE72C29E0BB00D34F8E /* testmode@3x.png in Resources */, + 3AE1C7A78FB5B220F5200F49 /* search@3x.png in Resources */, + BD3C87E03EB44F7D1C11664C /* spinner@3x.png in Resources */, + 97032B101B54E6A98178FD73 /* stripe_logo@3x.png in Resources */, + 87E22AF1E35FB63C20AEE9DF /* warning_triangle@3x.png in Resources */, + D949AE695F3288F84258BACD /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 019D2A9648EC14A85E873494 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 490B00112C93582900B1A489 /* FinancialConnectionsAccountPickerPane.swift in Sources */, + 648FA50974B14CC861B08ECB /* APIPollingHelper.swift in Sources */, + 6A732C9A2B61C51C00828CB1 /* ShimmeringView.swift in Sources */, + 49C911392C597EAF00589E0D /* LinkLoginViewController.swift in Sources */, + BCEA321423DF0E7674C2544C /* APIVersion.swift in Sources */, + 6A732CA22B69821300828CB1 /* RoundedTextField.swift in Sources */, + 11FB97AC840FEB5B5BF85BF9 /* FinancialConnectionsAPIClient.swift in Sources */, + 432463EBF562CDDC6D3DC252 /* BankAccountToken.swift in Sources */, + 6A732CA82B69C34F00828CB1 /* PhoneCountryCodeSelectorView.swift in Sources */, + 6A732CAA2B69CCDD00828CB1 /* PhoneCountryCodePickerView.swift in Sources */, + 6A3DA1F52C34A37F005C3F6E /* GenericInfoViewController.swift in Sources */, + FBF513C7F73002FA30CC7C21 /* ConsumerSessionModels.swift in Sources */, + 6A3739142C40558900D1F765 /* GenericInfoBodyView.swift in Sources */, + EC74B719F0FA1A977EF4708C /* FinancialConnectionsAccount.swift in Sources */, + 460C7685096AA6C693309647 /* FinancialConnectionsAuthSession.swift in Sources */, + AB5AFAC3C70D6195075DE5AE /* FinancialConnectionsBulletPoint.swift in Sources */, + B9A24A47454134F2B869C969 /* FinancialConnectionsConsent.swift in Sources */, + 49AC518C2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift in Sources */, + CBF7BE2271D309F2B1E794CC /* FinancialConnectionsDataAccessNotice.swift in Sources */, + F67624595BD2CD7B6793BFDA /* FinancialConnectionsImage.swift in Sources */, + 07712610C7D2F484AAB96982 /* FinancialConnectionsInstitution.swift in Sources */, + 7386E1F9256B23CE29BF996D /* FinancialConnectionsInstitutionSearchResultResource.swift in Sources */, + C7D2763ACCE2CC71E788E18F /* FinancialConnectionsLegalDetailsNotice.swift in Sources */, + B271AAF41C9FE6AE392B88D3 /* FinancialConnectionsMixedOAuthParams.swift in Sources */, + DAA51ABB496551074DBA1A20 /* FinancialConnectionsNetworkedAccountsResponse.swift in Sources */, + 6A732CA62B69A46D00828CB1 /* PhoneTextField.swift in Sources */, + 6FE9F171CF9A5D0EDB2035AA /* FinancialConnectionsNetworkingLinkSignup.swift in Sources */, + 87198EFD873751CA4E4B5005 /* FinancialConnectionsOAuthPrepane.swift in Sources */, + 6A732C9E2B64787E00828CB1 /* LinkAccountPickerLoadingView.swift in Sources */, + 77C7F9A1DD0461FA2B1B4328 /* FinancialConnectionsPartnerAccount.swift in Sources */, + 6A1D43092B17CB04005A1EB0 /* InstitutionNoResultsView.swift in Sources */, + D926228B6C7601AE4C806C93 /* FinancialConnectionsPaymentAccountResource.swift in Sources */, + 6944E131D351784058C7D734 /* FinancialConnectionsPaymentMethodType.swift in Sources */, + 1C043C73281C0856D2C979C6 /* FinancialConnectionsSession.swift in Sources */, + 9E0044ABEC04E2A8C50E3658 /* FinancialConnectionsSessionManifest.swift in Sources */, + 6A6F989C2C4F1BF00035C03D /* CreatePaneParameters.swift in Sources */, + 49F047552C63C2E5006BAD3E /* FinancialConnectionsPaymentDetails.swift in Sources */, + D936C8A9F6E018DB144A5B0A /* FinancialConnectionsSynchronize.swift in Sources */, + 15EC9F36187C341800164428 /* FinancialConnectionsAnalyticsClient.swift in Sources */, + C61D5957D3276991795F7D16 /* FinancialConnectionsSheetAnalytics.swift in Sources */, + D3AB52D5AE87FE51642C50C1 /* ExperimentHelper.swift in Sources */, + 6A13B9FA2B4E182A00FFA327 /* SpinnerView.swift in Sources */, + 6A384A842C24DD720044AB99 /* FinancialConnectionsGenericInfoScreen.swift in Sources */, + 0D56BD448019185656DF9310 /* FinancialConnectionsCustomManualEntryRequiredError.swift in Sources */, + 6E6E30D01D4E9629DB07E97B /* FinancialConnectionsNavigationController.swift in Sources */, + 01C820ECDBFC041A741A5499 /* FlowRouter.swift in Sources */, + 933F9DFE970FAB4715369086 /* HostController.swift in Sources */, + C258E0D849083BCC8A9B5068 /* HostViewController.swift in Sources */, + C59DBA5A86A3331113D6ED7E /* LoadingView.swift in Sources */, + 9B2CAE99344C26D524EDCF26 /* ModalPresentationWrapperViewController.swift in Sources */, + 6ABFE5522B72BE630037437C /* PrepaneViews.swift in Sources */, + FE268512851E63E4E111DECD /* FinancialConnectionsSDKImplementation.swift in Sources */, + E85DCFCA61299EF27B3201CF /* FinancialConnectionsSheet.swift in Sources */, + F22DE4B785D51B318A1A3D08 /* FinancialConnectionsSheetError.swift in Sources */, + 49F047532C63B430006BAD3E /* StripeSchemeAddress.swift in Sources */, + EABA08E892B087D89C97AE4F /* FinancialConnectionsEvent+Extensions.swift in Sources */, + 34E12CB27B60F6A53D030765 /* FinancialConnectionsFont.swift in Sources */, + C3338FA5019EC8E99E2BA62F /* Helpers.swift in Sources */, + A573468B2800DABF384CAB43 /* Image.swift in Sources */, + 6A43202A2B7E8C5400A67A70 /* Constants.swift in Sources */, + A79D6A26EE9FF96D24F4AC5C /* NSAttributedString+Extensions.swift in Sources */, + 7DEC399FFE0BAAAB2026E684 /* PaymentAccount+Extensions.swift in Sources */, + 76FB143918C5463B587091BB /* STPLocalizedString.swift in Sources */, + 6A1D43052B17AD76005A1EB0 /* InstitutionTableFooterView.swift in Sources */, + ABB28C3F6604C2BA2FCA079D /* String+Extensions.swift in Sources */, + 3FE4DEFAD6FF77B8D9EE68D3 /* String+Localized.swift in Sources */, + 3ECA346F75060BD954376EBF /* StripeFinancialConnectionsBundleLocator.swift in Sources */, + E760C94B619A8934D1D5E1D0 /* UIColor+Extensions.swift in Sources */, + 6ABFE5572B7449390037437C /* ErrorDataSource.swift in Sources */, + 4A537AE0C50CAFF3889EFE28 /* UIViewController+Extensions.swift in Sources */, + C38BEDD99477C83C91B105DD /* AccountPickerAccountLoadErrorView.swift in Sources */, + C1A079E8E76A02EBCB2588DA /* AccountPickerDataSource.swift in Sources */, + BFF222008EEEDC3FACE342D9 /* AccountPickerFooterView.swift in Sources */, + C0831318A33A32BF2EAB641A /* AccountPickerHelpers.swift in Sources */, + 1889ECB24D40EF331974C288 /* AccountPickerNoAccountEligibleErrorView.swift in Sources */, + A10B5A3E5E8AE8767CF09C15 /* AccountPickerSelectionListView.swift in Sources */, + 163E387D567068E4A64A4C13 /* AccountPickerSelectionView.swift in Sources */, + 492651662C24C9E7001DDBCA /* TestModeAutofillBannerView.swift in Sources */, + 23DBA4240ED1727C47937A6B /* AccountPickerViewController.swift in Sources */, + E4D00DB842047E595DD85BEF /* CheckboxView.swift in Sources */, + 6A1D43032B16B3DC005A1EB0 /* InstitutionCellView.swift in Sources */, + 6A1D43072B17AE37005A1EB0 /* RoundedIconView.swift in Sources */, + 6A26A3AB2B991A4D00215510 /* FeedbackGeneratorAdapter.swift in Sources */, + 49C9113B2C59932300589E0D /* LinkSignupFormView.swift in Sources */, + 716E12A9AC0B790F14FB72C6 /* AccountNumberRetrievalErrorView.swift in Sources */, + 3446145FCA3278D51A9D4B80 /* AttachLinkedPaymentAccountDataSource.swift in Sources */, + E3F62D2F9C344A1178030E8E /* AttachLinkedPaymentAccountViewController.swift in Sources */, + 707C265C4179A8FEC98913FE /* ConsentBodyView.swift in Sources */, + 465AE8A58AD2183E1E2042FE /* ConsentDataSource.swift in Sources */, + 97C528CE821C6A55D58F68A4 /* ConsentFooterView.swift in Sources */, + 8927328EE28A0C94B5AB69DB /* ConsentLogoView.swift in Sources */, + E9866D5CA186A242BBEA69E1 /* ConsentViewController.swift in Sources */, + D9D84D6FF624CF4363D87CEB /* InstitutionDataSource.swift in Sources */, + 6D29E55F6A3864ED52799169 /* InstitutionPickerViewController.swift in Sources */, + C6B99A1C34886D3B5E1AF1A2 /* InstitutionSearchBar.swift in Sources */, + 44203505ED2F64D07632566B /* LinkAccountPickerBodyView.swift in Sources */, + 6A13B9822B48BD6C00FFA327 /* AccountPickerRowView.swift in Sources */, + C55F79F4B85E1EB8730B02C6 /* LinkAccountPickerDataSource.swift in Sources */, + 166ACB3BF53BDB4443E276E3 /* LinkAccountPickerFooterView.swift in Sources */, + F0495231F4C70E054149C03A /* LinkAccountPickerNewAccountRowView.swift in Sources */, + 72BB9389206F10DE9B18E542 /* LinkAccountPickerViewController.swift in Sources */, + 4A0D015C978BD79BBFE6CE57 /* ManualEntryDataSource.swift in Sources */, + 6A78141A2B45D53700168992 /* DataAccessNoticeViewController.swift in Sources */, + 2343C58289259920DD81620D /* ManualEntryErrorView.swift in Sources */, + 6ABFE5552B74479A0037437C /* ErrorViewController.swift in Sources */, + 6A732C9C2B61C56A00828CB1 /* InstitutionTableLoadingView.swift in Sources */, + 6D018BB3C1253ED4C1674E0B /* ManualEntryFormView.swift in Sources */, + 6A78140D2B30F15400168992 /* SheetViewController.swift in Sources */, + 07A86CEB6B4F6BEB524EFE37 /* ManualEntryValidator.swift in Sources */, + 19D1548A5A4034D349DB0947 /* ManualEntryViewController.swift in Sources */, + 2CE89100448F26DDA831F455 /* NativeFlowController.swift in Sources */, + 6A1D43012B1687AB005A1EB0 /* InstitutionTableViewCell.swift in Sources */, + 74CC216C8A71AD357B8AA544 /* NativeFlowDataManager.swift in Sources */, + FFD76E78070ECBB283D43D5E /* NetworkingLinkLoginWarmupBodyView.swift in Sources */, + 1E0C39EB65B8CB04F218D0BD /* NetworkingLinkLoginWarmupDataSource.swift in Sources */, + F7C10A1AB247D0F6E111DE36 /* NetworkingLinkLoginWarmupViewController.swift in Sources */, + 95B2A73AC5DA9FA64017B3CB /* NetworkingLinkSignupBodyView.swift in Sources */, + 6A13B9842B48BF4300FFA327 /* AccountPickerRowLabelView.swift in Sources */, + 3AC5CA5F5529B55026342A54 /* NetworkingLinkSignupDataSource.swift in Sources */, + 6A13B9862B4CD04100FFA327 /* RetrieveAccountsLoadingView.swift in Sources */, + 6A3739162C4060BD00D1F765 /* AutoResizableUIView.swift in Sources */, + 8DC6C2A239456994091BF3EE /* NetworkingLinkSignupFooterView.swift in Sources */, + 6A3DA1F72C34B254005C3F6E /* GenericInfoFooterView.swift in Sources */, + 54B51EA1F75B9607D7C29B08 /* NetworkingLinkSignupViewController.swift in Sources */, + 3F835D5A1C797C1C9BCF05D0 /* NetworkingLinkStepUpVerificationBodyView.swift in Sources */, + 11782289208971CCAA1037A5 /* NetworkingLinkStepUpVerificationDataSource.swift in Sources */, + C128C1681E46F0F12EB4EB9F /* NetworkingLinkStepUpVerificationViewController.swift in Sources */, + 333B9C3E3349F5369FBA7C32 /* NetworkingLinkVerificationDataSource.swift in Sources */, + 31AD3BE92B0C2F000080C800 /* ScreenNativeScale.swift in Sources */, + C5FEC806A31021B7D119A73C /* NetworkingLinkVerificationViewController.swift in Sources */, + 2671241DE661B675E575C0AB /* NetworkingSaveToLinkVerificationDataSource.swift in Sources */, + 6BC6DB482984F9288944FE25 /* NetworkingSaveToLinkVerificationViewController.swift in Sources */, + 2AA0942F22A323B33CA6B7CA /* PartnerAuthDataSource.swift in Sources */, + F03F840B9E896F1B09742191 /* PartnerAuthViewController.swift in Sources */, + AD5B496425E2993C87F0B770 /* PrepaneImageView.swift in Sources */, + C906FC4DE38F16032B787607 /* ResetFlowDataSource.swift in Sources */, + A9F9E63FD6B72F5552A8A850 /* ResetFlowViewController.swift in Sources */, + 6A732CA42B69871F00828CB1 /* EmailTextField.swift in Sources */, + 22426A37E01AE759BF93C422 /* AttributedLabel.swift in Sources */, + 2FADCA33DEC08E6551D94811 /* AttributedTextView.swift in Sources */, + 6ABFE5592B7451710037437C /* TerminalErrorView.swift in Sources */, + 368DFF9D68F1F8D6A4353961 /* AuthFlowHelpers.swift in Sources */, + 9AF6EC34D666BEB3C1397092 /* BulletPointLabelView.swift in Sources */, + 313F5F7F2B0BE5D100BD98A9 /* Docs.docc in Sources */, + F65E8D16DE691EB6C99C4521 /* Button+Extensions.swift in Sources */, + 33FA1684CE79F21271D14F23 /* HitTestStackView.swift in Sources */, + 691619AE9A989548ABA36535 /* HitTestView.swift in Sources */, + 91A3583A0BDE0F8F0C4AD3E2 /* InstitutionIconView.swift in Sources */, + 0375F8C6D79947C992C32362 /* NetworkingOTPDataSource.swift in Sources */, + 40FF444E6CF20E9DA7D90448 /* NetworkingOTPView.swift in Sources */, + 444884F264D13FF654EA7471 /* PaneLayoutView.swift in Sources */, + 49C911372C597EAF00589E0D /* LinkLoginDataSource.swift in Sources */, + 6A7814182B361C5000168992 /* PaneLayoutView+Extensions.swift in Sources */, + 99F41681B77ECB0090F34E31 /* SFSafariViewController+Extensions.swift in Sources */, + AA80602323C28AFAC391358D /* TimeInterval+Extensions.swift in Sources */, + E637387728FA1597B1B51E5D /* UIImage+Extensions.swift in Sources */, + 495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */, + 486E50E6CB90208AB98C031E /* UIImageView+Extensions.swift in Sources */, + F0FB346A0F86C3561CD3C048 /* UITableView+Extensions.swift in Sources */, + A34AB3AC6D071605CABFFC9B /* UIViewController+KeyboardAvoiding.swift in Sources */, + B2970FE2753A4D79E428BA73 /* SuccessDataSource.swift in Sources */, + C39214EA5995D85B847406BE /* SuccessFooterView.swift in Sources */, + 6A1D42FF2B1686FC005A1EB0 /* InstitutionTableView.swift in Sources */, + B45B8DC3DAACDD5F04B1B1BE /* SuccessViewController.swift in Sources */, + 3BFED24B6DF835A0F2FB4939 /* TerminalErrorViewController.swift in Sources */, + 7AE7474B7AFF416B6072721C /* StripeCore+Import.swift in Sources */, + DC4DFC847378AC9E9112B443 /* AuthenticationSessionManager.swift in Sources */, + B5EEF34D158C08A1745FA150 /* ContinueStateView.swift in Sources */, + 2D14461B27B3DEE2CC19B090 /* FinancialConnectionsAccountFetcher.swift in Sources */, + 82FD3CEE526DE8B6519F666E /* FinancialConnectionsSessionFetcher.swift in Sources */, + 6A78141C2B462D5D00168992 /* LegalDetailsNoticeViewController.swift in Sources */, + 6A7814112B32462100168992 /* CloseConfirmationViewController.swift in Sources */, + AB7C9A26484953762FFBB4A5 /* FinancialConnectionsWebFlowViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EF6A0BEC1B066774A0D985E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CBEAB081DD7353928F485071 /* APIPollingHelperTests.swift in Sources */, + 0AF88791C01102CDCC31F419 /* AccountFetcherTests.swift in Sources */, + 492039952CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift in Sources */, + 700B745FEF43088D9E34C0E4 /* AccountPickerHelpersTests.swift in Sources */, + 6744CB1B182C5F7220B0B804 /* AuthFlowHelpersTests.swift in Sources */, + 39E5D4531961150E9CB3262F /* EmptyFinancialConnectionsAPIClient.swift in Sources */, + CB734C25A19D38A87876FB2B /* FinancialConnectionsAnalyticsTest.swift in Sources */, + 497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */, + 77D3B375B9DBF80BA209BC99 /* FinancialConnectionsSessionTests.swift in Sources */, + 846D1D7429B9E414744DEC99 /* FinancialConnectionsSheetTests.swift in Sources */, + C19996D0AC7E046DA87B6B32 /* ManualEntryValidatorTests.swift in Sources */, + 779C729BB49FD4B99DCD517B /* MarkdownBoldAttributedStringTests.swift in Sources */, + 49A0B5862C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift in Sources */, + BF5F964E1CA6312755D4161E /* SessionFetcherTests.swift in Sources */, + ED818E10F37230678B9B73CC /* SoftLinkTests.swift in Sources */, + D9F35B3B31CA2E52055D6B1D /* StringExtensionsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3257A97C053FFC03C4806278 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeFinancialConnections; + target = 44C90013B7C82C80A2F69956 /* StripeFinancialConnections */; + targetProxy = 99584176FCBCA6DC9B8E22E4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + BF6810BAADB14ACB95216C2B /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + E898E7D173685669E31FC58F /* bg-BG */, + 0DA7868C9DD47582244B47C8 /* ca-ES */, + AC7FED22D9EAC568EA6B35EB /* cs-CZ */, + 7C402C24A15DC6167E2C593F /* da */, + 0F4FC108D8C162EEE1EEA97E /* de */, + 24D4A72B4CCA677F45C29A5C /* el-GR */, + 5C09425306344278C7B55089 /* en */, + 106427315CD279EAAD7D1B74 /* en-GB */, + 6B70A0C4DBFE46805549CF8B /* es */, + CF80A9614EB3ADA9E81397F8 /* es-419 */, + CE10909F3FC7D60E13B65226 /* et-EE */, + 9312AAE1BFF1D9BBEA44E8AA /* fi */, + 742D94AC4B2D17F8282A6788 /* fil */, + E90CF6AD88E530CE63D57269 /* fr */, + 24701CABF53C21DD7BCF3E48 /* fr-CA */, + 6C81D547F6BAD96C62E1E4D3 /* hr */, + A3A2815DF2EE9447CE7A3826 /* hu */, + 51EEC3A9E3BC863ED054B1DC /* id */, + 5B65388786D25271A87D34CE /* it */, + F9A847D2AAA7271F507DC9F3 /* ja */, + 5E48DB3155C1546B196DF97B /* ko */, + C8AFA09E86048B4325C36CC8 /* lt-LT */, + BC4D2368AC577A5233DEC72C /* lv-LV */, + 846D9EF58B02C69F9629AE79 /* ms-MY */, + F25B2AB87C9548245C28D14C /* mt */, + EFB09DF9C9434032F387E081 /* nb */, + C0467CE507A92557C72885DF /* nl */, + 5AC5D8EE52FE5D305F78E3A0 /* nn-NO */, + F2BA0F04A5A7D3B1DBF34AEE /* pl-PL */, + 9EA0AA05BC9FC60A06AC1B5E /* pt-BR */, + 66D2857E68EA69AC6F658BEA /* pt-PT */, + B2B74140FCD8F5871F42C881 /* ro-RO */, + 88F7731972F5FB12FD4FA48B /* ru */, + A37D7E687494FAE048945144 /* sk-SK */, + 1CFE14532C10471EC61BB05A /* sl-SI */, + D2C62B6AA6891A4214E0754E /* sv */, + 33B1E2861FA7CA86FF79236C /* tr */, + DD9E2537472B2ED4AA3ED6A2 /* vi */, + F2B7ECC6F6A4DA1F5F376467 /* zh-Hans */, + 50B4E948868910ADA557F50D /* zh-Hant */, + 1CE32B7E492EFD8143F687F2 /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E780C5BEA516D21120ACE2 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 14CED33665ED3D8EE8D5D7B7 /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeFinancialConnectionsTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeFinancialConnectionsTests; + PRODUCT_NAME = StripeFinancialConnectionsTests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Release; + }; + 0F5472F7DE76FFA97369CE47 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2C10E841FF9EBFEA8C2E30AF /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 11E0E3D8EFAC643C1CF22071 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1CD19E0601599AE89976DB4D /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeFinancialConnections/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-financial-connections"; + PRODUCT_NAME = StripeFinancialConnections; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; + 7D2EBF6B1293E586F89B7BA4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 314462DF7856349FF9775598 /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeFinancialConnections/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-financial-connections"; + PRODUCT_NAME = StripeFinancialConnections; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; + 929F106FFD819D993A187A71 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CA2DA47ECE153F888FA675CE /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeFinancialConnectionsTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeFinancialConnectionsTests; + PRODUCT_NAME = StripeFinancialConnectionsTests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Debug; + }; + F7799318348B9FA5263B14D1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B5FFA1B806BC6AD3500B0567 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8C428C73E0383F9203731DCB /* Build configuration list for PBXNativeTarget "StripeFinancialConnectionsTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 929F106FFD819D993A187A71 /* Debug */, + 00E780C5BEA516D21120ACE2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DE1BF3F953C39B1173504C4A /* Build configuration list for PBXNativeTarget "StripeFinancialConnections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7D2EBF6B1293E586F89B7BA4 /* Debug */, + 11E0E3D8EFAC643C1CF22071 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F7F4B17FFDBC5691F1A51423 /* Build configuration list for PBXProject "StripeFinancialConnections" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0F5472F7DE76FFA97369CE47 /* Debug */, + F7799318348B9FA5263B14D1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3D00B888AF0B02587576A83F /* Project object */; +} diff --git a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcshareddata/xcschemes/StripeFinancialConnections.xcscheme b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcshareddata/xcschemes/StripeFinancialConnections.xcscheme new file mode 100644 index 00000000..d58bf207 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcshareddata/xcschemes/StripeFinancialConnections.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeFinancialConnections/StripeFinancialConnections/Docs.docc/StripeFinancialConnections.md b/StripeFinancialConnections/StripeFinancialConnections/Docs.docc/StripeFinancialConnections.md new file mode 100644 index 00000000..1902a698 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Docs.docc/StripeFinancialConnections.md @@ -0,0 +1,3 @@ +# ``StripeFinancialConnections`` + +Placeholder diff --git a/StripeFinancialConnections/StripeFinancialConnections/Info.plist b/StripeFinancialConnections/StripeFinancialConnections/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeFinancialConnections/StripeFinancialConnections/PrivacyInfo.xcprivacy b/StripeFinancialConnections/StripeFinancialConnections/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..a0ed8d32 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/PrivacyInfo.xcprivacy @@ -0,0 +1,45 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeProductInteraction + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePaymentInfo + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/add@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/add@3x.png new file mode 100644 index 00000000..702b211b Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/add@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/back_arrow@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/back_arrow@3x.png new file mode 100644 index 00000000..d605eaf0 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/back_arrow@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/brandicon_default@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/brandicon_default@3x.png new file mode 100644 index 00000000..5ae9bbca Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/brandicon_default@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/bullet@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/bullet@3x.png new file mode 100644 index 00000000..95a877d7 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/bullet@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/cancel_circle@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/cancel_circle@3x.png new file mode 100644 index 00000000..028a7809 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/cancel_circle@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/check@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/check@3x.png new file mode 100644 index 00000000..92f638d5 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/check@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/chevron_down@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/chevron_down@3x.png new file mode 100644 index 00000000..3215a75a Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/chevron_down@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/close@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/close@3x.png new file mode 100644 index 00000000..0ad3bea7 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/close@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/generic_error@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/generic_error@3x.png new file mode 100644 index 00000000..f1847118 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/generic_error@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/info@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/info@3x.png new file mode 100644 index 00000000..4951f0fa Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/info@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/link_logo@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/link_logo@3x.png new file mode 100644 index 00000000..28c60291 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/link_logo@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/panel_arrow_right@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/panel_arrow_right@3x.png new file mode 100644 index 00000000..5c8a2fb3 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/panel_arrow_right@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/person@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/person@3x.png new file mode 100644 index 00000000..5047ce2d Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/person@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/search@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/search@3x.png new file mode 100644 index 00000000..d844f046 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/search@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/spinner@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/spinner@3x.png new file mode 100644 index 00000000..b5921fb5 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/spinner@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/stripe_logo@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/stripe_logo@3x.png new file mode 100644 index 00000000..1e5feb8d Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/stripe_logo@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/testmode@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/testmode@3x.png new file mode 100644 index 00000000..548e69b3 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/testmode@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/warning_triangle@3x.png b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/warning_triangle@3x.png new file mode 100644 index 00000000..3e1a2799 Binary files /dev/null and b/StripeFinancialConnections/StripeFinancialConnections/Resources/Images/warning_triangle@3x.png differ diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/bg-BG.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..e5bc2dde --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/bg-BG.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Неуспешно свързване"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ca-ES.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..9439cb30 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ca-ES.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "No s'ha pogut connectar"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/cs-CZ.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..f3a5cdb9 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Nepodařilo se připojit"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/da.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/da.lproj/Localizable.strings new file mode 100644 index 00000000..80d08acc --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/da.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Kunne ikke oprette forbindelse"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/de.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/de.lproj/Localizable.strings new file mode 100644 index 00000000..a1259e27 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/de.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Verbindung fehlgeschlagen"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/el-GR.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..f48c4cd8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/el-GR.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Δεν ήταν δυνατή η σύνδεση"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/en-GB.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..abb1b524 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/en-GB.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Failed to connect"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/en.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/en.lproj/Localizable.strings @@ -0,0 +1 @@ + diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/es-419.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..6202c93e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/es-419.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Error al establecer conexión"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/es.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/es.lproj/Localizable.strings new file mode 100644 index 00000000..6202c93e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/es.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Error al establecer conexión"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/et-EE.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..9398af76 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/et-EE.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Ühendamine ebaõnnestus"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fi.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fi.lproj/Localizable.strings new file mode 100644 index 00000000..7983b67d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fi.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Yhteyden muodostaminen epäonnistui"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fil.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fil.lproj/Localizable.strings new file mode 100644 index 00000000..7f7ae7ed --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fil.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Nabigong kumonekta"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fr-CA.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..d2681a7a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fr-CA.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Échec de la connexion"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fr.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fr.lproj/Localizable.strings new file mode 100644 index 00000000..d2681a7a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/fr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Échec de la connexion"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/hr.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/hr.lproj/Localizable.strings new file mode 100644 index 00000000..f83d2471 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/hr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Povezivanje nije uspjelo"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/hu.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/hu.lproj/Localizable.strings new file mode 100644 index 00000000..c06a443e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/hu.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Nem sikerült csatlakozni"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/id.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/id.lproj/Localizable.strings new file mode 100644 index 00000000..e36462c2 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/id.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Gagal menghubungkan"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/it.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/it.lproj/Localizable.strings new file mode 100644 index 00000000..dc0b24c8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/it.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Connessione non riuscita"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ja.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ja.lproj/Localizable.strings new file mode 100644 index 00000000..e44fb008 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ja.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "接続できませんでした"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ko.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ko.lproj/Localizable.strings new file mode 100644 index 00000000..727a2712 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ko.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "연결 실패"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/lt-LT.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..57a96610 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/lt-LT.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Nepavyko prisijungti"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/lv-LV.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..29e846ce --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/lv-LV.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Neizdevās izveidot savienojumu"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ms-MY.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..079c9cf4 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ms-MY.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Gagal disambungkan"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/mt.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/mt.lproj/Localizable.strings new file mode 100644 index 00000000..33a45ec3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/mt.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Ma nistgħux naqbdu"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nb.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nb.lproj/Localizable.strings new file mode 100644 index 00000000..b553a8ae --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nb.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Kunne ikke koble til"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nl.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nl.lproj/Localizable.strings new file mode 100644 index 00000000..53931e49 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nl.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Kan geen verbinding maken"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nn-NO.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..61f27cd8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/nn-NO.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Kunne ikkje kople til"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pl-PL.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..335453a1 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pl-PL.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Nie udało się połączyć"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pt-BR.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..64267766 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pt-BR.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Falha ao conectar"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pt-PT.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..05f1c3a3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/pt-PT.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Impossível ligar"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ro-RO.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..2e3b60b6 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ro-RO.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Eroare de conexiune"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ru.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ru.lproj/Localizable.strings new file mode 100644 index 00000000..65fa4f39 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/ru.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Не удалось подключиться"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sk-SK.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..6e1c8cfb --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sk-SK.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Pripojenie sa nepodarilo"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sl-SI.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..a225e8d3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sl-SI.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Povezave ni bilo mogoče vzpostaviti"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sv.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sv.lproj/Localizable.strings new file mode 100644 index 00000000..e9207cf7 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/sv.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Det gick inte att ansluta"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/tr.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/tr.lproj/Localizable.strings new file mode 100644 index 00000000..e5895e8c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/tr.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Bağlantı başarısız."; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/vi.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/vi.lproj/Localizable.strings new file mode 100644 index 00000000..b0ec7065 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/vi.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "Không thể kết nối"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-HK.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..bf70575c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-HK.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "連接失敗"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..308068f8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "连接失败"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-Hant.lproj/Localizable.strings b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..bf70575c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Resources/Localizations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,2 @@ +/* Error message that displays when we're unable to connect to the server. */ +"Failed to connect" = "連接失敗"; \ No newline at end of file diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/APIPollingHelper.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/APIPollingHelper.swift new file mode 100644 index 00000000..0c301f54 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/APIPollingHelper.swift @@ -0,0 +1,108 @@ +// +// APIPollingHelper.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/2/22. +// + +import Foundation +@_spi(STP) import StripeCore + +final class APIPollingHelper { + + struct PollTimingOptions { + let initialPollDelay: TimeInterval + let maxNumberOfRetries: Int + let retryInterval: TimeInterval + + init( + initialPollDelay: TimeInterval = 1.75, + maxNumberOfRetries: Int = 180, + retryInterval: TimeInterval = 0.25 + ) { + self.initialPollDelay = initialPollDelay + self.maxNumberOfRetries = maxNumberOfRetries + self.retryInterval = retryInterval + } + } + + private let apiCall: () -> Future + private let originalPromise: Promise + private let pollTimingOptions: PollTimingOptions + + private var strongSelfReference: APIPollingHelper? + private var currentApiCallTimer: Timer? + private var numberOfRetriesLeft: Int + + init( + apiCall: @escaping () -> Future, + pollTimingOptions: PollTimingOptions = PollTimingOptions() + ) { + self.apiCall = apiCall + self.pollTimingOptions = pollTimingOptions + self.numberOfRetriesLeft = pollTimingOptions.maxNumberOfRetries + self.originalPromise = Promise() + } + + deinit { + invalidateTimer() + } + + func startPollingApiCall() -> Future { + assertMainQueue() + // polling helper will keep a strong reference to itself + // until `originalPromise` is fulfilled + self.strongSelfReference = self + originalPromise + .observe(on: .main) { [weak self] _ in + // clear the strong reference once the original + // promise is fulfilled... + self?.strongSelfReference = nil + } + + callApi(afterDelay: pollTimingOptions.initialPollDelay) + return originalPromise + } + + private func callApi(afterDelay delay: TimeInterval) { + assertMainQueue() + self.currentApiCallTimer = Timer.scheduledTimer( + withTimeInterval: delay, + repeats: false, + block: { [weak self] _ in + guard let self = self else { return } + self.invalidateTimer() + self.callApi() + } + ) + } + + private func callApi() { + assertMainQueue() + apiCall() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success: + self.originalPromise.fullfill(with: result) + case .failure(let error): + if self.numberOfRetriesLeft > 0, + let error = error as? StripeError, + case .apiError(let apiError) = error, + // we want to retry in the case of a 202 + apiError.statusCode == 202 + { + self.numberOfRetriesLeft -= 1 + self.callApi(afterDelay: self.pollTimingOptions.retryInterval) + } else { + self.originalPromise.fullfill(with: result) + } + } + } + } + + private func invalidateTimer() { + currentApiCallTimer?.invalidate() + currentApiCallTimer = nil + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/APIVersion.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/APIVersion.swift new file mode 100644 index 00000000..f3c3dd75 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/APIVersion.swift @@ -0,0 +1,26 @@ +// +// APIVersion.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 9/13/22. +// + +import Foundation +@_spi(STP) import StripeCore + +struct APIVersion { + /** + The latest production-ready version of the Financial Connections API that the + SDK is capable of using. + + - Note: Update this value when a new API version is ready for use in production. + */ + private static let apiVersion: Int = 1 // WARNING: this is also referenced in other places, so double check changes! + private static let header = "financial_connections_client_api_beta=v\(apiVersion)" + + static func configureFinancialConnectionsAPIVersion(apiClient: STPAPIClient) { + var betas = apiClient.betas + betas.insert(header) + apiClient.betas = betas + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/FinancialConnectionsAPIClient.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/FinancialConnectionsAPIClient.swift new file mode 100644 index 00000000..bf33076c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/FinancialConnectionsAPIClient.swift @@ -0,0 +1,1140 @@ +// +// FinancialConnectionsAPIClient.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/1/21. +// + +import Foundation +@_spi(STP) import StripeCore + +final class FinancialConnectionsAPIClient { + private enum EncodingError: Error { + case cannotCastToDictionary + } + + let backingAPIClient: STPAPIClient + + var isLinkWithStripe: Bool = false + var consumerPublishableKey: String? + var consumerSession: ConsumerSessionData? + + var requestSurface: String { + isLinkWithStripe ? "ios_instant_debits" : "ios_connections" + } + + init(apiClient: STPAPIClient) { + self.backingAPIClient = apiClient + } + + /// Returns the `consumerPublishableKey` for scenarios where it is valid to do so. That is; + /// - `canUseConsumerKey` must be `true`. This is a flag passed in by each API request. + /// - `isLinkWithStripe` must be `true`. This represents whether we're in the Instant Debits flow. + /// - `consumerSession` must be verified. This represents whether we have a verified Link user. + func consumerPublishableKeyProvider(canUseConsumerKey: Bool) -> String? { + guard canUseConsumerKey, isLinkWithStripe, consumerSession?.isVerified == true else { + return nil + } + return consumerPublishableKey + } + + /// Passthrough to `STPAPIClient.get` which uses the `consumerPublishableKey` whenever it should be used. + /// As a rule of thumb, `useConsumerPublishableKeyIfNeeded` should be `true` for requests that happen after the user is verified. + /// However, there are some exceptions to this rules (such as the create payment method request). + private func get( + resource: String, + parameters: [String: Any], + useConsumerPublishableKeyIfNeeded: Bool + ) -> Promise { + let possibleConsumerPublishableKey = consumerPublishableKeyProvider(canUseConsumerKey: useConsumerPublishableKeyIfNeeded) + return backingAPIClient.get( + resource: resource, + parameters: parameters, + consumerPublishableKey: possibleConsumerPublishableKey + ) + } + + /// Passthrough to `STPAPIClient.post` which uses the `consumerPublishableKey` whenever it should be used. + private func post( + resource: String, + parameters: [String: Any], + useConsumerPublishableKeyIfNeeded: Bool + ) -> Promise { + let possibleConsumerPublishableKey = consumerPublishableKeyProvider(canUseConsumerKey: useConsumerPublishableKeyIfNeeded) + return backingAPIClient.post( + resource: resource, + parameters: parameters, + consumerPublishableKey: possibleConsumerPublishableKey + ) + } + + private func updateAndApplyFraudDetection(to parameters: [String: Any]) -> Future<[String: Any]> { + let promise = Promise<[String: Any]>() + STPTelemetryClient.shared.updateFraudDetectionIfNecessary { _ in + // Fire and forget operation. Ignore any possible errors here. + var paramsWithTelemetry = parameters + paramsWithTelemetry = STPTelemetryClient.shared.paramsByAddingTelemetryFields(toParams: paramsWithTelemetry) + promise.fulfill { paramsWithTelemetry } + } + return promise + } + + static func encodeAsParameters(_ value: any Encodable) throws -> [String: Any]? { + let jsonData = try JSONEncoder().encode(value) + let jsonObject = try JSONSerialization.jsonObject(with: jsonData) + + if let dictionary = jsonObject as? [String: Any] { + return dictionary.isEmpty ? nil : dictionary + } else { + throw EncodingError.cannotCastToDictionary + } + } +} + +protocol FinancialConnectionsAPI { + func synchronize( + clientSecret: String, + returnURL: String? + ) -> Future + + func fetchFinancialConnectionsAccounts( + clientSecret: String, + startingAfterAccountId: String? + ) -> Promise + + func fetchFinancialConnectionsSession(clientSecret: String) -> Promise + + func markConsentAcquired(clientSecret: String) -> Promise + + func fetchFeaturedInstitutions(clientSecret: String) -> Promise + + func fetchInstitutions(clientSecret: String, query: String) -> Future + + func createAuthSession(clientSecret: String, institutionId: String) -> Promise + + func cancelAuthSession(clientSecret: String, authSessionId: String) -> Promise + + func retrieveAuthSession( + clientSecret: String, + authSessionId: String + ) -> Future + + func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) -> Future< + FinancialConnectionsMixedOAuthParams + > + + func authorizeAuthSession( + clientSecret: String, + authSessionId: String, + publicToken: String? + ) -> Promise + + func fetchAuthSessionAccounts( + clientSecret: String, + authSessionId: String, + initialPollDelay: TimeInterval + ) -> Future + + func selectAuthSessionAccounts( + clientSecret: String, + authSessionId: String, + selectedAccountIds: [String] + ) -> Promise + + func markLinkingMoreAccounts(clientSecret: String) -> Promise + + func completeFinancialConnectionsSession( + clientSecret: String, + terminalError: String? + ) -> Future + + func attachBankAccountToLinkAccountSession( + clientSecret: String, + accountNumber: String, + routingNumber: String, + consumerSessionClientSecret: String? + ) -> Future + + func attachLinkedAccountIdToLinkAccountSession( + clientSecret: String, + linkedAccountId: String, + consumerSessionClientSecret: String? + ) -> Future + + func recordAuthSessionEvent( + clientSecret: String, + authSessionId: String, + eventNamespace: String, + eventName: String + ) -> Future + + // MARK: - Networking + + func saveAccountsToNetworkAndLink( + shouldPollAccounts: Bool, + selectedAccounts: [FinancialConnectionsPartnerAccount]?, + emailAddress: String?, + phoneNumber: String?, + country: String?, + consumerSessionClientSecret: String?, + clientSecret: String + ) -> Future<( + manifest: FinancialConnectionsSessionManifest, + customSuccessPaneMessage: String? + )> + + func disableNetworking( + disabledReason: String?, + clientSuggestedNextPaneOnDisableNetworking: String?, + clientSecret: String + ) -> Future + + func fetchNetworkedAccounts( + clientSecret: String, + consumerSessionClientSecret: String + ) -> Future + + func selectNetworkedAccounts( + selectedAccountIds: [String], + clientSecret: String, + consumerSessionClientSecret: String, + consentAcquired: Bool? + ) -> Future + + func markLinkStepUpAuthenticationVerified( + clientSecret: String + ) -> Future + + func consumerSessionLookup( + emailAddress: String, + clientSecret: String + ) -> Future + + // MARK: - Link API's + + func consumerSessionStartVerification( + otpType: String, + customEmailType: String?, + connectionsMerchantName: String?, + consumerSessionClientSecret: String + ) -> Future + + func consumerSessionConfirmVerification( + otpCode: String, + otpType: String, + consumerSessionClientSecret: String + ) -> Future + + func markLinkVerified( + clientSecret: String + ) -> Future + + func linkAccountSignUp( + emailAddress: String, + phoneNumber: String, + country: String, + amount: Int?, + currency: String?, + intentId: ElementsSessionContext.IntentID? + ) -> Future + + func attachLinkConsumerToLinkAccountSession( + linkAccountSession: String, + consumerSessionClientSecret: String + ) -> Future + + func paymentDetails( + consumerSessionClientSecret: String, + bankAccountId: String, + billingAddress: BillingAddress?, + billingEmail: String? + ) -> Future + + func sharePaymentDetails( + consumerSessionClientSecret: String, + paymentDetailsId: String, + expectedPaymentMethodType: String, + billingEmail: String?, + billingPhone: String? + ) -> Future + + func paymentMethods( + consumerSessionClientSecret: String, + paymentDetailsId: String, + billingDetails: ElementsSessionContext.BillingDetails? + ) -> Future +} + +extension FinancialConnectionsAPIClient: FinancialConnectionsAPI { + + func fetchFinancialConnectionsAccounts( + clientSecret: String, + startingAfterAccountId: String? + ) -> Promise { + var parameters = ["client_secret": clientSecret] + if let startingAfterAccountId = startingAfterAccountId { + parameters["starting_after"] = startingAfterAccountId + } + return self.get( + resource: APIEndpointListAccounts, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func fetchFinancialConnectionsSession(clientSecret: String) -> Promise { + return self.get( + resource: APIEndpointSessionReceipt, + parameters: ["client_secret": clientSecret], + useConsumerPublishableKeyIfNeeded: false + ) + } + + func synchronize( + clientSecret: String, + returnURL: String? + ) -> Future { + let parameters: [String: Any] = [ + "expand": ["manifest.active_auth_session"], + "client_secret": clientSecret, + "mobile": { + var mobileParameters: [String: Any] = [ + "fullscreen": true, + "hide_close_button": true, + "forced_authflow_version": "v3", + ] + mobileParameters["app_return_url"] = returnURL + return mobileParameters + }(), + "locale": Locale.current.toLanguageTag(), + ] + return self.post( + resource: "financial_connections/sessions/synchronize", + parameters: parameters, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func markConsentAcquired(clientSecret: String) -> Promise { + let parameters: [String: Any] = [ + "client_secret": clientSecret, + "expand": ["active_auth_session"], + ] + return self.post( + resource: APIEndpointConsentAcquired, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func fetchFeaturedInstitutions(clientSecret: String) -> Promise { + let parameters = [ + "client_secret": clientSecret, + ] + return self.get( + resource: APIEndpointFeaturedInstitutions, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func fetchInstitutions(clientSecret: String, query: String) -> Future { + let parameters = [ + "client_secret": clientSecret, + "query": query, + "limit": "20", + ] + return self.get( + resource: APIEndpointSearchInstitutions, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func createAuthSession(clientSecret: String, institutionId: String) -> Promise { + let body: [String: Any] = [ + "client_secret": clientSecret, + "institution": institutionId, + "use_mobile_handoff": "false", + "use_abstract_flow": true, + "return_url": "ios", + ] + return self.post( + resource: APIEndpointAuthSessions, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func cancelAuthSession(clientSecret: String, authSessionId: String) -> Promise { + let body = [ + "client_secret": clientSecret, + "id": authSessionId, + ] + return self.post( + resource: APIEndpointAuthSessionsCancel, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func retrieveAuthSession( + clientSecret: String, + authSessionId: String + ) -> Future { + let body: [String: Any] = [ + "client_secret": clientSecret, + "id": authSessionId, + ] + return self.post( + resource: APIEndpointAuthSessionsRetrieve, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) -> Future< + FinancialConnectionsMixedOAuthParams + > { + let body = [ + "client_secret": clientSecret, + "id": authSessionId, + ] + let pollingHelper = APIPollingHelper( + apiCall: { [weak self] in + guard let self = self else { + return Promise( + error: FinancialConnectionsSheetError.unknown(debugDescription: "STPAPIClient deallocated.") + ) + } + return self.post( + resource: APIEndpointAuthSessionsOAuthResults, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + }, + pollTimingOptions: APIPollingHelper.PollTimingOptions( + initialPollDelay: 0, + maxNumberOfRetries: 300, // Stripe.js has 600 second timeout, 600 / 2 = 300 retries + retryInterval: 2.0 + ) + ) + return pollingHelper.startPollingApiCall() + } + + func authorizeAuthSession( + clientSecret: String, + authSessionId: String, + publicToken: String? = nil + ) -> Promise { + var body = [ + "client_secret": clientSecret, + "id": authSessionId, + ] + body["public_token"] = publicToken // not all integrations require public_token + return self.post( + resource: APIEndpointAuthSessionsAuthorized, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func fetchAuthSessionAccounts( + clientSecret: String, + authSessionId: String, + initialPollDelay: TimeInterval + ) -> Future { + let body: [String: Any] = [ + "client_secret": clientSecret, + "id": authSessionId, + "expand": ["data.institution"], + ] + let pollingHelper = APIPollingHelper( + apiCall: { [weak self] in + guard let self = self else { + return Promise( + error: FinancialConnectionsSheetError.unknown(debugDescription: "STPAPIClient deallocated.") + ) + } + return self.post( + resource: APIEndpointAuthSessionsAccounts, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + }, + pollTimingOptions: APIPollingHelper.PollTimingOptions( + initialPollDelay: initialPollDelay + ) + ) + return pollingHelper.startPollingApiCall() + } + + func selectAuthSessionAccounts( + clientSecret: String, + authSessionId: String, + selectedAccountIds: [String] + ) -> Promise { + let body: [String: Any] = [ + "client_secret": clientSecret, + "id": authSessionId, + "selected_accounts": selectedAccountIds, + "expand": ["data.institution"], + ] + return self.post( + resource: APIEndpointAuthSessionsSelectedAccounts, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func markLinkingMoreAccounts(clientSecret: String) -> Promise { + let body: [String: Any] = [ + "client_secret": clientSecret, + "expand": ["active_auth_session"], + ] + return self.post( + resource: APIEndpointLinkMoreAccounts, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func completeFinancialConnectionsSession( + clientSecret: String, + terminalError: String? + ) -> Future { + var body: [String: Any] = [ + "client_secret": clientSecret, + ] + body["terminal_error"] = terminalError + return self.post( + resource: APIEndpointComplete, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + .chained { (session: StripeAPI.FinancialConnectionsSession) in + if session.accounts.hasMore { + // de-paginate the accounts we get from the session because + // we want to give the clients a full picture of the number + // of accounts that were linked + let accountAPIFetcher = FinancialConnectionsAccountAPIFetcher( + api: self, + clientSecret: clientSecret + ) + return accountAPIFetcher + .fetchAccounts(initial: session.accounts.data) + .chained { [accountAPIFetcher] accounts in + _ = accountAPIFetcher // retain `accountAPIFetcher` for the duration of the network call + return Promise( + value: StripeAPI.FinancialConnectionsSession( + clientSecret: session.clientSecret, + id: session.id, + accounts: StripeAPI.FinancialConnectionsSession.AccountList( + data: accounts, + hasMore: false + ), + livemode: session.livemode, + paymentAccount: session.paymentAccount, + bankAccountToken: session.bankAccountToken, + status: session.status, + statusDetails: session.statusDetails + ) + ) + } + } else { + return Promise(value: session) + } + } + } + + func attachBankAccountToLinkAccountSession( + clientSecret: String, + accountNumber: String, + routingNumber: String, + consumerSessionClientSecret: String? + ) -> Future { + return attachPaymentAccountToLinkAccountSession( + clientSecret: clientSecret, + accountNumber: accountNumber, + routingNumber: routingNumber, + consumerSessionClientSecret: consumerSessionClientSecret + ) + } + + func attachLinkedAccountIdToLinkAccountSession( + clientSecret: String, + linkedAccountId: String, + consumerSessionClientSecret: String? + ) -> Future { + return attachPaymentAccountToLinkAccountSession( + clientSecret: clientSecret, + linkedAccountId: linkedAccountId, + consumerSessionClientSecret: consumerSessionClientSecret + ) + } + + private func attachPaymentAccountToLinkAccountSession( + clientSecret: String, + accountNumber: String? = nil, + routingNumber: String? = nil, + linkedAccountId: String? = nil, + consumerSessionClientSecret: String? = nil + ) -> Future { + var body: [String: Any] = [ + "client_secret": clientSecret, + ] + body["consumer_session_client_secret"] = consumerSessionClientSecret // optional for Link + if let accountNumber = accountNumber, let routingNumber = routingNumber { + body["type"] = "bank_account" + body["bank_account"] = [ + "routing_number": routingNumber, + "account_number": accountNumber, + ] + } else if let linkedAccountId = linkedAccountId { + body["type"] = "linked_account" + body["linked_account"] = [ + "id": linkedAccountId, + ] + } else { + assertionFailure() + return Promise( + error: + FinancialConnectionsSheetError + .unknown(debugDescription: "Invalid usage of \(#function).") + ) + } + + let pollingHelper = APIPollingHelper( + apiCall: { [weak self] in + guard let self = self else { + return Promise( + error: FinancialConnectionsSheetError.unknown(debugDescription: "STPAPIClient deallocated.") + ) + } + return self.post( + resource: APIEndpointAttachPaymentAccount, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + }, + pollTimingOptions: APIPollingHelper.PollTimingOptions( + initialPollDelay: 1.0 + ) + ) + return pollingHelper.startPollingApiCall() + } + + func recordAuthSessionEvent( + clientSecret: String, + authSessionId: String, + eventNamespace: String, + eventName: String + ) -> Future { + let clientTimestamp = Date().timeIntervalSince1970.milliseconds + var body: [String: Any] = [ + "id": authSessionId, + "client_secret": clientSecret, + "client_timestamp": clientTimestamp, + "frontend_events": [ + [ + "event_namespace": eventNamespace, + "event_name": eventName, + "client_timestamp": clientTimestamp, + "raw_event_details": "{}", + ] as [String: Any], + ], + ] + body["key"] = backingAPIClient.publishableKey + return self.post( + resource: APIEndpointAuthSessionsEvents, + parameters: body, + useConsumerPublishableKeyIfNeeded: true + ) + } + + // MARK: - Networking + + func saveAccountsToNetworkAndLink( + shouldPollAccounts: Bool, + selectedAccounts: [FinancialConnectionsPartnerAccount]?, + emailAddress: String?, + phoneNumber: String?, + country: String?, + consumerSessionClientSecret: String?, + clientSecret: String + ) -> Future<( + manifest: FinancialConnectionsSessionManifest, + customSuccessPaneMessage: String? + )> { + let saveAccountsToLinkHandler: () -> Future<( + manifest: FinancialConnectionsSessionManifest, + customSuccessPaneMessage: String? + )> = { + return self.saveAccountsToLink( + emailAddress: emailAddress, + phoneNumber: phoneNumber, + country: country, + selectedAccountIds: selectedAccounts?.map({ $0.id }), + consumerSessionClientSecret: consumerSessionClientSecret, + clientSecret: clientSecret + ) + .chained { manifest in + return Promise( + value: ( + manifest: manifest, + customSuccessPaneMessage: manifest.displayText?.successPane?.subCaption + ) + ) + } + } + if + let linkedAccountIds = selectedAccounts?.compactMap({ $0.linkedAccountId }), + shouldPollAccounts, + !linkedAccountIds.isEmpty + { + let promise = Promise<( + manifest: FinancialConnectionsSessionManifest, + customSuccessPaneMessage: String? + )>() + pollAccountNumbersForSelectedAccounts( + linkedAccountIds: linkedAccountIds + ) + .observe { result in + switch result { + case .success: + saveAccountsToLinkHandler() + .observe { result in + promise.fullfill(with: result) + } + case .failure(let error): + self.disableNetworking( + disabledReason: "account_numbers_not_available", + clientSuggestedNextPaneOnDisableNetworking: nil, + clientSecret: clientSecret + ).observe { _ in } // ignoring return is intentional + + promise.reject(with: error) + } + } + return promise + } else { + return saveAccountsToLinkHandler() + } + } + + private func pollAccountNumbersForSelectedAccounts( + linkedAccountIds: [String] + ) -> Future { + let body: [String: Any] = [ + "linked_accounts": linkedAccountIds, + ] + let pollingHelper = APIPollingHelper( + apiCall: { [weak self] in + guard let self = self else { + return Promise( + error: FinancialConnectionsSheetError.unknown( + debugDescription: "STPAPIClient deallocated." + ) + ) + } + return self.get( + resource: APIEndpointPollAccountNumbers, + parameters: body, + useConsumerPublishableKeyIfNeeded: false + ) + }, + pollTimingOptions: APIPollingHelper.PollTimingOptions( + initialPollDelay: 1.0, + maxNumberOfRetries: 20 + ) + ) + return pollingHelper.startPollingApiCall() + } + + private func saveAccountsToLink( + emailAddress: String?, + phoneNumber: String?, + country: String?, + selectedAccountIds: [String]?, + consumerSessionClientSecret: String?, + clientSecret: String + ) -> Future { + var body: [String: Any] = [ + "client_secret": clientSecret, + "expand": ["active_auth_session"], + ] + body["selected_accounts"] = selectedAccountIds // null for manual entry + body["email_address"] = emailAddress? + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + body["phone_number"] = phoneNumber + body["country"] = country + body["locale"] = (phoneNumber != nil) ? Locale.current.toLanguageTag() : nil + body["consumer_session_client_secret"] = consumerSessionClientSecret + return post( + resource: APIEndpointSaveAccountsToLink, + parameters: body, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func disableNetworking( + disabledReason: String?, + clientSuggestedNextPaneOnDisableNetworking: String?, + clientSecret: String + ) -> Future { + var body: [String: Any] = [ + "client_secret": clientSecret, + "expand": ["active_auth_session"], + ] + body["disabled_reason"] = disabledReason + body["client_requested_next_pane_on_disable_networking"] = clientSuggestedNextPaneOnDisableNetworking + return post( + resource: APIEndpointDisableNetworking, + parameters: body, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func markLinkVerified( + clientSecret: String + ) -> Future { + let parameters: [String: Any] = [ + "client_secret": clientSecret, + "expand": ["active_auth_session"], + ] + return post( + resource: APIEndpointLinkVerified, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func fetchNetworkedAccounts( + clientSecret: String, + consumerSessionClientSecret: String + ) -> Future { + let parameters: [String: Any] = [ + "client_secret": clientSecret, + "consumer_session_client_secret": consumerSessionClientSecret, + "expand": ["data.institution"], + ] + return get( + resource: APIEndpointNetworkedAccounts, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func selectNetworkedAccounts( + selectedAccountIds: [String], + clientSecret: String, + consumerSessionClientSecret: String, + consentAcquired: Bool? + ) -> Future { + var parameters: [String: Any] = [ + "selected_accounts": selectedAccountIds, + "client_secret": clientSecret, + "consumer_session_client_secret": consumerSessionClientSecret, + ] + parameters["consent_acquired"] = consentAcquired + return post( + resource: APIEndpointShareNetworkedAccount, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func markLinkStepUpAuthenticationVerified( + clientSecret: String + ) -> Future { + let parameters: [String: Any] = [ + "client_secret": clientSecret, + "expand": ["active_auth_session"], + ] + return post( + resource: APIEndpointLinkStepUpAuthenticationVerified, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func consumerSessionLookup( + emailAddress: String, + clientSecret: String + ) -> Future { + let parameters: [String: Any] = [ + "email_address": + emailAddress + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased(), + "client_secret": clientSecret, + ] + return post( + resource: APIEndpointConsumerSessions, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + // MARK: - Link API's + + func consumerSessionStartVerification( + otpType: String, + customEmailType: String?, + connectionsMerchantName: String?, + consumerSessionClientSecret: String + ) -> Future { + var parameters: [String: Any] = [ + "request_surface": requestSurface, + "type": otpType, + "credentials": [ + "consumer_session_client_secret": consumerSessionClientSecret, + ], + "locale": Locale.current.toLanguageTag(), + ] + parameters["custom_email_type"] = customEmailType + parameters["connections_merchant_name"] = connectionsMerchantName + return post( + resource: "consumers/sessions/start_verification", + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func consumerSessionConfirmVerification( + otpCode: String, + otpType: String, + consumerSessionClientSecret: String + ) -> Future { + let parameters: [String: Any] = [ + "type": otpType, + "code": otpCode, + "credentials": [ + "consumer_session_client_secret": consumerSessionClientSecret, + ], + "request_surface": requestSurface, + ] + return post( + resource: "consumers/sessions/confirm_verification", + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func linkAccountSignUp( + emailAddress: String, + phoneNumber: String, + country: String, + amount: Int?, + currency: String?, + intentId: ElementsSessionContext.IntentID? + ) -> Future { + var parameters: [String: Any] = [ + "request_surface": requestSurface, + "email_address": emailAddress + .trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased(), + "phone_number": phoneNumber, + "country": country, + "country_inferring_method": "PHONE_NUMBER", + "locale": Locale.current.toLanguageTag(), + "consent_action": "entered_phone_number_clicked_save_to_link", + ] + + if let amount, let currency { + parameters["amount"] = amount + parameters["currency"] = currency + } + + if let intentId { + switch intentId { + case .payment(let paymentIntentId): + parameters["financial_incentive"] = [ + "payment_intent": paymentIntentId, + ] + case .setup(let setupIntentId): + parameters["financial_incentive"] = [ + "setup_intent": setupIntentId, + ] + } + } + + return post( + resource: APIEndpointLinkAccountsSignUp, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func attachLinkConsumerToLinkAccountSession( + linkAccountSession: String, + consumerSessionClientSecret: String + ) -> Future { + let parameters: [String: Any] = [ + "request_surface": requestSurface, + "link_account_session": linkAccountSession, + "credentials": [ + "consumer_session_client_secret": consumerSessionClientSecret + ], + ] + return post( + resource: APIEndpointAttachLinkConsumerToLinkAccountSession, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: false + ) + } + + func paymentDetails( + consumerSessionClientSecret: String, + bankAccountId: String, + billingAddress: BillingAddress?, + billingEmail: String? + ) -> Future { + var parameters: [String: Any] = [ + "request_surface": requestSurface, + "credentials": [ + "consumer_session_client_secret": consumerSessionClientSecret + ], + "bank_account": [ + "account": bankAccountId + ], + "type": "bank_account", + ] + + if let billingAddress { + do { + let encodedBillingAddress = try Self.encodeAsParameters(billingAddress) + parameters["billing_address"] = encodedBillingAddress + } catch let error { + let promise = Promise() + promise.reject(with: error) + return promise + } + } + + if let billingEmail, !billingEmail.isEmpty { + parameters["billing_email_address"] = billingEmail.lowercased() + } + + return post( + resource: APIEndpointPaymentDetails, + parameters: parameters, + useConsumerPublishableKeyIfNeeded: true + ) + } + + func sharePaymentDetails( + consumerSessionClientSecret: String, + paymentDetailsId: String, + expectedPaymentMethodType: String, + billingEmail: String?, + billingPhone: String? + ) -> Future { + var parameters: [String: Any] = [ + "request_surface": requestSurface, + "id": paymentDetailsId, + "credentials": [ + "consumer_session_client_secret": consumerSessionClientSecret + ], + "expected_payment_method_type": expectedPaymentMethodType, + "expand": ["payment_method"], + ] + + if let billingEmail { + parameters["billing_email"] = billingEmail + } + + if let billingPhone { + parameters["billing_phone"] = billingPhone + } + + return updateAndApplyFraudDetection(to: parameters) + .chained { [weak self] parametersWithTelemetry -> Future in + guard let self else { + return Promise( + error: FinancialConnectionsSheetError.unknown(debugDescription: "FinancialConnectionsAPIClient was deallocated.") + ) + } + return self.post( + resource: APIEndpointSharePaymentDetails, + parameters: parametersWithTelemetry, + useConsumerPublishableKeyIfNeeded: false + ) + } + } + + func paymentMethods( + consumerSessionClientSecret: String, + paymentDetailsId: String, + billingDetails: ElementsSessionContext.BillingDetails? + ) -> Future { + var parameters: [String: Any] = [ + "link": [ + "credentials": [ + "consumer_session_client_secret": consumerSessionClientSecret + ], + "payment_details_id": paymentDetailsId, + ], + "type": "link", + ] + + if let billingDetails { + do { + let encodedBillingDetails = try Self.encodeAsParameters(billingDetails) + parameters["billing_details"] = encodedBillingDetails + } catch let error { + let promise = Promise() + promise.reject(with: error) + return promise + } + } + + return updateAndApplyFraudDetection(to: parameters) + .chained { [weak self] parametersWithTelemetry -> Future in + guard let self else { + return Promise( + error: FinancialConnectionsSheetError.unknown(debugDescription: "FinancialConnectionsAPIClient was deallocated.") + ) + } + return self.post( + resource: APIEndpointPaymentMethods, + parameters: parametersWithTelemetry, + useConsumerPublishableKeyIfNeeded: false + ) + } + } +} + +private let APIEndpointListAccounts = "link_account_sessions/list_accounts" +private let APIEndpointAttachPaymentAccount = "link_account_sessions/attach_payment_account" +private let APIEndpointSessionReceipt = "link_account_sessions/session_receipt" +private let APIEndpointGenerateHostedURL = "link_account_sessions/generate_hosted_url" +private let APIEndpointConsentAcquired = "link_account_sessions/consent_acquired" +private let APIEndpointLinkMoreAccounts = "link_account_sessions/link_more_accounts" +private let APIEndpointComplete = "link_account_sessions/complete" +private let APIEndpointFeaturedInstitutions = "connections/featured_institutions" +private let APIEndpointSearchInstitutions = "connections/institutions" +private let APIEndpointAuthSessions = "connections/auth_sessions" +private let APIEndpointAuthSessionsCancel = "connections/auth_sessions/cancel" +private let APIEndpointAuthSessionsRetrieve = "connections/auth_sessions/retrieve" +private let APIEndpointAuthSessionsOAuthResults = "connections/auth_sessions/oauth_results" +private let APIEndpointAuthSessionsAuthorized = "connections/auth_sessions/authorized" +private let APIEndpointAuthSessionsAccounts = "connections/auth_sessions/accounts" +private let APIEndpointAuthSessionsSelectedAccounts = "connections/auth_sessions/selected_accounts" +private let APIEndpointAuthSessionsEvents = "connections/auth_sessions/events" +// Networking +private let APIEndpointDisableNetworking = "link_account_sessions/disable_networking" +private let APIEndpointLinkStepUpAuthenticationVerified = "link_account_sessions/link_step_up_authentication_verified" +private let APIEndpointLinkVerified = "link_account_sessions/link_verified" +private let APIEndpointNetworkedAccounts = "link_account_sessions/networked_accounts" +private let APIEndpointSaveAccountsToLink = "link_account_sessions/save_accounts_to_link" +private let APIEndpointShareNetworkedAccount = "link_account_sessions/share_networked_account" +private let APIEndpointConsumerSessions = "connections/link_account_sessions/consumer_sessions" +private let APIEndpointPollAccountNumbers = "link_account_sessions/poll_account_numbers" +// Instant Debits +private let APIEndpointLinkAccountsSignUp = "consumers/accounts/sign_up" +private let APIEndpointAttachLinkConsumerToLinkAccountSession = "consumers/attach_link_consumer_to_link_account_session" +private let APIEndpointPaymentDetails = "consumers/payment_details" +private let APIEndpointSharePaymentDetails = "consumers/payment_details/share" +private let APIEndpointPaymentMethods = "payment_methods" diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/BankAccountToken.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/BankAccountToken.swift new file mode 100644 index 00000000..d6faae49 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/BankAccountToken.swift @@ -0,0 +1,40 @@ +// +// BankAccountToken.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 4/8/22. +// + +import Foundation +@_spi(STP) import StripeCore + +public extension StripeAPI { + + struct BankAccountToken { + + // MARK: - Types + + public struct BankAccount { + public let id: String + public let accountHolderName: String? + public let bankName: String? + public let country: String + public let currency: String + public let fingerprint: String? + public let last4: String + public let routingNumber: String? + public let status: String + } + + public let id: String + public let bankAccount: BankAccountToken.BankAccount? + public let clientIp: String? + public let livemode: Bool + public let used: Bool + } +} + +// MARK: - Decodable + +@_spi(STP) extension StripeAPI.BankAccountToken: Decodable {} +@_spi(STP) extension StripeAPI.BankAccountToken.BankAccount: Decodable {} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/ConsumerSession/ConsumerSessionModels.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/ConsumerSession/ConsumerSessionModels.swift new file mode 100644 index 00000000..045d5986 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/ConsumerSession/ConsumerSessionModels.swift @@ -0,0 +1,65 @@ +// +// LookupConsumerSessionResponse.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/25/23. +// + +import Foundation +@_spi(STP) import StripeCore + +struct ConsumerSessionData: Decodable { + let clientSecret: String + let emailAddress: String + let redactedFormattedPhoneNumber: String + let verificationSessions: [VerificationSession] + + /// A consumer session is considered verified if the `state == .verified` or the `type == .signUp`. + var isVerified: Bool { + verificationSessions.contains(where: { $0.state == .verified }) + || verificationSessions.contains(where: { $0.type == .signUp }) + } +} + +struct VerificationSession: Decodable { + enum SessionType: String, SafeEnumDecodable, Equatable { + case signUp = "SIGNUP" + case email = "EMAIL" + case sms = "SMS" + case unparsable + } + + enum SessionState: String, SafeEnumDecodable, Equatable { + case started = "STARTED" + case failed = "FAILED" + case verified = "VERIFIED" + case canceled = "CANCELED" + case expired = "EXPIRED" + case unparsable + } + + let type: SessionType + let state: SessionState +} + +struct LookupConsumerSessionResponse: Decodable { + let exists: Bool + let accountId: String? + let publishableKey: String? + let consumerSession: ConsumerSessionData? +} + +struct LinkSignUpResponse: Decodable { + let accountId: String + let publishableKey: String + let consumerSession: ConsumerSessionData +} + +struct AttachLinkConsumerToLinkAccountSessionResponse: Decodable { + let id: String + let clientSecret: String +} + +struct ConsumerSessionResponse: Decodable { + let consumerSession: ConsumerSessionData +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAccount.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAccount.swift new file mode 100644 index 00000000..d9f4b545 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAccount.swift @@ -0,0 +1,138 @@ +// +// FinancialConnectionsAccount.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/17/21. +// + +import Foundation +@_spi(STP) import StripeCore + +public extension StripeAPI { + + /// A Financial Connections Account represents an account that exists outside of Stripe, to which you have been granted some degree of access. + /// - seealso: https://stripe.com/docs/api/financial_connections/accounts/object + struct FinancialConnectionsAccount { + + // MARK: - Types + + public struct BalanceRefresh { + @frozen public enum Status: String, SafeEnumCodable, Equatable { + case failed = "failed" + case pending = "pending" + case succeeded = "succeeded" + case unparsable + } + /** The time at which the last refresh attempt was initiated. Measured in seconds since the Unix epoch. */ + public let lastAttemptedAt: Int + public let status: Status + } + + public struct CashBalance { + /** The funds available to the account holder. Typically this is the current balance less any holds. Each key is a three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Each value is an integer amount. A positive amount indicates money owed to the account holder. A negative amount indicates money owed by the account holder. */ + public let available: [String: Int]? + } + + public struct CreditBalance { + /** The credit that has been used by the account holder. Each key is a three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Each value is a integer amount. A positive amount indicates money owed to the account holder. A negative amount indicates money owed by the account holder. */ + public let used: [String: Int]? + } + + public struct Balance { + @frozen public enum ModelType: String, SafeEnumCodable, Equatable { + case cash = "cash" + case credit = "credit" + case unparsable + } + /** The time that the external institution calculated this balance. Measured in seconds since the Unix epoch. */ + public let asOf: Int + public let cash: CashBalance? + public let credit: CreditBalance? + /** The balances owed to (or by) the account holder. Each key is a three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. Each value is a integer amount. A positive amount indicates money owed to the account holder. A negative amount indicates money owed by the account holder. */ + public let current: [String: Int] + public let type: ModelType + } + + public struct OwnershipRefresh: Codable, Equatable { + @frozen public enum Status: String, SafeEnumCodable, Equatable { + case failed = "failed" + case pending = "pending" + case succeeded = "succeeded" + case unparsable + } + /// The time at which the last refresh attempt was initiated. Measured in seconds since the Unix epoch. + public let lastAttemptedAt: Int + /// The status of the last refresh attempt. + public let status: OwnershipRefresh.Status + } + + @frozen public enum Category: String, SafeEnumCodable, Equatable { + case cash = "cash" + case credit = "credit" + case investment = "investment" + case other = "other" + case unparsable + } + + @frozen public enum Permissions: String, SafeEnumCodable, Equatable { + case balances = "balances" + case ownership = "ownership" + case paymentMethod = "payment_method" + case transactions = "transactions" + case accountNumbers = "account_numbers" + case unparsable + } + + @frozen public enum Status: String, SafeEnumCodable, Equatable { + case active = "active" + case disconnected = "disconnected" + case inactive = "inactive" + case unparsable + } + + @frozen public enum Subcategory: String, SafeEnumCodable, Equatable { + case checking = "checking" + case creditCard = "credit_card" + case lineOfCredit = "line_of_credit" + case mortgage = "mortgage" + case other = "other" + case savings = "savings" + case unparsable + } + + @frozen public enum SupportedPaymentMethodTypes: String, SafeEnumCodable, Equatable { + case link = "link" + case usBankAccount = "us_bank_account" + case unparsable + } + + // MARK: - Public Fields + + public let balance: Balance? + public let balanceRefresh: BalanceRefresh? + public let ownership: String? + /// The state of the most recent attempt to refresh the account owners. + public let ownershipRefresh: OwnershipRefresh? + public let displayName: String? + public let institutionName: String + public let last4: String? + public let category: Category + public let created: Int + public let id: String + public let livemode: Bool + public let permissions: [Permissions]? + public let status: Status + public let subcategory: Subcategory + /** The [PaymentMethod type](https://stripe.com/docs/api/payment_methods/object#payment_method_object-type)(s) that can be created from this FinancialConnectionsAccount. */ + public let supportedPaymentMethodTypes: [SupportedPaymentMethodTypes] + } + +} + +// MARK: - Decodable + +@_spi(STP) extension StripeAPI.FinancialConnectionsAccount: Decodable {} +@_spi(STP) extension StripeAPI.FinancialConnectionsAccount.BalanceRefresh: Decodable {} +@_spi(STP) extension StripeAPI.FinancialConnectionsAccount.CashBalance: Decodable {} +@_spi(STP) extension StripeAPI.FinancialConnectionsAccount.CreditBalance: Decodable {} +@_spi(STP) extension StripeAPI.FinancialConnectionsAccount.Balance: Decodable {} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAccountPickerPane.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAccountPickerPane.swift new file mode 100644 index 00000000..215920dc --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAccountPickerPane.swift @@ -0,0 +1,12 @@ +// +// FinancialConnectionsAccountPickerPane.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-09-12. +// + +import Foundation + +struct FinancialConnectionsAccountPickerPane: Decodable { + let dataAccessNotice: String +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAuthSession.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAuthSession.swift new file mode 100644 index 00000000..14425a55 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsAuthSession.swift @@ -0,0 +1,58 @@ +// +// FinancialConnectionsPartner.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +struct FinancialConnectionsAuthSession: Decodable { + enum Flow: String, SafeEnumCodable, Equatable { + case directWebview = "direct_webview" + case finicityConnectV2Lite = "finicity_connect_v2_lite" + case finicityConnectV2Oauth = "finicity_connect_v2_oauth" + case finicityConnectV2OauthWebview = "finicity_connect_v2_oauth_webview" + case finicityConnectV2OauthRedirect = "finicity_connect_v2_oauth_redirect" + case mxConnect = "mx_connect" + case mxOauth = "mx_oauth" + case mxOauthWebview = "mx_oauth_webview" + case mxOauthAppToApp = "mx_oauth_app_to_app" + case testmode = "testmode" + case testmodeOauth = "testmode_oauth" + case testmodeOauthWebview = "testmode_oauth_webview" + case truelayerEmbedded = "truelayer_embedded" + case truelayerOauth = "truelayer_oauth" + case wellsFargo = "wells_fargo" + case unparsable + } + + let id: String + let flow: Flow? + let institutionSkipAccountSelection: Bool? + let nextPane: FinancialConnectionsSessionManifest.NextPane + let showPartnerDisclosure: Bool? + let skipAccountSelection: Bool? + let url: String? + let isOauth: Bool? + let display: Display? + + var isOauthNonOptional: Bool { + return isOauth ?? false + } + + var requiresNativeRedirect: Bool { + return url?.hasNativeRedirectPrefix ?? false + } + + struct Display: Decodable { + let text: Text? + + struct Text: Decodable { + let oauthPrepane: FinancialConnectionsOAuthPrepane? + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsBulletPoint.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsBulletPoint.swift new file mode 100644 index 00000000..038c8a9c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsBulletPoint.swift @@ -0,0 +1,20 @@ +// +// FinancialConnectionsBulletPoint.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/19/23. +// + +import Foundation + +struct FinancialConnectionsBulletPoint: Decodable { + let icon: FinancialConnectionsImage? + let title: String? + let content: String? + + init(icon: FinancialConnectionsImage, title: String? = nil, content: String? = nil) { + self.icon = icon + self.title = title + self.content = content + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsConsent.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsConsent.swift new file mode 100644 index 00000000..777aa169 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsConsent.swift @@ -0,0 +1,23 @@ +// +// FinancialConnectionsConsent.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/19/23. +// + +import Foundation + +struct FinancialConnectionsConsent: Decodable { + let title: String + let body: Body + let aboveCta: String + let cta: String + let belowCta: String? + + let dataAccessNotice: FinancialConnectionsDataAccessNotice? + let legalDetailsNotice: FinancialConnectionsLegalDetailsNotice + + struct Body: Decodable { + let bullets: [FinancialConnectionsBulletPoint] + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsDataAccessNotice.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsDataAccessNotice.swift new file mode 100644 index 00000000..479a0912 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsDataAccessNotice.swift @@ -0,0 +1,27 @@ +// +// FinancialConnectionsDataAccessNotice.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/19/23. +// + +import Foundation + +struct FinancialConnectionsDataAccessNotice: Decodable { + let icon: FinancialConnectionsImage? + let title: String + let connectedAccountNotice: ConnectedAccountNotice? + let subtitle: String? + let body: Body + let disclaimer: String? + let cta: String + + struct Body: Decodable { + let bullets: [FinancialConnectionsBulletPoint] + } + + struct ConnectedAccountNotice: Decodable { + let subtitle: String + let body: Body + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsGenericInfoScreen.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsGenericInfoScreen.swift new file mode 100644 index 00000000..a3e6fa59 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsGenericInfoScreen.swift @@ -0,0 +1,141 @@ +// +// FinancialConnectionsGenericInfoScreen.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 6/20/24. +// + +import Foundation + +struct FinancialConnectionsGenericInfoScreen: Decodable { + + let id: String + let header: Header? + let body: Body? + let footer: Footer? + let options: Options? + + struct Header: Decodable { + let title: String? + let subtitle: String? + let icon: FinancialConnectionsImage? + let alignment: Alignment? + + enum Alignment: String, SafeEnumCodable { + case left = "left" + case center = "center" + case right = "right" + case unparsable + } + } + + struct Body: Decodable { + + let entries: [BodyEntry] + + enum BodyEntry: Decodable { + case text(TextBodyEntry) + case image(ImageBodyEntry) + case bullets(BulletsBodyEntry) + case unparasable + + public init(from decoder: Decoder) throws { + let type = try? decoder + .container(keyedBy: TypeDecodingContainer.CodingKeys.self) + .decode(TypeDecodingContainer.self, forKey: .type) + .type + let container = try decoder.singleValueContainer() + if type == .text, let value = try? container.decode(TextBodyEntry.self) { + self = .text(value) + } else if type == .image, let value = try? container.decode(ImageBodyEntry.self) { + self = .image(value) + } else if type == .bullets, let value = try? container.decode(BulletsBodyEntry.self) { + self = .bullets(value) + } else { + self = .unparasable + } + } + + // this struct is here + private struct TypeDecodingContainer: Codable { + let type: BodyEntryType + + enum BodyEntryType: String, Codable { + case text = "text" + case image = "image" + case bullets = "bullets" + } + + enum CodingKeys: String, CodingKey { + case type + } + } + } + + struct TextBodyEntry: Decodable { + + let id: String + let text: String + let alignment: Alignment? + let size: Size? + + enum Alignment: String, SafeEnumCodable { + case left = "left" + case center = "center" + case right = "right" + case unparsable + } + + enum Size: String, SafeEnumCodable { + case xsmall = "x-small" + case small = "small" + case medium = "medium" + case unparsable + } + } + + struct ImageBodyEntry: Decodable { + let id: String + let image: FinancialConnectionsImage + let alt: String + } + + struct BulletsBodyEntry: Decodable { // TODO(kgaidis): implement the bullets body entry as a type + let id: String + let bullets: [GenericBulletPoint] + + struct GenericBulletPoint: Decodable { + let id: String + let icon: FinancialConnectionsImage? + let title: String? + let content: String? + } + } + } + + struct Footer: Decodable { + let disclaimer: String? + let primaryCta: GenericInfoAction? + let secondaryCta: GenericInfoAction? + let belowCta: String? + + struct GenericInfoAction: Decodable { + let id: String + let label: String + let icon: FinancialConnectionsImage? + let action: String? + let testId: String? + } + } + + struct Options: Decodable { + let fullWidthContent: Bool? + let verticalAlignment: VerticalAlignment? + + enum VerticalAlignment: String, SafeEnumCodable { + case `default` = "default" + case centered = "centered" + case unparsable + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsImage.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsImage.swift new file mode 100644 index 00000000..a3283bad --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsImage.swift @@ -0,0 +1,13 @@ +// +// FinancialConnectionsImage.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/31/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +struct FinancialConnectionsImage: Decodable, Equatable, Hashable { + let `default`: String? +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsInstitution.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsInstitution.swift new file mode 100644 index 00000000..23fdec7e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsInstitution.swift @@ -0,0 +1,43 @@ +// +// FinancialConnectionsInstitution.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/6/22. +// + +import Foundation +@_spi(STP) import StripeCore + +struct FinancialConnectionsInstitution: Decodable, Hashable, Equatable { + + let id: String + let name: String + let url: String? + let icon: FinancialConnectionsImage? + let logo: FinancialConnectionsImage? +} + +// MARK: - Institution List + +struct FinancialConnectionsInstitutionList: Decodable { + let data: [FinancialConnectionsInstitution] +} + +struct ShareNetworkedAccountsResponse: Decodable { + let data: [FinancialConnectionsInstitution] + let nextPane: FinancialConnectionsSessionManifest.NextPane? + let displayText: DisplayText? + + struct DisplayText: Decodable { + let text: Text? + + struct Text: Decodable { + let succcessPane: SuccessPane? + + struct SuccessPane: Decodable { + let caption: String + let subCaption: String + } + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsInstitutionSearchResultResource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsInstitutionSearchResultResource.swift new file mode 100644 index 00000000..cab05833 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsInstitutionSearchResultResource.swift @@ -0,0 +1,13 @@ +// +// FinancialConnectionsInstitutionSearchResultResource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 3/23/23. +// + +import Foundation + +struct FinancialConnectionsInstitutionSearchResultResource: Decodable { + let data: [FinancialConnectionsInstitution] + let showManualEntry: Bool +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsLegalDetailsNotice.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsLegalDetailsNotice.swift new file mode 100644 index 00000000..132ed029 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsLegalDetailsNotice.swift @@ -0,0 +1,27 @@ +// +// FinancialConnectionsLegalDetailsNotice.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/19/23. +// + +import Foundation + +struct FinancialConnectionsLegalDetailsNotice: Decodable { + + let icon: FinancialConnectionsImage? + let title: String + let subtitle: String? + let body: Body + let disclaimer: String? + let cta: String + + struct Body: Decodable { + let links: [Link] + + struct Link: Decodable { + let title: String + let content: String? + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsLinkLoginPane.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsLinkLoginPane.swift new file mode 100644 index 00000000..778ebde5 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsLinkLoginPane.swift @@ -0,0 +1,15 @@ +// +// FinancialConnectionsLinkLoginPane.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-07-25. +// + +import Foundation + +struct FinancialConnectionsLinkLoginPane: Decodable { + let title: String + let body: String + let aboveCta: String + let cta: String +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsMixedOAuthParams.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsMixedOAuthParams.swift new file mode 100644 index 00000000..e36c5c8e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsMixedOAuthParams.swift @@ -0,0 +1,13 @@ +// +// FinancialConnectionsMixedOAuthParams.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +struct FinancialConnectionsMixedOAuthParams: Decodable { + let publicToken: String? +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsNetworkedAccountsResponse.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsNetworkedAccountsResponse.swift new file mode 100644 index 00000000..75126095 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsNetworkedAccountsResponse.swift @@ -0,0 +1,52 @@ +// +// FinancialConnectionsNetworkedAccountsResponse.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 6/16/23. +// + +import Foundation + +struct FinancialConnectionsNetworkedAccountsResponse: Decodable { + let data: [FinancialConnectionsPartnerAccount] + let display: Display? + let nextPaneOnAddAccount: FinancialConnectionsSessionManifest.NextPane? + let acquireConsentOnPrimaryCtaClick: Bool? + + struct Display: Decodable { + let text: Text? + + struct Text: Decodable { + let returningNetworkingUserAccountPicker: FinancialConnectionsNetworkingAccountPicker? + } + } +} + +struct FinancialConnectionsNetworkingAccountPicker: Decodable { + let title: String + let defaultCta: String + let addNewAccount: AddNewAccount + let accounts: [FinancialConnectionsNetworkingAccountPicker.Account] + let aboveCta: String? + let multipleAccountTypesSelectedDataAccessNotice: FinancialConnectionsDataAccessNotice? + + struct AddNewAccount: Decodable { + let body: String + let icon: FinancialConnectionsImage? + } + + struct Account: Decodable { + let id: String + let allowSelection: Bool + // ex. "Select to repair and connect" + let caption: String? + // ex. "Repair and connect account" + let selectionCta: String? + // trailing icon on the account row + let icon: FinancialConnectionsImage? + let selectionCtaIcon: FinancialConnectionsImage? + let drawerOnSelection: FinancialConnectionsGenericInfoScreen? + let accountIcon: FinancialConnectionsImage? + let dataAccessNotice: FinancialConnectionsDataAccessNotice? + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsNetworkingLinkSignup.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsNetworkingLinkSignup.swift new file mode 100644 index 00000000..b7a240a5 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsNetworkingLinkSignup.swift @@ -0,0 +1,21 @@ +// +// FinancialConnectionsNetworkingLinkSignup.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 3/28/23. +// + +import Foundation + +struct FinancialConnectionsNetworkingLinkSignup: Decodable { + let title: String + let body: Body + let aboveCta: String + let cta: String + let skipCta: String + let legalDetailsNotice: FinancialConnectionsLegalDetailsNotice? + + struct Body: Decodable { + let bullets: [FinancialConnectionsBulletPoint] + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsOAuthPrepane.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsOAuthPrepane.swift new file mode 100644 index 00000000..dbfcd352 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsOAuthPrepane.swift @@ -0,0 +1,76 @@ +// +// FinancialConnectionsOAuthPrepane.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/6/23. +// + +import Foundation +@_spi(STP) import StripeUICore + +struct FinancialConnectionsOAuthPrepane: Decodable { + + let institutionIcon: FinancialConnectionsImage? + let title: String + let subtitle: String? + let body: OauthPrepaneBody + let partnerNotice: OauthPrepanePartnerNotice? + let cta: OauthPrepaneCTA + let dataAccessNotice: FinancialConnectionsDataAccessNotice + + struct OauthPrepaneBody: Decodable { + let entries: [OauthPrepaneBodyEntry]? + + struct OauthPrepaneBodyEntry: Decodable { + + enum Content { + case text(String) + case image(FinancialConnectionsImage) + case unparsable + } + + let content: Content + + init(content: Content) { + self.content = content + } + + enum CodingKeys: String, CodingKey { + case type + case content + } + + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + + // check the `type` before we unwrap `content` because we + // want to avoid cases where an unknown/new `type` has + // the same underlying data-type (ex. String) as a known `type` + guard let type = try? values.decode(String.self, forKey: .type) else { + self.content = .unparsable + return + } + + if type == "text", let text = try? values.decode(String.self, forKey: .content) { + self.content = .text(text) + } else if type == "image", + let image = try? values.decode(FinancialConnectionsImage.self, forKey: .content) + { + self.content = .image(image) + } else { + self.content = .unparsable + } + } + } + } + + struct OauthPrepanePartnerNotice: Decodable { + let partnerIcon: FinancialConnectionsImage? + let text: String + } + + struct OauthPrepaneCTA: Decodable { + let text: String + let icon: FinancialConnectionsImage? + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPartnerAccount.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPartnerAccount.swift new file mode 100644 index 00000000..5d9bbd2e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPartnerAccount.swift @@ -0,0 +1,41 @@ +// +// FinancialConnectionsPartnerAccount.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +struct FinancialConnectionsPartnerAccount: Decodable { + let id: String + let name: String + let displayableAccountNumbers: String? + let linkedAccountId: String? + let balanceAmount: Int? + let currency: String? + let supportedPaymentMethodTypes: [FinancialConnectionsPaymentMethodType] + let allowSelection: Bool? + let allowSelectionMessage: String? + let status: String? + let institution: FinancialConnectionsInstitution? + let nextPaneOnSelection: FinancialConnectionsSessionManifest.NextPane? + + var allowSelectionNonOptional: Bool { + return allowSelection ?? true + } + var balanceInfo: (balanceAmount: Int, currency: String)? { + if let balanceAmount = balanceAmount, let currency = currency { + return (balanceAmount, currency) + } else { + return nil + } + } +} + +struct FinancialConnectionsAuthSessionAccounts: Decodable { + let data: [FinancialConnectionsPartnerAccount] + let nextPane: FinancialConnectionsSessionManifest.NextPane + let skipAccountSelection: Bool? +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentAccountResource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentAccountResource.swift new file mode 100644 index 00000000..8951e5ce --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentAccountResource.swift @@ -0,0 +1,24 @@ +// +// FinancialConnectionsPaymentAccountResource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +typealias MicrodepositVerificationMethod = FinancialConnectionsPaymentAccountResource.MicrodepositVerificationMethod +struct FinancialConnectionsPaymentAccountResource: Decodable { + + enum MicrodepositVerificationMethod: String, SafeEnumCodable, Equatable { + case descriptorCode = "descriptor_code" + case amounts = "amounts" + case unparsable + } + + let id: String + let nextPane: FinancialConnectionsSessionManifest.NextPane? + let microdepositVerificationMethod: MicrodepositVerificationMethod? + let networkingSuccessful: Bool? +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentDetails.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentDetails.swift new file mode 100644 index 00000000..aeaa9d9e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentDetails.swift @@ -0,0 +1,27 @@ +// +// FinancialConnectionsPaymentDetails.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-08-07. +// + +import Foundation +@_spi(STP) import StripeCore + +struct FinancialConnectionsPaymentDetails: Decodable { + let redactedPaymentDetails: RedactedPaymentDetails +} + +struct RedactedPaymentDetails: Decodable { + let id: String + let bankAccountDetails: BankAccountDetails? +} + +struct BankAccountDetails: Decodable { + let bankName: String? + let last4: String? +} + +struct FinancialConnectionsSharePaymentDetails: Decodable { + let paymentMethod: LinkBankPaymentMethod +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentMethodType.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentMethodType.swift new file mode 100644 index 00000000..00e5eb24 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsPaymentMethodType.swift @@ -0,0 +1,15 @@ +// +// FinancialConnectionsPaymentMethodType.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +enum FinancialConnectionsPaymentMethodType: String, SafeEnumCodable, Equatable { + case usBankAccount = "us_bank_account" + case link = "link" + case unparsable +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSession.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSession.swift new file mode 100644 index 00000000..8cc6f0f7 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSession.swift @@ -0,0 +1,173 @@ +// +// FinancialConnectionsSession.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 1/19/22. +// + +import Foundation +@_spi(STP) import StripeCore + +public extension StripeAPI { + + /** + Financial Connections Session is the programatic representation of the session for connecting financial accounts. + - seealso: https://stripe.com/docs/api/financial_connections/session + */ + struct FinancialConnectionsSession { + + // MARK: - Types + + /// An object representing a list of FinancialConnectionsAccounts. + public struct AccountList { + public let data: [StripeAPI.FinancialConnectionsAccount] + /** True if this list has another page of items after this one that can be fetched. */ + public let hasMore: Bool + + // MARK: - Internal Init + + internal init( + data: [StripeAPI.FinancialConnectionsAccount], + hasMore: Bool + ) { + self.data = data + self.hasMore = hasMore + } + } + + @_spi(STP) public enum PaymentAccount: Decodable { + + // MARK: - Types + + @_spi(STP) public struct BankAccount: Decodable { + public let bankName: String? + public let id: String + public let last4: String + public let routingNumber: String? + + /// Whether the account should be considered instantly verified. This field isn't part of the API response + /// and is being set later on. + public var instantlyVerified: Bool = false + + private enum CodingKeys: String, CodingKey { + case bankName, id, last4, routingNumber + } + } + + case linkedAccount(StripeAPI.FinancialConnectionsAccount) + case bankAccount(StripeAPI.FinancialConnectionsSession.PaymentAccount.BankAccount) + case unparsable + + // MARK: - Decodable + + /** + Per API specification paymentAccount is a polymorphic field denoted by openAPI anyOf modifier. + We are translating it to an enum with associated types. + */ + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let value = try? container.decode(FinancialConnectionsAccount.self) { + self = .linkedAccount(value) + } else if let value = try? container.decode(FinancialConnectionsSession.PaymentAccount.BankAccount.self) { + self = .bankAccount(value) + } else { + self = .unparsable + } + } + } + + enum Status: String, SafeEnumCodable, Equatable { + case pending + case succeeded + case failed + case cancelled + case unparsable + } + + struct StatusDetails: Decodable { + struct CancelledStatusDetails: Decodable { + enum TerminalStateReason: String, SafeEnumCodable, Equatable { + case other + case customManualEntry = "custom_manual_entry" + case unparsable + } + + let reason: TerminalStateReason + } + + let cancelled: CancelledStatusDetails? + } + + // MARK: - Properties + + public let clientSecret: String + public let id: String + public let accounts: FinancialConnectionsSession.AccountList + public let livemode: Bool + @_spi(STP) public let paymentAccount: PaymentAccount? + @_spi(STP) public let bankAccountToken: BankAccountToken? + let status: Status? + let statusDetails: StatusDetails? + + // MARK: - Internal Init + + internal init( + clientSecret: String, + id: String, + accounts: FinancialConnectionsSession.AccountList, + livemode: Bool, + paymentAccount: PaymentAccount?, + bankAccountToken: BankAccountToken?, + status: Status?, + statusDetails: StatusDetails? + ) { + self.clientSecret = clientSecret + self.id = id + self.accounts = accounts + self.livemode = livemode + self.paymentAccount = paymentAccount + self.bankAccountToken = bankAccountToken + self.status = status + self.statusDetails = statusDetails + } + + // MARK: - Decodable + + enum CodingKeys: String, CodingKey { + case clientSecret = "client_secret" + case id = "id" + case accounts = "accounts" + case linkedAccounts = "linked_accounts" + case livemode = "livemode" + case paymentAccount = "payment_account" + case bankAccountToken = "bank_account_token" + case status = "status" + case statusDetails = "status_details" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let accounts: FinancialConnectionsSession.AccountList + do { + accounts = try container.decode(FinancialConnectionsSession.AccountList.self, forKey: .accounts) + } catch { + accounts = try container.decode(FinancialConnectionsSession.AccountList.self, forKey: .linkedAccounts) + } + self.init( + clientSecret: try container.decode(String.self, forKey: .clientSecret), + id: try container.decode(String.self, forKey: .id), + accounts: accounts, + livemode: try container.decode(Bool.self, forKey: .livemode), + paymentAccount: try? container.decode(PaymentAccount.self, forKey: .paymentAccount), + bankAccountToken: try? container.decode(BankAccountToken.self, forKey: .bankAccountToken), + status: try? container.decode(Status.self, forKey: .status), + statusDetails: try? container.decode(StatusDetails.self, forKey: .statusDetails) + ) + } + } +} + +// MARK: - Decodable + +@_spi(STP) extension StripeAPI.FinancialConnectionsSession: Decodable {} +@_spi(STP) extension StripeAPI.FinancialConnectionsSession.AccountList: Decodable {} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSessionManifest.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSessionManifest.swift new file mode 100644 index 00000000..6066790f --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSessionManifest.swift @@ -0,0 +1,248 @@ +// +// FinancialConnectionsSessionManifest.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +struct FinancialConnectionsSessionManifest: Decodable { + + // MARK: - Types + + enum NextPane: String, SafeEnumCodable, Equatable { + case accountPicker = "account_picker" + case attachLinkedPaymentAccount = "attach_linked_payment_account" + case authOptions = "auth_options" + case bankAuthRepair = "bank_auth_repair" + case consent = "consent" + case institutionPicker = "institution_picker" + case linkAccountPicker = "link_account_picker" + case linkConsent = "link_consent" + case linkLogin = "link_login" + case manualEntry = "manual_entry" + case manualEntrySuccess = "manual_entry_success" + case networkingLinkLoginWarmup = "networking_link_login_warmup" + case networkingLinkSignupPane = "networking_link_signup_pane" + case networkingLinkStepUpVerification = "networking_link_step_up_verification" + case networkingLinkVerification = "networking_link_verification" + case networkingSaveToLinkVerification = "networking_save_to_link_verification" + case partnerAuth = "partner_auth" + case success = "success" + case unexpectedError = "unexpected_error" + case unparsable + + // client-side only panes + case resetFlow = "reset_flow" + case terminalError = "terminal_error" + } + + enum AccountDisconnectionMethod: String, SafeEnumCodable, Equatable { + case dashboard + case support + case email + case link + case unparsable + } + + enum ManualEntryMode: String, SafeEnumCodable, Equatable { + case automatic + case custom + case unparsable + } + + struct DisplayText: Decodable { + let successPane: SuccessPane? + + struct SuccessPane: Decodable { + let subCaption: String? + } + } + + enum Theme: String, SafeEnumCodable, Equatable { + case light = "light" + case dashboardLight = "dashboard_light" + case linkLight = "link_light" + case unparsable + } + + // MARK: - Properties + + let accountholderCustomerEmailAddress: String? + let accountholderIsLinkConsumer: Bool? + let accountholderPhoneNumber: String? + let accountholderToken: String? + let accountDisconnectionMethod: AccountDisconnectionMethod? + let activeAuthSession: FinancialConnectionsAuthSession? + let activeInstitution: FinancialConnectionsInstitution? + let allowManualEntry: Bool + let assignmentEventId: String? + let businessName: String? + let cancelUrl: String? + let consentRequired: Bool + let customManualEntryHandling: Bool + let disableLinkMoreAccounts: Bool + let displayText: DisplayText? + let experimentAssignments: [String: String]? + let features: [String: Bool]? + let hostedAuthUrl: String? + let initialInstitution: FinancialConnectionsInstitution? + let instantVerificationDisabled: Bool + let institutionSearchDisabled: Bool + let isEndUserFacing: Bool? + let isLinkWithStripe: Bool? + let isNetworkingUserFlow: Bool? + let isStripeDirect: Bool? + let livemode: Bool + let manualEntryMode: ManualEntryMode + let manualEntryUsesMicrodeposits: Bool + let nextPane: NextPane + let paymentMethodType: FinancialConnectionsPaymentMethodType? + let permissions: [StripeAPI.FinancialConnectionsAccount.Permissions] + let product: String + let singleAccount: Bool + let skipSuccessPane: Bool? + let stepUpAuthenticationRequired: Bool? + let successUrl: String? + + private let _theme: Theme? + var theme: FinancialConnectionsTheme { + FinancialConnectionsTheme(from: _theme) + } + + var shouldAttachLinkedPaymentMethod: Bool { + return (paymentMethodType != nil) + } + + var isProductInstantDebits: Bool { + return (product == "instant_debits") + } + + var isTestMode: Bool { + !livemode + } + + init( + accountholderCustomerEmailAddress: String? = nil, + accountholderIsLinkConsumer: Bool? = nil, + accountholderPhoneNumber: String? = nil, + accountholderToken: String? = nil, + accountDisconnectionMethod: FinancialConnectionsSessionManifest.AccountDisconnectionMethod? = nil, + activeAuthSession: FinancialConnectionsAuthSession? = nil, + activeInstitution: FinancialConnectionsInstitution? = nil, + allowManualEntry: Bool, + assignmentEventId: String? = nil, + businessName: String? = nil, + cancelUrl: String? = nil, + consentRequired: Bool, + customManualEntryHandling: Bool, + disableLinkMoreAccounts: Bool, + displayText: FinancialConnectionsSessionManifest.DisplayText? = nil, + experimentAssignments: [String: String]? = nil, + features: [String: Bool]? = nil, + hostedAuthUrl: String? = nil, + initialInstitution: FinancialConnectionsInstitution? = nil, + instantVerificationDisabled: Bool, + institutionSearchDisabled: Bool, + isEndUserFacing: Bool? = nil, + isLinkWithStripe: Bool? = nil, + isNetworkingUserFlow: Bool? = nil, + isStripeDirect: Bool? = nil, + livemode: Bool, + manualEntryMode: FinancialConnectionsSessionManifest.ManualEntryMode, + manualEntryUsesMicrodeposits: Bool, + nextPane: FinancialConnectionsSessionManifest.NextPane, + paymentMethodType: FinancialConnectionsPaymentMethodType? = nil, + permissions: [StripeAPI.FinancialConnectionsAccount.Permissions], + product: String, + singleAccount: Bool, + skipSuccessPane: Bool? = nil, + stepUpAuthenticationRequired: Bool? = nil, + successUrl: String? = nil, + _theme: FinancialConnectionsSessionManifest.Theme? = nil + ) { + self.accountholderCustomerEmailAddress = accountholderCustomerEmailAddress + self.accountholderIsLinkConsumer = accountholderIsLinkConsumer + self.accountholderPhoneNumber = accountholderPhoneNumber + self.accountholderToken = accountholderToken + self.accountDisconnectionMethod = accountDisconnectionMethod + self.activeAuthSession = activeAuthSession + self.activeInstitution = activeInstitution + self.allowManualEntry = allowManualEntry + self.assignmentEventId = assignmentEventId + self.businessName = businessName + self.cancelUrl = cancelUrl + self.consentRequired = consentRequired + self.customManualEntryHandling = customManualEntryHandling + self.disableLinkMoreAccounts = disableLinkMoreAccounts + self.displayText = displayText + self.experimentAssignments = experimentAssignments + self.features = features + self.hostedAuthUrl = hostedAuthUrl + self.initialInstitution = initialInstitution + self.instantVerificationDisabled = instantVerificationDisabled + self.institutionSearchDisabled = institutionSearchDisabled + self.isEndUserFacing = isEndUserFacing + self.isLinkWithStripe = isLinkWithStripe + self.isNetworkingUserFlow = isNetworkingUserFlow + self.isStripeDirect = isStripeDirect + self.livemode = livemode + self.manualEntryMode = manualEntryMode + self.manualEntryUsesMicrodeposits = manualEntryUsesMicrodeposits + self.nextPane = nextPane + self.paymentMethodType = paymentMethodType + self.permissions = permissions + self.product = product + self.singleAccount = singleAccount + self.skipSuccessPane = skipSuccessPane + self.stepUpAuthenticationRequired = stepUpAuthenticationRequired + self.successUrl = successUrl + self._theme = _theme + } + + // MARK: - Coding Keys + + enum CodingKeys: String, CodingKey { + case accountholderCustomerEmailAddress + case accountholderIsLinkConsumer + case accountholderPhoneNumber + case accountholderToken + case accountDisconnectionMethod + case activeAuthSession + case activeInstitution + case allowManualEntry + case assignmentEventId + case businessName + case cancelUrl + case consentRequired + case customManualEntryHandling + case disableLinkMoreAccounts + case displayText + case experimentAssignments + case features + case hostedAuthUrl + case initialInstitution + case instantVerificationDisabled + case institutionSearchDisabled + case isEndUserFacing + case isLinkWithStripe + case isNetworkingUserFlow + case isStripeDirect + case livemode + case manualEntryMode + case manualEntryUsesMicrodeposits + case nextPane + case paymentMethodType + case permissions + case product + case singleAccount + case skipSuccessPane + case stepUpAuthenticationRequired + case successUrl + case _theme = "theme" + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSynchronize.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSynchronize.swift new file mode 100644 index 00000000..8b3b4740 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSynchronize.swift @@ -0,0 +1,27 @@ +// +// FinancialConnectionsSynchronize.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/20/22. +// + +import Foundation + +struct FinancialConnectionsSynchronize: Decodable { + let manifest: FinancialConnectionsSessionManifest + let text: Text? + let visual: VisualUpdate + + struct Text: Decodable { + let accountPickerPane: FinancialConnectionsAccountPickerPane? + let consentPane: FinancialConnectionsConsent? + let networkingLinkSignupPane: FinancialConnectionsNetworkingLinkSignup? + let linkLoginPane: FinancialConnectionsLinkLoginPane? + } + + struct VisualUpdate: Decodable { + let reducedBranding: Bool + let merchantLogo: [String] + let reduceManualEntryProminenceInErrors: Bool + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsTheme.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsTheme.swift new file mode 100644 index 00000000..1294a648 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsTheme.swift @@ -0,0 +1,125 @@ +// +// FinancialConnectionsTheme.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-07-17. +// + +import UIKit + +enum FinancialConnectionsTheme: String, Equatable { + case light + case linkLight + + init(from theme: FinancialConnectionsSessionManifest.Theme?) { + switch theme { + case .linkLight: + self = .linkLight + case .light, .dashboardLight, .unparsable, .none: + self = .light + } + } +} + +extension FinancialConnectionsTheme { + var logo: Image { + switch self { + case .linkLight: + return .link_logo + case .light: + return .stripe_logo + } + } + + var primaryColor: UIColor { + switch self { + case .linkLight: + return .linkGreen200 + case .light: + return .brand500 + } + } + + var primaryAccentColor: UIColor { + switch self { + case .linkLight: + return .linkGreen900 + case .light: + return .white + } + } + + var textFieldFocusedColor: UIColor { + switch self { + case .linkLight: + return .linkGreen200 + case .light: + return .brand600 + } + } + + var logoColor: UIColor { + switch self { + case .linkLight: + return .linkGreen900 + case .light: + return .textActionPrimary + } + } + + var iconTintColor: UIColor { + switch self { + case .linkLight: + return .linkGreen500 + case .light: + return .iconActionPrimary + } + } + + var iconBackgroundColor: UIColor { + switch self { + case .linkLight: + return .linkGreen50 + case .light: + return .brand25 + } + } + + var textActionColor: UIColor { + switch self { + case .linkLight: + return .linkGreen500 + case .light: + return .brand600 + } + } + + var spinnerColor: UIColor { + switch self { + case .linkLight: + return .linkGreen200 + case .light: + return .brand500 + } + } + + var borderColor: UIColor { + switch self { + case .linkLight: + return .linkGreen200 + case .light: + return .brand600 + } + } +} + +extension FinancialConnectionsTheme? { + var spinnerColor: UIColor { + switch self { + case .some(let theme): + return theme.spinnerColor + case .none: + return .neutral200 + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Analytics/FinancialConnectionsAnalyticsClient.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Analytics/FinancialConnectionsAnalyticsClient.swift new file mode 100644 index 00000000..7526b07e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Analytics/FinancialConnectionsAnalyticsClient.swift @@ -0,0 +1,221 @@ +// +// FinancialConnectionsAnalyticsClient.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/13/22. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +protocol FinancialConnectionsAnalyticsClientDelegate: AnyObject { + func analyticsClient( + _ analyticsClient: FinancialConnectionsAnalyticsClient, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +final class FinancialConnectionsAnalyticsClient { + + private let analyticsClient: AnalyticsClientV2Protocol + private var additionalParameters: [String: Any] = [:] + weak var delegate: FinancialConnectionsAnalyticsClientDelegate? + + init( + analyticsClient: AnalyticsClientV2Protocol = AnalyticsClientV2( + clientId: "mobile-clients-linked-accounts", + origin: "stripe-linked-accounts-ios" + ) + ) { + self.analyticsClient = analyticsClient + additionalParameters["is_webview"] = false + additionalParameters["navigator_language"] = Locale.current.toLanguageTag() + } + + public func log( + eventName: String, + parameters: [String: Any] = [:], + pane: FinancialConnectionsSessionManifest.NextPane + ) { + let eventName = "linked_accounts.\(eventName)" + + var parameters = parameters + // !!! BE CAREFUL MODIFYING "PANE" ANALYTICS CODE + // ITS CRITICAL FOR PANE CONVERSION !!! + assert(parameters["pane"] == nil, "Unexpected logic: will override 'pane' parameter.") + parameters["pane"] = pane.rawValue + parameters = parameters.merging( + additionalParameters, + uniquingKeysWith: { eventParameter, _ in + // prioritize event `parameters` over `additionalParameters` + return eventParameter + } + ) + + assert( + !parameters.contains(where: { $0.key == "duration" && type(of: $0.value) == TimeInterval.self }), + "Duration is expected to be sent as an Int (miliseconds)." + ) + assert( + !parameters.contains(where: { type(of: $0.value) == FinancialConnectionsSessionManifest.NextPane.self }), + "Do not pass NextPane enum. Use the raw value." + ) + assert((parameters["pane"] as? String) != nil, "We expect pane to be set as a String for all analytics events.") + analyticsClient.log(eventName: eventName, parameters: parameters) + } + + public func logExposure( + experimentName: String, + assignmentEventId: String, + accountholderToken: String + ) { + var parameters = additionalParameters + parameters["experiment_retrieved"] = experimentName + parameters["arb_id"] = assignmentEventId + parameters["account_holder_id"] = accountholderToken + analyticsClient.log(eventName: "preloaded_experiment_retrieved", parameters: parameters) + } +} + +// MARK: - Helpers + +extension FinancialConnectionsAnalyticsClient { + + func logPaneLoaded(pane: FinancialConnectionsSessionManifest.NextPane) { + log(eventName: "pane.loaded", pane: pane) + } + + func logExpectedError( + _ error: Error, + errorName: String, + pane: FinancialConnectionsSessionManifest.NextPane + ) { + log( + error: error, + errorName: errorName, + eventName: "error.expected", + pane: pane + ) + } + + func logUnexpectedError( + _ error: Error, + errorName: String, + pane: FinancialConnectionsSessionManifest.NextPane + ) { + log( + error: error, + errorName: errorName, + eventName: "error.unexpected", + pane: pane + ) + } + + private func log( + error: Error, + errorName: String, + eventName: String, + pane: FinancialConnectionsSessionManifest.NextPane + ) { + FeedbackGeneratorAdapter.errorOccurred() + FinancialConnectionsEvent + .events(fromError: error) + .forEach { event in + delegate?.analyticsClient(self, didReceiveEvent: event) + } + + var parameters: [String: Any] = [:] + parameters["error"] = errorName + if let stripeError = error as? StripeError, + case .apiError(let apiError) = stripeError + { + parameters["error_type"] = apiError.type.rawValue + parameters["error_message"] = apiError.message + parameters["code"] = apiError.code + } else { + parameters["error_type"] = (error as NSError).domain + parameters["error_message"] = { + if let sheetError = error as? FinancialConnectionsSheetError { + switch sheetError { + case .unknown(let debugDescription): + return debugDescription + } + } else { + return (error as NSError).localizedDescription + } + }() as String + parameters["code"] = (error as NSError).code + } + log(eventName: eventName, parameters: parameters, pane: pane) + } + + func logMerchantDataAccessLearnMore(pane: FinancialConnectionsSessionManifest.NextPane) { + log( + eventName: "click.data_access.learn_more", + pane: pane + ) + } + + func setAdditionalParameters( + linkAccountSessionClientSecret: String, + publishableKey: String?, + stripeAccount: String? + ) { + additionalParameters["las_client_secret"] = linkAccountSessionClientSecret + additionalParameters["key"] = publishableKey + additionalParameters["stripe_account"] = stripeAccount + } + + func setAdditionalParameters(fromManifest manifest: FinancialConnectionsSessionManifest) { + additionalParameters["livemode"] = manifest.livemode + additionalParameters["product"] = manifest.product + additionalParameters["is_stripe_direct"] = manifest.isStripeDirect + additionalParameters["single_account"] = manifest.singleAccount + additionalParameters["allow_manual_entry"] = manifest.allowManualEntry + additionalParameters["account_holder_id"] = manifest.accountholderToken + } + + static func paneFromViewController( + _ viewController: UIViewController? + ) -> FinancialConnectionsSessionManifest.NextPane { + switch viewController { + case is ConsentViewController: + return .consent + case is InstitutionPickerViewController: + return .institutionPicker + case is PartnerAuthViewController: + return .partnerAuth + case is AccountPickerViewController: + return .accountPicker + case is AttachLinkedPaymentAccountViewController: + return .attachLinkedPaymentAccount + case is SuccessViewController: + return .success + case is ManualEntryViewController: + return .manualEntry + case is ResetFlowViewController: + return .resetFlow + case is TerminalErrorViewController: + return .terminalError + case is NetworkingLinkSignupViewController: + return .networkingLinkSignupPane + case is NetworkingLinkLoginWarmupViewController: + return .networkingLinkLoginWarmup + case is NetworkingLinkVerificationViewController: + return .networkingLinkVerification + case is NetworkingLinkStepUpVerificationViewController: + return .networkingLinkStepUpVerification + case is NetworkingSaveToLinkVerificationViewController: + return .networkingSaveToLinkVerification + case is LinkAccountPickerViewController: + return .linkAccountPicker + case is LinkLoginViewController: + return .linkLogin + case is ErrorViewController: + return .unexpectedError + default: + return .unparsable + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Analytics/FinancialConnectionsSheetAnalytics.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Analytics/FinancialConnectionsSheetAnalytics.swift new file mode 100644 index 00000000..94e49e5b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Analytics/FinancialConnectionsSheetAnalytics.swift @@ -0,0 +1,116 @@ +// +// FinancialConnectionsSheetAnalytics.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/9/21. +// + +import Foundation +@_spi(STP) import StripeCore + +/// Analytic that contains a `financial connections session clientSecret` payload param +protocol FinancialConnectionsSheetAnalytic: Analytic { + var clientSecret: String { get } + var additionalParams: [String: Any] { get } +} + +extension FinancialConnectionsSheetAnalytic { + var params: [String: Any] { + var params = additionalParams + params["las_client_secret"] = clientSecret + return params + } +} + +/// Logged when the sheet is presented +struct FinancialConnectionsSheetPresentedAnalytic: FinancialConnectionsSheetAnalytic { + let event = STPAnalyticEvent.financialConnectionsSheetPresented + let clientSecret: String + let additionalParams: [String: Any] = [:] +} + +/// Logged when the sheet is closed by the end-user +struct FinancialConnectionsSheetClosedAnalytic: FinancialConnectionsSheetAnalytic { + let event = STPAnalyticEvent.financialConnectionsSheetClosed + let clientSecret: String + let result: String + + var additionalParams: [String: Any] { + return [ + "session_result": result, + ] + } +} + +/// Logged when the financial connections sheet flow is determined +struct FinancialConnectionsSheetFlowDetermined: FinancialConnectionsSheetAnalytic { + let event = STPAnalyticEvent.financialConnectionsSheetFlowDetermined + let clientSecret: String + let flow: FlowRouter.Flow + let killswitchActive: Bool + + var additionalParams: [String: Any] { + [ + "flow": flow.rawValue, + "killswitchActive": killswitchActive, + ] + } +} + +/// Logged if there's an error presenting the sheet +struct FinancialConnectionsSheetFailedAnalytic: FinancialConnectionsSheetAnalytic { + let event = STPAnalyticEvent.financialConnectionsSheetFailed + let clientSecret: String + let additionalParams: [String: Any] = [:] + let error: Error +} + +/// Logged at the begining of the initial `synchronize` API call. +struct FinancialConnectionsSheetInitialSynchronizeStarted: FinancialConnectionsSheetAnalytic { + let event = STPAnalyticEvent.financialConnectionsSheetInitialSynchronizeStarted + let clientSecret: String + let additionalParams: [String: Any] = [:] +} + +/// Logged when the initial `synchronize` API call completes. Includes whether or not the call was a success, and the error otherwise. +struct FinancialConnectionsSheetInitialSynchronizeCompleted: FinancialConnectionsSheetAnalytic { + let event = STPAnalyticEvent.financialConnectionsSheetInitialSynchronizeCompleted + let clientSecret: String + let success: Bool + let possibleError: Error? + + var additionalParams: [String: Any] { + var params: [String: Any] = ["success": success] + if let error = possibleError { + params["error"] = error.serializeForV1Analytics() + } + return params + } +} + +/// Helper to determine if we should log a failed analytic or closed analytic from the sheet's completion block +struct FinancialConnectionsSheetCompletionAnalytic { + /// Returns either a `FinancialConnectionsSheetClosedAnalytic` or `FinancialConnectionsSheetFailedAnalytic` depending on the result + static func make( + clientSecret: String, + result: HostControllerResult + ) -> FinancialConnectionsSheetAnalytic { + switch result { + case .completed: + return FinancialConnectionsSheetClosedAnalytic( + clientSecret: clientSecret, + result: "completed" + ) + case .canceled: + return FinancialConnectionsSheetClosedAnalytic( + clientSecret: clientSecret, + result: "cancelled" + ) + case .failed(let error): + return FinancialConnectionsSheetFailedAnalytic( + clientSecret: clientSecret, + error: error + ) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/ExperimentHelper.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/ExperimentHelper.swift new file mode 100644 index 00000000..a7d5d607 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/ExperimentHelper.swift @@ -0,0 +1,69 @@ +// +// ExperimentHelper.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 12/22/22. +// + +import Foundation + +/// Abstracts experimentation logic for a specific experiment. +final class ExperimentHelper { + + private let experimentName: String + private let manifest: FinancialConnectionsSessionManifest + private let analyticsClient: FinancialConnectionsAnalyticsClient + private var didLogExposure = false + + private var isExperimentValid: Bool { + return experimentVariant != nil && manifest.assignmentEventId != nil && manifest.accountholderToken != nil + } + private var experimentVariant: String? { + return manifest.experimentAssignments?[experimentName] + } + + init( + experimentName: String, + manifest: FinancialConnectionsSessionManifest, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.experimentName = experimentName + self.manifest = manifest + self.analyticsClient = analyticsClient + } + + // Helper where we assume that we have two groups: "control" and "treatment." + // If user is in "treatment" group, we return `true`. + func isEnabled(logExposure: Bool) -> Bool { + guard isExperimentValid else { + return false + } + if logExposure { + logExposureIfNeeded() + } + return experimentVariant == "treatment" + } + + private func logExposureIfNeeded() { + guard isExperimentValid else { + return + } + guard let assignmentEventId = manifest.assignmentEventId else { + assertionFailure("`isExperimentValid` should ensure `assignmentEventId` is non-null") + return + } + guard let accountholderToken = manifest.accountholderToken else { + assertionFailure("`isExperimentValid` should ensure `accountholderToken` is non-null") + return + } + + if !didLogExposure { + didLogExposure = true + analyticsClient.logExposure( + experimentName: experimentName, + assignmentEventId: assignmentEventId, + accountholderToken: accountholderToken + ) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FinancialConnectionsCustomManualEntryRequiredError.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FinancialConnectionsCustomManualEntryRequiredError.swift new file mode 100644 index 00000000..9c8052f6 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FinancialConnectionsCustomManualEntryRequiredError.swift @@ -0,0 +1,10 @@ +// +// FinancialConnectionsCustomManualEntryRequiredError.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/3/23. +// + +import Foundation + +public struct FinancialConnectionsCustomManualEntryRequiredError: Error {} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FinancialConnectionsNavigationController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FinancialConnectionsNavigationController.swift new file mode 100644 index 00000000..f8da3a1e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FinancialConnectionsNavigationController.swift @@ -0,0 +1,217 @@ +// +// FinancialConnectionsNavigationController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/6/22. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class FinancialConnectionsNavigationController: UINavigationController { + + // Swift 5.8 requires us to manually mark inits as unavailable as well: + override public init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) { + super.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass) + } + + override public init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + // only currently set for native flow + weak var analyticsClient: FinancialConnectionsAnalyticsClient? + private var lastInteractivePopGestureRecognizerEndedDate: Date? + private weak var lastShownViewController: UIViewController? + + // MARK: - UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + // disable the ability for a user to swipe down to dismiss + // because we want to make a network call (and wait for it) + // before a user can fully dismiss + isModalInPresentation = true + listenToInteractivePopGestureRecognizer() + navigationBar.accessibilityIdentifier = "fc_navigation_bar" + } + + private func logNavigationBackEvent(fromViewController: UIViewController, source: String) { + guard let analyticsClient = analyticsClient else { + assertionFailure("Expected `analyticsClient` (\(FinancialConnectionsAnalyticsClient.self)) to be set.") + return + } + analyticsClient + .log( + // we use the same event name for both clicks and swipes to + // simplify analytics logging (same event, different parameters) + eventName: "click.nav_bar.back", + parameters: [ + "source": source, + ], + pane: FinancialConnectionsAnalyticsClient.paneFromViewController(fromViewController) + ) + } +} + +// MARK: - Track Swipe Back Analytics Events + +extension FinancialConnectionsNavigationController: UINavigationControllerDelegate { + + private func listenToInteractivePopGestureRecognizer() { + delegate = self + assert(interactivePopGestureRecognizer != nil) + interactivePopGestureRecognizer?.addTarget(self, action: #selector(interactivePopGestureRecognizerDidChange)) + } + + @objc private func interactivePopGestureRecognizerDidChange() { + if interactivePopGestureRecognizer?.state == .ended { + // As soon as user releases the "interactive pop" the gesture will + // move to the `.ended` state. Note that this does NOT mean + // that the user actually finished the pop gesture and + // popped the view controller. + lastInteractivePopGestureRecognizerEndedDate = Date() + } + } + + func navigationController( + _ navigationController: UINavigationController, + didShow viewController: UIViewController, + animated: Bool + ) { + if let lastInteractivePopGestureRecognizerEndedDate = lastInteractivePopGestureRecognizerEndedDate, + Date().timeIntervalSince(lastInteractivePopGestureRecognizerEndedDate) < 0.7, + let lastShownViewController = lastShownViewController + { + // If user _recently_ ended the interactive pop gesture + // AND navigation controller presented a new view controller + // it's extremely likely that user popped a view controller + // by using the swipe gesture. + logNavigationBackEvent(fromViewController: lastShownViewController, source: "interactive_pop_gesture") + } + lastInteractivePopGestureRecognizerEndedDate = nil + lastShownViewController = viewController + } +} + +// MARK: - Track Back Button Press Analytics Events + +extension FinancialConnectionsNavigationController: UINavigationBarDelegate { + + // `UINavigationBarDelegate` methods "just work" on `UINavigationController` + // without having to set any delegates + func navigationBar( + _ navigationBar: UINavigationBar, + shouldPop item: UINavigationItem + ) -> Bool { + if let topViewController = topViewController { + logNavigationBackEvent(fromViewController: topViewController, source: "navigation_bar_button") + } else { + assertionFailure( + "Expected a `topViewConroller` to exist for \(FinancialConnectionsNavigationController.self)" + ) + } + return true + } +} + +// MARK: - `UINavigationController` Modifications + +// The purpose of this extension is to consolidate in one place +// all the common changes to `UINavigationController` +extension FinancialConnectionsNavigationController { + + func configureAppearanceForNative() { + let backButtonImage = Image + .back_arrow + .makeImage(template: false) + .withAlignmentRectInsets(UIEdgeInsets(top: 0, left: -13, bottom: -2, right: 0)) + let appearance = UINavigationBarAppearance() + appearance.setBackIndicatorImage(backButtonImage, transitionMaskImage: backButtonImage) + appearance.backgroundColor = .customBackgroundColor + appearance.shadowColor = .clear // remove border + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + navigationBar.compactAppearance = appearance + + // change the back button color + navigationBar.tintColor = UIColor.iconDefault + navigationBar.isTranslucent = false + } + + static func configureNavigationItemForNative( + _ navigationItem: UINavigationItem?, + closeItem: UIBarButtonItem, + shouldHideLogo: Bool, + theme: FinancialConnectionsTheme, + isTestMode: Bool + ) { + let iconHeight: CGFloat = 20 + var testModeImageViewWidth: CGFloat = 0 + var logoImageViewWidth: CGFloat = 0 + + let testModeBadgeView: UIImageView? = { + guard isTestMode else { return nil } + + let testModeImage = UIImageView(image: Image.testmode.makeImage()) + testModeImage.contentMode = .scaleAspectFit + testModeImage.sizeToFit() + testModeImageViewWidth = testModeImage.bounds.width * (iconHeight / max(1, testModeImage.bounds.height)) + testModeImage.frame = CGRect( + x: 0, + y: 0, + width: testModeImageViewWidth, + height: iconHeight + ) + return testModeImage + }() + + let logoView: UIImageView? = { + guard !shouldHideLogo else { return nil } + + let logoImage = UIImageView(image: theme.logo.makeImage(template: true)) + logoImage.tintColor = theme.logoColor + logoImage.contentMode = .scaleAspectFit + logoImage.sizeToFit() + + logoImageViewWidth = logoImage.bounds.width * (iconHeight / max(1, logoImage.bounds.height)) + logoImage.frame = CGRect( + x: 0, + y: 0, + width: logoImageViewWidth, + height: iconHeight + ) + return logoImage + }() + + let spacing: CGFloat = 6 + let imageViews = [logoView, testModeBadgeView].compactMap { $0.self } + let stackView = UIStackView(arrangedSubviews: imageViews) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = spacing + stackView.frame = CGRect( + x: 0, + y: 0, + width: logoImageViewWidth + testModeImageViewWidth + spacing, + height: iconHeight + ) + + // If `titleView` is directly set to the custom view + // we can't control the sizing...so we create a `containerView` + // so we can control its sizing. + let containerView = UIView() + containerView.frame = stackView.bounds + containerView.addSubview(stackView) + stackView.center = containerView.center + + navigationItem?.titleView = stackView + navigationItem?.backButtonTitle = "" + navigationItem?.rightBarButtonItem = closeItem + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FlowRouter.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FlowRouter.swift new file mode 100644 index 00000000..616c36f5 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/FlowRouter.swift @@ -0,0 +1,134 @@ +// +// FlowRouter.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/1/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +class FlowRouter { + + enum Flow: String { + case webInstantDebits = "web_instant_debits" + case nativeInstantDebits = "native_instant_debits" + case webFinancialConnections = "web_financial_connections" + case nativeFinancialConnections = "native_financial_connections" + } + + private enum ExampleAppOverride: Equatable { + case native + case web + case none + + var shouldUseNativeFlow: Bool { + self == .native + } + } + + private let synchronizePayload: FinancialConnectionsSynchronize + private let analyticsClient: FinancialConnectionsAnalyticsClient + + // MARK: - Init + + init( + synchronizePayload: FinancialConnectionsSynchronize, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.synchronizePayload = synchronizePayload + self.analyticsClient = analyticsClient + } + + // MARK: - Public + + var flow: Flow { + if synchronizePayload.manifest.isProductInstantDebits { + return shouldUseNativeInstantDebits ? .nativeInstantDebits : .webInstantDebits + } else { + logExposureIfNeeded() + return shouldUseNativeFinancialConnections ? .nativeFinancialConnections : .webFinancialConnections + } + } + + var killswitchActive: Bool { + // If the manifest is missing features map, fallback to webview. + guard let features = synchronizePayload.manifest.features else { return true } + + // If native version killswitch feature is missing, fallback to webview. + guard let killswitchValue = features[Constants.killswitchFeature] else { return true } + + return killswitchValue + } + + // MARK: - Private + + /// Returns `.native` if the FC example app has the native SDK selected, `.web` if the web SDK is selected, and `.none` otherwise. + private var exampleAppSdkOverride: ExampleAppOverride { + if let nativeOverride = UserDefaults.standard.value( + forKey: "FINANCIAL_CONNECTIONS_EXAMPLE_APP_ENABLE_NATIVE" + ) as? Bool { + return nativeOverride ? .native : .web + } + return .none + } + + private var shouldUseNativeFinancialConnections: Bool { + // Override all other conditions if the example app has native or web selected. + guard case .none = exampleAppSdkOverride else { + return exampleAppSdkOverride.shouldUseNativeFlow + } + + // if this version is killswitched by server, fallback to webview. + if killswitchActive { return false } + + // If native experiment is missing, fallback to webview. + guard let experimentVariant = experimentVariant else { return false } + + return experimentVariant == Constants.nativeExperimentTreatment + } + + private var shouldUseNativeInstantDebits: Bool { + // Override all other conditions if the example app has native or web selected. + guard case .none = exampleAppSdkOverride else { + return exampleAppSdkOverride.shouldUseNativeFlow + } + + return !killswitchActive + } + + private var experimentVariant: String? { + return synchronizePayload.manifest.experimentAssignments?[Constants.nativeExperiment] + } + + func logExposureIfNeeded() { + + // if this version is killswitched by server, don't log exposure. + if killswitchActive { return } + + // If native experiment is missing, don't log exposure. + if experimentVariant == nil { return } + + // If assignmentIdIsMissing, don't log exposure. + guard let assignmentEventId = synchronizePayload.manifest.assignmentEventId else { return } + + // If account holder is unknown, don't log exposure. + guard let accountHolder = synchronizePayload.manifest.accountholderToken else { return } + + analyticsClient.logExposure( + experimentName: Constants.nativeExperiment, + assignmentEventId: assignmentEventId, + accountholderToken: accountHolder + ) + } +} + +// MARK: - Constants + +private extension FlowRouter { + enum Constants { + static let killswitchFeature = "bank_connections_mobile_native_version_killswitch" + static let nativeExperiment = "connections_mobile_native" + static let nativeExperimentTreatment = "treatment" + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/HostController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/HostController.swift new file mode 100644 index 00000000..b7cac298 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/HostController.swift @@ -0,0 +1,279 @@ +// +// HostController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/3/22. +// + +@_spi(STP) import StripeCore +import UIKit + +/// An internal result type that helps us handle both +/// Financial Connections and Instant Debits +@_spi(STP) public enum HostControllerResult { + case completed(Completed) + case failed(error: Error) + case canceled + + @_spi(STP) public enum Completed { + case financialConnections(StripeAPI.FinancialConnectionsSession) + case instantDebits(InstantDebitsLinkedBank) + } +} + +extension HostControllerResult { + + /// Updates the `HostControllerResult` from the manifest to populate any fields that aren't part of the actual API response, + /// but that are still necessary to produce the correct result in the host surface. + func updateWith(_ manifest: FinancialConnectionsSessionManifest) -> Self { + guard case .completed(.financialConnections(let session)) = self else { + return self + } + + let instantlyVerified = !manifest.manualEntryUsesMicrodeposits + + let updatedSession = StripeAPI.FinancialConnectionsSession( + clientSecret: session.clientSecret, + id: session.id, + accounts: session.accounts, + livemode: session.livemode, + paymentAccount: session.paymentAccount?.setInstantlyVerifiedIfNeeded(instantlyVerified), + bankAccountToken: session.bankAccountToken, + status: session.status, + statusDetails: session.statusDetails + ) + + return .completed(.financialConnections(updatedSession)) + } +} + +private extension StripeAPI.FinancialConnectionsSession.PaymentAccount { + + func setInstantlyVerifiedIfNeeded(_ value: Bool) -> Self { + guard case .bankAccount(var bankAccount) = self else { + return self + } + + bankAccount.instantlyVerified = value + return .bankAccount(bankAccount) + } +} + +protocol HostControllerDelegate: AnyObject { + + func hostController( + _ hostController: HostController, + viewController: UIViewController, + didFinish result: HostControllerResult + ) + + func hostController( + _ hostController: HostController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +class HostController { + + // MARK: - Properties + + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + private let returnURL: String? + private let elementsSessionContext: ElementsSessionContext? + private let analyticsClient: FinancialConnectionsAnalyticsClient + private let analyticsClientV1: STPAnalyticsClientProtocol + + private var nativeFlowController: NativeFlowController? + lazy var hostViewController = HostViewController( + analyticsClientV1: analyticsClientV1, + clientSecret: clientSecret, + returnURL: returnURL, + apiClient: apiClient, + delegate: self + ) + lazy var navigationController = FinancialConnectionsNavigationController(rootViewController: hostViewController) + + weak var delegate: HostControllerDelegate? + + // MARK: - Init + + init( + apiClient: FinancialConnectionsAPIClient, + analyticsClientV1: STPAnalyticsClientProtocol, + clientSecret: String, + elementsSessionContext: ElementsSessionContext?, + returnURL: String?, + publishableKey: String?, + stripeAccount: String? + ) { + self.apiClient = apiClient + self.analyticsClientV1 = analyticsClientV1 + self.clientSecret = clientSecret + self.elementsSessionContext = elementsSessionContext + self.returnURL = returnURL + self.analyticsClient = FinancialConnectionsAnalyticsClient() + analyticsClient.setAdditionalParameters( + linkAccountSessionClientSecret: clientSecret, + publishableKey: publishableKey, + stripeAccount: stripeAccount + ) + analyticsClient.delegate = self + } +} + +// MARK: - HostViewControllerDelegate + +extension HostController: HostViewControllerDelegate { + + func hostViewControllerDidFinish(_ viewController: HostViewController, lastError: Error?) { + guard let error = lastError else { + delegate?.hostController(self, viewController: viewController, didFinish: .canceled) + return + } + + delegate?.hostController(self, viewController: viewController, didFinish: .failed(error: error)) + } + + func hostViewController( + _ viewController: HostViewController, + didFetch synchronizePayload: FinancialConnectionsSynchronize + ) { + delegate?.hostController(self, didReceiveEvent: FinancialConnectionsEvent(name: .open)) + + let flowRouter = FlowRouter( + synchronizePayload: synchronizePayload, + analyticsClient: analyticsClient + ) + + let flow = flowRouter.flow + analyticsClientV1.log( + analytic: FinancialConnectionsSheetFlowDetermined( + clientSecret: clientSecret, + flow: flow, + killswitchActive: flowRouter.killswitchActive + ), + apiClient: apiClient.backingAPIClient + ) + + switch flow { + case .webInstantDebits, .webFinancialConnections: + continueWithWebFlow(synchronizePayload.manifest) + case .nativeInstantDebits, .nativeFinancialConnections: + continueWithNativeFlow(synchronizePayload) + } + } + + func hostViewController( + _ hostViewController: HostViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) { + delegate?.hostController(self, didReceiveEvent: event) + } +} + +// MARK: - Helpers + +private extension HostController { + + func continueWithWebFlow(_ manifest: FinancialConnectionsSessionManifest) { + delegate?.hostController( + self, + didReceiveEvent: FinancialConnectionsEvent( + name: .flowLaunchedInBrowser + ) + ) + + let accountFetcher = FinancialConnectionsAccountAPIFetcher(api: apiClient, clientSecret: clientSecret) + let sessionFetcher = FinancialConnectionsSessionAPIFetcher( + api: apiClient, + clientSecret: clientSecret, + accountFetcher: accountFetcher + ) + let webFlowViewController = FinancialConnectionsWebFlowViewController( + clientSecret: clientSecret, + apiClient: apiClient, + manifest: manifest, + sessionFetcher: sessionFetcher, + returnURL: returnURL, + elementsSessionContext: elementsSessionContext + ) + webFlowViewController.delegate = self + navigationController.setViewControllers([webFlowViewController], animated: true) + } + + func continueWithNativeFlow(_ synchronizePayload: FinancialConnectionsSynchronize) { + navigationController.configureAppearanceForNative() + + let dataManager = NativeFlowAPIDataManager( + manifest: synchronizePayload.manifest, + visualUpdate: synchronizePayload.visual, + returnURL: returnURL, + consentPaneModel: synchronizePayload.text?.consentPane, + accountPickerPane: synchronizePayload.text?.accountPickerPane, + apiClient: apiClient, + clientSecret: clientSecret, + analyticsClient: analyticsClient, + elementsSessionContext: elementsSessionContext + ) + nativeFlowController = NativeFlowController( + dataManager: dataManager, + navigationController: navigationController + ) + nativeFlowController?.delegate = self + nativeFlowController?.startFlow() + } +} + +// MARK: - ConnectionsWebFlowViewControllerDelegate + +extension HostController: FinancialConnectionsWebFlowViewControllerDelegate { + + func webFlowViewController( + _ viewController: FinancialConnectionsWebFlowViewController, + didFinish result: HostControllerResult + ) { + delegate?.hostController(self, viewController: viewController, didFinish: result) + } + + func webFlowViewController( + _ webFlowViewController: UIViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) { + delegate?.hostController(self, didReceiveEvent: event) + } +} + +// MARK: - NativeFlowControllerDelegate + +extension HostController: NativeFlowControllerDelegate { + func nativeFlowController( + _ nativeFlowController: NativeFlowController, + didFinish result: HostControllerResult + ) { + guard let viewController = navigationController.topViewController else { + assertionFailure("Navigation stack is empty") + return + } + delegate?.hostController(self, viewController: viewController, didFinish: result) + } + + func nativeFlowController( + _ nativeFlowController: NativeFlowController, + didReceiveEvent event: FinancialConnectionsEvent + ) { + delegate?.hostController(self, didReceiveEvent: event) + } +} + +// MARK: - FinancialConnectionsAnalyticsClientDelegate + +extension HostController: FinancialConnectionsAnalyticsClientDelegate { + + func analyticsClient( + _ analyticsClient: FinancialConnectionsAnalyticsClient, + didReceiveEvent event: FinancialConnectionsEvent + ) { + delegate?.hostController(self, didReceiveEvent: event) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/HostViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/HostViewController.swift new file mode 100644 index 00000000..e8be42f3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/HostViewController.swift @@ -0,0 +1,165 @@ +// +// HostViewController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/3/22. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol HostViewControllerDelegate: AnyObject { + + func hostViewControllerDidFinish( + _ viewController: HostViewController, + lastError: Error? + ) + + func hostViewController( + _ viewController: HostViewController, + didFetch synchronizePayload: FinancialConnectionsSynchronize + ) + + func hostViewController( + _ hostViewController: HostViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +final class HostViewController: UIViewController { + + // MARK: - UI + + private lazy var closeItem: UIBarButtonItem = { + let item = UIBarButtonItem( + image: Image.close.makeImage(template: false), + style: .plain, + target: self, + action: #selector(didTapClose) + ) + item.tintColor = .iconDefault + item.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5) + return item + }() + + // We haven't loaded the manifest yet, so we use a nil theme to show a neutral-colored spinner. + private let loadingView = LoadingView(frame: .zero, theme: nil) + + // MARK: - Properties + + weak var delegate: HostViewControllerDelegate? + + private let analyticsClientV1: STPAnalyticsClientProtocol + private let clientSecret: String + private let apiClient: FinancialConnectionsAPIClient + private let returnURL: String? + + private var lastError: Error? + + // MARK: - Init + + init( + analyticsClientV1: STPAnalyticsClientProtocol, + clientSecret: String, + returnURL: String?, + apiClient: FinancialConnectionsAPIClient, + delegate: HostViewControllerDelegate? + ) { + self.analyticsClientV1 = analyticsClientV1 + self.clientSecret = clientSecret + self.returnURL = returnURL + self.apiClient = apiClient + self.delegate = delegate + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + + view.addSubview(loadingView) + view.backgroundColor = .customBackgroundColor + navigationItem.rightBarButtonItem = closeItem + loadingView.tryAgainButton.addTarget(self, action: #selector(didTapTryAgainButton), for: .touchUpInside) + getManifest() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + loadingView.frame = view.bounds.inset(by: view.safeAreaInsets) + } +} + +// MARK: - Helpers + +extension HostViewController { + private func getManifest() { + loadingView.errorView.isHidden = true + loadingView.showLoading(true) + + analyticsClientV1.log( + analytic: FinancialConnectionsSheetInitialSynchronizeStarted(clientSecret: clientSecret), + apiClient: apiClient.backingAPIClient + ) + + apiClient + .synchronize( + clientSecret: clientSecret, + returnURL: returnURL + ) + .observe { [weak self] result in + guard let self = self else { return } + + analyticsClientV1.log( + analytic: FinancialConnectionsSheetInitialSynchronizeCompleted( + clientSecret: clientSecret, + success: result.success, + possibleError: result.error + ), + apiClient: apiClient.backingAPIClient + ) + + switch result { + case .success(let synchronizePayload): + self.lastError = nil + self.delegate?.hostViewController(self, didFetch: synchronizePayload) + case .failure(let error): + FinancialConnectionsEvent + .events(fromError: error) + .forEach { event in + self.delegate?.hostViewController(self, didReceiveEvent: event) + } + + self.loadingView.showLoading(false) + self.loadingView.errorView.isHidden = false + self.lastError = error + } + } + } +} + +// MARK: - UI Helpers + +private extension HostViewController { + + @objc + func didTapTryAgainButton() { + getManifest() + } + + @objc + func didTapClose() { + delegate?.hostViewController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .cancel) + ) + delegate?.hostViewControllerDidFinish(self, lastError: lastError) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/LoadingView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/LoadingView.swift new file mode 100644 index 00000000..7ac6c0e9 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/LoadingView.swift @@ -0,0 +1,104 @@ +// +// LoadingView.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/3/22. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class LoadingView: UIView { + + private let theme: FinancialConnectionsTheme? + + // MARK: - Subview Properties + + private lazy var errorLabel: UILabel = { + let label = UILabel() + label.text = STPLocalizedString( + "Failed to connect", + "Error message that displays when we're unable to connect to the server." + ) + label.textAlignment = .center + label.numberOfLines = 0 + label.font = Styling.errorLabelFont + label.textColor = .textDefault + return label + }() + + private(set) lazy var tryAgainButton: StripeUICore.Button = { + + let button = StripeUICore.Button( + configuration: .primary(), + title: String.Localized.tryAgain + ) + return button + }() + + internal let errorView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .center + stackView.spacing = Styling.errorViewSpacing + return stackView + }() + + private lazy var spinnerView = { + SpinnerView(theme: theme, shouldStartAnimating: false) + }() + + // MARK: - Init + + init(frame: CGRect, theme: FinancialConnectionsTheme?) { + self.theme = theme + super.init(frame: frame) + + errorView.addArrangedSubview(errorLabel) + errorView.addArrangedSubview(tryAgainButton) + addSubview(errorView) + + // Add constraints + errorView.translatesAutoresizingMaskIntoConstraints = false + + tryAgainButton.setContentHuggingPriority(.required, for: .vertical) + tryAgainButton.setContentCompressionResistancePriority(.required, for: .vertical) + errorLabel.setContentHuggingPriority(.required, for: .vertical) + errorLabel.setContentCompressionResistancePriority(.required, for: .vertical) + + NSLayoutConstraint.activate([ + // Pin error view to top + errorView.centerYAnchor.constraint(equalTo: centerYAnchor), + errorView.centerXAnchor.constraint(equalTo: centerXAnchor), + ]) + + addAndPinSubview(spinnerView) + showLoading(false) + } + + func showLoading(_ showLoading: Bool) { + spinnerView.isHidden = !showLoading + if showLoading { + spinnerView.startAnimating() + } else { + spinnerView.stopAnimating() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Styling + +private extension LoadingView { + enum Styling { + static let errorViewSpacing: CGFloat = 16 + static var errorLabelFont: UIFont { + UIFont.preferredFont(forTextStyle: .body, weight: .medium) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/ModalPresentationWrapperViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/ModalPresentationWrapperViewController.swift new file mode 100644 index 00000000..81645af8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/ModalPresentationWrapperViewController.swift @@ -0,0 +1,50 @@ +// +// ModalPresentationWrapperViewController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/22/22. +// + +import UIKit + +class ModalPresentationWrapperViewController: UIViewController { + + private weak var vc: UIViewController? + + // MARK: - Init + + init(vc: UIViewController) { + self.vc = vc + super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .overFullScreen + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + + view.alpha = 0.3 + view.backgroundColor = .black + view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap))) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let vc = vc, presentedViewController == nil { + self.present(vc, animated: true) + } + } + + // MARK: - Touch Handler + + @objc + private func didTap() { + dismiss(animated: false) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Common/TestModeAutofillBannerView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/TestModeAutofillBannerView.swift new file mode 100644 index 00000000..6937f04a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Common/TestModeAutofillBannerView.swift @@ -0,0 +1,163 @@ +// +// TestModeAutofillBannerView.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-06-20. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class TestModeAutofillBannerView: UIView { + + enum Context { + case account + case otp + + var buttonLabel: String { + switch self { + case .account: + STPLocalizedString( + "Use test account", + "Label for a button shown to a user who is in test mode, which will give them the option to autofill mock account credentials." + ) + case .otp: + STPLocalizedString( + "Use test code", + "Label for a button shown to a user who is in test mode, which will give them the option to autofill a mock one time passcode." + ) + } + } + } + + private let context: Context + private let theme: FinancialConnectionsTheme + private let didTapAutofill: () -> Void + + // MARK: - Subviews + + private lazy var messageLabel: UILabel = { + // Create icon as a text attachement. + let icon = Image.info + .makeImage(template: true) + .withTintColor(.attention300) + let textAttachment = NSTextAttachment(image: icon) + + textAttachment.bounds = CGRect( + x: 0, + y: -3, + width: icon.size.width, + height: icon.size.height + ) + + let attributedString = NSMutableAttributedString() + attributedString.append(NSAttributedString(attachment: textAttachment)) + // Add a spacer between the icon and the label in the form of a space. + attributedString.append(NSAttributedString(string: " ")) + attributedString.append(NSAttributedString(string: STPLocalizedString( + "You're in test mode.", + "Message shown to a user who is in test mode, which will give them the option to autofill mock credentials." + ))) + + let label = UILabel() + label.attributedText = attributedString + label.font = FinancialConnectionsFont.body(.small).uiFont + label.textColor = .textDefault + label.numberOfLines = 0 + label.lineBreakMode = .byWordWrapping + return label + }() + + private lazy var autofillDataButton: UIButton = { + let button = UIButton() + button.setTitle(context.buttonLabel, for: .normal) + button.setTitleColor(theme.textActionColor, for: .normal) + button.titleLabel?.textAlignment = .right + button.titleLabel?.font = FinancialConnectionsFont.label(.mediumEmphasized).uiFont + button.addTarget(self, action: #selector(autofillTapped), for: .touchUpInside) + button.accessibilityIdentifier = "test_mode_autofill_button" + return button + }() + + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .fill + return stackView + }() + + // MARK: - Init and setup + + init(context: Context, theme: FinancialConnectionsTheme, didTapAutofill: @escaping () -> Void) { + self.context = context + self.theme = theme + self.didTapAutofill = didTapAutofill + super.init(frame: .zero) + setupLayout() + } + + private func setupLayout() { + backgroundColor = .attention50 + layer.cornerRadius = 12 + clipsToBounds = true + + stackView.addArrangedSubview(messageLabel) + stackView.addArrangedSubview(autofillDataButton) + + messageLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) + autofillDataButton.setContentHuggingPriority(.required, for: .horizontal) + autofillDataButton.setContentCompressionResistancePriority(.required, for: .horizontal) + + stackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(stackView) + + NSLayoutConstraint.activate([ + heightAnchor.constraint(equalTo: stackView.heightAnchor, constant: 12), + stackView.centerYAnchor.constraint(equalTo: centerYAnchor), + stackView.widthAnchor.constraint(equalTo: safeAreaLayoutGuide.widthAnchor, constant: -24), + stackView.centerXAnchor.constraint(equalTo: centerXAnchor), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func autofillTapped() { + didTapAutofill() + } +} + +#if DEBUG + +import SwiftUI + +private struct TestModeAutofillBannerViewRepresentable: UIViewRepresentable { + let bannerContext: TestModeAutofillBannerView.Context + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> TestModeAutofillBannerView { + TestModeAutofillBannerView(context: bannerContext, theme: theme, didTapAutofill: {}) + } + + func updateUIView(_ uiView: TestModeAutofillBannerView, context: Context) {} +} + +struct TestModeAutofillBannerView_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + TestModeAutofillBannerViewRepresentable(bannerContext: .account, theme: .light) + .frame(height: 38) + + TestModeAutofillBannerViewRepresentable(bannerContext: .otp, theme: .light) + .frame(height: 38) + + TestModeAutofillBannerViewRepresentable(bannerContext: .otp, theme: .linkLight) + .frame(height: 38) + } + .padding() + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSDK/FinancialConnectionsSDKImplementation.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSDK/FinancialConnectionsSDKImplementation.swift new file mode 100644 index 00000000..c427a01b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSDK/FinancialConnectionsSDKImplementation.swift @@ -0,0 +1,105 @@ +// +// FinancialConnectionsSDKImplementation.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 2/24/22. +// + +@_spi(STP) import StripeCore +import UIKit + +/** + NOTE: If you change the name of this class, make sure to also change it FinancialConnectionsSDKAvailability file + */ +@_spi(STP) +public class FinancialConnectionsSDKImplementation: FinancialConnectionsSDKInterface { + + required public init() {} + + public func presentFinancialConnectionsSheet( + apiClient: STPAPIClient, + clientSecret: String, + returnURL: String?, + elementsSessionContext: ElementsSessionContext?, + onEvent: ((StripeCore.FinancialConnectionsEvent) -> Void)?, + from presentingViewController: UIViewController, + completion: @escaping (FinancialConnectionsSDKResult) -> Void + ) { + let financialConnectionsSheet = FinancialConnectionsSheet( + financialConnectionsSessionClientSecret: clientSecret, + returnURL: returnURL + ) + financialConnectionsSheet.apiClient = apiClient + financialConnectionsSheet.elementsSessionContext = elementsSessionContext + financialConnectionsSheet.onEvent = onEvent + // Captures self explicitly until the callback is invoked + financialConnectionsSheet.present( + from: presentingViewController, + completion: { result in + switch result { + case .completed(let hostControllerResult): + switch hostControllerResult { + case .financialConnections(let session): + guard let paymentAccount = session.paymentAccount else { + completion( + .failed( + error: FinancialConnectionsSheetError.unknown( + debugDescription: "PaymentAccount is not set on FinancialConnectionsSession" + ) + ) + ) + return + } + if let linkedBank = self.linkedBankFor(paymentAccount: paymentAccount, session: session) { + completion(.completed(.financialConnections(linkedBank))) + } else { + completion( + .failed( + error: FinancialConnectionsSheetError.unknown( + debugDescription: "Unknown PaymentAccount is set on FinancialConnectionsSession" + ) + ) + ) + } + case .instantDebits(let instantDebitsLinkedBank): + completion(.completed(.instantDebits(instantDebitsLinkedBank))) + } + case .canceled: + completion(.cancelled) + case .failed(let error): + completion(.failed(error: error)) + } + } + ) + } + + // MARK: - Helpers + + private func linkedBankFor( + paymentAccount: StripeAPI.FinancialConnectionsSession.PaymentAccount, + session: StripeAPI.FinancialConnectionsSession + ) -> FinancialConnectionsLinkedBank? { + switch paymentAccount { + case .linkedAccount(let linkedAccount): + return FinancialConnectionsLinkedBank( + sessionId: session.id, + accountId: linkedAccount.id, + displayName: linkedAccount.displayName, + bankName: linkedAccount.institutionName, + last4: linkedAccount.last4, + instantlyVerified: true + ) + case .bankAccount(let bankAccount): + return FinancialConnectionsLinkedBank( + sessionId: session.id, + accountId: bankAccount.id, + displayName: bankAccount.bankName, + bankName: bankAccount.bankName, + last4: bankAccount.last4, + instantlyVerified: bankAccount.instantlyVerified + ) + case .unparsable: + return nil + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSheet.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSheet.swift new file mode 100644 index 00000000..36a5913b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSheet.swift @@ -0,0 +1,296 @@ +// +// FinancialConnectionsSheet.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/10/21. +// + +@_spi(STP) import StripeCore +import UIKit + +/** + A drop-in class that presents a sheet for a user to connect their financial accounts. + */ +final public class FinancialConnectionsSheet { + + // MARK: - Types + + /// The result of financial account connection flow + @frozen public enum Result { + /// User completed the financialConnections session + case completed(session: StripeAPI.FinancialConnectionsSession) + /// Failed with error + case failed(error: Error) + /// User canceled out of the financialConnections session + case canceled + } + + @frozen public enum TokenResult { + // User completed the financialConnections session + case completed( + result: ( + session: StripeAPI.FinancialConnectionsSession, + token: StripeAPI.BankAccountToken? + ) + ) + // Failed with error + case failed(error: Error) + // User canceled out of the financialConnections session + case canceled + } + + // MARK: - Properties + + /** + The client secret of the Stripe FinancialConnectionsSession object. + See https://stripe.com/docs/api/financial_connections/sessions/object#financial_connections_session_object-client_secret + */ + public let financialConnectionsSessionClientSecret: String + + /// A URL that redirects back to your app that FinancialConnectionsSheet can use + /// get back to your app after completing authentication in another app (such as bank app or Safari). + public let returnURL: String? + + /// The `onEvent` closure is triggered upon the occurrence of specific events + /// during the process of a user connecting their financial accounts. + /// + /// Refer to `FinancialConnectionsEvent.Name` for a list of possible event types. + /// + /// Every `FinancialConnectionsEvent` can carry additional metadata, + /// the content of which can vary based on the specific type of occurring event. + public var onEvent: ((FinancialConnectionsEvent) -> Void)? + + /// The APIClient instance used to make requests to Stripe + public var apiClient: STPAPIClient = STPAPIClient.shared { + didSet { + APIVersion.configureFinancialConnectionsAPIVersion(apiClient: apiClient) + } + } + + /// Completion block called when the sheet is closed or fails to open + private var completion: ((HostControllerResult) -> Void)? + + private var hostController: HostController? + + private var wrapperViewController: ModalPresentationWrapperViewController? + + // Any additional Elements context useful for the Financial Connections SDK. + @_spi(STP) public var elementsSessionContext: StripeCore.ElementsSessionContext? + + // Analytics client to use for logging analytics + @_spi(STP) public let analyticsClient: STPAnalyticsClientProtocol + + // MARK: - Init + + /** + Initializes a `FinancialConnectionsSheet`. + + - Parameters: + - financialConnectionsSessionClientSecret: The [client secret](https://stripe.com/docs/api/financial_connections/sessions/object#financial_connections_session_object-client_secret) of a Stripe FinancialConnectionsSession object. + - returnURL: A URL that redirects back to your application. FinancialConnectionsSheet uses it after completing authentication in another application (such as a bank application or Safari). + */ + public convenience init(financialConnectionsSessionClientSecret: String, returnURL: String? = nil) { + self.init( + financialConnectionsSessionClientSecret: financialConnectionsSessionClientSecret, + returnURL: returnURL, + analyticsClient: STPAnalyticsClient.sharedClient + ) + } + + init( + financialConnectionsSessionClientSecret: String, + returnURL: String?, + analyticsClient: STPAnalyticsClientProtocol + ) { + self.financialConnectionsSessionClientSecret = financialConnectionsSessionClientSecret + self.returnURL = returnURL + self.analyticsClient = analyticsClient + + analyticsClient.addClass(toProductUsageIfNecessary: FinancialConnectionsSheet.self) + APIVersion.configureFinancialConnectionsAPIVersion(apiClient: apiClient) + } + + // MARK: - Public + + public func presentForToken( + from presentingViewController: UIViewController, + completion: @escaping (TokenResult) -> Void + ) { + present(from: presentingViewController) { result in + switch result { + case .completed(let session): + completion(.completed(result: (session: session, token: session.bankAccountToken))) + case .failed(let error): + completion(.failed(error: error)) + case .canceled: + completion(.canceled) + } + } + } + + /** + Presents a sheet for a customer to connect their financial account. + - Parameters: + - presentingViewController: The view controller to present the financial connections sheet. + - completion: Called with the result of the financial connections session after the financial connections sheet is dismissed. + */ + public func present( + from presentingViewController: UIViewController, + completion: @escaping (Result) -> Void + ) { + present( + from: presentingViewController, + completion: { hostControllerResult in + switch hostControllerResult { + case .completed(let completedResult): + switch completedResult { + case .financialConnections(let session): + completion(.completed(session: session)) + case .instantDebits(let linkedBank): + // TODO(mats): Add support for instant debits. + let errorDescription = "Instant Debits is not currently supported via this interface." + let sessionInfo = + """ + paymentMethodId=\(linkedBank.paymentMethod.id) + bankName=\(linkedBank.bankName ?? "N/A") + last4=\(linkedBank.last4 ?? "N/A") + """ + + completion( + .failed( + error: FinancialConnectionsSheetError + .unknown(debugDescription: "\(errorDescription)\n\n\(sessionInfo)") + ) + ) + } + case .canceled: + completion(.canceled) + case .failed(let error): + completion(.failed(error: error)) + } + } + ) + } + + @_spi(STP) public func present( + from presentingViewController: UIViewController, + completion: @escaping (HostControllerResult) -> Void + ) { + // Overwrite completion closure to retain self until called + let completion: (HostControllerResult) -> Void = { result in + self.analyticsClient.log( + analytic: FinancialConnectionsSheetCompletionAnalytic.make( + clientSecret: self.financialConnectionsSessionClientSecret, + result: result + ), + apiClient: self.apiClient + ) + completion(result) + self.completion = nil + } + self.completion = completion + + // Guard against basic user error + guard presentingViewController.presentedViewController == nil else { + assertionFailure("presentingViewController is already presenting a view controller") + let error = FinancialConnectionsSheetError.unknown( + debugDescription: "presentingViewController is already presenting a view controller" + ) + completion(.failed(error: error)) + return + } + + if let urlString = returnURL { + guard URL(string: urlString) != nil else { + assertionFailure( + "invalid returnURL: \(urlString) parameter passed in when creating FinancialConnectionsSheet" + ) + let error = FinancialConnectionsSheetError.unknown( + debugDescription: + "invalid returnURL: \(urlString) parameter passed in when creating FinancialConnectionsSheet" + ) + completion(.failed(error: error)) + return + } + } + + let financialConnectionsApiClient = FinancialConnectionsAPIClient(apiClient: apiClient) + hostController = HostController( + apiClient: financialConnectionsApiClient, + analyticsClientV1: analyticsClient, + clientSecret: financialConnectionsSessionClientSecret, + elementsSessionContext: elementsSessionContext, + returnURL: returnURL, + publishableKey: apiClient.publishableKey, + stripeAccount: apiClient.stripeAccount + ) + hostController?.delegate = self + + analyticsClient.log( + analytic: FinancialConnectionsSheetPresentedAnalytic( + clientSecret: self.financialConnectionsSessionClientSecret + ), + apiClient: apiClient + ) + let navigationController = hostController!.navigationController + present(navigationController, presentingViewController) + } + + private func present( + _ navigationController: FinancialConnectionsNavigationController, + _ presentingViewController: UIViewController + ) { + let toPresent: UIViewController + let animated: Bool + if UIDevice.current.userInterfaceIdiom == .pad { + navigationController.modalPresentationStyle = .formSheet + toPresent = navigationController + animated = true + } else { + wrapperViewController = ModalPresentationWrapperViewController(vc: navigationController) + toPresent = wrapperViewController! + animated = false + } + presentingViewController.present(toPresent, animated: animated, completion: nil) + } +} + +// MARK: - HostControllerDelegate + +/// :nodoc: +extension FinancialConnectionsSheet: HostControllerDelegate { + func hostController( + _ hostController: HostController, + viewController: UIViewController, + didFinish result: HostControllerResult + ) { + viewController.dismiss( + animated: true, + completion: { + if let wrapperViewController = self.wrapperViewController { + wrapperViewController.dismiss( + animated: false, + completion: { + self.completion?(result) + } + ) + self.wrapperViewController = nil + } else { + self.completion?(result) + } + } + ) + } + + func hostController(_ hostController: HostController, didReceiveEvent event: FinancialConnectionsEvent) { + onEvent?(event) + } +} + +// MARK: - STPAnalyticsProtocol + +/// :nodoc: +@_spi(STP) +extension FinancialConnectionsSheet: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier = "FinancialConnectionsSheet" +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSheetError.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSheetError.swift new file mode 100644 index 00000000..ffe1f54b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/FinancialConnectionsSheetError.swift @@ -0,0 +1,43 @@ +// +// FinancialConnectionsSheetError.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/17/21. +// + +import Foundation +@_spi(STP) import StripeCore + +/** + Errors specific to the `FinancialConnectionsSheet`. + */ +public enum FinancialConnectionsSheetError: Error, LocalizedError { + /// An unknown error. + case unknown(debugDescription: String) + + /// Localized description of the error + public var localizedDescription: String { + return NSError.stp_unexpectedErrorMessage() + } +} + +/// :nodoc: +@_spi(STP) extension FinancialConnectionsSheetError: AnalyticLoggableErrorV2 { + + /// The error code + public var errorCode: Int { + switch self { + case .unknown: + return 0 + } + } + + /// Serializes this error + /// - Returns: an error with a domain and code + public func analyticLoggableSerializeForLogging() -> [String: Any] { + return [ + "domain": "Stripe.\(FinancialConnectionsSheetError.self)", + "code": errorCode, + ] + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/FinancialConnectionsEvent+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/FinancialConnectionsEvent+Extensions.swift new file mode 100644 index 00000000..6696a294 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/FinancialConnectionsEvent+Extensions.swift @@ -0,0 +1,51 @@ +// +// FinancialConnectionsEvent+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/10/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension FinancialConnectionsEvent { + + static func events(fromError error: Error) -> [FinancialConnectionsEvent] { + var errorCodes: [FinancialConnectionsEvent.ErrorCode] = [] + if + let error = error as? StripeError, + case .apiError(let apiError) = error, + let extraFields = apiError.allResponseFields["extra_fields"] as? [String: Any], + let eventsToEmitString = extraFields["events_to_emit"] as? String, + let eventsToEmitData = eventsToEmitString.data(using: .utf8), + let eventsToEmitArray = try? JSONSerialization.jsonObject( + with: eventsToEmitData, + options: [] + ) as? [[String: Any]], + let errorEventsToEmit = [[String: Any]]?(eventsToEmitArray.filter({ ($0["type"] as? String) == "error" })), + !errorEventsToEmit.isEmpty + { + errorEventsToEmit.forEach { eventToEmit in + if + let errorDictionary = eventToEmit["error"] as? [String: Any], + let errorCodeString = errorDictionary["error_code"] as? String, + let errorCode = FinancialConnectionsEvent.ErrorCode(rawValue: errorCodeString) + { + errorCodes.append(errorCode) + } else { + errorCodes.append(.unexpectedError) + } + } + } else { + errorCodes.append(.unexpectedError) + } + return errorCodes.map { errorCode in + FinancialConnectionsEvent( + name: .error, + metadata: FinancialConnectionsEvent.Metadata( + errorCode: errorCode + ) + ) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/FinancialConnectionsFont.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/FinancialConnectionsFont.swift new file mode 100644 index 00000000..eca3862d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/FinancialConnectionsFont.swift @@ -0,0 +1,181 @@ +// +// FinancialConnectionsFont.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 5/2/23. +// + +import Foundation +import UIKit + +// A wrapper around `UIFont` that allows us to specify a `lineHeight`. +// `UIFont` does not support modifying `lineHeight` so this struct +// helps us to easily pass around font + line height. +struct FinancialConnectionsFont { + + let uiFont: UIFont + let lineHeight: CGFloat + + // An estimated "top padding of the font character" + var topPadding: CGFloat { + return max(0, ((lineHeight - uiFont.lineHeight) / 2)) + (uiFont.ascender - uiFont.capHeight) + } + + enum HeadingToken { + /// 20 size / 28 line height / 700 weight + case medium + /// 24 size / 32 line height / 700 weight + case large + /// 28 size / 36 line height / 700 weight + case extraLarge + } + static func heading(_ token: HeadingToken) -> FinancialConnectionsFont { + let font: UIFont + let lineHeight: CGFloat + let appleTextStyle: UIFont.TextStyle + switch token { + case .medium: + font = UIFont.systemFont(ofSize: 20, weight: .bold) + lineHeight = 28 + appleTextStyle = .title3 + case .large: + font = UIFont.systemFont(ofSize: 24, weight: .bold) + lineHeight = 32 + appleTextStyle = .title2 + case .extraLarge: + font = UIFont.systemFont(ofSize: 28, weight: .bold) + lineHeight = 36 + appleTextStyle = .title1 + } + return .create(font: font, lineHeight: lineHeight, appleTextStyle: appleTextStyle) + } + + enum BodyToken { + /// 12 size / 16 line height / 400 weight + case extraSmall + /// 12 size / 16 line height / 600 weight + case extraSmallEmphasized + /// 14 size / 20 line height / 400 weight + case small + /// 14 size / 20 line height / 600 weight + case smallEmphasized + /// 16 size / 24 line height / 400 weight + case medium + /// 16 size / 24 line height / 600 weight + case mediumEmphasized + } + static func body(_ token: BodyToken) -> FinancialConnectionsFont { + let font: UIFont + let lineHeight: CGFloat + let appleTextStyle: UIFont.TextStyle + switch token { + case .extraSmall: + font = UIFont.systemFont(ofSize: 12, weight: .regular) + lineHeight = 16 + appleTextStyle = .caption1 + case .extraSmallEmphasized: + font = UIFont.systemFont(ofSize: 12, weight: .bold) + lineHeight = 16 + appleTextStyle = .caption1 + case .small: + font = UIFont.systemFont(ofSize: 14, weight: .regular) + lineHeight = 20 + appleTextStyle = .footnote + case .smallEmphasized: + font = UIFont.systemFont(ofSize: 14, weight: .semibold) + lineHeight = 20 + appleTextStyle = .footnote + case .medium: + font = UIFont.systemFont(ofSize: 16, weight: .regular) + lineHeight = 24 + appleTextStyle = .callout + case .mediumEmphasized: + font = UIFont.systemFont(ofSize: 16, weight: .semibold) + lineHeight = 24 + appleTextStyle = .callout + } + return .create(font: font, lineHeight: lineHeight, appleTextStyle: appleTextStyle) + } + + enum LabelToken { + /// 12 size / 16 line height / 400 weight + case small + /// 12 size / 16 line height / 600 weight + case smallEmphasized + /// 14 size / 20 line height / 400 weight + case medium + /// 14 size / 20 line height / 600 weight + case mediumEmphasized + /// 16 size / 24 line height / 400 weight + case large + /// 16 size / 24 line height / 600 weight + case largeEmphasized + } + static func label(_ token: LabelToken) -> FinancialConnectionsFont { + let font: UIFont + let lineHeight: CGFloat + let appleTextStyle: UIFont.TextStyle + switch token { + case .small: + font = UIFont.systemFont(ofSize: 12, weight: .regular) + lineHeight = 16 + appleTextStyle = .caption1 + case .smallEmphasized: + font = UIFont.systemFont(ofSize: 12, weight: .semibold) + lineHeight = 16 + appleTextStyle = .caption1 + case .medium: + font = UIFont.systemFont(ofSize: 14, weight: .regular) + lineHeight = 20 + appleTextStyle = .footnote + case .mediumEmphasized: + font = UIFont.systemFont(ofSize: 14, weight: .semibold) + lineHeight = 20 + appleTextStyle = .footnote + case .large: + font = UIFont.systemFont(ofSize: 16, weight: .regular) + lineHeight = 24 + appleTextStyle = .callout + case .largeEmphasized: + font = UIFont.systemFont(ofSize: 16, weight: .semibold) + lineHeight = 24 + appleTextStyle = .callout + } + return .create(font: font, lineHeight: lineHeight, appleTextStyle: appleTextStyle) + } + + enum CodeToken { + /// 16 size / 24 line height / 600 weight + case largeEmphasized + } + static func code(_ token: CodeToken) -> FinancialConnectionsFont { + let font: UIFont + let lineHeight: CGFloat + let appleTextStyle: UIFont.TextStyle + switch token { + case .largeEmphasized: + font = UIFont.monospacedSystemFont(ofSize: 16, weight: .semibold) + lineHeight = 24 + appleTextStyle = .body + } + return .create(font: font, lineHeight: lineHeight, appleTextStyle: appleTextStyle) + } + + private static func create(font: UIFont, lineHeight: CGFloat, appleTextStyle: UIFont.TextStyle) -> FinancialConnectionsFont { + let scaledFont = scaleFont(font, appleTextStyle: appleTextStyle) + return FinancialConnectionsFont( + uiFont: scaledFont, + lineHeight: scaleLineHeight(lineHeight, font: font, scaledFont: scaledFont) + ) + } + + private static func scaleFont(_ font: UIFont, appleTextStyle: UIFont.TextStyle) -> UIFont { + let metrics = UIFontMetrics(forTextStyle: appleTextStyle) + let scaledFont = metrics.scaledFont(for: font) + return scaledFont + } + + private static func scaleLineHeight(_ lineHeight: CGFloat, font: UIFont, scaledFont: UIFont) -> CGFloat { + return lineHeight * (scaledFont.pointSize / max(1, font.pointSize)) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Helpers.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Helpers.swift new file mode 100644 index 00000000..28597118 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Helpers.swift @@ -0,0 +1,15 @@ +// +// Foundation.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +func assertMainQueue() { + #if DEBUG + dispatchPrecondition(condition: .onQueue(DispatchQueue.main)) + #endif +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Image.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Image.swift new file mode 100644 index 00000000..6d0c3c46 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/Image.swift @@ -0,0 +1,34 @@ +// +// Image.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/22/21. +// + +import Foundation +@_spi(STP) import StripeUICore + +/// The canonical set of all image files in the `StripeFinancialConnections` module. +/// This helps us avoid duplicates and automatically test that all images load properly +enum Image: String, ImageMaker { + typealias BundleLocator = StripeFinancialConnectionsBundleLocator + + case add + case back_arrow + case brandicon_default + case cancel_circle + case check + case chevron_down + case close + case generic_error + case info + case link_logo + case panel_arrow_right + case person + case search + case stripe_logo + case spinner + case testmode + case warning_triangle + case bullet +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/NSAttributedString+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/NSAttributedString+Extensions.swift new file mode 100644 index 00000000..c3eed32e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/NSAttributedString+Extensions.swift @@ -0,0 +1,78 @@ +// +// NSAttributedString+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/22/22. +// + +import Foundation +import UIKit + +// MARK: - Markdown Bold + +extension NSMutableAttributedString { + + /// Adds `boldFont` as an attribute in all the places that are surrounded by asterisks (ex. `**bold string here**). + /// + /// For example, `Click **here**` returns `Click here` with "here" being applied the `boldFont` as attribute. + func addBoldFontAttributesByMarkdownRules(boldFont: UIFont) { + guard + // The regex will find all occurrances of tokens formatted as: `**bold string here**` + let regularExpression = try? NSRegularExpression( + pattern: #"\*\*[^\*\n]+\*\*"#, + options: NSRegularExpression.Options(rawValue: 0) + ) + else { + return + } + + while let textCheckingResult = regularExpression.firstMatch( + in: string, + range: NSRange(location: 0, length: string.count) + ) { + // range where `**bold string here**` token is + let markdownBoldRange = textCheckingResult.range + // the string `**bold string here**` + let markdownBoldString = attributedSubstring(from: markdownBoldRange) + + // the string `bold string here` + let nonmarkdownBoldString = markdownBoldString.extractStringInAsterisks() + + if let nonmarkdownBoldString = nonmarkdownBoldString?.mutableCopy() as? NSMutableAttributedString { + // apply a "bold font attribute to the string `bold string here` + nonmarkdownBoldString + .addAttribute( + .font, + value: boldFont, + range: NSRange(location: 0, length: nonmarkdownBoldString.length) + ) + + replaceCharacters(in: markdownBoldRange, with: nonmarkdownBoldString) + } + } + } +} + +extension NSAttributedString { + + /// Extracts a substring out of the first set of asterisks. + /// + /// For example, `Bold Text` out of `**Bold Text**`. + fileprivate func extractStringInAsterisks() -> NSAttributedString? { + guard + let regularExpression = try? NSRegularExpression( + pattern: #"(?<=\*\*)[^\*\n]*(?=\*\*)"#, + options: NSRegularExpression.Options(rawValue: 0) + ) + else { + return nil + } + guard + let range = regularExpression.firstMatch(in: string, range: NSRange(location: 0, length: string.count))? + .range + else { + return nil + } + return attributedSubstring(from: range) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/PaymentAccount+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/PaymentAccount+Extensions.swift new file mode 100644 index 00000000..7deec919 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/PaymentAccount+Extensions.swift @@ -0,0 +1,20 @@ +// +// PaymentAccount+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/17/23. +// + +import Foundation + +extension StripeAPI.FinancialConnectionsSession.PaymentAccount { + + var isManualEntry: Bool { + switch self { + case .bankAccount: + return true + default: + return false + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/STPLocalizedString.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/STPLocalizedString.swift new file mode 100644 index 00000000..cb6a7f7d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/STPLocalizedString.swift @@ -0,0 +1,16 @@ +// +// STPLocalizedString.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/11/21. +// + +import Foundation +@_spi(STP) import StripeCore + +@inline(__always) func STPLocalizedString(_ key: String, _ comment: String?) -> String { + return STPLocalizationUtils.localizedStripeString( + forKey: key, + bundleLocator: StripeFinancialConnectionsBundleLocator.self + ) +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/ScreenNativeScale.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/ScreenNativeScale.swift new file mode 100644 index 00000000..1da36024 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/ScreenNativeScale.swift @@ -0,0 +1,16 @@ +// +// ScreenNativeScale.swift +// StripeFinancialConnections +// +// Created by David Estes on 11/20/23. +// + +import UIKit + +var stp_screenNativeScale: CGFloat { + #if canImport(CompositorServices) + return 1.0 + #else + return UIScreen.main.nativeScale + #endif +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/String+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/String+Extensions.swift new file mode 100644 index 00000000..5d6be461 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/String+Extensions.swift @@ -0,0 +1,133 @@ +// +// String+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/11/22. +// + +import Foundation + +// MARK: - Native Redirect Helpers + +private let nativeRedirectPrefix = "stripe-auth://native-redirect/" + +extension String { + + func dropPrefix(_ prefix: String) -> String { + guard self.hasPrefix(prefix) else { return self } + return String(self.dropFirst(prefix.count)) + } + + func droppingNativeRedirectPrefix() -> String { + return dropPrefix(nativeRedirectPrefix) + } + + var hasNativeRedirectPrefix: Bool { + return self.hasPrefix(nativeRedirectPrefix) + } +} + +// MARK: - Markdown Links + +extension String { + + struct Link: Equatable { + let range: NSRange + let urlString: String + } + + /// Extracts markdown links from a string. + /// + /// For example, `You can [visit](https://stripe.com/) the website` returns + /// "You can visit the website" with a `Link` of "https://stripe.com/". + func extractLinks() -> (linklessString: String, links: [Link]) { + + let originalString = self + guard + // Matches markdown links. For example, the regex will find all + // occurrances of tokens like: `[Stripe Link Here](https://stripe.com/)` + let regularExpression = try? NSRegularExpression( + pattern: #"\[[^\[]*]*\]\([^\)]*\)"#, + options: NSRegularExpression.Options(rawValue: 0) + ) + else { + return (originalString, []) + } + + var modifiedString = originalString + var links: [Link] = [] + while let textCheckingResult = regularExpression.firstMatch( + in: modifiedString, + range: NSRange(location: 0, length: modifiedString.count) + ) { + let markdownLinkRange = textCheckingResult.range + // Ex. [Terms](https://stripe.com/legal/end-users#linked-financial-account-terms) + let markdownLinkString = (modifiedString as NSString).substring(with: markdownLinkRange) + + var replacementString = "" + if + let linkSubstring = markdownLinkString.extractStringInBrackets(), + let urlString = markdownLinkString.extractStringInParentheses() + { + // `nonBreakingSpace` ensures links are always on the same line + let nonBreakingSpace = "\u{00a0}" + let linkSubstring = linkSubstring.replacingOccurrences( + of: " ", + with: nonBreakingSpace + ) + replacementString = linkSubstring + let linkRange = NSRange( + location: markdownLinkRange.location, + length: linkSubstring.count + ) + let link = Link(range: linkRange, urlString: urlString) + links.append(link) + } + + modifiedString = (modifiedString as NSString).replacingCharacters( + in: markdownLinkRange, + with: replacementString + ) + } + + return (modifiedString, links) + } + + /// Extracts a substring out of the first bracket. + /// + /// For example, `Terms` out of `[Terms]`. + private func extractStringInBrackets() -> String? { + guard + let regularExpression = try? NSRegularExpression( + pattern: #"(?<=\[)[^\[\n]*(?=\])"#, + options: NSRegularExpression.Options(rawValue: 0) + ) + else { + return nil + } + guard let range = regularExpression.firstMatch(in: self, range: NSRange(location: 0, length: count))?.range + else { + return nil + } + return (self as NSString).substring(with: range) + } + + /// Extracts a substring out of the first parantheses. + /// + /// For example, `https://stripe.com/` out of `(https://stripe.com/)`. + private func extractStringInParentheses() -> String? { + guard + let regularExpression = try? NSRegularExpression( + pattern: #"(?<=\()[^\)\(\n]*(?=\))"#, + options: NSRegularExpression.Options(rawValue: 0) + ) + else { + return nil + } + guard let range = regularExpression.firstMatch(in: self, range: NSRange(location: 0, length: count))?.range + else { + return nil + } + return (self as NSString).substring(with: range) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/String+Localized.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/String+Localized.swift new file mode 100644 index 00000000..91809544 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/String+Localized.swift @@ -0,0 +1,35 @@ +// +// String+Localized.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/22/22. +// + +import Foundation +@_spi(STP) import StripeCore + +// Localized strings that are used in multiple contexts. Collected here to avoid re-translation +// We use snake case to make long names easier to read. +extension String.Localized { + + static var learn_more: String { + return STPLocalizedString( + "Learn more", + "Represents the text of a button that can be clicked to learn more about some topic. Once clicked, a web-browser will be opened to give users more info." + ) + } + + static var select_another_bank: String { + return STPLocalizedString( + "Select another bank", + "The title of a button. The button presents the user an option to select another bank. For example, we may show this button after user failed to link their primary bank, but maybe the user can try to link their secondary bank!" + ) + } + + static var enter_bank_details_manually: String { + return STPLocalizedString( + "Enter bank details manually", + "The title of a button. The button presents the user an option to enter their bank details (account number, routing number) manually. For example, we may show this button after user failed to link their bank 'automatically' with Stripe, so we offer them the option manually link it." + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/StripeFinancialConnectionsBundleLocator.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/StripeFinancialConnectionsBundleLocator.swift new file mode 100644 index 00000000..63eecdb3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/StripeFinancialConnectionsBundleLocator.swift @@ -0,0 +1,18 @@ +// +// StripeFinancialConnectionsBundleLocator.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/11/21. +// + +import Foundation +@_spi(STP) import StripeCore + +final class StripeFinancialConnectionsBundleLocator: BundleLocatorProtocol { + static let internalClass: AnyClass = StripeFinancialConnectionsBundleLocator.self + static let bundleName = "StripeFinancialConnectionsBundle" + #if SWIFT_PACKAGE + static let spmResourcesBundle = Bundle.module + #endif + static let resourcesBundle = StripeFinancialConnectionsBundleLocator.computeResourcesBundle() +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/UIColor+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/UIColor+Extensions.swift new file mode 100644 index 00000000..a8cbd2fc --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/UIColor+Extensions.swift @@ -0,0 +1,194 @@ +// +// Extensions.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/26/21. +// + +import UIKit + +extension UIColor { + + // The background color we use across across many screens. + // Added for future support around dark mode. + static var customBackgroundColor: UIColor { + return .white + } + + static var textDefault: UIColor { + return neutral800 + } + + static var textSubdued: UIColor { + return neutral600 + } + + static var textActionPrimary: UIColor { + return brand600 + } + + static var textActionPrimaryFocused: UIColor { + return brand600 + } + + static var textBrand: UIColor { + return brand500 + } + + static var textDisabled: UIColor { + return neutral300 + } + + static var textCritical: UIColor { + return critical500 + } + + static var textFeedbackCritical: UIColor { + return feedbackCritical600 + } + + static var textSuccess: UIColor { + return success500 + } + + static var iconDefault: UIColor { + return neutral700 + } + + static var iconActionPrimary: UIColor { + return brand500 + } + + static var borderNeutral: UIColor { + return neutral150 + } + + static var borderDefault: UIColor { + return neutral100 + } + + static var borderCritical: UIColor { + return critical500 + } + + static var backgroundContainer: UIColor { + return neutral50 + } + + static var backgroundOffset: UIColor { + return neutral25 + } + + static var attention50: UIColor { + return UIColor(red: 254 / 255.0, green: 249 / 255.0, blue: 218 / 255.0, alpha: 1) // #fef9da + } + + static var attention300: UIColor { + return UIColor(red: 247 / 255.0, green: 135 / 255.0, blue: 15 / 255.0, alpha: 1) // #f7870f + } + + static var neutral0: UIColor { + return .white + } + + static var neutral25: UIColor { + return UIColor(red: 245 / 255.0, green: 246 / 255.0, blue: 248 / 255.0, alpha: 1) // #f5f6f8 + } + + private static var neutral50: UIColor { + return UIColor(red: 246 / 255.0, green: 248 / 255.0, blue: 250 / 255.0, alpha: 1) // #f6f8fa + } + + private static var neutral100: UIColor { + return UIColor(red: 216 / 255.0, green: 222 / 255.0, blue: 228 / 255.0, alpha: 1) // #d8dee4 + } + + private static var neutral150: UIColor { + return UIColor(red: 224 / 255.0, green: 230 / 255.0, blue: 235 / 255.0, alpha: 1) // #e0e6eb + } + + static var neutral200: UIColor { + return UIColor(red: 192 / 255.0, green: 200 / 255.0, blue: 210 / 255.0, alpha: 1) // #c0c8d2 + } + + private static var neutral300: UIColor { + return UIColor(red: 163 / 255.0, green: 172 / 255.0, blue: 186 / 255.0, alpha: 1) // #a3acba + } + + private static var neutral500: UIColor { + return UIColor(red: 106 / 255.0, green: 115 / 255.0, blue: 131 / 255.0, alpha: 1) // #6a7383 + } + + private static var neutral600: UIColor { + return UIColor(red: 89 / 255.0, green: 97 / 255.0, blue: 113 / 255.0, alpha: 1) // #596171 + } + + private static var neutral700: UIColor { + return UIColor(red: 71 / 255.0, green: 78 / 255.0, blue: 90 / 255.0, alpha: 1) // #474e5a + } + + private static var neutral800: UIColor { + return UIColor(red: 53 / 255.0, green: 58 / 255.0, blue: 68 / 255.0, alpha: 1) // #353a44 + } + + static var brand25: UIColor { + return UIColor(red: 247 / 255.0, green: 245 / 255.0, blue: 253 / 255.0, alpha: 1) // #f7f5fd + } + + static var brand100: UIColor { + return UIColor(red: 242 / 255.0, green: 235 / 255.0, blue: 255 / 255.0, alpha: 1) // #f2ebff + } + + static var brand500: UIColor { + return UIColor(red: 103 / 255.0, green: 93 / 255.0, blue: 255 / 255.0, alpha: 1) // #675dff + } + + static var brand600: UIColor { + return UIColor(red: 83 / 255.0, green: 58 / 255.0, blue: 253 / 255.0, alpha: 1) // #533afd + } + + private static var critical500: UIColor { + return UIColor(red: 223 / 255.0, green: 27 / 255.0, blue: 65 / 255.0, alpha: 1) // #df1b41 + } + + private static var feedbackCritical600: UIColor { + return UIColor(red: 192 / 255.0, green: 18 / 255.0, blue: 60 / 255.0, alpha: 1) // #c0123c + } + + static var success100: UIColor { + return UIColor(red: 215 / 255.0, green: 247 / 255.0, blue: 194 / 255.0, alpha: 1) // #d7f7c2 + } + + private static var success500: UIColor { + return UIColor(red: 34 / 255.0, green: 132 / 255.0, blue: 3 / 255.0, alpha: 1) // #228403 + } + + static var linkGreen50: UIColor { + return UIColor(red: 230 / 255.0, green: 255 / 255.0, blue: 237 / 255.0, alpha: 1) // #e6ffed + } + + static var linkGreen200: UIColor { + return UIColor(red: 0 / 255.0, green: 214 / 255.0, blue: 111 / 255.0, alpha: 1) // #00D66F + } + + static var linkGreen500: UIColor { + return UIColor(red: 0 / 255.0, green: 133 / 255.0, blue: 69 / 255.0, alpha: 1) // #008545 + } + + static var linkGreen900: UIColor { + return UIColor(red: 1 / 255.0, green: 30 / 255.0, blue: 15 / 255.0, alpha: 1) // #011E0F + } + + static func dynamic(light: UIColor, dark: UIColor) -> UIColor { + return UIColor(dynamicProvider: { + switch $0.userInterfaceStyle { + case .light, .unspecified: + return light + case .dark: + return dark + @unknown default: + return light + } + }) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/UIViewController+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/UIViewController+Extensions.swift new file mode 100644 index 00000000..327e058c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Helpers/UIViewController+Extensions.swift @@ -0,0 +1,47 @@ +// +// UIViewController+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/15/22. +// + +import Foundation +import UIKit + +extension UIViewController { + + static func topMostViewController() -> UIViewController? { + guard let window = UIApplication.shared.customKeyWindow else { + return nil + } + var topMostViewController = window.rootViewController + while let presentedViewController = topMostViewController?.presentedViewController { + topMostViewController = presentedViewController + } + return topMostViewController + } +} + +extension UIApplication { + + fileprivate var customKeyWindow: UIWindow? { + let foregroundActiveWindow = + connectedScenes + .filter { $0.activationState == .foregroundActive } + .first(where: { $0 is UIWindowScene }) + .flatMap({ ($0 as? UIWindowScene) })?.windows + .first(where: \.isKeyWindow) + + if let foregroundActiveWindow = foregroundActiveWindow { + return foregroundActiveWindow + } + + // There are scenarios (ex. presenting from a notification) when + // no scenes are `foregroundActive` so here we ignore the parameter + return + connectedScenes + .first(where: { $0 is UIWindowScene }) + .flatMap({ ($0 as? UIWindowScene) })?.windows + .first(where: \.isKeyWindow) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerAccountLoadErrorView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerAccountLoadErrorView.swift new file mode 100644 index 00000000..c159fcbf --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerAccountLoadErrorView.swift @@ -0,0 +1,157 @@ +// +// AccountPickerAccountLoadFailureView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/23/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class AccountPickerAccountLoadErrorView: UIView { + + init( + institution: FinancialConnectionsInstitution, + theme: FinancialConnectionsTheme, + didSelectAnotherBank: @escaping () -> Void, + didSelectTryAgain: (() -> Void)?, // if nil, don't show button + didSelectEnterBankDetailsManually: (() -> Void)? // if nil, don't show button + ) { + super.init(frame: .zero) + + let subtitle: String + let primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration + let secondaryButtonConfiguration: PaneLayoutView.ButtonConfiguration? + + if let didSelectTryAgain = didSelectTryAgain { + subtitle = STPLocalizedString( + "Please select another bank or try again.", + "The subtitle/description of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we instruct the user to select another bank or to try loading their bank accounts again." + ) + primaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: String.Localized.select_another_bank, + action: didSelectAnotherBank + ) + secondaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: "Try again", // TODO: once we localize, pull in the string from StripeCore `String.Localized.tryAgain` + action: didSelectTryAgain + ) + + } else if let didSelectEnterBankDetailsManually = didSelectEnterBankDetailsManually { + subtitle = STPLocalizedString( + "Please enter your bank details manually or select another bank.", + "The subtitle/description of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we instruct the user to enter their bank details manually or to try selecting another bank." + ) + primaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: String.Localized.select_another_bank, + action: didSelectAnotherBank + ) + secondaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: String.Localized.enter_bank_details_manually, + action: didSelectEnterBankDetailsManually + ) + } else { + subtitle = STPLocalizedString( + "Please select another bank.", + "The subtitle/description of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we instruct the user to try selecting another bank." + ) + primaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: String.Localized.select_another_bank, + action: didSelectAnotherBank + ) + secondaryButtonConfiguration = nil + } + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institution.icon?.default) + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: institutionIconView, + title: String( + format: STPLocalizedString( + "There was a problem accessing your %@ account", + "The title of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we describe to the user that we had issues with the bank. '%@' gets replaced by the name of the bank." + ), + institution.name + ), + subtitle: subtitle, + contentView: nil + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: primaryButtonConfiguration, + secondaryButtonConfiguration: secondaryButtonConfiguration, + theme: theme + ).footerView + ) + paneLayoutView.addTo(view: self) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG + +import SwiftUI + +private struct AccountPickerAccountLoadErrorViewUIViewRepresentable: UIViewRepresentable { + + let institutionName: String + let theme: FinancialConnectionsTheme + let didSelectTryAgain: (() -> Void)? + let didSelectEnterBankDetailsManually: (() -> Void)? + + func makeUIView(context: Context) -> AccountPickerAccountLoadErrorView { + AccountPickerAccountLoadErrorView( + institution: FinancialConnectionsInstitution( + id: "123", + name: institutionName, + url: nil, + icon: nil, + logo: nil + ), + theme: theme, + didSelectAnotherBank: {}, + didSelectTryAgain: didSelectTryAgain, + didSelectEnterBankDetailsManually: didSelectEnterBankDetailsManually + ) + } + + func updateUIView(_ uiView: AccountPickerAccountLoadErrorView, context: Context) {} +} + +struct AccountPickerAccountLoadErrorView_Previews: PreviewProvider { + static var previews: some View { + AccountPickerAccountLoadErrorViewUIViewRepresentable( + institutionName: "Chase", + theme: .light, + didSelectTryAgain: {}, + didSelectEnterBankDetailsManually: {} + ) + + AccountPickerAccountLoadErrorViewUIViewRepresentable( + institutionName: "Ally", + theme: .light, + didSelectTryAgain: nil, + didSelectEnterBankDetailsManually: {} + ) + + AccountPickerAccountLoadErrorViewUIViewRepresentable( + institutionName: "Chase", + theme: .light, + didSelectTryAgain: {}, + didSelectEnterBankDetailsManually: nil + ) + + AccountPickerAccountLoadErrorViewUIViewRepresentable( + institutionName: "Chase", + theme: .light, + didSelectTryAgain: nil, + didSelectEnterBankDetailsManually: nil + ) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerDataSource.swift new file mode 100644 index 00000000..1bf333ba --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerDataSource.swift @@ -0,0 +1,151 @@ +// +// AccountPickerDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/5/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol AccountPickerDataSourceDelegate: AnyObject { + func accountPickerDataSource( + _ dataSource: AccountPickerDataSource, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) +} + +protocol AccountPickerDataSource: AnyObject { + + var delegate: AccountPickerDataSourceDelegate? { get set } + var manifest: FinancialConnectionsSessionManifest { get } + var accountPickerPane: FinancialConnectionsAccountPickerPane? { get } + var authSession: FinancialConnectionsAuthSession { get } + var institution: FinancialConnectionsInstitution { get } + var selectedAccounts: [FinancialConnectionsPartnerAccount] { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var reduceManualEntryProminenceInErrors: Bool { get } + var dataAccessNotice: FinancialConnectionsDataAccessNotice? { get } + var consumerSessionClientSecret: String? { get } + + func pollAuthSessionAccounts() -> Future + func updateSelectedAccounts(_ selectedAccounts: [FinancialConnectionsPartnerAccount]) + func selectAuthSessionAccounts() -> Promise + func saveToLink( + accounts: [FinancialConnectionsPartnerAccount], + consumerSessionClientSecret: String + ) -> Future +} + +final class AccountPickerDataSourceImplementation: AccountPickerDataSource { + + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let accountPickerPane: FinancialConnectionsAccountPickerPane? + let authSession: FinancialConnectionsAuthSession + let manifest: FinancialConnectionsSessionManifest + let institution: FinancialConnectionsInstitution + let analyticsClient: FinancialConnectionsAnalyticsClient + let reduceManualEntryProminenceInErrors: Bool + let dataAccessNotice: FinancialConnectionsDataAccessNotice? + let consumerSessionClientSecret: String? + + private(set) var selectedAccounts: [FinancialConnectionsPartnerAccount] = [] { + didSet { + delegate?.accountPickerDataSource(self, didSelectAccounts: selectedAccounts) + } + } + weak var delegate: AccountPickerDataSourceDelegate? + + init( + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + accountPickerPane: FinancialConnectionsAccountPickerPane?, + authSession: FinancialConnectionsAuthSession, + manifest: FinancialConnectionsSessionManifest, + institution: FinancialConnectionsInstitution, + analyticsClient: FinancialConnectionsAnalyticsClient, + reduceManualEntryProminenceInErrors: Bool, + dataAccessNotice: FinancialConnectionsDataAccessNotice?, + consumerSessionClientSecret: String? + ) { + self.apiClient = apiClient + self.clientSecret = clientSecret + self.accountPickerPane = accountPickerPane + self.authSession = authSession + self.manifest = manifest + self.institution = institution + self.analyticsClient = analyticsClient + self.reduceManualEntryProminenceInErrors = reduceManualEntryProminenceInErrors + self.dataAccessNotice = dataAccessNotice + self.consumerSessionClientSecret = consumerSessionClientSecret + } + + func pollAuthSessionAccounts() -> Future { + return apiClient.fetchAuthSessionAccounts( + clientSecret: clientSecret, + authSessionId: authSession.id, + initialPollDelay: AuthSessionAccountsInitialPollDelay(forFlow: authSession.flow) + ) + } + + func updateSelectedAccounts(_ selectedAccounts: [FinancialConnectionsPartnerAccount]) { + self.selectedAccounts = selectedAccounts + } + + func selectAuthSessionAccounts() -> Promise { + return apiClient.selectAuthSessionAccounts( + clientSecret: clientSecret, + authSessionId: authSession.id, + selectedAccountIds: selectedAccounts.map({ $0.id }) + ) + } + + func saveToLink( + accounts: [FinancialConnectionsPartnerAccount], + consumerSessionClientSecret: String + ) -> Future { + let shouldPollAccounts = !manifest.shouldAttachLinkedPaymentMethod + assert( + shouldPollAccounts, + "expected to only save accounts to link for non-payment flows" + ) + return apiClient.saveAccountsToNetworkAndLink( + shouldPollAccounts: shouldPollAccounts, + selectedAccounts: accounts, + emailAddress: nil, + phoneNumber: nil, + country: nil, + consumerSessionClientSecret: consumerSessionClientSecret, + clientSecret: clientSecret + ) + .chained { (_, customSuccessPaneMessage) in + return Promise(value: customSuccessPaneMessage) + } + } +} + +private func AuthSessionAccountsInitialPollDelay( + forFlow flow: FinancialConnectionsAuthSession.Flow? +) -> TimeInterval { + let defaultInitialPollDelay: TimeInterval = 1.75 + guard let flow = flow else { + return defaultInitialPollDelay + } + switch flow { + case .testmode: + fallthrough + case .testmodeOauth: + fallthrough + case .testmodeOauthWebview: + fallthrough + case .finicityConnectV2Lite: + // Post auth flow, Finicity non-OAuth account retrieval latency is extremely quick - p90 < 1sec. + return 0 + case .mxConnect: + // 10 account retrieval latency on MX non-OAuth sessions is currently 460 ms + return 0.5 + default: + return defaultInitialPollDelay + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerFooterView.swift new file mode 100644 index 00000000..56bf6404 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerFooterView.swift @@ -0,0 +1,123 @@ +// +// AccountPickerFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/10/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class AccountPickerFooterView: UIView { + + private let singleAccount: Bool + private let theme: FinancialConnectionsTheme + private let didSelectLinkAccounts: () -> Void + + private lazy var linkAccountsButton: Button = { + let linkAccountsButton = Button.primary(theme: theme) + linkAccountsButton.addTarget(self, action: #selector(didSelectLinkAccountsButton), for: .touchUpInside) + linkAccountsButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + linkAccountsButton.heightAnchor.constraint(equalToConstant: 56) + ]) + linkAccountsButton.accessibilityIdentifier = "connect_accounts_button" + return linkAccountsButton + }() + + init( + dataAccessNotice: String?, + singleAccount: Bool, + theme: FinancialConnectionsTheme, + didSelectLinkAccounts: @escaping () -> Void, + didSelectMerchantDataAccessLearnMore: @escaping (URL) -> Void + ) { + self.singleAccount = singleAccount + self.theme = theme + self.didSelectLinkAccounts = didSelectLinkAccounts + super.init(frame: .zero) + + let verticalStackView = HitTestStackView() + if let dataAccessNotice { + verticalStackView.addArrangedSubview(CreateDataAccessLabel( + dataAccessNotice: dataAccessNotice, + didSelectLearnMore: didSelectMerchantDataAccessLearnMore + )) + } + verticalStackView.addArrangedSubview(linkAccountsButton) + + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 24, + bottom: 16, + trailing: 24 + ) + addSubview(verticalStackView) + addAndPinSubviewToSafeArea(verticalStackView) + + didSelectAccounts(count: 0) // set the button title + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didSelectLinkAccountsButton() { + didSelectLinkAccounts() + } + + func didSelectAccounts(count numberOfAccountsSelected: Int) { + linkAccountsButton.isEnabled = (numberOfAccountsSelected > 0) + + let singleAccountButtonTitle = STPLocalizedString( + "Connect account", + "A button that allows users to confirm the process of saving their bank account for future payments. This button appears in a screen that allows users to select which bank accounts they want to use to pay for something." + ) + let multipleAccountButtonTitle = STPLocalizedString( + "Connect accounts", + "A button that allows users to confirm the process of saving their bank accounts for future payments. This button appears in a screen that allows users to select which bank accounts they want to use to pay for something." + ) + + if numberOfAccountsSelected == 0 { + if singleAccount { + linkAccountsButton.title = singleAccountButtonTitle + } else { + linkAccountsButton.title = multipleAccountButtonTitle + } + } else if numberOfAccountsSelected == 1 { + linkAccountsButton.title = singleAccountButtonTitle + } else { // numberOfAccountsSelected > 1 + linkAccountsButton.title = multipleAccountButtonTitle + } + } + + func startLoading() { + linkAccountsButton.isLoading = true + } +} + +private func CreateDataAccessLabel( + dataAccessNotice: String, + didSelectLearnMore: @escaping (URL) -> Void +) -> HitTestView { + let label = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + label.setText( + dataAccessNotice, + action: { url in + didSelectLearnMore(url) + } + ) + let hitTestView = HitTestView() + hitTestView.addAndPinSubview(label) + return hitTestView +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerHelpers.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerHelpers.swift new file mode 100644 index 00000000..3f302a40 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerHelpers.swift @@ -0,0 +1,129 @@ +// +// AccountPickerHeleprs.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/8/22. +// + +import UIKit + +final class AccountPickerHelpers { + + static func rowInfo( + forAccount account: FinancialConnectionsPartnerAccount + ) -> ( + accountName: String, + accountNumbers: String?, + balanceString: String? + ) { + return ( + accountName: account.name, + accountNumbers: { + if let displayableAccountNumbers = account.displayableAccountNumbers { + return "••••\(displayableAccountNumbers)" + } else { + return nil + } + }(), + balanceString: { + if let balanceInfo = account.balanceInfo { + return currencyString(currency: balanceInfo.currency, balanceAmount: balanceInfo.balanceAmount) + } else { + return nil + } + }() + ) + } + + static func rowTitles( + forAccount account: FinancialConnectionsPartnerAccount, + // caption, for networked accounts, will hide account numbers, so we should show account numbers in the title + captionWillHideAccountNumbers: Bool + ) -> ( + leadingTitle: String, trailingTitle: String? + ) { + // balance info in subtitle will hide account numbers, so we should show account numbers in the title + let balanceWillHideAccountNumbersInSubtitle = account.balanceInfo != nil + if balanceWillHideAccountNumbersInSubtitle || captionWillHideAccountNumbers { + return (account.name, "••••\(account.displayableAccountNumbers ?? "")") + } else { + return (account.name, nil) + } + } + + static func rowSubtitle(forAccount account: FinancialConnectionsPartnerAccount) -> String? { + if let balanceInfo = account.balanceInfo { + return currencyString(currency: balanceInfo.currency, balanceAmount: balanceInfo.balanceAmount) + } else { + if let displayableAccountNumbers = account.displayableAccountNumbers { + return "••••••••\(displayableAccountNumbers)" + } else { + return nil + } + } + } + + // exposed for testing purposes + static func currencyString( + currency: String, + balanceAmount: Int, + locale: Locale = .current + ) -> String? { + let numberFormatter = NumberFormatter() + numberFormatter.currencyCode = currency + numberFormatter.numberStyle = .currency + numberFormatter.locale = locale + return numberFormatter.string( + for: NSDecimalNumber.stp_fn_decimalNumber(withAmount: balanceAmount, currency: currency) + ) + } +} + +// TODO(kgaidis): move this to StripeCore + +extension NSDecimalNumber { + @objc class func stp_fn_decimalNumber( + withAmount amount: Int, + currency: String? + ) -> NSDecimalNumber { + let isAmountNegative = amount < 0 + let amount = abs(amount) + + let noDecimalCurrencies = self.stp_fn_currenciesWithNoDecimal() + let number = self.init(mantissa: UInt64(amount), exponent: 0, isNegative: isAmountNegative) + if noDecimalCurrencies.contains(currency?.lowercased() ?? "") { + return number + } + return number.multiplying(byPowerOf10: -2) + } + + @objc func stp_fn_amount(withCurrency currency: String?) -> Int { + let noDecimalCurrencies = NSDecimalNumber.stp_fn_currenciesWithNoDecimal() + + var ourNumber = self + if !(noDecimalCurrencies.contains(currency?.lowercased() ?? "")) { + ourNumber = multiplying(byPowerOf10: 2) + } + return Int(ourNumber.doubleValue) + } + + class func stp_fn_currenciesWithNoDecimal() -> [String] { + return [ + "bif", + "clp", + "djf", + "gnf", + "jpy", + "kmf", + "krw", + "mga", + "pyg", + "rwf", + "vnd", + "vuv", + "xaf", + "xof", + "xpf", + ] + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerNoAccountEligibleErrorView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerNoAccountEligibleErrorView.swift new file mode 100644 index 00000000..e2bbdac4 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerNoAccountEligibleErrorView.swift @@ -0,0 +1,226 @@ +// +// AccountPickerNoAccountAvailableErrorView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/23/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +// Same as Stripe.js `AccountNoneEligibleForPaymentMethodFailure` +final class AccountPickerNoAccountEligibleErrorView: UIView { + + init( + institution: FinancialConnectionsInstitution, + bussinessName: String?, + institutionSkipAccountSelection: Bool, + numberOfIneligibleAccounts: Int, + paymentMethodType: FinancialConnectionsPaymentMethodType, + theme: FinancialConnectionsTheme, + didSelectAnotherBank: @escaping () -> Void + ) { + super.init(frame: .zero) + assert( + numberOfIneligibleAccounts >= 1, + "this error should never be displayed if 0 accounts were selected by the user" + ) + + // Financial Connections support credit cards, but not in all flows + // (ex. ACH only supports checking/savings). + let supportedAccountTypes: String = { + if paymentMethodType == .link { + return STPLocalizedString( + "US checking", + "A type of payment account. We will insert this string into other messages to explain users what payment accounts are eligible for payments. For example, we may display a message that says 'The accounts you selected aren't US checking accounts.'" + ) + } else { + return STPLocalizedString( + "checking or savings", + "A type of payment account. We will insert this string into other messages to explain users what payment accounts are eligible for payments. For example, we may display a message that says 'The accounts you selected aren't checking or savings accounts.'" + ) + } + }() + let subtitleFirstSentence: String = { + if let bussinessName = bussinessName { + if numberOfIneligibleAccounts == 1 { + let localizedString = STPLocalizedString( + "We found 1 %@ account but you can only link %@ accounts to %@.", + "A description/subtitle that instructs the user that the bank account they selected is not eligible. For example, maybe the user selected a credit card, but we only accept debit cards. The first '%@' is replaced by the name of the bank. The second '%@' is replaced by the supported payment accounts (ex. US checking). The third '%@' is replaced by the business name (Ex. Coca-Cola Inc). For example, it may read 'We found 1 Chase account but you can only link checking or savings to Coca-Cola Inc.'" + ) + return String(format: localizedString, institution.name, supportedAccountTypes, bussinessName) + } else { + let localizedString = STPLocalizedString( + "We found %d %@ accounts but you can only link %@ accounts to %@.", + "A description/subtitle that instructs the user that the bank accounts they selected are not eligible. For example, maybe the user selected credit cards, but we only accept debit cards. The '%d' is replaced by the number of ineligible accounts. The first '%@' is replaced by the name of the bank. The second '%@' is replaced by the supported payment accounts (ex. US checking). The third '%@' is replaced by the business name (Ex. Coca-Cola Inc). For example, it may read 'We found 2 Chase accounts but you can only link checking or savings to Coca-Cola Inc.'" + ) + return String( + format: localizedString, + numberOfIneligibleAccounts, + institution.name, + supportedAccountTypes, + bussinessName + ) + } + } else { + if numberOfIneligibleAccounts == 1 { + let localizedString = STPLocalizedString( + "We found 1 %@ account but you can only link %@ accounts.", + "A description/subtitle that instructs the user that the bank account they selected is not eligible. For example, maybe the user selected a credit card, but we only accept debit cards. The first '%@' is replaced by the name of the bank. The second '%@' is replaced by the supported payment accounts (ex. US checking). For example, it may read 'We found 1 Chase account but you can only link checking or savings.'" + ) + return String(format: localizedString, institution.name, supportedAccountTypes) + } else { + let localizedString = STPLocalizedString( + "We found %d %@ accounts but you can only link %@ accounts.", + "A description/subtitle that instructs the user that the bank accounts they selected are not eligible. For example, maybe the user selected credit cards, but we only accept debit cards. The '%d' is replaced by the number of ineligible accounts. The first '%@' is replaced by the name of the bank. The second '%@' is replaced by the supported payment accounts (ex. US checking). For example, it may read 'We found 2 Chase accounts but you can only link checking or savings.'" + ) + return String( + format: localizedString, + numberOfIneligibleAccounts, + institution.name, + supportedAccountTypes + ) + } + } + }() + let subtitleSecondSentence: String = { + if institutionSkipAccountSelection { + return STPLocalizedString( + "Please try selecting another bank account.", + "The subtitle/description of a screen that shows an error. The error appears after user selected bank accounts, but we found that none of them are eligible to be linked. Here we instruct the user to try selecting another bank account at the same bank." + ) + } else { + return STPLocalizedString( + "Please try selecting another bank.", + "The subtitle/description of a screen that shows an error. The error appears after user selected bank accounts, but we found that none of them are eligible to be linked. Here we instruct the user to try selecting another bank account at a different bank." + ) + } + }() + + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institution.icon?.default) + return institutionIconView + }(), + title: { + if institutionSkipAccountSelection { + if numberOfIneligibleAccounts == 1 { + return STPLocalizedString( + "The account you selected isn't available for payments", + "The title of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we describe to the user that the account they selected isn't eligible." + ) + } else { + return STPLocalizedString( + "The accounts you selected aren't available for payments", + "The title of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we describe to the user that the accounts they selected aren't eligible. '%@' gets replaced by the eligible type of bank accounts, i.e. checking or savings. For example, maybe user selected a credit card, but we only support debit cards." + ) + } + } else { + return STPLocalizedString( + "No payment accounts available", + "The title of a screen that shows an error. The error appears after we failed to load users bank accounts. Here we describe to the user that the accounts they selected aren't eligible. '%@' gets replaced by the eligible type of bank accounts, i.e. checking or savings. For example, maybe user selected a credit card, but we only support debit cards." + ) + } + }(), + subtitle: subtitleFirstSentence + " " + subtitleSecondSentence, + contentView: nil + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: { + if institutionSkipAccountSelection { + return STPLocalizedString( + "Connect another account", + "The title of a button. The button presents the user an option to select another bank account. For example, we may show this button after user failed to link their primary bank account, but maybe the user can try to link their secondary bank account!" + ) + } else { + return String.Localized.select_another_bank + } + }(), + action: didSelectAnotherBank + ), + theme: theme + ).footerView + ) + paneLayoutView.addTo(view: self) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG + +import SwiftUI + +private struct AccountPickerNoAccountEligibleErrorViewUIViewRepresentable: UIViewRepresentable { + + let institutionName: String + let businessName: String? + let institutionSkipAccountSelection: Bool + let numberOfIneligibleAccounts: Int + let paymentMethodType: FinancialConnectionsPaymentMethodType + + func makeUIView(context: Context) -> AccountPickerNoAccountEligibleErrorView { + AccountPickerNoAccountEligibleErrorView( + institution: FinancialConnectionsInstitution( + id: "123", + name: institutionName, + url: nil, + icon: nil, + logo: nil + ), + bussinessName: businessName, + institutionSkipAccountSelection: institutionSkipAccountSelection, + numberOfIneligibleAccounts: numberOfIneligibleAccounts, + paymentMethodType: paymentMethodType, + theme: .light, + didSelectAnotherBank: {} + ) + } + + func updateUIView(_ uiView: AccountPickerNoAccountEligibleErrorView, context: Context) {} +} + +struct AccountPickerNoAccountEligibleErrorView_Previews: PreviewProvider { + static var previews: some View { + AccountPickerNoAccountEligibleErrorViewUIViewRepresentable( + institutionName: "Chase", + businessName: "The Coca-Cola Company", + institutionSkipAccountSelection: false, + numberOfIneligibleAccounts: 1, + paymentMethodType: .link + ) + + AccountPickerNoAccountEligibleErrorViewUIViewRepresentable( + institutionName: "Chase", + businessName: "The Coca-Cola Company", + institutionSkipAccountSelection: false, + numberOfIneligibleAccounts: 3, + paymentMethodType: .usBankAccount + ) + + AccountPickerNoAccountEligibleErrorViewUIViewRepresentable( + institutionName: "Chase", + businessName: nil, + institutionSkipAccountSelection: false, + numberOfIneligibleAccounts: 1, + paymentMethodType: .link + ) + + AccountPickerNoAccountEligibleErrorViewUIViewRepresentable( + institutionName: "Chase", + businessName: nil, + institutionSkipAccountSelection: true, + numberOfIneligibleAccounts: 3, + paymentMethodType: .unparsable + ) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerSelectionListView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerSelectionListView.swift new file mode 100644 index 00000000..0378e281 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerSelectionListView.swift @@ -0,0 +1,109 @@ +// +// AccountPickerSelectionListView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/22/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +protocol AccountPickerSelectionListViewDelegate: AnyObject { + func accountPickerSelectionListView( + _ view: AccountPickerSelectionListView, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) +} + +final class AccountPickerSelectionListView: UIView { + + private let selectionType: AccountPickerSelectionType + private let enabledAccounts: [FinancialConnectionsPartnerAccount] + private let disabledAccounts: [FinancialConnectionsPartnerAccount] + private let theme: FinancialConnectionsTheme + weak var delegate: AccountPickerSelectionListViewDelegate? + + private lazy var verticalStackView: UIStackView = { + let verticalStackView = UIStackView() + verticalStackView.spacing = 12 + verticalStackView.axis = .vertical + return verticalStackView + }() + + init( + selectionType: AccountPickerSelectionType, + enabledAccounts: [FinancialConnectionsPartnerAccount], + disabledAccounts: [FinancialConnectionsPartnerAccount], + theme: FinancialConnectionsTheme + ) { + self.selectionType = selectionType + self.enabledAccounts = enabledAccounts + self.disabledAccounts = disabledAccounts + self.theme = theme + super.init(frame: .zero) + addAndPinSubviewToSafeArea(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func selectAccounts(_ selectedAccounts: [FinancialConnectionsPartnerAccount]) { + // clear all previous state + verticalStackView.arrangedSubviews.forEach { arrangedSubview in + arrangedSubview.removeFromSuperview() + } + + // list enabled accounts + enabledAccounts.forEach { account in + let accountRowView = AccountPickerRowView( + isDisabled: false, + isFaded: false, + theme: theme, + didSelect: { [weak self] in + guard let self = self else { return } + var selectedAccounts = selectedAccounts + if let index = selectedAccounts.firstIndex(where: { $0.id == account.id }) { + selectedAccounts.remove(at: index) + } else { + if self.selectionType == .multiple { + selectedAccounts.append(account) + } else { // single select + selectedAccounts = [account] // select only one account + } + } + self.delegate?.accountPickerSelectionListView(self, didSelectAccounts: selectedAccounts) + } + ) + let rowInfo = AccountPickerHelpers.rowInfo(forAccount: account) + accountRowView.set( + title: rowInfo.accountName, + subtitle: rowInfo.accountNumbers, + balanceString: rowInfo.balanceString, + isSelected: selectedAccounts.contains(where: { $0.id == account.id }) + ) + verticalStackView.addArrangedSubview(accountRowView) + } + + // list disabled accounts + disabledAccounts.forEach { disabledAccount in + let accountRowView = AccountPickerRowView( + isDisabled: true, + isFaded: true, + theme: theme, + didSelect: { + // can't select disabled accounts + } + ) + let rowInfo = AccountPickerHelpers.rowInfo(forAccount: disabledAccount) + accountRowView.set( + title: rowInfo.accountName, + subtitle: disabledAccount.allowSelectionMessage, + balanceString: nil, + isSelected: false + ) + verticalStackView.addArrangedSubview(accountRowView) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerSelectionView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerSelectionView.swift new file mode 100644 index 00000000..5f902bd5 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerSelectionView.swift @@ -0,0 +1,63 @@ +// +// AccountPickerSelectionView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/10/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +protocol AccountPickerSelectionViewDelegate: AnyObject { + func accountPickerSelectionView( + _ view: AccountPickerSelectionView, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) +} + +final class AccountPickerSelectionView: UIView { + + private weak var delegate: AccountPickerSelectionViewDelegate? + private let listView: AccountPickerSelectionListView + + init( + selectionType: AccountPickerSelectionType, + enabledAccounts: [FinancialConnectionsPartnerAccount], + disabledAccounts: [FinancialConnectionsPartnerAccount], + institution: FinancialConnectionsInstitution, + theme: FinancialConnectionsTheme, + delegate: AccountPickerSelectionViewDelegate + ) { + self.delegate = delegate + self.listView = AccountPickerSelectionListView( + selectionType: selectionType, + enabledAccounts: enabledAccounts, + disabledAccounts: disabledAccounts, + theme: theme + ) + super.init(frame: .zero) + listView.delegate = self + addAndPinSubviewToSafeArea(listView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func selectAccounts(_ selectedAccounts: [FinancialConnectionsPartnerAccount]) { + listView.selectAccounts(selectedAccounts) + } +} + +// MARK: - AccountPickerSelectionListViewDelegate + +extension AccountPickerSelectionView: AccountPickerSelectionListViewDelegate { + + func accountPickerSelectionListView( + _ view: AccountPickerSelectionListView, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) { + delegate?.accountPickerSelectionView(self, didSelectAccounts: selectedAccounts) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerViewController.swift new file mode 100644 index 00000000..7cbfdce0 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/AccountPickerViewController.swift @@ -0,0 +1,572 @@ +// +// AccountPickerViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/5/22. +// + +import Foundation +import SafariServices +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol AccountPickerViewControllerDelegate: AnyObject { + func accountPickerViewController( + _ viewController: AccountPickerViewController, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount], + nextPane: FinancialConnectionsSessionManifest.NextPane, + customSuccessPaneMessage: String?, + saveToLinkWithStripeSucceeded: Bool? + ) + func accountPickerViewControllerDidSelectAnotherBank(_ viewController: AccountPickerViewController) + func accountPickerViewControllerDidSelectManualEntry(_ viewController: AccountPickerViewController) + func accountPickerViewController( + _ viewController: AccountPickerViewController, + didReceiveTerminalError error: Error + ) + func accountPickerViewController( + _ viewController: AccountPickerViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +enum AccountPickerSelectionType { + case single + case multiple +} + +final class AccountPickerViewController: UIViewController { + + private let dataSource: AccountPickerDataSource + private let selectionType: AccountPickerSelectionType + weak var delegate: AccountPickerViewControllerDelegate? + private weak var accountPickerSelectionView: AccountPickerSelectionView? + private var businessName: String? { + return dataSource.manifest.businessName + } + private var didSelectAnotherBank: () -> Void { + return { [weak self] in + guard let self = self else { return } + self.delegate?.accountPickerViewControllerDidSelectAnotherBank(self) + } + } + // we only allow to retry account polling once + private var allowAccountPollingRetry = true + private var didSelectTryAgain: (() -> Void)? { + return allowAccountPollingRetry + ? { [weak self] in + guard let self = self else { return } + self.allowAccountPollingRetry = false + self.showErrorView(nil) + self.pollAuthSessionAccounts() + } : nil + } + private var didSelectManualEntry: (() -> Void)? { + return (dataSource.manifest.allowManualEntry && !dataSource.reduceManualEntryProminenceInErrors) + ? { [weak self] in + guard let self = self else { return } + self.delegate?.accountPickerViewControllerDidSelectManualEntry(self) + } : nil + } + private var errorView: UIView? + + private lazy var footerView: AccountPickerFooterView = { + return AccountPickerFooterView( + dataAccessNotice: dataSource.accountPickerPane?.dataAccessNotice, + singleAccount: dataSource.manifest.singleAccount, + theme: dataSource.manifest.theme, + didSelectLinkAccounts: { [weak self] in + guard let self = self else { + return + } + self.dataSource + .analyticsClient + .log( + eventName: "click.link_accounts", + pane: .accountPicker + ) + self.delegate?.accountPickerViewController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .accountsSelected) + ) + self.didSelectLinkAccounts(isSkipAccountSelection: false) + }, + didSelectMerchantDataAccessLearnMore: { [weak self] url in + guard let self = self else { return } + self.dataSource + .analyticsClient + .logMerchantDataAccessLearnMore(pane: .accountPicker) + + if let dataAccessNotice = self.dataSource.dataAccessNotice { + let dataAccessNoticeViewController = DataAccessNoticeViewController( + dataAccessNotice: dataAccessNotice, + theme: self.dataSource.manifest.theme, + didSelectUrl: { [weak self] url in + guard let self = self else { return } + AuthFlowHelpers.handleURLInTextFromBackend( + url: url, + pane: .accountPicker, + analyticsClient: self.dataSource.analyticsClient, + handleURL: { _, _ in } + ) + } + ) + dataAccessNoticeViewController.present(on: self) + } else { + SFSafariViewController.present(url: url) + } + } + ) + }() + + init(dataSource: AccountPickerDataSource) { + self.dataSource = dataSource + self.selectionType = dataSource.manifest.singleAccount ? .single : .multiple + super.init(nibName: nil, bundle: nil) + dataSource.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + // account picker ALWAYS hides the back button + navigationItem.hidesBackButton = true + view.backgroundColor = .customBackgroundColor + pollAuthSessionAccounts() + } + + private func pollAuthSessionAccounts() { + let retreivingAccountsLoadingView = RetrieveAccountsLoadingView( + institutionIconUrl: dataSource.institution.icon?.default + ) + view.addAndPinSubviewToSafeArea(retreivingAccountsLoadingView) + + let pollingStartDate = Date() + dataSource + .pollAuthSessionAccounts() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let accountsPayload): + self.dataSource + .analyticsClient + .logPaneLoaded(pane: .accountPicker) + + let accounts = accountsPayload.data + guard !accounts.isEmpty else { + // if there were no accounts returned, API should have thrown an error + // ...handle it here since API did not throw error + self.showAccountLoadErrorView( + error: FinancialConnectionsSheetError.unknown( + debugDescription: "API returned an empty list of accounts" + ) + ) + return + } + + self.dataSource + .analyticsClient + .log( + eventName: "polling.accounts.success", + parameters: [ + "duration": Date().timeIntervalSince(pollingStartDate).milliseconds, + "authSessionId": self.dataSource.authSession.id, + ], + pane: .accountPicker + ) + + // note that this uses ?? instead of ||, we do NOT want to skip account selection + // if EITHER of these are true, we only want to skip account selection when + // `accountsPayload.skipAccountSelection` is true, OR `accountsPayload.skipAccountSelection` nil + // AND `authSession.skipAccountSelection` is true + let skipAccountSelection = (accountsPayload.skipAccountSelection ?? self.dataSource.authSession.skipAccountSelection ?? false) + if skipAccountSelection { + let selectableAccounts = accounts.filter(\.allowSelectionNonOptional) + if self.dataSource.manifest.singleAccount { + if let firstEnabledAccount = selectableAccounts.first { + self.dataSource.updateSelectedAccounts([firstEnabledAccount]) + } else { + self.showNoEligibleAccountsErrorView( + numberOfIneligibleAccounts: accounts.count, + error: FinancialConnectionsSheetError.unknown( + debugDescription: "No eligible accounts found" + ) + ) + } + } else { + self.dataSource.updateSelectedAccounts(selectableAccounts) + } + self.didSelectLinkAccounts(isSkipAccountSelection: true) + } else if + self.dataSource.manifest.singleAccount, + self.dataSource.authSession.institutionSkipAccountSelection ?? false, + accounts.count == 1 + { + // the user saw an OAuth account selection screen and selected + // just one to send back in a single-account context. treat these as if + // we had done account selection, and submit. + self.dataSource.updateSelectedAccounts(accounts) + self.didSelectLinkAccounts(isSkipAccountSelection: true) + } else { + let (enabledAccounts, disabledAccounts) = + accounts + .reduce( + ([FinancialConnectionsPartnerAccount](), [FinancialConnectionsPartnerAccount]()) + ) { accountsTuple, account in + if !account.allowSelectionNonOptional { + return ( + accountsTuple.0, + accountsTuple.1 + [account] + ) + } else { + return ( + accountsTuple.0 + [account], + accountsTuple.1 + ) + } + } + self.displayAccounts(enabledAccounts, disabledAccounts) + } + case .failure(let error): + if let error = error as? StripeError, + case .apiError(let apiError) = error, + let extraFields = apiError.allResponseFields["extra_fields"] as? [String: Any], + let reason = extraFields["reason"] as? String, + reason == "no_supported_payment_method_type_accounts_found", + let numberOfIneligibleAccounts = extraFields["total_accounts_count"] as? Int, + // it should never happen, but if numberOfIneligibleAccounts is < 1, we should + // show "AccountLoadErrorView." + numberOfIneligibleAccounts > 0 + { + self.showNoEligibleAccountsErrorView( + numberOfIneligibleAccounts: numberOfIneligibleAccounts, + error: error + ) + } else { + // if we didn't get that specific error back, we don't know what's wrong. could the be + // aggregator, could be Stripe. + self.showAccountLoadErrorView(error: error) + } + } + retreivingAccountsLoadingView.removeFromSuperview() + } + } + + private func showNoEligibleAccountsErrorView(numberOfIneligibleAccounts: Int, error: Error) { + let errorView = AccountPickerNoAccountEligibleErrorView( + institution: self.dataSource.institution, + bussinessName: self.businessName, + institutionSkipAccountSelection: self.dataSource.authSession.institutionSkipAccountSelection + ?? false, + numberOfIneligibleAccounts: numberOfIneligibleAccounts, + paymentMethodType: self.dataSource.manifest.paymentMethodType ?? .usBankAccount, + theme: self.dataSource.manifest.theme, + didSelectAnotherBank: self.didSelectAnotherBank + ) + // the user will never enter this instance of `AccountPickerViewController` + // again...they can only choose manual entry or go through "ResetFlow" + self.showErrorView(errorView) + self.dataSource + .analyticsClient + .logExpectedError( + error, + errorName: "AccountNoneEligibleForPaymentMethodError", + pane: .accountPicker + ) + } + + private func displayAccounts( + _ enabledAccounts: [FinancialConnectionsPartnerAccount], + _ disabledAccounts: [FinancialConnectionsPartnerAccount] + ) { + let accountPickerSelectionView = AccountPickerSelectionView( + selectionType: selectionType, + enabledAccounts: enabledAccounts, + disabledAccounts: disabledAccounts, + institution: dataSource.institution, + theme: dataSource.manifest.theme, + delegate: self + ) + self.accountPickerSelectionView = accountPickerSelectionView + + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: { + if let institutionIconUrl = dataSource.institution.icon?.default { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institutionIconUrl) + return institutionIconView + } else { + return nil + } + }(), + title: { + if dataSource.manifest.singleAccount { + return STPLocalizedString( + "Select account", + "The title of a screen that allows users to select which bank accounts they want to use to pay for something." + ) + } else { + return STPLocalizedString( + "Select accounts", + "The title of a screen that allows users to select which bank accounts they want to use to pay for something." + ) + } + }(), + subtitle: nil, + contentView: accountPickerSelectionView + ), + footerView: footerView + ) + paneLayoutView.addTo(view: view) + + switch selectionType { + case .multiple: + // select all accounts + dataSource.updateSelectedAccounts(enabledAccounts) + case .single: + if let firstAccount = enabledAccounts.first { + dataSource.updateSelectedAccounts([firstAccount]) + } else { + // defensive programming; it should never happen that we have 0 accounts + dataSource.updateSelectedAccounts([]) + } + } + dataSource + .analyticsClient + .log( + eventName: "account_picker.accounts_auto_selected", + parameters: [ + "account_ids": dataSource.selectedAccounts.map({ $0.id }), + "is_single_account": (selectionType == .single), + ], + pane: .accountPicker + ) + } + + private func showAccountLoadErrorView(error: Error) { + let errorView = AccountPickerAccountLoadErrorView( + institution: dataSource.institution, + theme: dataSource.manifest.theme, + didSelectAnotherBank: didSelectAnotherBank, + didSelectTryAgain: didSelectTryAgain, + didSelectEnterBankDetailsManually: didSelectManualEntry + ) + showErrorView(errorView) + dataSource + .analyticsClient + .logExpectedError( + error, + errorName: "AccountLoadError", + pane: .accountPicker + ) + } + + private func showErrorView(_ errorView: UIView?) { + if let errorView = errorView { + view.addAndPinSubview(errorView) + } else { + // clear last error + self.errorView?.removeFromSuperview() + } + self.errorView = errorView + } + + private func didSelectLinkAccounts(isSkipAccountSelection: Bool) { + footerView.startLoading() + // the `footerView` only shows loading view on the button, + // so we need to prevent interactions elsewhere on the + // screen while its loading + view.isUserInteractionEnabled = false + + dataSource + .analyticsClient + .log( + eventName: "account_picker.accounts_submitted", + parameters: [ + "account_ids": dataSource.selectedAccounts.map({ $0.id }), + "is_skip_account_selection": isSkipAccountSelection, + ], + pane: .accountPicker + ) + + dataSource + .selectAuthSessionAccounts() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let linkedAccounts): + // the response from the API is the list of accounts that were selected, + // with the `linked_account_id` populated. + // + // in cases of skip-account-selection in multi-account flows, + // the API returns no accounts since no "new" accounts were linked, + // so we need to fall back to the list of selected accounts + let selectedAccounts: [FinancialConnectionsPartnerAccount] + if linkedAccounts.data.count >= 1 { + selectedAccounts = linkedAccounts.data + } else { + selectedAccounts = self.dataSource.selectedAccounts + } + + // for data flows (external_api), where the user is already + // determined to be a Link consumer, we need to take an + // additional step of saving the accounts to Link + if + self.dataSource.manifest.isNetworkingUserFlow == true, + // make sure the current user is a Link consumer + self.dataSource.manifest.accountholderIsLinkConsumer == true, + // make sure this is not a payment flow; in payment flows, + // the AttachLinkedPaymentAccount handles saving to link + !self.dataSource.manifest.shouldAttachLinkedPaymentMethod, + selectedAccounts.count > 0, + let consumerSessionClientSecret = self.dataSource.consumerSessionClientSecret + { + self.dataSource.saveToLink( + accounts: selectedAccounts, + consumerSessionClientSecret: consumerSessionClientSecret + ) + .observe { [weak self] result in + guard let self = self else { return } + + let saveToLinkWithStripeSucceeded: Bool + let customSuccessPaneMessage: String? + switch result { + case .success(let _customSuccessPaneMessage): + saveToLinkWithStripeSucceeded = true + customSuccessPaneMessage = _customSuccessPaneMessage + case .failure(let error): + saveToLinkWithStripeSucceeded = false + customSuccessPaneMessage = nil + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "SaveToLinkError", + pane: .accountPicker + ) + } + self.delegate?.accountPickerViewController( + self, + didSelectAccounts: selectedAccounts, + nextPane: .success, + customSuccessPaneMessage: customSuccessPaneMessage, + saveToLinkWithStripeSucceeded: saveToLinkWithStripeSucceeded + ) + } + } + // for networking payment flows, and networking data flows where + // user will go through Link sign-up (or Link sign-in verification), + // we do not need to call `saveAccountsToLink` + else { + self.delegate?.accountPickerViewController( + self, + didSelectAccounts: selectedAccounts, + nextPane: linkedAccounts.nextPane, + customSuccessPaneMessage: nil, + saveToLinkWithStripeSucceeded: nil + ) + } + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "SelectAuthSessionAccountsError", + pane: .accountPicker + ) + self.delegate?.accountPickerViewController(self, didReceiveTerminalError: error) + } + } + } + + private func logAccountSelectOrDeselect(selectedAccounts: [FinancialConnectionsPartnerAccount]) { + let isSelect: Bool? // false when deselected, and nil when event shouldn't be sent + let accountId: String? + switch selectionType { + case .single: + // user deselected an account by selecting the same row + if selectedAccounts.isEmpty { + isSelect = false + accountId = dataSource.selectedAccounts.first?.id + } + // user selected a new account + else { + isSelect = true + accountId = selectedAccounts.first?.id + } + case .multiple: + // if user selects or deselects more than two accounts at the same time, we assume user pressed + // "All accounts" which we have decided to exclude due to V3 changes + let pressedAllAccountsButton = (abs(selectedAccounts.count - dataSource.selectedAccounts.count) >= 2) + if !pressedAllAccountsButton { + if selectedAccounts.count > dataSource.selectedAccounts.count { + // selected a new, additional account + isSelect = true + accountId = selectedAccounts + .filter({ newSelectedAccount in + return !dataSource.selectedAccounts.contains(where: { $0.id == newSelectedAccount.id }) + }) + .first? + .id + } + // selectedAccounts.count < dataSource.selectedAccounts.count + else { + // deselected an account + isSelect = false + accountId = dataSource.selectedAccounts + .filter({ oldSelectedAccount in + return !selectedAccounts.contains(where: { $0.id == oldSelectedAccount.id }) + }) + .first? + .id + } + } else { + isSelect = nil + accountId = nil + } + } + if let isSelect = isSelect, let accountId = accountId { + dataSource + .analyticsClient + .log( + eventName: isSelect ? "click.account_picker.account_selected" : "click.account_picker.account_unselected", + parameters: [ + "account": accountId, + "is_single_account": dataSource.manifest.singleAccount, + ], + pane: .accountPicker + ) + } + } +} + +// MARK: - AccountPickerSelectionViewDelegate + +extension AccountPickerViewController: AccountPickerSelectionViewDelegate { + + func accountPickerSelectionView( + _ view: AccountPickerSelectionView, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) { + FeedbackGeneratorAdapter.selectionChanged() + logAccountSelectOrDeselect(selectedAccounts: selectedAccounts) + dataSource.updateSelectedAccounts(selectedAccounts) + } +} + +// MARK: - AccountPickerDataSourceDelegate + +extension AccountPickerViewController: AccountPickerDataSourceDelegate { + func accountPickerDataSource( + _ dataSource: AccountPickerDataSource, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) { + footerView.didSelectAccounts(count: selectedAccounts.count) + accountPickerSelectionView?.selectAccounts(selectedAccounts) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/CheckboxView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/CheckboxView.swift new file mode 100644 index 00000000..4d59cb13 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/CheckboxView.swift @@ -0,0 +1,39 @@ +// +// CheckboxView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/10/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class CheckboxView: UIView { + + private let theme: FinancialConnectionsTheme + private lazy var checkboxImageView: UIImageView = { + let checkboxImageView = UIImageView() + checkboxImageView.contentMode = .scaleAspectFit + checkboxImageView.image = Image.check.makeImage() + .withTintColor(theme.primaryColor, renderingMode: .alwaysOriginal) + return checkboxImageView + }() + + var isSelected: Bool = false { + didSet { + checkboxImageView.isHidden = !isSelected + } + } + + init(theme: FinancialConnectionsTheme) { + self.theme = theme + super.init(frame: .zero) + addAndPinSubview(checkboxImageView) + isSelected = false // fire off setter to draw + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/RetrieveAccountsLoadingView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/RetrieveAccountsLoadingView.swift new file mode 100644 index 00000000..b6fe6d80 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AccountPicker/RetrieveAccountsLoadingView.swift @@ -0,0 +1,72 @@ +// +// RetrieveAccountsLoadingView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/8/24. +// + +import Foundation +import UIKit + +final class RetrieveAccountsLoadingView: UIView { + + init(institutionIconUrl: String?) { + super.init(frame: .zero) + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: { + if let institutionIconUrl = institutionIconUrl { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institutionIconUrl) + return institutionIconView + } else { + return nil + } + }(), + title: STPLocalizedString( + "Retrieving accounts...", + "The title of the loading screen that appears when a user just logged into their bank account, and now is waiting for their bank accounts to load. Once the bank accounts are loaded, user will be able to pick the bank account they want to to use for things like payments." + ), + subtitle: nil, + contentView: { + let verticalStackView = UIStackView( + arrangedSubviews: [ + ShimmeringAccountPickerRow(), + ShimmeringAccountPickerRow(), + ShimmeringAccountPickerRow(), + ShimmeringAccountPickerRow(), + ] + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + return verticalStackView + }() + ), + footerView: nil + ) + paneLayoutView.addTo(view: self) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private class ShimmeringAccountPickerRow: ShimmeringView { + + override init(frame: CGRect) { + super.init(frame: frame) + clipsToBounds = true + layer.cornerRadius = 12 + backgroundColor = .backgroundOffset + + translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + heightAnchor.constraint(equalToConstant: 76), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AccountNumberRetrievalErrorView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AccountNumberRetrievalErrorView.swift new file mode 100644 index 00000000..403df8aa --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AccountNumberRetrievalErrorView.swift @@ -0,0 +1,120 @@ +// +// AccountNumberRetrievalErrorView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/29/22. +// + +import Foundation + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class AccountNumberRetrievalErrorView: UIView { + + init( + institution: FinancialConnectionsInstitution, + theme: FinancialConnectionsTheme, + didSelectAnotherBank: @escaping () -> Void, + didSelectEnterBankDetailsManually: (() -> Void)? // if nil, don't show button + ) { + super.init(frame: .zero) + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institution.icon?.default) + return institutionIconView + }(), + title: STPLocalizedString( + "Your account number couldn’t be accessed at this time", + "The title of a screen that shows an error. The error appears after we failed to access users bank account." + ), + subtitle: { + let isManualEntryEnabled = didSelectEnterBankDetailsManually != nil + if isManualEntryEnabled { + return STPLocalizedString( + "Please enter your bank details manually or select another bank.", + "The subtitle/description of a screen that shows an error. The error appears after we failed to access users bank account. Here we instruct the user to enter their bank details manually or to try selecting another bank." + ) + } else { + return STPLocalizedString( + "Please select another bank.", + "The subtitle/description of a screen that shows an error. The error appears after we failed to access users bank account. Here we instruct the user to try selecting another bank." + ) + } + }(), + contentView: nil + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: String.Localized.select_another_bank, + action: didSelectAnotherBank + ), + secondaryButtonConfiguration: { + if let didSelectEnterBankDetailsManually = didSelectEnterBankDetailsManually { + return PaneLayoutView.ButtonConfiguration( + title: String.Localized.enter_bank_details_manually, + action: didSelectEnterBankDetailsManually + ) + } else { + return nil + } + }(), + theme: theme + ).footerView + ) + paneLayoutView.addTo(view: self) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG + +import SwiftUI + +private struct AccountNumberRetrievalErrorViewUIViewRepresentable: UIViewRepresentable { + + let institutionName: String + let theme: FinancialConnectionsTheme + let didSelectEnterBankDetailsManually: (() -> Void)? + + func makeUIView(context: Context) -> AccountNumberRetrievalErrorView { + AccountNumberRetrievalErrorView( + institution: FinancialConnectionsInstitution( + id: "123", + name: institutionName, + url: nil, + icon: nil, + logo: nil + ), + theme: theme, + didSelectAnotherBank: {}, + didSelectEnterBankDetailsManually: didSelectEnterBankDetailsManually + ) + } + + func updateUIView(_ uiView: AccountNumberRetrievalErrorView, context: Context) {} +} + +struct AccountNumberRetrievalErrorView_Previews: PreviewProvider { + static var previews: some View { + AccountNumberRetrievalErrorViewUIViewRepresentable( + institutionName: "Chase", + theme: .light, + didSelectEnterBankDetailsManually: {} + ) + + AccountNumberRetrievalErrorViewUIViewRepresentable( + institutionName: "Bank of America", + theme: .light, + didSelectEnterBankDetailsManually: nil + ) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AttachLinkedPaymentAccountDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AttachLinkedPaymentAccountDataSource.swift new file mode 100644 index 00000000..ca23642c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AttachLinkedPaymentAccountDataSource.swift @@ -0,0 +1,63 @@ +// +// AttachLinkedPaymentAccountDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/28/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol AttachLinkedPaymentAccountDataSource: AnyObject { + + var manifest: FinancialConnectionsSessionManifest { get } + var institution: FinancialConnectionsInstitution { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var authSessionId: String? { get } + var reduceManualEntryProminenceInErrors: Bool { get } + + func attachLinkedAccountIdToLinkAccountSession() -> Future +} + +final class AttachLinkedPaymentAccountDataSourceImplementation: AttachLinkedPaymentAccountDataSource { + + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let manifest: FinancialConnectionsSessionManifest + let institution: FinancialConnectionsInstitution + private let linkedAccountId: String + let analyticsClient: FinancialConnectionsAnalyticsClient + let authSessionId: String? + private let consumerSessionClientSecret: String? + let reduceManualEntryProminenceInErrors: Bool + + init( + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + manifest: FinancialConnectionsSessionManifest, + institution: FinancialConnectionsInstitution, + linkedAccountId: String, + analyticsClient: FinancialConnectionsAnalyticsClient, + authSessionId: String?, + consumerSessionClientSecret: String?, + reduceManualEntryProminenceInErrors: Bool + ) { + self.apiClient = apiClient + self.clientSecret = clientSecret + self.manifest = manifest + self.institution = institution + self.linkedAccountId = linkedAccountId + self.analyticsClient = analyticsClient + self.authSessionId = authSessionId + self.consumerSessionClientSecret = consumerSessionClientSecret + self.reduceManualEntryProminenceInErrors = reduceManualEntryProminenceInErrors + } + + func attachLinkedAccountIdToLinkAccountSession() -> Future { + return apiClient.attachLinkedAccountIdToLinkAccountSession( + clientSecret: clientSecret, + linkedAccountId: linkedAccountId, + consumerSessionClientSecret: consumerSessionClientSecret // used for Link + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AttachLinkedPaymentAccountViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AttachLinkedPaymentAccountViewController.swift new file mode 100644 index 00000000..0ec45660 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/AttachLinkedPaymentAccount/AttachLinkedPaymentAccountViewController.swift @@ -0,0 +1,173 @@ +// +// AttachLinkedPaymentAccountViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/28/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol AttachLinkedPaymentAccountViewControllerDelegate: AnyObject { + func attachLinkedPaymentAccountViewController( + _ viewController: AttachLinkedPaymentAccountViewController, + didFinishWithPaymentAccountResource paymentAccountResource: FinancialConnectionsPaymentAccountResource, + saveToLinkWithStripeSucceeded: Bool? + ) + func attachLinkedPaymentAccountViewControllerDidSelectAnotherBank( + _ viewController: AttachLinkedPaymentAccountViewController + ) + func attachLinkedPaymentAccountViewControllerDidSelectManualEntry( + _ viewController: AttachLinkedPaymentAccountViewController + ) + func attachLinkedPaymentAccountViewController( + _ viewController: AttachLinkedPaymentAccountViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +final class AttachLinkedPaymentAccountViewController: UIViewController { + + private let dataSource: AttachLinkedPaymentAccountDataSource + weak var delegate: AttachLinkedPaymentAccountViewControllerDelegate? + + private var didSelectAnotherBank: () -> Void { + return { [weak self] in + guard let self = self else { return } + self.delegate?.attachLinkedPaymentAccountViewControllerDidSelectAnotherBank(self) + } + } + // we only allow to retry once + private var allowRetry = true + private var didSelectTryAgain: (() -> Void)? { + return allowRetry + ? { [weak self] in + guard let self = self else { return } + self.allowRetry = false + self.showErrorView(nil) + self.attachLinkedAccountIdToLinkAccountSession() + } : nil + } + private var didSelectManualEntry: (() -> Void)? { + return (dataSource.manifest.allowManualEntry && !dataSource.reduceManualEntryProminenceInErrors) + ? { [weak self] in + guard let self = self else { return } + self.delegate?.attachLinkedPaymentAccountViewControllerDidSelectManualEntry(self) + } : nil + } + private var errorView: UIView? + + init(dataSource: AttachLinkedPaymentAccountDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + navigationItem.hidesBackButton = true + + dataSource + .analyticsClient + .logPaneLoaded(pane: .attachLinkedPaymentAccount) + + attachLinkedAccountIdToLinkAccountSession() + } + + private func attachLinkedAccountIdToLinkAccountSession() { + let loadingView = SpinnerView(theme: dataSource.manifest.theme) + view.addAndPinSubviewToSafeArea(loadingView) + + let pollingStartDate = Date() + dataSource.attachLinkedAccountIdToLinkAccountSession() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let paymentAccountResource): + var saveToLinkWithStripeSucceeded: Bool? + if self.dataSource.manifest.isNetworkingUserFlow == true { + if self.dataSource.manifest.accountholderIsLinkConsumer == true { + saveToLinkWithStripeSucceeded = paymentAccountResource.networkingSuccessful + } + } + + self.dataSource + .analyticsClient + .log( + eventName: "polling.attachPayment.success", + parameters: [ + "duration": Date().timeIntervalSince(pollingStartDate).milliseconds, + "authSessionId": self.dataSource.authSessionId ?? "unknown", + ], + pane: .attachLinkedPaymentAccount + ) + + self.delegate?.attachLinkedPaymentAccountViewController( + self, + didFinishWithPaymentAccountResource: paymentAccountResource, + saveToLinkWithStripeSucceeded: saveToLinkWithStripeSucceeded + ) + // we don't remove `linkingAccountsLoadingView` on success + // because this is the last time the user will see this + // screen, and we don't want to show a blank background + // while we transition to the next pane + case .failure(let error): + loadingView.removeFromSuperview() + if let error = error as? StripeError, + case .apiError(let apiError) = error, + let extraFields = apiError.allResponseFields["extra_fields"] as? [String: Any], + let reason = extraFields["reason"] as? String, + reason == "account_number_retrieval_failed" + { + let errorView = AccountNumberRetrievalErrorView( + institution: self.dataSource.institution, + theme: self.dataSource.manifest.theme, + didSelectAnotherBank: self.didSelectAnotherBank, + didSelectEnterBankDetailsManually: self.didSelectManualEntry + ) + self.showErrorView(errorView) + self.dataSource + .analyticsClient + .logExpectedError( + error, + errorName: "AccountNumberRetrievalError", + pane: .attachLinkedPaymentAccount + ) + } else { + // something unknown happened here, allow a retry + let errorView = AccountPickerAccountLoadErrorView( + institution: self.dataSource.institution, + theme: self.dataSource.manifest.theme, + didSelectAnotherBank: self.didSelectAnotherBank, + didSelectTryAgain: self.didSelectTryAgain, + didSelectEnterBankDetailsManually: self.didSelectManualEntry + ) + self.showErrorView(errorView) + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "AttachLinkedPaymentAccountError", + pane: .attachLinkedPaymentAccount + ) + } + } + } + } + + private func showErrorView(_ errorView: UIView?) { + if let errorView = errorView { + view.addAndPinSubview(errorView) + } else { + // clear last error + self.errorView?.removeFromSuperview() + } + self.errorView = errorView + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentBodyView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentBodyView.swift new file mode 100644 index 00000000..fd8251b2 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentBodyView.swift @@ -0,0 +1,149 @@ +// +// ConsentBodyView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 6/15/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class ConsentBodyView: UIView { + + init( + bulletItems: [FinancialConnectionsBulletPoint], + didSelectURL: @escaping (URL) -> Void + ) { + super.init(frame: .zero) + backgroundColor = .customBackgroundColor + + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 24 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 8, + bottom: 0, + trailing: 8 + ) + bulletItems.forEach { bulletItem in + verticalStackView.addArrangedSubview( + CreateLabelView( + title: bulletItem.title, + content: bulletItem.content, + iconUrl: bulletItem.icon?.default, + action: didSelectURL + ) + ) + } + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private func CreateLabelView( + title: String?, + content: String?, + iconUrl: String?, + action: @escaping (URL) -> Void +) -> UIView { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.setImage(with: iconUrl) + imageView.translatesAutoresizingMaskIntoConstraints = false + let imageDiameter: CGFloat = 20 + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: imageDiameter), + imageView.heightAnchor.constraint(equalToConstant: imageDiameter), + ]) + + let labelView = BulletPointLabelView( + title: title, + content: content, + didSelectURL: action + ) + + let horizontalStackView = HitTestStackView( + arrangedSubviews: [ + { + // add padding to the `imageView` so the + // image is aligned with the label + let paddingStackView = UIStackView( + arrangedSubviews: [imageView] + ) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + // center the image in the middle of the first line height + top: max(0, (labelView.topLineHeight - imageDiameter) / 2), + leading: 0, + bottom: 0, + trailing: 0 + ) + return paddingStackView + }(), + labelView, + ] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 16 + horizontalStackView.alignment = .top + return horizontalStackView +} + +#if DEBUG + +import SwiftUI + +private struct ConsentBodyViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> ConsentBodyView { + ConsentBodyView( + bulletItems: [ + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: + "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--reserve-primary-3x.png" + ), + content: + "Stripe will allow Goldilocks to access only the [data requested](https://www.stripe.com). We never share your login details with them." + ), + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: + "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--reserve-primary-3x.png" + ), + content: "Your data is encrypted for your protection." + ), + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: + "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--reserve-primary-3x.png" + ), + content: "You can [disconnect](https://www.stripe.com) your accounts at any time." + ), + ], + didSelectURL: { _ in } + ) + } + + func updateUIView(_ uiView: ConsentBodyView, context: Context) {} +} + +struct ConsentBodyView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + ConsentBodyViewUIViewRepresentable() + .frame(maxHeight: 200) + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentDataSource.swift new file mode 100644 index 00000000..892836a2 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentDataSource.swift @@ -0,0 +1,48 @@ +// +// ConsentDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/13/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol ConsentDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var consent: FinancialConnectionsConsent { get } + var merchantLogo: [String]? { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + + func markConsentAcquired() -> Promise +} + +final class ConsentDataSourceImplementation: ConsentDataSource { + + let manifest: FinancialConnectionsSessionManifest + let consent: FinancialConnectionsConsent + let merchantLogo: [String]? + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + + init( + manifest: FinancialConnectionsSessionManifest, + consent: FinancialConnectionsConsent, + merchantLogo: [String]?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.manifest = manifest + self.consent = consent + self.merchantLogo = merchantLogo + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + } + + func markConsentAcquired() -> Promise { + return apiClient.markConsentAcquired(clientSecret: clientSecret) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentFooterView.swift new file mode 100644 index 00000000..8d5aa305 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentFooterView.swift @@ -0,0 +1,142 @@ +// +// ConsentFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 6/14/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class ConsentFooterView: HitTestView { + + private let agreeButtonText: String + private let theme: FinancialConnectionsTheme + private let didSelectAgree: () -> Void + + private lazy var agreeButton: StripeUICore.Button = { + let agreeButton = Button.primary(theme: theme) + agreeButton.title = agreeButtonText + agreeButton.addTarget(self, action: #selector(didSelectAgreeButton), for: .touchUpInside) + agreeButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + agreeButton.heightAnchor.constraint(equalToConstant: 56) + ]) + agreeButton.accessibilityIdentifier = "consent_agree_button" + return agreeButton + }() + + init( + aboveCtaText: String, + ctaText: String, + belowCtaText: String?, + theme: FinancialConnectionsTheme, + didSelectAgree: @escaping () -> Void, + didSelectURL: @escaping (URL) -> Void + ) { + self.agreeButtonText = ctaText + self.theme = theme + self.didSelectAgree = didSelectAgree + super.init(frame: .zero) + backgroundColor = .customBackgroundColor + + let termsAndPrivacyPolicyLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + termsAndPrivacyPolicyLabel.setText( + aboveCtaText, + action: didSelectURL + ) + + let verticalStackView = HitTestStackView( + arrangedSubviews: [ + termsAndPrivacyPolicyLabel, + agreeButton, + ] + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 24, + bottom: 16, + trailing: 24 + ) + + if let belowCtaText = belowCtaText { + let manuallyVerifyLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + manuallyVerifyLabel.setText( + belowCtaText, + action: didSelectURL + ) + manuallyVerifyLabel.accessibilityIdentifier = "consent_manually_verify_label" + verticalStackView.addArrangedSubview(manuallyVerifyLabel) + verticalStackView.setCustomSpacing(24, after: agreeButton) + } + + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didSelectAgreeButton() { + didSelectAgree() + } + + func setIsLoading(_ isLoading: Bool) { + agreeButton.isLoading = isLoading + } +} + +#if DEBUG + +import SwiftUI + +private struct ConsentFooterViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> ConsentFooterView { + ConsentFooterView( + aboveCtaText: + "You agree to Stripe's [Terms](https://stripe.com/legal/end-users#linked-financial-account-terms) and [Privacy Policy](https://stripe.com/privacy). [Learn more](https://stripe.com/privacy-center/legal#linking-financial-accounts)", + ctaText: "Agree", + belowCtaText: "[Manually verify instead](https://www.stripe.com) (takes 1-2 business days)", + theme: .light, + didSelectAgree: {}, + didSelectURL: { _ in } + ) + } + + func updateUIView(_ uiView: ConsentFooterView, context: Context) { + uiView.sizeToFit() + } +} + +struct ConsentFooterView_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + VStack { + ConsentFooterViewUIViewRepresentable() + .frame(maxHeight: 200) + Spacer() + } + .padding() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentLogoView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentLogoView.swift new file mode 100644 index 00000000..9e6279fb --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentLogoView.swift @@ -0,0 +1,376 @@ +// +// ConsentLogoView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 12/22/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +private let blurRadius = 3 +private let ellipsisViewWidth: CGFloat = 32.0 + +final class ConsentLogoView: UIView { + + private var multipleDotView: UIView? + + init(merchantLogo: [String], showsAnimatedDots: Bool) { + super.init(frame: .zero) + let horizontalStackView = UIStackView() + horizontalStackView.axis = .horizontal + horizontalStackView.alignment = .center + + if merchantLogo.count == 2 || merchantLogo.count == 3 { + for i in 0.. UIView { + let cornerRadius: CGFloat = 16.0 + let shadowContainerView = UIView() + shadowContainerView.layer.shadowColor = UIColor.black.cgColor + shadowContainerView.layer.shadowOpacity = 0.18 + shadowContainerView.layer.shadowOffset = CGSize(width: 0, height: 3) + shadowContainerView.layer.shadowRadius = 5 + shadowContainerView.layer.cornerRadius = cornerRadius + let radius: CGFloat = 72.0 + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.layer.cornerRadius = cornerRadius + imageView.setImage(with: urlString) + imageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: radius), + imageView.heightAnchor.constraint(equalToConstant: radius), + ]) + shadowContainerView.addAndPinSubview(imageView) + return shadowContainerView +} + +private func CreateEllipsisView( + leftLogoUrl: String?, + rightLogoUrl: String? +) -> (ellipsisView: UIView, multipleDotView: UIView) { + let backgroundView = MergedLogoView( + leftImageUrl: leftLogoUrl, + rightImageUrl: rightLogoUrl + ) + backgroundView.clipsToBounds = true + backgroundView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + backgroundView.widthAnchor.constraint(equalToConstant: ellipsisViewWidth), + backgroundView.heightAnchor.constraint(equalToConstant: 6), + ]) + + let multipleDotView = MultipleDotView() + backgroundView.mask = multipleDotView + + return (backgroundView, multipleDotView) +} + +private func CreateCenteringView(centeredView: UIView) -> UIView { + let leftSpacerView = UIView() + leftSpacerView.setContentHuggingPriority(.defaultLow, for: .horizontal) + let rightSpacerView = UIView() + rightSpacerView.setContentHuggingPriority(.defaultLow, for: .horizontal) + let horizontalStackView = UIStackView( + arrangedSubviews: [leftSpacerView, centeredView, rightSpacerView] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.distribution = .equalCentering + horizontalStackView.alignment = .center + return horizontalStackView +} + +/// A view that blurs two logos together. +/// +/// Takes two logos. Puts one on the left side. Puts the other on the right side. +/// The logos are allocated half the width of the view, are blurred, +/// and then overlap at the middle. +private class MergedLogoView: UIView { + + private lazy var leftImageView: UIImageView = { + let leftImageView = BlurredImageView() + return leftImageView + }() + private lazy var rightImageView: UIImageView = { + let rightImageView = BlurredImageView() + return rightImageView + }() + + init(leftImageUrl: String?, rightImageUrl: String?) { + super.init(frame: .zero) + addSubview(rightImageView) + // add the `left` over `right` to prioritize the + // left image in the overlap as its the 'dominant' one + addSubview(leftImageView) + leftImageView.setImage(with: leftImageUrl) { [weak self] _ in + self?.setNeedsLayout() + self?.layoutIfNeeded() + } + rightImageView.setImage(with: rightImageUrl) { [weak self] _ in + self?.setNeedsLayout() + self?.layoutIfNeeded() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + // an estimated value to overlap the two blurred + // images while avoiding white-space + let imageOverlapWidth: CGFloat = CGFloat(blurRadius) * 4.5 + + let leftImageSize = leftImageView.image?.size ?? CGSize( + width: bounds.width / 2, + height: bounds.height / 2 + ) + leftImageView.frame = CGRect( + x: -leftImageSize.width + (bounds.width / 2) + (imageOverlapWidth), + y: -(leftImageSize.height / 2) + (bounds.height / 2), + width: leftImageSize.width, + height: leftImageSize.height + ) + + let rightImageSize = rightImageView.image?.size ?? CGSize( + width: bounds.width / 2, + height: bounds.height / 2 + ) + rightImageView.frame = CGRect( + x: bounds.width / 2 - (imageOverlapWidth), + y: -(rightImageSize.height / 2) + (bounds.height / 2), + width: rightImageSize.width, + height: rightImageSize.height + ) + } +} + +// A view that renders multiple dots (like an ellipis). +private class MultipleDotView: UIView { + + private static let numberOfDots = 5 + static let dotSpacing: CGFloat = 4.0 + static let dotRadius: CGFloat = 6.0 + static let size = CGSize( + width: CGFloat(numberOfDots) * dotRadius + CGFloat(numberOfDots - 1) * dotSpacing, + height: dotRadius + ) + + override init(frame: CGRect) { + super.init( + frame: CGRect( + x: 0, + y: 0, + width: Self.size.width, + height: Self.size.height + ) + ) + + var currentX: CGFloat = 0 + for _ in 0.. Void + ) { + DispatchQueue.global(qos: .default).async { + guard let inputCIImage = CIImage(image: originalImage) else { + DispatchQueue.main.async { + completionHandler(originalImage) + } + return + } + + let motionBlurFilter = CIFilter(name: "CIMotionBlur") + motionBlurFilter?.setValue(inputCIImage, forKey: kCIInputImageKey) + motionBlurFilter?.setValue(NSNumber(value: blurRadius), forKey: kCIInputRadiusKey) + + guard let blurredCIImage = motionBlurFilter?.outputImage else { + DispatchQueue.main.async { + completionHandler(originalImage) + } + return + } + + let context = CIContext() + guard + let cgImage = context.createCGImage(blurredCIImage, from: blurredCIImage.extent) + else { + DispatchQueue.main.async { + completionHandler(originalImage) + } + return + } + + let finalImage = UIImage(cgImage: cgImage) + DispatchQueue.main.async { + completionHandler(finalImage) + } + } + } +} + +#if DEBUG + +import SwiftUI + +private struct ConsentLogoViewUIViewRepresentable: UIViewRepresentable { + + var showsAnimatedDots: Bool + + func makeUIView(context: Context) -> ConsentLogoView { + ConsentLogoView( + merchantLogo: [ + "https://stripe-camo.global.ssl.fastly.net/a2f7a55341b7cdb849d5b2d68a465f95cc06ee6ec2449ea468b3623a61c17393/68747470733a2f2f66696c65732e7374726970652e636f6d2f6c696e6b732f4d44423859574e6a64463878546d5978575664445356553457474a6f52326c5966475a7358327870646d56664d58526f617a5a526454523354573144566a4a4e57584659526d6c3161464646303077384f5a645a3166", + "https://b.stripecdn.com/connections-statics-srv/assets/BrandIcon--stripe-4x.png", + ], + showsAnimatedDots: showsAnimatedDots + ) + } + + func updateUIView(_ uiView: ConsentLogoView, context: Context) {} +} + +struct ConsentLogoView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .center) { + ConsentLogoViewUIViewRepresentable(showsAnimatedDots: true) + Spacer() + ConsentLogoViewUIViewRepresentable(showsAnimatedDots: false) + } + .padding() + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentViewController.swift new file mode 100644 index 00000000..f1f29906 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Consent/ConsentViewController.swift @@ -0,0 +1,228 @@ +// +// ConsentViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 6/14/22. +// + +import Foundation +import SafariServices +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol ConsentViewControllerDelegate: AnyObject { + func consentViewController( + _ viewController: ConsentViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + nextPaneOrDrawerOnSecondaryCta: String? + ) + func consentViewController( + _ viewController: ConsentViewController, + didConsentWithManifest manifest: FinancialConnectionsSessionManifest + ) +} + +class ConsentViewController: UIViewController { + + private let dataSource: ConsentDataSource + weak var delegate: ConsentViewControllerDelegate? + + private lazy var titleLabel: AttributedTextView = { + let titleLabel = AttributedTextView( + font: .heading(.extraLarge), + boldFont: .heading(.extraLarge), + linkFont: .heading(.extraLarge), + textColor: .textDefault, + alignment: .center + ) + titleLabel.setText( + dataSource.consent.title, + action: { [weak self] url in + // there are no known cases where we add a link to the title + // but we add this handling regardless in case this changes + // in the future + self?.didSelectURLInTextFromBackend(url) + } + ) + return titleLabel + }() + private lazy var footerView: ConsentFooterView = { + return ConsentFooterView( + aboveCtaText: dataSource.consent.aboveCta, + ctaText: dataSource.consent.cta, + belowCtaText: dataSource.consent.belowCta, + theme: dataSource.manifest.theme, + didSelectAgree: { [weak self] in + self?.didSelectAgree() + }, + didSelectURL: { [weak self] url in + self?.didSelectURLInTextFromBackend(url) + } + ) + }() + private var consentLogoView: ConsentLogoView? + + init(dataSource: ConsentDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + + let paneLayoutView = PaneLayoutView( + contentView: { + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 24 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 24, + leading: 24, + bottom: 8, + trailing: 24 + ) + if let merchantLogo = dataSource.merchantLogo { + let showsAnimatedDots = dataSource.manifest.isLinkWithStripe != true + let consentLogoView = ConsentLogoView( + merchantLogo: merchantLogo, + showsAnimatedDots: showsAnimatedDots + ) + self.consentLogoView = consentLogoView + verticalStackView.addArrangedSubview(consentLogoView) + } + verticalStackView.addArrangedSubview(titleLabel) + verticalStackView.addArrangedSubview( + ConsentBodyView( + bulletItems: dataSource.consent.body.bullets, + didSelectURL: { [weak self] url in + self?.didSelectURLInTextFromBackend(url) + } + ) + ) + return verticalStackView + }(), + footerView: footerView + ) + paneLayoutView.addTo(view: view) + + dataSource.analyticsClient.logPaneLoaded(pane: .consent) + + NotificationCenter.default.addObserver( + self, + selector: #selector(appWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // this fixes an issue where presenting a UIViewController + // on top of ConsentViewController would stop the dot animation + consentLogoView?.animateDots() + } + + @objc private func appWillEnterForeground() { + // Fixes an issue where the dot animation was stopped when the app + // was backgrounded, then reopened. + consentLogoView?.animateDots() + } + + private func didSelectAgree() { + dataSource.analyticsClient.log( + eventName: "click.agree", + pane: .consent + ) + + footerView.setIsLoading(true) + dataSource.markConsentAcquired() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let manifest): + self.delegate?.consentViewController(self, didConsentWithManifest: manifest) + case .failure(let error): + // we display no errors on failure + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "ConsentAcquiredError", + pane: .consent + ) + } + self.footerView.setIsLoading(false) + } + } + + private func didSelectURLInTextFromBackend(_ url: URL) { + AuthFlowHelpers.handleURLInTextFromBackend( + url: url, + pane: .consent, + analyticsClient: dataSource.analyticsClient, + handleURL: { urlHost, nextPaneOrDrawerOnSecondaryCta in + guard let urlHost, let address = StripeSchemeAddress(rawValue: urlHost) else { + self.dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError.unknown( + debugDescription: "Unknown Stripe-scheme URL detected: \(urlHost ?? "nil")." + ), + errorName: "ConsentStripeURLError", + pane: .consent + ) + return + } + + switch address { + case .manualEntry: + delegate?.consentViewController( + self, + didRequestNextPane: .manualEntry, + nextPaneOrDrawerOnSecondaryCta: nextPaneOrDrawerOnSecondaryCta + ) + case .dataAccessNotice: + if let dataAccessNotice = dataSource.consent.dataAccessNotice { + let dataAccessNoticeViewController = DataAccessNoticeViewController( + dataAccessNotice: dataAccessNotice, + theme: dataSource.manifest.theme, + didSelectUrl: { [weak self] url in + self?.didSelectURLInTextFromBackend(url) + } + ) + dataAccessNoticeViewController.present(on: self) + } + case .legalDatailsNotice: + let legalDetailsNoticeModel = dataSource.consent.legalDetailsNotice + let legalDetailsNoticeViewController = LegalDetailsNoticeViewController( + legalDetailsNotice: legalDetailsNoticeModel, + theme: dataSource.manifest.theme, + didSelectUrl: { [weak self] url in + self?.didSelectURLInTextFromBackend(url) + } + ) + legalDetailsNoticeViewController.present(on: self) + case .linkAccountPicker: + delegate?.consentViewController( + self, + didRequestNextPane: .linkAccountPicker, + nextPaneOrDrawerOnSecondaryCta: nextPaneOrDrawerOnSecondaryCta + ) + case .linkLogin: + delegate?.consentViewController( + self, + didRequestNextPane: .networkingLinkLoginWarmup, + nextPaneOrDrawerOnSecondaryCta: nextPaneOrDrawerOnSecondaryCta + ) + } + } + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Error/ErrorDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Error/ErrorDataSource.swift new file mode 100644 index 00000000..f261d6ce --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Error/ErrorDataSource.swift @@ -0,0 +1,35 @@ +// +// ErrorDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/7/24. +// + +import Foundation +@_spi(STP) import StripeCore + +final class ErrorDataSource { + + let error: Error + let referrerPane: FinancialConnectionsSessionManifest.NextPane + let manifest: FinancialConnectionsSessionManifest + let reduceManualEntryProminenceInErrors: Bool + let analyticsClient: FinancialConnectionsAnalyticsClient + let institution: FinancialConnectionsInstitution? + + init( + error: Error, + referrerPane: FinancialConnectionsSessionManifest.NextPane, + manifest: FinancialConnectionsSessionManifest, + reduceManualEntryProminenceInErrors: Bool, + analyticsClient: FinancialConnectionsAnalyticsClient, + institution: FinancialConnectionsInstitution? + ) { + self.error = error + self.referrerPane = referrerPane + self.manifest = manifest + self.reduceManualEntryProminenceInErrors = reduceManualEntryProminenceInErrors + self.analyticsClient = analyticsClient + self.institution = institution + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Error/ErrorViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Error/ErrorViewController.swift new file mode 100644 index 00000000..b4abaca3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Error/ErrorViewController.swift @@ -0,0 +1,222 @@ +// +// ErrorViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/7/24. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol ErrorViewControllerDelegate: AnyObject { + func errorViewControllerDidSelectAnotherBank(_ viewController: ErrorViewController) + func errorViewControllerDidSelectManualEntry(_ viewController: ErrorViewController) + func errorViewController( + _ viewController: ErrorViewController, + didSelectCloseWithError error: Error + ) +} + +/// Represents the VC for `unexpected_error` pane. It's used for +/// all types of errors and the naming of "unexpected_error" is just a +/// convention from old backend naming. +final class ErrorViewController: UIViewController { + + private let dataSource: ErrorDataSource + private(set) var isTerminal = false + + weak var delegate: ErrorViewControllerDelegate? + + init(dataSource: ErrorDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + navigationItem.hidesBackButton = true + + let error = dataSource.error + let allowManualEntryInNonTerminalErrors = (dataSource.manifest.allowManualEntry && !dataSource.reduceManualEntryProminenceInErrors) + let errorView: UIView + if let error = dataSource.error as? StripeError, + case .apiError(let apiError) = error, + let extraFields = apiError.allResponseFields["extra_fields"] as? [String: Any], + let institutionUnavailable = extraFields["institution_unavailable"] as? Bool, + institutionUnavailable + { + assert( + dataSource.institution != nil, + "expected institution to be set before handling institution errors" + ) + + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(dataSource.institution?.icon?.default) + let primaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: String.Localized.select_another_bank, + accessibilityIdentifier: "select_another_bank_button", + action: { [weak self] in + guard let self else { return } + self.delegate?.errorViewControllerDidSelectAnotherBank(self) + } + ) + if let expectedToBeAvailableAt = extraFields["expected_to_be_available_at"] as? TimeInterval { + let expectedToBeAvailableDate = Date(timeIntervalSince1970: expectedToBeAvailableAt) + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .short + let expectedToBeAvailableTimeString = dateFormatter.string(from: expectedToBeAvailableDate) + errorView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: institutionIconView, + title: String( + format: STPLocalizedString( + "%@ is undergoing maintenance", + "Title of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ), + dataSource.institution?.name ?? "Bank" + ), + subtitle: { + let beginningOfSubtitle: String = { + if isToday(expectedToBeAvailableDate) { + return String( + format: STPLocalizedString( + "Maintenance is scheduled to end at %@.", + "The first part of a subtitle/description of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ), + expectedToBeAvailableTimeString + ) + } else { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .short + let expectedToBeAvailableDateString = dateFormatter.string( + from: expectedToBeAvailableDate + ) + return String( + format: STPLocalizedString( + "Maintenance is scheduled to end on %@ at %@.", + "The first part of a subtitle/description of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ), + expectedToBeAvailableDateString, + expectedToBeAvailableTimeString + ) + } + }() + let endOfSubtitle: String = { + if allowManualEntryInNonTerminalErrors { + return STPLocalizedString( + "Please enter your bank details manually or select another bank.", + "The second part of a subtitle/description of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ) + } else { + return STPLocalizedString( + "Please select another bank or try again later.", + "The second part of a subtitle/description of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ) + } + }() + return beginningOfSubtitle + " " + endOfSubtitle + }(), + contentView: nil + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: primaryButtonConfiguration, + secondaryButtonConfiguration: allowManualEntryInNonTerminalErrors + ? PaneLayoutView.ButtonConfiguration( + title: String.Localized.enter_bank_details_manually, + action: { [weak self] in + guard let self = self else { return } + self.delegate?.errorViewControllerDidSelectManualEntry(self) + } + ) : nil, + theme: dataSource.manifest.theme + ).footerView + ).createView() + dataSource.analyticsClient.logExpectedError( + error, + errorName: "InstitutionPlannedDowntimeError", + pane: dataSource.referrerPane + ) + } else { + errorView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: institutionIconView, + title: String( + format: STPLocalizedString( + "%@ is currently unavailable", + "Title of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ), + dataSource.institution?.name ?? "Bank" + ), + subtitle: { + if allowManualEntryInNonTerminalErrors { + return STPLocalizedString( + "Please enter your bank details manually or select another bank.", + "The subtitle/description of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ) + } else { + return STPLocalizedString( + "Please select another bank or try again later.", + "The subtitle/description of a screen that shows an error. The error indicates that the bank user selected is currently under maintenance." + ) + } + }(), + contentView: nil + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: primaryButtonConfiguration, + secondaryButtonConfiguration: allowManualEntryInNonTerminalErrors + ? PaneLayoutView.ButtonConfiguration( + title: String.Localized.enter_bank_details_manually, + action: { [weak self] in + guard let self else { return } + self.delegate?.errorViewControllerDidSelectManualEntry(self) + } + ) : nil, + theme: dataSource.manifest.theme + ).footerView + ).createView() + dataSource.analyticsClient.logExpectedError( + error, + errorName: "InstitutionUnplannedDowntimeError", + pane: dataSource.referrerPane + ) + } + } else { + isTerminal = true + + dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "UnexpectedErrorPaneError", + pane: dataSource.referrerPane + ) + + // if we didn't get specific errors back, we don't know + // what's wrong, so show a generic error + errorView = TerminalErrorView( + allowManualEntry: dataSource.manifest.allowManualEntry, + theme: dataSource.manifest.theme, + didSelectManualEntry: { [weak self] in + guard let self else { return } + self.delegate?.errorViewControllerDidSelectManualEntry(self) + }, + didSelectClose: { [weak self] in + guard let self else { return } + self.delegate?.errorViewController(self, didSelectCloseWithError: error) + } + ) + } + + view.addAndPinSubview(errorView) + } +} + +private func isToday(_ comparisonDate: Date) -> Bool { + return Calendar.current.startOfDay(for: comparisonDate) == Calendar.current.startOfDay(for: Date()) +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionCellView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionCellView.swift new file mode 100644 index 00000000..41dd1a37 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionCellView.swift @@ -0,0 +1,119 @@ +// +// InstitutionCellView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/28/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class InstitutionCellView: UIView { + private let theme: FinancialConnectionsTheme + + private lazy var horizontalStackView: UIStackView = { + let horizontalStackView = UIStackView() + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 12 + horizontalStackView.alignment = .center + horizontalStackView.isLayoutMarginsRelativeArrangement = true + horizontalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 24, + bottom: 8, + trailing: 24 + ) + return horizontalStackView + }() + private lazy var labelStackView: UIStackView = { + let labelStackView = UIStackView( + arrangedSubviews: [ + titleLabel, + ] + ) + labelStackView.axis = .vertical + labelStackView.spacing = 0 + return labelStackView + }() + private lazy var titleLabel: AttributedLabel = { + let titleLabel = AttributedLabel( + font: .label(.largeEmphasized), + textColor: .textDefault + ) + return titleLabel + }() + private lazy var subtitleLabel: AttributedLabel = { + let subtitleLabel = AttributedLabel( + font: .label(.medium), + textColor: .textSubdued + ) + return subtitleLabel + }() + private var iconView: UIView? + private lazy var loadingView: ActivityIndicator = { + let activityIndicator = ActivityIndicator(size: .medium) + activityIndicator.color = theme.spinnerColor + activityIndicator.startAnimating() + + // re-size `ActivityIndicator` to a size we desire + // because its hard-coded + let mediumIconDiameter: CGFloat = 20 + let desiredIconDiameter: CGFloat = 24 + let transform = CGAffineTransform( + scaleX: desiredIconDiameter / mediumIconDiameter, + y: desiredIconDiameter / mediumIconDiameter + ) + activityIndicator.transform = transform + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + activityIndicator.widthAnchor.constraint(equalToConstant: desiredIconDiameter), + activityIndicator.heightAnchor.constraint(equalToConstant: desiredIconDiameter), + ]) + return activityIndicator + }() + + init(theme: FinancialConnectionsTheme) { + self.theme = theme + super.init(frame: .zero) + backgroundColor = .clear + addAndPinSubview(horizontalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func customize(iconView: UIView?, title: String, subtitle: String?) { + // clear previous state + self.iconView?.removeFromSuperview() + self.iconView = nil + labelStackView.removeFromSuperview() + subtitleLabel.removeFromSuperview() + + // setup labels + titleLabel.setText(title) + if let subtitle = subtitle { + subtitleLabel.setText(subtitle) + labelStackView.addArrangedSubview(subtitleLabel) + } + + // setup the main view + if let iconView = iconView { + horizontalStackView.addArrangedSubview(iconView) + self.iconView = iconView + } + horizontalStackView.addArrangedSubview(labelStackView) + } + + func showLoadingView(_ show: Bool) { + loadingView.removeFromSuperview() + + if show { + horizontalStackView.addArrangedSubview(loadingView) + loadingView.startAnimating() + } else { + loadingView.stopAnimating() + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionDataSource.swift new file mode 100644 index 00000000..94883135 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionDataSource.swift @@ -0,0 +1,70 @@ +// +// InstitutionDataSource.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/8/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol InstitutionDataSource: AnyObject { + + var manifest: FinancialConnectionsSessionManifest { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var featuredInstitutions: [FinancialConnectionsInstitution] { get } + + func fetchInstitutions(searchQuery: String) -> Future + func fetchFeaturedInstitutions() -> Future<[FinancialConnectionsInstitution]> + func createAuthSession(institutionId: String) -> Future +} + +class InstitutionAPIDataSource: InstitutionDataSource { + + // MARK: - Properties + + let manifest: FinancialConnectionsSessionManifest + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + var featuredInstitutions: [FinancialConnectionsInstitution] = [] + + // MARK: - Init + + init( + manifest: FinancialConnectionsSessionManifest, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.manifest = manifest + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + } + + // MARK: - InstitutionDataSource + + func fetchInstitutions(searchQuery: String) -> Future { + return apiClient.fetchInstitutions( + clientSecret: clientSecret, + query: searchQuery + ) + } + + func fetchFeaturedInstitutions() -> Future<[FinancialConnectionsInstitution]> { + return apiClient.fetchFeaturedInstitutions(clientSecret: clientSecret) + .chained { [weak self] list in + let featuredInstitutions = list.data + self?.featuredInstitutions = featuredInstitutions + return Promise(value: featuredInstitutions) + } + } + + func createAuthSession(institutionId: String) -> Future { + return apiClient.createAuthSession( + clientSecret: clientSecret, + institutionId: institutionId + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionNoResultsView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionNoResultsView.swift new file mode 100644 index 00000000..0fea104c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionNoResultsView.swift @@ -0,0 +1,133 @@ +// +// InstitutionNoResultsView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/29/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class InstitutionNoResultsView: UIView { + + private let didSelectManuallyEnterDetails: (() -> Void)? + + init( + didSelectManuallyEnterDetails: (() -> Void)? + ) { + self.didSelectManuallyEnterDetails = didSelectManuallyEnterDetails + super.init(frame: .zero) + + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 24, + leading: 24, + bottom: 0, + trailing: 24 + ) + + let titleLabel = AttributedLabel( + font: .heading(.large), + textColor: .textDefault + ) + titleLabel.textAlignment = .center + titleLabel.setText( + STPLocalizedString( + "No results", + "The title of a notice that appears at the bottom of search results. It appears when a user is searching for their bank, but no results are returned." + ) + ) + verticalStackView.addArrangedSubview(titleLabel) + + let subtitleLabel = AttributedTextView( + font: .body(.medium), + boldFont: .body(.mediumEmphasized), + linkFont: .body(.mediumEmphasized), + textColor: .textDefault, + linkColor: .textActionPrimaryFocused, + showLinkUnderline: false, + alignment: .center + ) + subtitleLabel.accessibilityIdentifier = "institution_search_no_results_subtitle" + if let didSelectManuallyEnterDetails = didSelectManuallyEnterDetails { + let subtitleText = STPLocalizedString( + "Try searching another bank or %@", + "Part of a subtitle of a notice that appears at the bottom of search results. It appears when a user is searching for their bank, but no results are returned. The '%@' will be replaced by a tappable text that says: 'manually enter details'." + ) + let manuallyEnterDetailsText = STPLocalizedString( + "manually enter details", + "Part of a subtitle of a notice that appears at the bottom of search results. It appears when a user is searching for their bank, but no results are returned. This part of the notice will be tappable. Tapping this will allow a user to manually enter their bank details (account and routing numbers)." + ) + subtitleLabel.setText( + String( + format: subtitleText, + "[\(manuallyEnterDetailsText)](stripe://this-url-will-be-ignored)" + ), + action: { _ in + didSelectManuallyEnterDetails() + } + ) + } else { + subtitleLabel.setText( + STPLocalizedString( + "Try searching another bank", + "The subtitle of a notice that appears at the bottom of search results. It appears when a user is searching for their bank, but no results are returned." + ) + ) + } + verticalStackView.addArrangedSubview(subtitleLabel) + + addAndPinSubview(verticalStackView) + + accessibilityIdentifier = "institution_no_results_footer_view" + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG + +import SwiftUI + +private struct InstitutionNoResultsViewUIViewRepresentable: UIViewRepresentable { + + let showManualEntry: Bool + + func makeUIView(context: Context) -> InstitutionNoResultsView { + InstitutionNoResultsView( + didSelectManuallyEnterDetails: (showManualEntry ? {} : nil) + ) + } + + func updateUIView(_ uiView: InstitutionNoResultsView, context: Context) { + uiView.sizeToFit() + } +} + +struct InstitutionNoResultsView_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + InstitutionNoResultsViewUIViewRepresentable( + showManualEntry: true + ) + .frame(maxHeight: 140) + + InstitutionNoResultsViewUIViewRepresentable( + showManualEntry: false + ) + .frame(maxHeight: 100) + + Spacer() + } + .padding() + .padding() + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionPickerViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionPickerViewController.swift new file mode 100644 index 00000000..45bcd8d2 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionPickerViewController.swift @@ -0,0 +1,509 @@ +// +// InstitutionPickerViewController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/7/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol InstitutionPickerViewControllerDelegate: AnyObject { + func institutionPickerViewController( + _ viewController: InstitutionPickerViewController, + didSelect institution: FinancialConnectionsInstitution + ) + func institutionPickerViewController( + _ viewController: InstitutionPickerViewController, + didFinishSelecting institution: FinancialConnectionsInstitution, + authSession: FinancialConnectionsAuthSession + ) + func institutionPickerViewControllerDidSelectManuallyAddYourAccount( + _ viewController: InstitutionPickerViewController + ) + func institutionPickerViewControllerDidSearch( + _ viewController: InstitutionPickerViewController + ) + func institutionPickerViewController( + _ viewController: InstitutionPickerViewController, + didReceiveError error: Error + ) +} + +class InstitutionPickerViewController: UIViewController { + + private static let headerAndSearchBarSpacing: CGFloat = 24 + + // MARK: - Properties + + private let dataSource: InstitutionDataSource + weak var delegate: InstitutionPickerViewControllerDelegate? + + private lazy var headerView: UIView = { + let verticalStackView = UIStackView( + arrangedSubviews: [ + CreateHeaderTitleLabel(), + ] + ) + verticalStackView.axis = .vertical + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: Constants.Layout.defaultHorizontalMargin, + bottom: Self.headerAndSearchBarSpacing, + trailing: Constants.Layout.defaultHorizontalMargin + ) + verticalStackView.backgroundColor = .customBackgroundColor + return verticalStackView + }() + private lazy var searchBarContainerView: UIView = { + let verticalStackView = UIStackView( + arrangedSubviews: [ + searchBar, + ] + ) + verticalStackView.axis = .vertical + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 0, // the `headerView` has bottom padding + leading: Constants.Layout.defaultHorizontalMargin, + bottom: 16, + trailing: Constants.Layout.defaultHorizontalMargin + ) + verticalStackView.backgroundColor = .customBackgroundColor + // the "shadow" fixes an issue where the "search bar sticky header" + // has a visible 1 pixel gap. the shadow is not actually a shadow, + // but rather a "top border" + verticalStackView.layer.shadowOpacity = 1.0 + verticalStackView.layer.shadowColor = verticalStackView.backgroundColor?.cgColor + verticalStackView.layer.shadowRadius = 0 + verticalStackView.layer.shadowOffset = CGSize( + width: 0, + // the `height` is greater than 1 px because this also fixes + // an issue where the sticky header animates to final position + // (this is default iOS/UITableView behavior), and the animation + // is slow, which can cause the institution cells to temporarily + // appear IF the user scrolls up very quickly + height: -Self.headerAndSearchBarSpacing + ) + return verticalStackView + }() + private lazy var searchBar: InstitutionSearchBar = { + let searchBar = InstitutionSearchBar(theme: dataSource.manifest.theme) + searchBar.delegate = self + return searchBar + }() + private lazy var institutionTableView: InstitutionTableView = { + let institutionTableView = InstitutionTableView( + frame: view.bounds, + allowManualEntry: dataSource.manifest.allowManualEntry, + institutionSearchDisabled: dataSource.manifest.institutionSearchDisabled, + theme: dataSource.manifest.theme + ) + institutionTableView.delegate = self + return institutionTableView + }() + private var isUserCurrentlySearching: Bool { + return !searchBar.text.isEmpty + } + + // MARK: - Debouncing Support + + private var fetchInstitutionsDispatchWorkItem: DispatchWorkItem? + private var lastInstitutionSearchFetchDate = Date() + + // MARK: - Init + + init(dataSource: InstitutionDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + setupView() + + showLoadingView(true) + fetchFeaturedInstitutions { [weak self] in + self?.showLoadingView(false) + } + } + + private func setupView() { + view.backgroundColor = UIColor.customBackgroundColor + + view.addAndPinSubview(institutionTableView) + institutionTableView.setTableHeaderView(headerView) + if !dataSource.manifest.institutionSearchDisabled { + institutionTableView.searchBarContainerView = searchBarContainerView + } + + let dismissSearchBarTapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(didTapOutsideOfSearchBar) + ) + dismissSearchBarTapGestureRecognizer.cancelsTouchesInView = false + dismissSearchBarTapGestureRecognizer.delegate = self + view.addGestureRecognizer(dismissSearchBarTapGestureRecognizer) + } + + @IBAction private func didTapOutsideOfSearchBar() { + searchBar.resignFirstResponder() + } + + private func didSelectInstitution(_ institution: FinancialConnectionsInstitution) { + FeedbackGeneratorAdapter.selectionChanged() + delegate?.institutionPickerViewController(self, didSelect: institution) + + searchBar.resignFirstResponder() + + let showLoadingView: (Bool) -> Void = { [weak self] show in + guard let self else { return } + self.view.isUserInteractionEnabled = !show // prevent accidental taps + self.institutionTableView.showLoadingView(show, forInstitution: institution) + } + + showLoadingView(true) + institutionTableView.showOverlayView( + true, + exceptForInstitution: institution + ) + + dataSource.createAuthSession(institutionId: institution.id) + .observe { [weak self] result in + guard let self else { return } + switch result { + case .success(let authSession): + self.delegate?.institutionPickerViewController( + self, + didFinishSelecting: institution, + authSession: authSession + ) + + if authSession.isOauthNonOptional { + // oauth presents a sheet where we do not hide + // the overlay until the sheet is dismissed + self.observePartnerAuthDismissToHideOverlay() + } else { + self.hideOverlayView() + } + case .failure(let error): + self.delegate?.institutionPickerViewController( + self, + didReceiveError: error + ) + } + showLoadingView(false) + } + } + + private func showLoadingView(_ show: Bool) { + institutionTableView.showLoadingView(show) + } + + private var partnerAuthDismissObserver: Any? + private func observePartnerAuthDismissToHideOverlay() { + partnerAuthDismissObserver = NotificationCenter.default.addObserver( + forName: .sheetViewControllerWillDismiss, + object: nil, + queue: nil + ) { [weak self] notification in + guard let self else { return } + guard notification.object is PartnerAuthViewController else { + return + } + self.hideOverlayView() + self.partnerAuthDismissObserver = nil + } + } + + private func hideOverlayView() { + institutionTableView.showOverlayView(false) + } + + private func scrollToTopOfSearchBar() { + let searchBarContainerFrame = CGRect( + x: 0, + y: headerView.frame.maxY, + width: institutionTableView.tableView.bounds.width, + height: searchBarContainerView.frame.height + ) + institutionTableView.tableView.scrollRectToVisible( + searchBarContainerFrame, + animated: true + ) + } +} + +// MARK: - Data + +extension InstitutionPickerViewController { + + private func fetchFeaturedInstitutions(completionHandler: @escaping () -> Void) { + assertMainQueue() + let fetchStartDate = Date() + dataSource + .fetchFeaturedInstitutions() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let institutions): + self.dataSource + .analyticsClient + .log( + eventName: "search.feature_institutions_loaded", + parameters: [ + "institutions": institutions.map({ $0.id }), + "result_count": institutions.count, + "duration": Date().timeIntervalSince(fetchStartDate).milliseconds, + ], + pane: .institutionPicker + ) + + self.institutionTableView.load( + institutions: institutions, + isUserSearching: false + ) + self.dataSource + .analyticsClient + .logPaneLoaded(pane: .institutionPicker) + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "FeaturedInstitutionsError", + pane: .institutionPicker + ) + } + completionHandler() + } + } + + private func fetchInstitutions(searchQuery: String) { + fetchInstitutionsDispatchWorkItem?.cancel() + institutionTableView.showError(false, isUserSearching: true) + + guard !searchQuery.isEmpty else { + showLoadingView(false) + // clear data because search query is empty + institutionTableView.load( + institutions: dataSource.featuredInstitutions, + isUserSearching: false + ) + return + } + + showLoadingView(true) + scrollToTopOfSearchBar() + let newFetchInstitutionsDispatchWorkItem = DispatchWorkItem(block: { [weak self] in + guard let self = self else { return } + + let lastInstitutionSearchFetchDate = Date() + self.lastInstitutionSearchFetchDate = lastInstitutionSearchFetchDate + self.dataSource + .fetchInstitutions(searchQuery: searchQuery) + .observe(on: DispatchQueue.main) { [weak self] result in + guard let self = self else { return } + guard lastInstitutionSearchFetchDate == self.lastInstitutionSearchFetchDate else { + // ignore any search result that came before + // the lastest search attempt + return + } + switch result { + case .success(let institutionList): + if self.isUserCurrentlySearching { + // only load the institutions IF the user has text in search box + self.institutionTableView.load( + institutions: institutionList.data, + isUserSearching: true, + showManualEntry: institutionList.showManualEntry + ) + } else { + self.institutionTableView.load( + institutions: self.dataSource.featuredInstitutions, + isUserSearching: false, + showManualEntry: institutionList.showManualEntry + ) + } + self.dataSource + .analyticsClient + .log( + eventName: "search.succeeded", + parameters: [ + "query": searchQuery, + "duration": Date().timeIntervalSince(lastInstitutionSearchFetchDate).milliseconds, + "result_count": institutionList.data.count, + ], + pane: .institutionPicker + ) + self.delegate?.institutionPickerViewControllerDidSearch(self) + case .failure(let error): + self.institutionTableView.load( + institutions: [], + isUserSearching: false + ) + self.institutionTableView.showError(true, isUserSearching: false) + + if + let error = error as? StripeError, + case .apiError(let apiError) = error, + apiError.type == .invalidRequestError, + apiError.param == "client_secret", + (apiError.message ?? "").contains("expired") + { + // Do not log for this case. + // + // This code fixes a a weird logging edge-case: + // 1. Type an invalid keyword ("abcde") in the search that iOS + // will auto-correct + // 2. While keyboard is still presented press to enter manual entry + // 3. If merchant has manual entry handoff, we will call + // complete API but another search call will execute + // due to iOS auto-correct, which will fail because + // session is completed. + } else { + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "SearchInstitutionsError", + pane: .institutionPicker + ) + } + } + self.showLoadingView(false) + } + }) + self.fetchInstitutionsDispatchWorkItem = newFetchInstitutionsDispatchWorkItem + DispatchQueue.main.asyncAfter( + deadline: .now() + TimeInterval(0.2), + execute: newFetchInstitutionsDispatchWorkItem + ) + } +} + +// MARK: - InstitutioNSearchBarDelegate + +extension InstitutionPickerViewController: InstitutionSearchBarDelegate { + + func institutionSearchBar(_ searchBar: InstitutionSearchBar, didChangeText text: String) { + fetchInstitutions(searchQuery: text) + } +} + +// MARK: - UIGestureRecognizerDelegate + +extension InstitutionPickerViewController: UIGestureRecognizerDelegate { + + func gestureRecognizer( + _ gestureRecognizer: UIGestureRecognizer, + shouldReceive touch: UITouch + ) -> Bool { + let scrollView = institutionTableView.tableView + let isTableViewScrolledToTop = scrollView.contentOffset.y <= -scrollView.contentInset.top + guard isTableViewScrolledToTop else { + // only consider `dismissSearchBarTapGestureRecognizer` when the + // table view is scrolled to the top + // + // because the dismiss functionality is purely optional, and because frame + // calculation gets complicated in a scroll view, this logic helps + // to keep the calculations simple so we avoid unintentionally + // blocking user interaction + return false + } + let touchPoint = touch.location(in: view) + return headerView.frame.contains(touchPoint) && !searchBar.frame.contains(touchPoint) + } +} + +// MARK: - InstitutionTableViewDelegate + +extension InstitutionPickerViewController: InstitutionTableViewDelegate { + + func institutionTableView( + _ tableView: InstitutionTableView, + didSelectInstitution institution: FinancialConnectionsInstitution + ) { + if isUserCurrentlySearching { + dataSource.analyticsClient.log( + eventName: "search.search_result_selected", + parameters: [ + "institution_id": institution.id, + ], + pane: .institutionPicker + ) + } else { + dataSource.analyticsClient.log( + eventName: "search.featured_institution_selected", + parameters: [ + "institution_id": institution.id, + ], + pane: .institutionPicker + ) + } + didSelectInstitution(institution) + } + + func institutionTableViewDidSelectSearchForMoreBanks(_ tableView: InstitutionTableView) { + scrollToTopOfSearchBar() + searchBar.becomeFirstResponder() + } + + func institutionTableView( + _ tableView: InstitutionTableView, + didSelectManuallyAddYourAccountWithInstitutions institutions: [FinancialConnectionsInstitution] + ) { + dataSource + .analyticsClient + .log( + eventName: "click.manual_entry", + parameters: [ + "institution_ids": institutions.map({ $0.id }), + ], + pane: .institutionPicker + ) + delegate?.institutionPickerViewControllerDidSelectManuallyAddYourAccount(self) + } + + func institutionTableView( + _ tableView: InstitutionTableView, + didScrollInstitutions institutions: [FinancialConnectionsInstitution] + ) { + if isUserCurrentlySearching { + dataSource + .analyticsClient + .log( + eventName: "search.scroll", + parameters: [ + "institution_ids": institutions.map({ $0.id }), + ], + pane: .institutionPicker + ) + } + } +} + +// MARK: - Helpers + +private func CreateHeaderTitleLabel() -> UIView { + let headerTitleLabel = AttributedLabel( + font: .heading(.extraLarge), + textColor: .textDefault + ) + headerTitleLabel.setText( + STPLocalizedString( + "Select bank", + "The title of the 'Institution Picker' screen where users get to select an institution (ex. a bank like Bank of America)." + ) + ) + return headerTitleLabel +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionSearchBar.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionSearchBar.swift new file mode 100644 index 00000000..a1a7a673 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionSearchBar.swift @@ -0,0 +1,274 @@ +// +// InstitutionSearchBar.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/30/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +protocol InstitutionSearchBarDelegate: AnyObject { + func institutionSearchBar( + _ searchBar: InstitutionSearchBar, + didChangeText text: String + ) +} + +final class InstitutionSearchBar: UIView { + + private let theme: FinancialConnectionsTheme + weak var delegate: InstitutionSearchBarDelegate? + var text: String { + get { + return textField.text ?? "" + } + set { + textField.text = newValue + // manual changes to `text` do not call `textFieldTextDidChange` + // so here we do it ourselves + textFieldTextDidChange() + } + } + + private lazy var textField: UITextField = { + let textField = IncreasedHitTestTextField() + textField.textColor = .textDefault + textField.tintColor = textField.textColor // caret color + textField.font = FinancialConnectionsFont.label(.large).uiFont + // this removes the `searchTextField` background color. + // for an unknown reason, setting the `backgroundColor` to + // a white color is a no-op + textField.borderStyle = .none + // use `NSAttributedString` to be able to change the placeholder color + textField.attributedPlaceholder = NSAttributedString( + string: STPLocalizedString( + "Search", + "The placeholder message that appears in a search bar. The placeholder appears before a user enters a search term. It instructs user that this is a search bar." + ), + attributes: [ + .foregroundColor: UIColor.textSubdued, + .font: FinancialConnectionsFont.label(.large).uiFont, + ] + ) + textField.returnKeyType = .search + // fixes a 'bug' where if a user types a keyword, and autocorrect + // wants to correct it, pressing "search" button will choose + // the autocorrected word even though the intent was to use the + // typed-in word + // + // also, bank names are not always friendly to autocorrect suggestions + textField.autocorrectionType = .no + textField.delegate = self + textField.addTarget( + self, + action: #selector(textFieldTextDidChange), + for: .editingChanged + ) + textField.accessibilityIdentifier = "search_bar_text_field" + textField.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textField.heightAnchor.constraint(greaterThanOrEqualToConstant: 24) + ]) + return textField + }() + private lazy var textFieldClearButton: UIButton = { + let imageView = UIImageView() + let textFieldClearButton = TextFieldClearButton() + let cancelImage = Image.cancel_circle.makeImage() + .withTintColor(.textSubdued) + textFieldClearButton.setImage(cancelImage, for: .normal) + textFieldClearButton.addTarget( + self, + action: #selector(didSelectClearButton), + for: .touchUpInside + ) + textFieldClearButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + textFieldClearButton.widthAnchor.constraint(equalToConstant: 16), + textFieldClearButton.heightAnchor.constraint(equalToConstant: 16), + ]) + return textFieldClearButton + }() + private lazy var searchIconView: UIView = { + let searchIconImageView = UIImageView() + searchIconImageView.image = Image.search.makeImage() + .withTintColor(.iconDefault) + searchIconImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + searchIconImageView.widthAnchor.constraint(equalToConstant: 20), + searchIconImageView.heightAnchor.constraint(equalToConstant: 20), + ]) + return searchIconImageView + }() + + init(theme: FinancialConnectionsTheme) { + self.theme = theme + super.init(frame: .zero) + layer.cornerRadius = 12 + + let horizontalStackView = UIStackView( + arrangedSubviews: [ + searchIconView, + textField, + textFieldClearButton, + ] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.alignment = .center + horizontalStackView.spacing = 12 + horizontalStackView.isLayoutMarginsRelativeArrangement = true + horizontalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 16, + bottom: 16, + trailing: 16 + ) + addAndPinSubview(horizontalStackView) + + highlightBorder(false) + adjustClearButtonVisibility() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } + + @discardableResult override func resignFirstResponder() -> Bool { + return textField.resignFirstResponder() + } + + @objc private func textFieldTextDidChange() { + adjustClearButtonVisibility() + delegate?.institutionSearchBar(self, didChangeText: text) + } + + @objc private func didSelectClearButton() { + FeedbackGeneratorAdapter.buttonTapped() + text = "" + } + + private func adjustClearButtonVisibility() { + textFieldClearButton.isHidden = text.isEmpty + } + + private func highlightBorder(_ shouldHighlightBorder: Bool) { + let searchBarBorderColor: UIColor + let searchBarBorderWidth: CGFloat + let shadowOpacity: Float + if shouldHighlightBorder { + searchBarBorderColor = theme.textFieldFocusedColor + searchBarBorderWidth = 2 + shadowOpacity = 0.1 + } else { + searchBarBorderColor = .borderDefault + searchBarBorderWidth = 1 + shadowOpacity = 0 + } + layer.borderColor = searchBarBorderColor.cgColor + layer.borderWidth = searchBarBorderWidth + layer.shadowOpacity = shadowOpacity + layer.shadowColor = UIColor.black.cgColor + layer.shadowRadius = 2 / UIScreen.main.nativeScale + layer.shadowOffset = CGSize( + width: 0, + height: 1 / UIScreen.main.nativeScale + ) + } +} + +// MARK: - UITextFieldDelegate + +extension InstitutionSearchBar: UITextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + highlightBorder(true) + } + + func textFieldDidEndEditing(_ textField: UITextField) { + highlightBorder(false) + } + + // called when user presses "Search" button in the keyboard + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } +} + +private class IncreasedHitTestTextField: UITextField { + // increase the area of TextField taps + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let largerBounds = bounds.insetBy(dx: 0, dy: -16) + return largerBounds.contains(point) + } +} + +#if DEBUG + +import SwiftUI + +private struct InstitutionSearchBarUIViewRepresentable: UIViewRepresentable { + + let text: String + var isSelected: Bool = false + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> InstitutionSearchBar { + InstitutionSearchBar(theme: theme) + } + + func updateUIView(_ searchBar: InstitutionSearchBar, context: Context) { + searchBar.text = text + + if isSelected { + _ = searchBar.becomeFirstResponder() + } + } +} + +struct InstitutionSearchBar_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + InstitutionSearchBarUIViewRepresentable(text: "", theme: .light) + .frame(width: 327) + .frame(height: 56) + + InstitutionSearchBarUIViewRepresentable(text: "Chase", theme: .light) + .frame(width: 327) + .frame(height: 56) + + Spacer() + } + .frame(maxWidth: .infinity) + + InstitutionSearchBarUIViewRepresentable(text: "Chase", isSelected: true, theme: .light) + .frame(width: 327) + .frame(height: 56) + .previewDisplayName("Selected - Light theme") + + InstitutionSearchBarUIViewRepresentable(text: "Chase", isSelected: true, theme: .linkLight) + .frame(width: 327) + .frame(height: 56) + .previewDisplayName("Selected - Link Light theme") + } +} + +#endif + +private class TextFieldClearButton: UIButton { + + // increase hit-test area of the clear button + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let largerBounds = bounds.insetBy( + dx: -(50 - bounds.width) / 2, + dy: -(50 - bounds.height) / 2 + ) + return largerBounds.contains(point) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableFooterView.swift new file mode 100644 index 00000000..1f66155c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableFooterView.swift @@ -0,0 +1,129 @@ +// +// InstitutionTableFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/29/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class InstitutionTableFooterView: UIView { + + private let didSelect: () -> Void + + init( + title: String, + subtitle: String?, + image: Image, + theme: FinancialConnectionsTheme, + didSelect: @escaping () -> Void + ) { + self.didSelect = didSelect + super.init(frame: .zero) + + let institutionCellView = InstitutionCellView(theme: theme) + institutionCellView.customize( + iconView: RoundedIconView( + image: .image(image), + style: .rounded, + theme: theme + ), + title: title, + subtitle: subtitle + ) + addAndPinSubview(institutionCellView) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapView)) + tapGestureRecognizer.delegate = self + addGestureRecognizer(tapGestureRecognizer) + + accessibilityIdentifier = "institution_search_footer_view" + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didTapView() { + didSelect() + } +} + +// MARK: - UITapGestureRecognizer + +extension InstitutionTableFooterView: UIGestureRecognizerDelegate { + + func gestureRecognizer( + _ gestureRecognizer: UIGestureRecognizer, + shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer + ) -> Bool { + // if user taps on the footer, we always want it to be recognized + // + // if the keyboard is on screen, then NOT having this method + // implemented will block the first tap in order to + // dismiss the keyboard + return true + } +} + +#if DEBUG + +import SwiftUI + +private struct InstitutionTableFooterViewUIViewRepresentable: UIViewRepresentable { + + let title: String + let subtitle: String + let image: Image + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> InstitutionTableFooterView { + InstitutionTableFooterView( + title: title, + subtitle: subtitle, + image: image, + theme: theme, + didSelect: {} + ) + } + + func updateUIView(_ uiView: InstitutionTableFooterView, context: Context) { + uiView.sizeToFit() + } +} + +struct InstitutionTableFooterView_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + InstitutionTableFooterViewUIViewRepresentable( + title: "Don't see your bank?", + subtitle: "Enter your bank account and routing numbers", + image: .search, + theme: .light + ) + .frame(maxHeight: 100) + + InstitutionTableFooterViewUIViewRepresentable( + title: "No results", + subtitle: "Double check your spelling and search terms", + image: .cancel_circle, + theme: .light + ) + .frame(maxHeight: 100) + + InstitutionTableFooterViewUIViewRepresentable( + title: "No results", + subtitle: "Double check your spelling and search terms", + image: .cancel_circle, + theme: .linkLight + ) + .frame(maxHeight: 100) + + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableLoadingView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableLoadingView.swift new file mode 100644 index 00000000..6bed151d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableLoadingView.swift @@ -0,0 +1,129 @@ +// +// InstitutionTableLoadingView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/24/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class InstitutionTableLoadingView: UIView { + + init() { + super.init(frame: UIScreen.main.bounds) + backgroundColor = .customBackgroundColor + let verticalStackView = UIStackView( + arrangedSubviews: (0..<10).map({ _ in + ShimmeringInstitutionRowView() + }) + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 0 // the rows have spacing through padding + addSubview(verticalStackView) + verticalStackView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + verticalStackView.topAnchor.constraint(equalTo: topAnchor), + verticalStackView.leadingAnchor.constraint(equalTo: leadingAnchor), + verticalStackView.trailingAnchor.constraint(equalTo: trailingAnchor), + // height will be automatically determined by the UIStackView and it will be clipped beyond the frame + ]) + clipsToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private class ShimmeringInstitutionRowView: ShimmeringView { + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .clear + + let horizontalStackView = UIStackView( + arrangedSubviews: [ + CreateRowIconView(), + CreateRowMultipleLabelView(), + ] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.alignment = .center + horizontalStackView.spacing = 12 + horizontalStackView.isLayoutMarginsRelativeArrangement = true + horizontalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 24, + bottom: 8, + trailing: 24 + ) + addAndPinSubview(horizontalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private func CreateRowIconView() -> UIView { + let iconView = UIView() + iconView.backgroundColor = .backgroundOffset + iconView.layer.cornerRadius = 12 + iconView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + iconView.heightAnchor.constraint(equalToConstant: 56), + iconView.widthAnchor.constraint(equalToConstant: 56), + ]) + return iconView +} + +private func CreateRowMultipleLabelView() -> UIView { + let verticalStackView = UIStackView( + arrangedSubviews: [ + CreateLabelView(width: 180), + CreateLabelView(width: 130), + ] + ) + verticalStackView.axis = .vertical + verticalStackView.alignment = .leading + verticalStackView.spacing = 8 + return verticalStackView +} + +private func CreateLabelView(width: CGFloat) -> UIView { + let labelView = UIView() + labelView.backgroundColor = .backgroundOffset + labelView.layer.cornerRadius = 8 + labelView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + labelView.widthAnchor.constraint(equalToConstant: width), + labelView.heightAnchor.constraint(equalToConstant: 16), + ]) + return labelView +} + +#if DEBUG + +import SwiftUI + +private struct InstitutionTableLoadingViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> InstitutionTableLoadingView { + InstitutionTableLoadingView() + } + + func updateUIView( + _ institutionTableLoadingView: InstitutionTableLoadingView, + context: Context + ) {} +} + +struct InstitutionTableLoadingView_Previews: PreviewProvider { + static var previews: some View { + InstitutionTableLoadingViewUIViewRepresentable() + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableView.swift new file mode 100644 index 00000000..0887324a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableView.swift @@ -0,0 +1,412 @@ +// +// InstitutionTableView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/28/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +private enum Section { + case main +} + +protocol InstitutionTableViewDelegate: AnyObject { + func institutionTableView( + _ tableView: InstitutionTableView, + didSelectInstitution institution: FinancialConnectionsInstitution + ) + func institutionTableViewDidSelectSearchForMoreBanks(_ tableView: InstitutionTableView) + func institutionTableView( + _ tableView: InstitutionTableView, + didSelectManuallyAddYourAccountWithInstitutions institutions: [FinancialConnectionsInstitution] + ) + func institutionTableView( + _ tableView: InstitutionTableView, + didScrollInstitutions institutions: [FinancialConnectionsInstitution] + ) +} + +final class InstitutionTableView: UIView { + + private let allowManualEntry: Bool + private let institutionSearchDisabled: Bool + private let theme: FinancialConnectionsTheme + let tableView: UITableView + private let dataSource: UITableViewDiffableDataSource + weak var delegate: InstitutionTableViewDelegate? + // the sticky header view for section 0 of the table view + weak var searchBarContainerView: UIView? { + didSet { + // as soon as the search bar is set, we want to + // force-layout the UITableView so it lays out + // the header that contains the search bar; + // + // correct search bar layout is important to + // position the loading view + tableView.reloadData() + } + } + private var institutions: [FinancialConnectionsInstitution] = [] + private var shouldLogScroll = true + + private lazy var manualEntryTableFooterView: InstitutionTableFooterView = { + let manualEntryTableFooterView = InstitutionTableFooterView( + title: STPLocalizedString( + "Can't find your bank?", + "The title of a button that appears at the bottom of search results. It appears when a user is searching for their bank. The purpose of the button is to give users the option to enter their bank account numbers manually (ex. routing and account number)." + ), + subtitle: STPLocalizedString( + "Manually enter details", + "The subtitle of a button that appears at the bottom of search results. It appears when a user is searching for their bank. The purpose of the button is to give users the option to enter their bank account numbers manually (ex. routing and account number)." + ), + image: .add, + theme: theme, + didSelect: { [weak self] in + guard let self = self else { return } + FeedbackGeneratorAdapter.buttonTapped() + self.delegate?.institutionTableView( + self, + didSelectManuallyAddYourAccountWithInstitutions: self.institutions + ) + } + ) + return manualEntryTableFooterView + }() + private lazy var searchMoreBanksTableFooterView: InstitutionTableFooterView? = { + if institutionSearchDisabled { + return nil + } else { + let manualEntryTableFooterView = InstitutionTableFooterView( + title: STPLocalizedString( + "Search for more banks", + "The title of a button that appears at the bottom of a list of banks. The purpose of the button is to give users the option to search for more banks than we feature in the initial list of banks (where only the most popular ones will appear)." + ), + subtitle: nil, + image: .search, + theme: theme, + didSelect: { [weak self] in + guard let self = self else { return } + FeedbackGeneratorAdapter.buttonTapped() + self.delegate?.institutionTableViewDidSelectSearchForMoreBanks(self) + } + ) + return manualEntryTableFooterView + } + }() + private var loadingView: UIView? + private var lastTableViewWidthForSearchBarSizing: CGFloat? + + init( + frame: CGRect, + allowManualEntry: Bool, + institutionSearchDisabled: Bool, + theme: FinancialConnectionsTheme + ) { + self.allowManualEntry = allowManualEntry + self.institutionSearchDisabled = institutionSearchDisabled + self.theme = theme + let cellIdentifier = "\(InstitutionTableViewCell.self)" + tableView = UITableView(frame: frame) + dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, _, institution in + guard + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) + as? InstitutionTableViewCell + else { + fatalError( + "Unable to dequeue cell \(InstitutionTableViewCell.self) with cell identifier \(cellIdentifier)" + ) + } + cell.customize(with: institution, theme: theme) + return cell + } + dataSource.defaultRowAnimation = .fade + super.init(frame: frame) + tableView.backgroundColor = .customBackgroundColor + tableView.separatorInset = .zero + tableView.separatorStyle = .none + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 72 + tableView.contentInset = UIEdgeInsets( + // add extra inset at the top/bottom to show the cell-selected-state separators + top: 1.0 / UIScreen.main.nativeScale, + left: 0, + bottom: 1.0 / UIScreen.main.nativeScale, + right: 0 + ) + tableView.keyboardDismissMode = .onDrag + if #available(iOS 15.0, *) { + // do not set this because it can cause unexpected + // scrolling behavior + tableView.sectionHeaderTopPadding = 0 + } + tableView.register(InstitutionTableViewCell.self, forCellReuseIdentifier: cellIdentifier) + tableView.delegate = self + addAndPinSubview(tableView) + // calling `load` activates the `UITableView` data source + // by appening a section, which in turn will display + // the section header (which contains the search bar) + load(institutions: [], isUserSearching: false) + showLoadingView(false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + // `UITableView` does not automatically resize `tableHeaderView` + // so here we do it manually + if let tableHeaderView = tableView.tableHeaderView { + let tableHeaderViewSize = tableHeaderView.systemLayoutSizeFitting( + CGSize( + width: tableView.bounds.size.width, + height: UIView.layoutFittingCompressedSize.height + ) + ) + if tableHeaderView.frame.size.height != tableHeaderViewSize.height { + tableHeaderView.frame.size.height = tableHeaderViewSize.height + tableView.tableHeaderView = tableHeaderView + } + } + + // `UITableView` does not automatically resize `tableFooterView` + // so here we do it manually + if let tableFooterView = tableView.tableFooterView { + let tableFooterViewSize = tableFooterView.systemLayoutSizeFitting( + CGSize( + width: tableView.bounds.size.width, + height: UIView.layoutFittingCompressedSize.height + ) + ) + if tableFooterView.frame.size.height != tableFooterViewSize.height { + tableFooterView.frame.size.height = tableFooterViewSize.height + tableView.tableFooterView = tableFooterView + } + } + + // resize loading view to always be below header view + let loadingViewY: CGFloat + if let searchBarContainerView = searchBarContainerView { + let searchBarContainerViewFrame = searchBarContainerView.convert( + searchBarContainerView.bounds, + to: self + ) + loadingViewY = searchBarContainerViewFrame.maxY + } else if let tableHeaderView = tableView.tableHeaderView { + let headerFrame = tableHeaderView.convert(tableHeaderView.bounds, to: self) + loadingViewY = headerFrame.maxY + } else { + loadingViewY = 0 + } + loadingView?.frame = CGRect( + x: 0, + y: loadingViewY, + width: bounds.width, + height: bounds.height - loadingViewY + ) + } + + func load( + institutions: [FinancialConnectionsInstitution], + isUserSearching: Bool, + showManualEntry: Bool? = nil + ) { + assertMainQueue() + self.institutions = institutions + shouldLogScroll = true + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([Section.main]) + snapshot.appendItems(institutions, toSection: Section.main) + dataSource.apply(snapshot, animatingDifferences: false, completion: nil) + + // clear state (some of this is defensive programming) + showError(false, isUserSearching: isUserSearching) + + if isUserSearching { + if institutions.isEmpty { + showTableFooterView( + true, + view: InstitutionNoResultsView( + didSelectManuallyEnterDetails: self.allowManualEntry ? { [weak self] in + guard let self = self else { return } + self.delegate?.institutionTableView( + self, + didSelectManuallyAddYourAccountWithInstitutions: [] + ) + } : nil + ) + ) + } else { + if allowManualEntry, showManualEntry == true { + showTableFooterView(true, view: manualEntryTableFooterView) + } else { + showTableFooterView(false, view: nil) + } + } + } else { + showTableFooterView(true, view: searchMoreBanksTableFooterView) + } + } + + func showLoadingView(_ show: Bool) { + if show { + if loadingView?.superview == nil { + let loadingView = InstitutionTableLoadingView() + addAndPinSubviewToSafeArea(loadingView) + self.loadingView = loadingView + } + } else { + loadingView?.removeFromSuperview() + loadingView = nil + } + + // ensure the loading view is resized to account for header view + setNeedsLayout() + layoutIfNeeded() + } + + func showError(_ showError: Bool, isUserSearching: Bool) { + if showError { + if allowManualEntry { + showTableFooterView(true, view: manualEntryTableFooterView) + } else { + if !isUserSearching { + showTableFooterView(true, view: searchMoreBanksTableFooterView) + } + } + } else { + if !isUserSearching { + showTableFooterView(true, view: searchMoreBanksTableFooterView) + } + } + } + + func setTableHeaderView(_ tableHeaderView: UIView?) { + if let tableHeaderView { + tableView.setTableHeaderViewWithCompressedFrameSize(tableHeaderView) + } else { + tableView.tableHeaderView = nil + } + } + + // the footer is always shown, except for when there is an error searching + private func showTableFooterView(_ show: Bool, view: UIView?) { + if show, let view = view { + tableView.setTableFooterViewWithCompressedFrameSize(view) + } else { + tableView.tableFooterView = nil + } + } + + func showLoadingView( + _ show: Bool, + forInstitution institution: FinancialConnectionsInstitution + ) { + guard + let index = institutions.firstIndex(where: { $0.id == institution.id }), + let loadingCell = tableView.cellForRow( + at: IndexPath(row: index, section: 0) + ) as? InstitutionTableViewCell + else { + return + } + loadingCell.showLoadingView(show) + } + + /// Grays out all visible rows except the one with `institution`. + func showOverlayView( + _ show: Bool, + exceptForInstitution institution: FinancialConnectionsInstitution? = nil + ) { + let exceptInstitutionCell: UITableViewCell? = { + if + let institution, + let index = institutions.firstIndex(where: { $0.id == institution.id }), + let cell = tableView.cellForRow( + at: IndexPath(row: index, section: 0) + ) + { + return cell + } else { + return nil + } + }() + + tableView + .visibleCells + .forEach { visibleCell in + guard + let visibleCell = visibleCell as? InstitutionTableViewCell, + visibleCell !== exceptInstitutionCell + else { + return + } + visibleCell.showOverlayView(show) + } + } +} + +// MARK: - UITableViewDelegate + +extension InstitutionTableView: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let institution = dataSource.itemIdentifier(for: indexPath) { + delegate?.institutionTableView(self, didSelectInstitution: institution) + } + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + // Every time the institutions change, we are open to sending the event again + if shouldLogScroll { + shouldLogScroll = false + + delegate?.institutionTableView( + self, + didScrollInstitutions: institutions + ) + } + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard section == 0 else { + return nil + } + return searchBarContainerView + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + guard section == 0 else { + return 0 + } + guard let searchBarContainerView = searchBarContainerView else { + return 0 + } + let width = tableView.bounds.width + // `lastTableViewWidthForSearchBarSizing` fixes an issue + // where resizing the searchBar sometimes caused a layout + // glitch each time a search character was inputted + // this logic ensures that we resize search bar only + // when needed + guard lastTableViewWidthForSearchBarSizing != width else { + return searchBarContainerView.bounds.height + } + lastTableViewWidthForSearchBarSizing = width + + searchBarContainerView.frame = CGRect( + origin: searchBarContainerView.frame.origin, + size: CGSize(width: width, height: 100) + ) + searchBarContainerView.layoutSubviews() + searchBarContainerView.layoutIfNeeded() + let size = searchBarContainerView.systemLayoutSizeFitting( + CGSize(width: width, height: .greatestFiniteMagnitude) + ) + return size.height + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableViewCell.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableViewCell.swift new file mode 100644 index 00000000..0abb3132 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionTableViewCell.swift @@ -0,0 +1,158 @@ +// +// InstitutionTableViewCell.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/28/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class InstitutionTableViewCell: UITableViewCell { + private lazy var institutionIconView: InstitutionIconView = { + return InstitutionIconView() + }() + + private var institutionCellView: InstitutionCellView? + + private lazy var overlayView: UIView = { + let overlayView = UIView() + overlayView.backgroundColor = .customBackgroundColor.withAlphaComponent(0.8) + overlayView.alpha = 0 + return overlayView + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + adjustBackgroundColor(isHighlighted: false) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + adjustBackgroundColor(isHighlighted: highlighted) + } + + private func adjustBackgroundColor(isHighlighted: Bool) { + contentView.backgroundColor = isHighlighted ? .backgroundContainer : .customBackgroundColor + backgroundColor = contentView.backgroundColor + + // fix a bug where the background color of a + // rotated, selected cell was wrong + let selectedBackgroundView = UIView() + selectedBackgroundView.backgroundColor = contentView.backgroundColor + self.selectedBackgroundView = selectedBackgroundView + } + + func showLoadingView(_ show: Bool) { + institutionCellView?.showLoadingView(show) + } + + func showOverlayView(_ show: Bool) { + if overlayView.superview == nil { + contentView.addAndPinSubview(overlayView) + } + UIView.animate( + withDuration: 0.3, + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0.3, + animations: { + self.overlayView.alpha = show ? 1.0 : 0 + } + ) + } +} + +// MARK: - Customize + +extension InstitutionTableViewCell { + + func customize(with institution: FinancialConnectionsInstitution, theme: FinancialConnectionsTheme) { + let institutionCellView = InstitutionCellView(theme: theme) + institutionIconView.setImageUrl(institution.icon?.default) + + institutionCellView.customize( + iconView: institutionIconView, + title: institution.name, + subtitle: AuthFlowHelpers.formatUrlString(institution.url) + ) + + // Ensure the cell view isn't added to superview more than once. + self.institutionCellView?.removeFromSuperview() + contentView.addAndPinSubview(institutionCellView) + + self.institutionCellView = institutionCellView + } +} + +#if DEBUG + +import SwiftUI + +@available(iOS 14.0, *) +private struct InstitutionTableViewCellUIViewRepresentable: UIViewRepresentable { + + let showLoadingView: Bool + let showOverlayView: Bool + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> InstitutionTableViewCell { + InstitutionTableViewCell(style: .default, reuseIdentifier: "test") + } + + func updateUIView(_ uiView: InstitutionTableViewCell, context: Context) { + uiView.sizeToFit() + uiView.customize( + with: FinancialConnectionsInstitution( + id: "abc", + name: "Bank of America", + url: "https://www.bankofamerica.com/", + icon: nil, + logo: nil + ), + theme: theme + ) + uiView.showLoadingView(showLoadingView) + uiView.showOverlayView(showOverlayView) + } +} + +@available(iOS 14.0, *) +struct InstitutionTableViewCell_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + InstitutionTableViewCellUIViewRepresentable( + showLoadingView: false, + showOverlayView: false, + theme: .light + ).frame(width: 343, height: 72) + + InstitutionTableViewCellUIViewRepresentable( + showLoadingView: true, + showOverlayView: false, + theme: .light + ).frame(width: 343, height: 72) + + InstitutionTableViewCellUIViewRepresentable( + showLoadingView: true, + showOverlayView: false, + theme: .linkLight + ).frame(width: 343, height: 72) + + InstitutionTableViewCellUIViewRepresentable( + showLoadingView: false, + showOverlayView: true, + theme: .light + ).frame(width: 343, height: 72) + Spacer() + } + .background(Color.gray.opacity(0.5).ignoresSafeArea()) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerBodyView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerBodyView.swift new file mode 100644 index 00000000..31e5c0c5 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerBodyView.swift @@ -0,0 +1,232 @@ +// +// LinkAccountPickerBodyView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/13/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol LinkAccountPickerBodyViewDelegate: AnyObject { + func linkAccountPickerBodyView( + _ view: LinkAccountPickerBodyView, + didSelectAccount selectedAccountTuple: FinancialConnectionsAccountTuple + ) + func linkAccountPickerBodyViewSelectedNewBankAccount(_ view: LinkAccountPickerBodyView) +} + +final class LinkAccountPickerBodyView: UIView { + + weak var delegate: LinkAccountPickerBodyViewDelegate? + private var partnerAccountIdToRowView: [String: AccountPickerRowView] = [:] + + init( + accountTuples: [FinancialConnectionsAccountTuple], + addNewAccount: FinancialConnectionsNetworkingAccountPicker.AddNewAccount, + theme: FinancialConnectionsTheme + ) { + super.init(frame: .zero) + + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + + // add account rows + accountTuples.forEach { accountTuple in + let accountRowView = AccountPickerRowView( + isDisabled: !accountTuple.accountPickerAccount.allowSelection && accountTuple.accountPickerAccount.drawerOnSelection == nil, + isFaded: !accountTuple.accountPickerAccount.allowSelection, + theme: theme, + didSelect: { [weak self] in + guard let self = self else { return } + self.delegate?.linkAccountPickerBodyView( + self, + didSelectAccount: accountTuple + ) + } + ) + let rowTitles = AccountPickerHelpers.rowInfo( + forAccount: accountTuple.partnerAccount + ) + accountRowView.set( + institutionIconUrl: (accountTuple.accountPickerAccount.accountIcon?.default ?? accountTuple.partnerAccount.institution?.icon?.default ?? accountTuple.accountPickerAccount.icon?.default), + title: rowTitles.accountName, + subtitle: { + if let caption = accountTuple.accountPickerAccount.caption { + return caption + } else { + return rowTitles.accountNumbers + } + }(), + underlineSubtitle: accountTuple.accountPickerAccount.drawerOnSelection != nil, + balanceString: + (accountTuple.accountPickerAccount.caption == nil) ? rowTitles.balanceString : nil, + isSelected: false // initially nothing is selected + ) + partnerAccountIdToRowView[accountTuple.partnerAccount.id] = accountRowView + verticalStackView.addArrangedSubview(accountRowView) + } + + // add a 'new bank account' button row + let newAccountRowView = LinkAccountPickerNewAccountRowView( + title: addNewAccount.body, + imageUrl: addNewAccount.icon?.default, + theme: theme, + didSelect: { [weak self] in + guard let self = self else { return } + self.delegate?.linkAccountPickerBodyViewSelectedNewBankAccount(self) + } + ) + newAccountRowView.accessibilityIdentifier = "add_bank_account" + verticalStackView.addArrangedSubview(newAccountRowView) + + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func selectAccounts(_ selectedAccounts: [FinancialConnectionsAccountTuple]) { + let selectedAccountIds = Set(selectedAccounts.map({ $0.partnerAccount.id })) + partnerAccountIdToRowView + .forEach { (partnerAccountId: String, rowView: AccountPickerRowView) in + rowView.set( + isSelected: selectedAccountIds.contains(partnerAccountId) + ) + } + } +} + +#if DEBUG + +import SwiftUI + +private struct LinkAccountPickerBodyViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> LinkAccountPickerBodyView { + LinkAccountPickerBodyView( + accountTuples: [ + ( + accountPickerAccount: FinancialConnectionsNetworkingAccountPicker.Account( + id: "123", + allowSelection: true, + caption: nil, + selectionCta: nil, + icon: nil, + selectionCtaIcon: nil, + drawerOnSelection: nil, + accountIcon: nil, + dataAccessNotice: nil + ), + partnerAccount: FinancialConnectionsPartnerAccount( + id: "abc", + name: "Advantage Plus Checking With Extra Words", + displayableAccountNumbers: "1324", + linkedAccountId: nil, + balanceAmount: 100000, + currency: "USD", + supportedPaymentMethodTypes: [.usBankAccount], + allowSelection: true, + allowSelectionMessage: nil, + status: "active", + institution: FinancialConnectionsInstitution( + id: "abc", + name: "N/A", + url: nil, + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/BrandIcon--stripe-4x.png" + ), + logo: nil + ), + nextPaneOnSelection: .success + ) + ), + ( + accountPickerAccount: FinancialConnectionsNetworkingAccountPicker.Account( + id: "123", + allowSelection: true, + caption: "Repair and connect account", + selectionCta: nil, + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--warning-orange-3x.png" + ), + selectionCtaIcon: nil, + drawerOnSelection: nil, + accountIcon: nil, + dataAccessNotice: nil + ), + partnerAccount: FinancialConnectionsPartnerAccount( + id: "abc", + name: "Advantage Plus Checking", + displayableAccountNumbers: "1324", + linkedAccountId: nil, + balanceAmount: 100000, + currency: "USD", + supportedPaymentMethodTypes: [.usBankAccount], + allowSelection: true, + allowSelectionMessage: nil, + status: "disabled", + institution: nil, + nextPaneOnSelection: .success + ) + ), + ( + accountPickerAccount: FinancialConnectionsNetworkingAccountPicker.Account( + id: "123", + allowSelection: false, + caption: nil, + selectionCta: nil, + icon: nil, + selectionCtaIcon: nil, + drawerOnSelection: nil, + accountIcon: nil, + dataAccessNotice: nil + ), + partnerAccount: FinancialConnectionsPartnerAccount( + id: "abc", + name: "Advantage Plus Checking", + displayableAccountNumbers: "1324", + linkedAccountId: nil, + balanceAmount: 100000, + currency: "USD", + supportedPaymentMethodTypes: [.usBankAccount], + allowSelection: true, + allowSelectionMessage: nil, + status: "disabled", + institution: nil, + nextPaneOnSelection: .success + ) + ), + ], + addNewAccount: FinancialConnectionsNetworkingAccountPicker.AddNewAccount( + body: "New bank account", + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--add-purple-3x.png" + ) + ), + theme: .light + ) + } + + func updateUIView(_ uiView: LinkAccountPickerBodyView, context: Context) { + uiView.selectAccounts([]) + } +} + +struct LinkAccountPickerBodyView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + Spacer() + LinkAccountPickerBodyViewUIViewRepresentable() + .frame(maxHeight: 300) + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerDataSource.swift new file mode 100644 index 00000000..186dc53c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerDataSource.swift @@ -0,0 +1,130 @@ +// +// LinkLinkAccountPickerDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/13/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol LinkAccountPickerDataSourceDelegate: AnyObject { + func linkAccountPickerDataSource( + _ dataSource: LinkAccountPickerDataSource, + didSelectAccounts selectedAccounts: [FinancialConnectionsAccountTuple] + ) +} + +protocol LinkAccountPickerDataSource: AnyObject { + + var delegate: LinkAccountPickerDataSourceDelegate? { get set } + var manifest: FinancialConnectionsSessionManifest { get } + var selectedAccounts: [FinancialConnectionsAccountTuple] { get } + var nextPaneOnAddAccount: FinancialConnectionsSessionManifest.NextPane? { get set } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var dataAccessNotice: FinancialConnectionsDataAccessNotice? { get } + var acquireConsentOnPrimaryCtaClick: Bool { get } + + func updateSelectedAccounts(_ selectedAccounts: [FinancialConnectionsAccountTuple]) + func fetchNetworkedAccounts() -> Future + func selectNetworkedAccounts( + _ selectedAccounts: [FinancialConnectionsPartnerAccount] + ) -> Future + func markConsentAcquired() -> Future +} + +final class LinkAccountPickerDataSourceImplementation: LinkAccountPickerDataSource { + + let manifest: FinancialConnectionsSessionManifest + var nextPaneOnAddAccount: FinancialConnectionsSessionManifest.NextPane? + let analyticsClient: FinancialConnectionsAnalyticsClient + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + private let consumerSession: ConsumerSessionData + var dataAccessNotice: FinancialConnectionsDataAccessNotice? { + var selectedAccountDataAccessNotice: FinancialConnectionsDataAccessNotice? + if + let networkedAccountsResponse, + let returningNetworkingUserAccountPicker = networkedAccountsResponse.display?.text?.returningNetworkingUserAccountPicker, + !selectedAccounts.isEmpty + { + // Bank accounts can have multiple types (ex. linked account, and manual entry account). + // + // Example output: + // ["bctmacct", "csmrbankacct"] + let selectedAccountTypes = Set(selectedAccounts + .map({ $0.partnerAccount.id.split(separator: "_").first }) + .compactMap({ $0 })) + if selectedAccountTypes.count > 1 { + // if user selected multiple different account types, + // present a special data access notice + selectedAccountDataAccessNotice = returningNetworkingUserAccountPicker.multipleAccountTypesSelectedDataAccessNotice + } else { + // we get here if user selected: + // 1) one account + // 2) or, multiple accounts of the same account type + selectedAccountDataAccessNotice = selectedAccounts.first?.accountPickerAccount.dataAccessNotice + } + } + return selectedAccountDataAccessNotice ?? consentDataAccessNotice + } + private let consentDataAccessNotice: FinancialConnectionsDataAccessNotice? + private var networkedAccountsResponse: FinancialConnectionsNetworkedAccountsResponse? + var acquireConsentOnPrimaryCtaClick: Bool { + return networkedAccountsResponse?.acquireConsentOnPrimaryCtaClick ?? false + } + + private(set) var selectedAccounts: [FinancialConnectionsAccountTuple] = [] { + didSet { + delegate?.linkAccountPickerDataSource(self, didSelectAccounts: selectedAccounts) + } + } + weak var delegate: LinkAccountPickerDataSourceDelegate? + + init( + manifest: FinancialConnectionsSessionManifest, + apiClient: FinancialConnectionsAPIClient, + analyticsClient: FinancialConnectionsAnalyticsClient, + clientSecret: String, + consumerSession: ConsumerSessionData, + dataAccessNotice: FinancialConnectionsDataAccessNotice? + ) { + self.manifest = manifest + self.apiClient = apiClient + self.analyticsClient = analyticsClient + self.clientSecret = clientSecret + self.consumerSession = consumerSession + self.consentDataAccessNotice = dataAccessNotice + } + + func fetchNetworkedAccounts() -> Future { + return apiClient.fetchNetworkedAccounts( + clientSecret: clientSecret, + consumerSessionClientSecret: consumerSession.clientSecret + ) + .chained { [weak self] response in + self?.networkedAccountsResponse = response + self?.nextPaneOnAddAccount = response.nextPaneOnAddAccount + return Promise(value: response) + } + } + + func updateSelectedAccounts(_ selectedAccounts: [FinancialConnectionsAccountTuple]) { + self.selectedAccounts = selectedAccounts + } + + func selectNetworkedAccounts( + _ selectedAccounts: [FinancialConnectionsPartnerAccount] + ) -> Future { + return apiClient.selectNetworkedAccounts( + selectedAccountIds: selectedAccounts.map({ $0.id }), + clientSecret: clientSecret, + consumerSessionClientSecret: consumerSession.clientSecret, + consentAcquired: acquireConsentOnPrimaryCtaClick + ) + } + + func markConsentAcquired() -> Future { + return apiClient.markConsentAcquired(clientSecret: clientSecret) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerFooterView.swift new file mode 100644 index 00000000..0236b171 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerFooterView.swift @@ -0,0 +1,97 @@ +// +// LinkAccountPickerFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/13/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class LinkAccountPickerFooterView: UIView { + + private let defaultCta: String + private let singleAccount: Bool + private let theme: FinancialConnectionsTheme + private let didSelectConnectAccount: () -> Void + + private lazy var connectAccountButton: Button = { + let connectAccountButton = Button.primary(theme: theme) + connectAccountButton.title = defaultCta + connectAccountButton.isEnabled = false // disable by default + connectAccountButton.addTarget(self, action: #selector(didSelectLinkAccountsButton), for: .touchUpInside) + connectAccountButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + connectAccountButton.heightAnchor.constraint(equalToConstant: 56) + ]) + connectAccountButton.accessibilityIdentifier = "connect_accounts_button" + return connectAccountButton + }() + + init( + defaultCta: String, + aboveCta: String?, + singleAccount: Bool, + theme: FinancialConnectionsTheme, + didSelectConnectAccount: @escaping () -> Void, + didSelectMerchantDataAccessLearnMore: @escaping (URL) -> Void + ) { + self.defaultCta = defaultCta + self.singleAccount = singleAccount + self.theme = theme + self.didSelectConnectAccount = didSelectConnectAccount + super.init(frame: .zero) + + let verticalStackView = HitTestStackView() + if let aboveCta { + let merchantDataAccessLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + merchantDataAccessLabel.setText( + aboveCta, + action: didSelectMerchantDataAccessLearnMore + ) + verticalStackView.addArrangedSubview(merchantDataAccessLabel) + } + verticalStackView.addArrangedSubview(connectAccountButton) + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 24, + bottom: 16, + trailing: 24 + ) + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didSelectLinkAccountsButton() { + didSelectConnectAccount() + } + + func didSelectAccounts(_ selectedAccounts: [FinancialConnectionsAccountTuple]) { + if + singleAccount, + let selectionCta = selectedAccounts.first?.accountPickerAccount.selectionCta + { + connectAccountButton.title = selectionCta + } else { + connectAccountButton.title = defaultCta + } + connectAccountButton.isEnabled = !selectedAccounts.isEmpty + } + + func showLoadingView(_ show: Bool) { + connectAccountButton.isLoading = show + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerLoadingView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerLoadingView.swift new file mode 100644 index 00000000..2491054e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerLoadingView.swift @@ -0,0 +1,35 @@ +// +// LinkAccountPickerLoadingView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/26/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class LinkAccountPickerLoadingView: ShimmeringView { + + init() { + super.init(frame: .zero) + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + for _ in 0..<3 { + let linkAccountRowView = UIView() + linkAccountRowView.backgroundColor = .backgroundOffset + linkAccountRowView.layer.cornerRadius = 12 + linkAccountRowView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + linkAccountRowView.heightAnchor.constraint(equalToConstant: 88) + ]) + verticalStackView.addArrangedSubview(linkAccountRowView) + } + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerNewAccountRowView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerNewAccountRowView.swift new file mode 100644 index 00000000..0b61e82f --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerNewAccountRowView.swift @@ -0,0 +1,142 @@ +// +// LinkAccountPickerNewAccountRowView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/14/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class LinkAccountPickerNewAccountRowView: UIView { + + private let didSelect: () -> Void + + init( + title: String, + imageUrl: String?, + theme: FinancialConnectionsTheme, + didSelect: @escaping () -> Void + ) { + self.didSelect = didSelect + super.init(frame: .zero) + + let horizontalStackView = CreateHorizontalStackView() + if let imageUrl = imageUrl { + horizontalStackView.addArrangedSubview( + CreateIconView(imageUrl: imageUrl, theme: theme) + ) + } + horizontalStackView.addArrangedSubview( + CreateTitleLabelView( + title: title + ) + ) + addAndPinSubviewToSafeArea(horizontalStackView) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapView)) + addGestureRecognizer(tapGestureRecognizer) + + layer.cornerRadius = 12 + layer.borderColor = UIColor.borderDefault.cgColor + layer.borderWidth = 1 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didTapView() { + self.didSelect() + } +} + +private func CreateIconView(imageUrl: String, theme: FinancialConnectionsTheme) -> UIView { + RoundedIconView( + image: .imageUrl(imageUrl, placeholder: Image.add), + style: .rounded, + theme: theme + ) +} + +private func CreateTitleLabelView(title: String) -> UIView { + let titleLabel = AttributedLabel( + font: .label(.largeEmphasized), + textColor: .textDefault + ) + titleLabel.text = title + titleLabel.lineBreakMode = .byCharWrapping + return titleLabel +} + +private func CreateHorizontalStackView() -> UIStackView { + let horizontalStackView = UIStackView() + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 12 + horizontalStackView.alignment = .center + horizontalStackView.isLayoutMarginsRelativeArrangement = true + horizontalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 16, + bottom: 16, + trailing: 16 + ) + return horizontalStackView +} + +#if DEBUG + +import SwiftUI + +private struct LinkAccountPickerNewAccountRowViewUIViewRepresentable: UIViewRepresentable { + + let title: String + let imageUrl: String? + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> LinkAccountPickerNewAccountRowView { + return LinkAccountPickerNewAccountRowView( + title: title, + imageUrl: imageUrl, + theme: theme, + didSelect: {} + ) + } + + func updateUIView(_ uiView: LinkAccountPickerNewAccountRowView, context: Context) {} +} + +struct LinkAccountPickerNewAccountRowView_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + ScrollView { + VStack(spacing: 10) { + LinkAccountPickerNewAccountRowViewUIViewRepresentable( + title: "New bank account", + imageUrl: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--add-purple-3x.png", + theme: .light + ) + .frame(height: 88) + + LinkAccountPickerNewAccountRowViewUIViewRepresentable( + title: "New bank account", + imageUrl: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--add-purple-3x.png", + theme: .linkLight + ) + .frame(height: 88) + + LinkAccountPickerNewAccountRowViewUIViewRepresentable( + title: "New bank account", + imageUrl: nil, + theme: .light + ) + .frame(height: 88) + } + .padding() + } + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerViewController.swift new file mode 100644 index 00000000..f2879118 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkAccountPicker/LinkAccountPickerViewController.swift @@ -0,0 +1,754 @@ +// +// LinkLinkAccountPickerViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/13/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol LinkAccountPickerViewControllerDelegate: AnyObject { + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + hideBackButtonOnNextPane: Bool + ) + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + customSuccessPaneCaption: String, + customSuccessPaneSubCaption: String + ) + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + requestedPartnerAuthWithInstitution institution: FinancialConnectionsInstitution + ) + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didReceiveTerminalError error: Error + ) + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +final class LinkAccountPickerViewController: UIViewController { + + private let dataSource: LinkAccountPickerDataSource + weak var delegate: LinkAccountPickerViewControllerDelegate? + private var businessName: String? { + return dataSource.manifest.businessName + } + private lazy var contentStackView: UIStackView = { + let verticalStackView = UIStackView( + arrangedSubviews: [ + // this sets up an initial `headerView` and `bodyView` + // for the loading state + PaneLayoutView.createHeaderView( + iconView: nil, + title: { + if dataSource.manifest.singleAccount { + return STPLocalizedString( + "Select account", + "The title of a screen that allows users to select which bank accounts they want to use to pay for something." + ) + } else { + return STPLocalizedString( + "Select accounts", + "The title of a screen that allows users to select which bank accounts they want to use to pay for something." + ) + } + }() + ), + // `createBodyView` adds extra padding + // around the loading view + PaneLayoutView.createBodyView( + text: nil, + contentView: LinkAccountPickerLoadingView() + ), + ] + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 0 + return verticalStackView + }() + private lazy var footerContainerView: UIView = { + return UIView() + }() + private weak var bodyView: LinkAccountPickerBodyView? + private weak var footerView: LinkAccountPickerFooterView? + + init(dataSource: LinkAccountPickerDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + dataSource.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + // link account picker ALWAYS hides the back button + navigationItem.hidesBackButton = true + view.backgroundColor = .customBackgroundColor + + let paneLayoutView = PaneLayoutView( + contentView: contentStackView, + footerView: footerContainerView + ) + paneLayoutView.addTo(view: view) + + fetchNetworkedAccounts() + } + + private func fetchNetworkedAccounts() { + dataSource + .fetchNetworkedAccounts() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let networkedAccountsResponse): + if networkedAccountsResponse.data.isEmpty { + let nextPaneOnAddAccount = dataSource.nextPaneOnAddAccount ?? .institutionPicker + // Don't show a back button on the next pane so they don't return to an empty list. + self.delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: nextPaneOnAddAccount, + hideBackButtonOnNextPane: true + ) + } else if let returningNetworkingUserAccountPicker = networkedAccountsResponse.display?.text?.returningNetworkingUserAccountPicker { + self.display( + partnerAccounts: networkedAccountsResponse.data, + networkingAccountPicker: returningNetworkingUserAccountPicker + ) + } else { + self.delegate?.linkAccountPickerViewController( + self, + didReceiveTerminalError: FinancialConnectionsSheetError.unknown( + debugDescription: "Tried fetching networked accounts but received no display parameter." + ) + ) + } + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "FetchNetworkedAccountsError", + pane: .linkAccountPicker + ) + self.delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: .institutionPicker, + hideBackButtonOnNextPane: false + ) + } + } + } + + private func display( + partnerAccounts: [FinancialConnectionsPartnerAccount], + networkingAccountPicker: FinancialConnectionsNetworkingAccountPicker + ) { + let accountTuples: [FinancialConnectionsAccountTuple] = ZipAccounts( + partnerAccounts: partnerAccounts, + accountPickerAccounts: networkingAccountPicker.accounts + ) + + let bodyView = LinkAccountPickerBodyView( + accountTuples: accountTuples, + addNewAccount: networkingAccountPicker.addNewAccount, + theme: dataSource.manifest.theme + ) + bodyView.delegate = self + self.bodyView = bodyView + + // clear the stack + contentStackView.subviews.forEach { subview in + subview.removeFromSuperview() + } + contentStackView.addArrangedSubview( + PaneLayoutView.createHeaderView( + iconView: nil, + title: networkingAccountPicker.title + ) + ) + contentStackView.addArrangedSubview( + PaneLayoutView.createBodyView( + text: nil, + contentView: bodyView + ) + ) + + let footerView = LinkAccountPickerFooterView( + defaultCta: networkingAccountPicker.defaultCta, + aboveCta: networkingAccountPicker.aboveCta, + singleAccount: dataSource.manifest.singleAccount, + theme: dataSource.manifest.theme, + didSelectConnectAccount: { [weak self] in + guard let self = self else { + return + } + self.didSelectConnectAccounts() + }, + didSelectMerchantDataAccessLearnMore: { [weak self] _ in + guard let self = self else { return } + self.dataSource + .analyticsClient + .logMerchantDataAccessLearnMore(pane: .linkAccountPicker) + + if let dataAccessNotice = self.dataSource.dataAccessNotice { + let dataAccessNoticeViewController = DataAccessNoticeViewController( + dataAccessNotice: dataAccessNotice, + theme: dataSource.manifest.theme, + didSelectUrl: { [weak self] url in + guard let self = self else { return } + self.didSelectURLInTextFromBackend(url) + } + ) + dataAccessNoticeViewController.present(on: self) + } + } + ) + self.footerView = footerView + footerContainerView.addAndPinSubview(footerView) + + let firstSelectableAccount = accountTuples.first { accountTuple in + accountTuple.accountPickerAccount.allowSelection && accountTuple.accountPickerAccount.drawerOnSelection == nil + } + let firstAccount = [firstSelectableAccount].compactMap({ $0.self }) + dataSource.updateSelectedAccounts(firstAccount) + } + + private func didSelectConnectAccounts() { + let nextPane: FinancialConnectionsSessionManifest.NextPane? = { + if let mostRecentlySelectedAccount = dataSource.selectedAccounts.last { + return mostRecentlySelectedAccount.partnerAccount.nextPaneOnSelection + } else { + return nil + } + }() + guard let nextPane = nextPane else { + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError + .unknown( + debugDescription: "Selected connect account, but next pane is NULL." + ), + errorName: "ConnectUnselectedAccountError", + pane: .linkAccountPicker + ) + // instead of having the user be stuck, we forward them to pick a bank instead + delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: .institutionPicker, + hideBackButtonOnNextPane: false + ) + return + } + + let selectedPartnerAccounts = dataSource.selectedAccounts.map({ $0.partnerAccount }) + + // update data model with selected accounts + delegate?.linkAccountPickerViewController( + self, + didSelectAccounts: selectedPartnerAccounts + ) + + self.delegate?.linkAccountPickerViewController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .accountsSelected) + ) + + dataSource + .analyticsClient + .log( + eventName: "click.link_accounts", + pane: .linkAccountPicker + ) + + dataSource + .analyticsClient + .log( + eventName: "account_picker.accounts_submitted", + parameters: [ + "account_ids": selectedPartnerAccounts.map({ $0.id }) + ], + pane: .linkAccountPicker + ) + + if + dataSource.acquireConsentOnPrimaryCtaClick, + let selectedAccount = dataSource.selectedAccounts.first( + // to better handle multi-select, we want to ensure that + // the account we pick has a `drawerOnSelection` + where: { $0.accountPickerAccount.drawerOnSelection != nil } + ), + let drawerOnSelection = selectedAccount.accountPickerAccount.drawerOnSelection, + IsAccountUpdateRequired(forAccount: selectedAccount.partnerAccount) + { + footerView?.showLoadingView(true) + dataSource + .markConsentAcquired() + .observe { [weak self] result in + guard let self = self else { return } + footerView?.showLoadingView(false) + switch result { + case .success: + self.presentAccountUpdateRequiredDrawer( + drawerOnSelection: drawerOnSelection, + partnerAccount: selectedAccount.partnerAccount + ) + case .failure(let error): + self.dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "ConsentAcquiredError", + pane: .linkAccountPicker + ) + self.delegate?.linkAccountPickerViewController(self, didReceiveTerminalError: error) + } + } + } else if nextPane == .success { + footerView?.showLoadingView(true) + // prevent user from accidentally pressing + // a button on the screen; this is safe because + // next step will transition away from this screen + view.isUserInteractionEnabled = false + + dataSource + .selectNetworkedAccounts(selectedPartnerAccounts) + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let response): + let nextPane = response.nextPane ?? .success + if let successPane = response.displayText?.text?.succcessPane { + self.delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: nextPane, + customSuccessPaneCaption: successPane.caption, + customSuccessPaneSubCaption: successPane.subCaption + ) + } else { + self.delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: nextPane, + hideBackButtonOnNextPane: false + ) + } + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "SelectNetworkedAccountError", + pane: .linkAccountPicker + ) + self.delegate?.linkAccountPickerViewController(self, didReceiveTerminalError: error) + } + } + } else { + + let pushToNextPane = { [weak self] in + guard let self = self else { return } + // we should never push here to these panes here since we will present + // as sheet when the user selects an account that needs to be repaired + // or requires additional permissions (supportability) + if nextPane == .partnerAuth { + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError + .unknown( + debugDescription: "Connecting a supportability account, but user shouldn't be able to." + ), + errorName: "ConnectSupportabilityAccountError", + pane: .linkAccountPicker + ) + delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: .institutionPicker, + hideBackButtonOnNextPane: false + ) + } else if nextPane == .bankAuthRepair { + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError + .unknown( + debugDescription: "Connecting a repair account, but user shouldn't be able to." + ), + errorName: "ConnectRepairAccountError", + pane: .linkAccountPicker + ) + delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: .institutionPicker, + hideBackButtonOnNextPane: false + ) + } else { + // non-sheet next pane -- likely step up + delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: nextPane, + hideBackButtonOnNextPane: false + ) + } + } + + if dataSource.acquireConsentOnPrimaryCtaClick { + footerView?.showLoadingView(true) + dataSource + .markConsentAcquired() + .observe { [weak self] result in + guard let self = self else { return } + footerView?.showLoadingView(false) + switch result { + case .success: + pushToNextPane() + case .failure(let error): + self.dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "ConsentAcquiredError", + pane: .linkAccountPicker + ) + self.delegate?.linkAccountPickerViewController(self, didReceiveTerminalError: error) + } + + } + } else { + pushToNextPane() + } + } + } + + // the "account update required drawer" offers the user two choices: + // 1. re-link bank account by going through the bank auth flow again (partner_auth pane) + // 2. repair the bank account (bank_auth_repair) + private func presentAccountUpdateRequiredDrawer( + drawerOnSelection: FinancialConnectionsGenericInfoScreen, + partnerAccount: FinancialConnectionsPartnerAccount + ) { + let deselectPreviouslySelectedAccount = { [weak self] in + guard let self = self else { return } + self.dataSource.updateSelectedAccounts( + self.dataSource.selectedAccounts.filter( + { $0.partnerAccount.id != partnerAccount.id } + ) + ) + } + + var delayDeselectingAccounts = false + let willDismissSheet = { + if delayDeselectingAccounts { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + deselectPreviouslySelectedAccount() + } + } else { + deselectPreviouslySelectedAccount() + } + } + + let didSelectContinue: () -> Void = { [weak self] in + guard let self else { return } + if partnerAccount.nextPaneOnSelection == .partnerAuth { + if let institution = partnerAccount.institution { + self.delegate?.linkAccountPickerViewController( + self, + requestedPartnerAuthWithInstitution: institution + ) + } else { + self.delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: .institutionPicker, + hideBackButtonOnNextPane: false + ) + } + } + // nextPaneOnSelection == bankAuthRepair + else { + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError + .unknown( + debugDescription: "Updating a repair account, but repairs are not supported in Mobile." + ), + errorName: "UpdateRepairAccountError", + pane: .linkAccountPicker + ) + delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: .institutionPicker, + hideBackButtonOnNextPane: false + ) + } + } + + let genericInfoViewController = GenericInfoViewController( + genericInfoScreen: drawerOnSelection, + theme: dataSource.manifest.theme, + panePresentationStyle: .sheet, + iconView: { + if let institutionIconUrl = partnerAccount.institution?.icon?.default { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institutionIconUrl) + return institutionIconView + } else { + return nil + } + }(), + // "did select continue" + didSelectPrimaryButton: { genericInfoViewController in + // delay deselecting accounts while we animate to the + // next screen to reduce "animation jank" of + // the account getting deselected + delayDeselectingAccounts = true + genericInfoViewController.dismiss( + animated: true, + completion: { + didSelectContinue() + } + ) + }, + // "did select cancel" + didSelectSecondaryButton: { genericInfoViewController in + delayDeselectingAccounts = false + genericInfoViewController.dismiss( + animated: true + ) + }, + didSelectURL: { [weak self] url in + guard let self = self else { return } + self.didSelectURLInTextFromBackend(url) + }, + willDismissSheet: willDismissSheet + ) + genericInfoViewController.present(on: self) + } + + private func didSelectURLInTextFromBackend(_ url: URL) { + AuthFlowHelpers.handleURLInTextFromBackend( + url: url, + pane: .linkAccountPicker, + analyticsClient: self.dataSource.analyticsClient, + handleURL: { _, _ in } + ) + } +} + +// MARK: - LinkAccountPickerBodyViewDelegate + +extension LinkAccountPickerViewController: LinkAccountPickerBodyViewDelegate { + func linkAccountPickerBodyView( + _ view: LinkAccountPickerBodyView, + didSelectAccount selectedAccountTuple: FinancialConnectionsAccountTuple + ) { + FeedbackGeneratorAdapter.selectionChanged() + + let selectedPartnerAccount = selectedAccountTuple.partnerAccount + let eligibleToPresentAccountUpdateRequiredDrawer = IsAccountUpdateRequired( + forAccount: selectedPartnerAccount + ) + + if let drawerOnSelection = selectedAccountTuple.accountPickerAccount.drawerOnSelection { + // we need the `eligibleToPresentAccountUpdateRequiredDrawer` check here because + // of confusing evolution of code...`drawerOnSelection` is used for two different + // types of drawers, so we check `eligibleToPresentAccountUpdateRequiredDrawer` + // to avoid presenting a drawer here because we will present another drawer later + if !eligibleToPresentAccountUpdateRequiredDrawer { + // the "account selection drawer" gives user an explanation of + // why they can't use this bank account + let accountSelectionDrawerViewController = GenericInfoViewController( + genericInfoScreen: drawerOnSelection, + theme: dataSource.manifest.theme, + panePresentationStyle: .sheet, + didSelectPrimaryButton: { genericInfoViewController in + genericInfoViewController.dismiss(animated: true) + }, + didSelectURL: { [weak self] url in + guard let self = self else { return } + self.didSelectURLInTextFromBackend(url) + } + ) + accountSelectionDrawerViewController.present(on: self) + } else { + // we will (likely) be presenting a different drawer further down the function + } + + // this extra `allowSelection` check is necessary because we override + // `isDisabled` for `AccountPickerRowView` when `drawerOnSelection != nil` + if !selectedAccountTuple.accountPickerAccount.allowSelection { + // if the account is not selectable, then we return early + return + } + } + + // unselecting + if + // unselecting in single account flow is not allowed + !dataSource.manifest.singleAccount, + // unselect if the account is already selected + dataSource.selectedAccounts.contains( + where: { $0.partnerAccount.id == selectedPartnerAccount.id } + ) + { + dataSource + .analyticsClient + .log( + eventName: "click.account_picker.account_unselected", + parameters: [ + "account": selectedPartnerAccount.id, + "is_single_account": dataSource.manifest.singleAccount, + ], + pane: .linkAccountPicker + ) + + dataSource.updateSelectedAccounts( + dataSource.selectedAccounts.filter( + { $0.partnerAccount.id != selectedPartnerAccount.id } + ) + ) + } + // selecting + else { + dataSource + .analyticsClient + .log( + eventName: "click.account_picker.account_selected", + parameters: [ + "account": selectedPartnerAccount.id, + "is_single_account": dataSource.manifest.singleAccount, + ], + pane: .linkAccountPicker + ) + + if dataSource.manifest.singleAccount { + dataSource.updateSelectedAccounts([selectedAccountTuple]) + } + // multi-select + else { + dataSource.updateSelectedAccounts( + dataSource.selectedAccounts + [selectedAccountTuple] + ) + } + + // some values for nextPane require immediate action (ie. popping up a sheet for repair) + // as opposed to pushing the next pane upon CTA click (ie. step-up verification) + if eligibleToPresentAccountUpdateRequiredDrawer { + if let drawerOnSelection = selectedAccountTuple.accountPickerAccount.drawerOnSelection { + if selectedPartnerAccount.nextPaneOnSelection == .bankAuthRepair { + dataSource + .analyticsClient + .log( + eventName: "click.repair_accounts", + pane: .linkAccountPicker + ) + } else { + dataSource + .analyticsClient + .log( + eventName: "click.supportability_account", + pane: .linkAccountPicker + ) + } + + // if we need to acquire consent, instead of showing the drawer now, + // we will show the drawer when user presses the CTA where + // pressing the CTA will make a call to `markConsentAcquired` + if !dataSource.acquireConsentOnPrimaryCtaClick { + presentAccountUpdateRequiredDrawer( + drawerOnSelection: drawerOnSelection, + partnerAccount: selectedPartnerAccount + ) + } + } else { + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError.unknown( + debugDescription: "User was eligible for AccountUpdateRequiredDrawer but backend didn't return drawerOnSelection" + ), + errorName: "CantPresentAccountUpdateRequiredDrawer", + pane: .linkAccountPicker + ) + } + } + } + } + + func linkAccountPickerBodyViewSelectedNewBankAccount(_ view: LinkAccountPickerBodyView) { + FeedbackGeneratorAdapter.buttonTapped() + dataSource + .analyticsClient + .log( + eventName: "click.new_account", + pane: .linkAccountPicker + ) + delegate?.linkAccountPickerViewController( + self, + didRequestNextPane: dataSource.nextPaneOnAddAccount ?? .institutionPicker, + hideBackButtonOnNextPane: false + ) + } +} + +// MARK: - LinkAccountPickerDataSourceDelegate + +extension LinkAccountPickerViewController: LinkAccountPickerDataSourceDelegate { + + func linkAccountPickerDataSource( + _ dataSource: LinkAccountPickerDataSource, + didSelectAccounts selectedAccounts: [FinancialConnectionsAccountTuple] + ) { + bodyView?.selectAccounts(selectedAccounts) + footerView?.didSelectAccounts(selectedAccounts) + } +} + +/// Combines two different `account` types into one type +typealias FinancialConnectionsAccountTuple = ( + accountPickerAccount: FinancialConnectionsNetworkingAccountPicker.Account, + partnerAccount: FinancialConnectionsPartnerAccount +) +private func ZipAccounts( + partnerAccounts: [FinancialConnectionsPartnerAccount], + accountPickerAccounts: [FinancialConnectionsNetworkingAccountPicker.Account] +) -> [FinancialConnectionsAccountTuple] { + var accountTuples: [FinancialConnectionsAccountTuple] = [] + let idToPartnerAccount = Dictionary(uniqueKeysWithValues: partnerAccounts.map({ ($0.id, $0) })) + // use `accountPickerAccounts` to determine the order as its + // used for defining how we display the account + for accountPickerAccount in accountPickerAccounts { + if let partnerAccount = idToPartnerAccount[accountPickerAccount.id] { + accountTuples.append((accountPickerAccount, partnerAccount)) + } + } + return accountTuples +} + +private func IsAccountUpdateRequired( + forAccount account: FinancialConnectionsPartnerAccount +) -> Bool { + // repair flow + return account.nextPaneOnSelection == .bankAuthRepair + // supportability -- account requires re-sharing with additonal permissions + || account.nextPaneOnSelection == .partnerAuth + || account.nextPaneOnSelection == .institutionPicker +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkLogin/LinkLoginDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkLogin/LinkLoginDataSource.swift new file mode 100644 index 00000000..423054b1 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkLogin/LinkLoginDataSource.swift @@ -0,0 +1,117 @@ +// +// LinkLoginDataSource.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-07-25. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol LinkLoginDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var elementsSessionContext: ElementsSessionContext? { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + + func synchronize() -> Future + func lookup(emailAddress: String) -> Future + func signUp( + emailAddress: String, + phoneNumber: String, + country: String + ) -> Future + func attachToAccountAndSynchronize( + with linkSignUpResponse: LinkSignUpResponse + ) -> Future +} + +final class LinkLoginDataSourceImplementation: LinkLoginDataSource { + private static let deallocatedError = FinancialConnectionsSheetError.unknown(debugDescription: "data source deallocated") + + let manifest: FinancialConnectionsSessionManifest + let elementsSessionContext: ElementsSessionContext? + let analyticsClient: FinancialConnectionsAnalyticsClient + + private let clientSecret: String + private let returnURL: String? + private let apiClient: FinancialConnectionsAPIClient + + init( + manifest: FinancialConnectionsSessionManifest, + analyticsClient: FinancialConnectionsAnalyticsClient, + clientSecret: String, + returnURL: String?, + apiClient: FinancialConnectionsAPIClient, + elementsSessionContext: ElementsSessionContext? + ) { + self.manifest = manifest + self.analyticsClient = analyticsClient + self.clientSecret = clientSecret + self.returnURL = returnURL + self.apiClient = apiClient + self.elementsSessionContext = elementsSessionContext + } + + func synchronize() -> Future { + apiClient.synchronize( + clientSecret: clientSecret, + returnURL: returnURL + ) + .chained { synchronize in + if let linkLoginPane = synchronize.text?.linkLoginPane { + return Promise(value: linkLoginPane) + } else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "no linkLoginPane data attached")) + } + } + } + + func lookup(emailAddress: String) -> Future { + return apiClient.consumerSessionLookup(emailAddress: emailAddress, clientSecret: clientSecret) + } + + func signUp( + emailAddress: String, + phoneNumber: String, + country: String + ) -> Future { + apiClient.linkAccountSignUp( + emailAddress: emailAddress, + phoneNumber: phoneNumber, + country: country, + amount: elementsSessionContext?.amount, + currency: elementsSessionContext?.currency, + // TODO(tillh): Only pass `intentId` when the session is eligible for incentives. + intentId: nil + ) + } + + func attachToAccountAndSynchronize( + with linkSignUpResponse: LinkSignUpResponse + ) -> Future { + attachLinkConsumerToLinkAccountSessionResponse( + linkAccountSession: clientSecret, + consumerSessionClientSecret: linkSignUpResponse.consumerSession.clientSecret + ) + .chained { [weak self] _ in + guard let self else { + return Promise(error: Self.deallocatedError) + } + + return apiClient.synchronize( + clientSecret: self.clientSecret, + returnURL: self.returnURL + ) + } + } + + private func attachLinkConsumerToLinkAccountSessionResponse( + linkAccountSession: String, + consumerSessionClientSecret: String + ) -> Future { + apiClient.attachLinkConsumerToLinkAccountSession( + linkAccountSession: linkAccountSession, + consumerSessionClientSecret: consumerSessionClientSecret + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkLogin/LinkLoginViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkLogin/LinkLoginViewController.swift new file mode 100644 index 00000000..11740e2f --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/LinkLogin/LinkLoginViewController.swift @@ -0,0 +1,247 @@ +// +// LinkLoginViewController.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-07-25. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol LinkLoginViewControllerDelegate: AnyObject { + func linkLoginViewController( + _ viewController: LinkLoginViewController, + foundReturningUserWith lookupConsumerSessionResponse: LookupConsumerSessionResponse + ) + + func linkLoginViewController( + _ viewController: LinkLoginViewController, + receivedLinkSignUpResponse linkSignUpResponse: LinkSignUpResponse + ) + + func linkLoginViewController( + _ viewController: LinkLoginViewController, + signedUpAttachedAndSynchronized synchronizePayload: FinancialConnectionsSynchronize + ) + + func linkLoginViewController( + _ viewController: LinkLoginViewController, + didReceiveTerminalError error: Error + ) +} + +final class LinkLoginViewController: UIViewController { + private let dataSource: LinkLoginDataSource + weak var delegate: LinkLoginViewControllerDelegate? + + private lazy var loadingView: SpinnerView = { + return SpinnerView(theme: dataSource.manifest.theme) + }() + + private lazy var formView: LinkSignupFormView = { + let formView = LinkSignupFormView( + accountholderPhoneNumber: dataSource.manifest.accountholderPhoneNumber, + theme: dataSource.manifest.theme + ) + formView.delegate = self + return formView + }() + + private var paneLayoutView: PaneLayoutView? + private var footerButton: StripeUICore.Button? + + init(dataSource: LinkLoginDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + + showLoadingView(true) + dataSource + .synchronize() + .observe { [weak self] result in + guard let self else { return } + self.showLoadingView(false) + + switch result { + case .success(let linkLoginPane): + self.showContent(linkLoginPane: linkLoginPane) + case .failure(let error): + self.delegate?.linkLoginViewController(self, didReceiveTerminalError: error) + } + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + setContinueWithLinkButtonDisabledState() + } + + private func showContent(linkLoginPane: FinancialConnectionsLinkLoginPane) { + let contentView = PaneLayoutView.createContentView( + iconView: nil, + title: linkLoginPane.title, + subtitle: linkLoginPane.body, + contentView: formView + ) + let footerView = PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: linkLoginPane.cta, + accessibilityIdentifier: "link_login.primary_button", + action: didSelectContinueWithLink + ), + topText: linkLoginPane.aboveCta, + theme: dataSource.manifest.theme, + didSelectURL: didSelectURLInTextFromBackend + ) + self.footerButton = footerView.primaryButton + + self.paneLayoutView = PaneLayoutView( + contentView: contentView, + footerView: footerView.footerView, + keepFooterAboveKeyboard: true + ) + + paneLayoutView?.addTo(view: view) + + #if !canImport(CompositorServices) + // if user drags, dismiss keyboard so the CTA buttons can be shown + paneLayoutView?.scrollView.keyboardDismissMode = .onDrag + #endif + + let emailAddress = dataSource.manifest.accountholderCustomerEmailAddress ?? dataSource.elementsSessionContext?.prefillDetails?.email + if let emailAddress, !emailAddress.isEmpty { + // Immediately set the button state to loading here to bypass the debouncing by the textfield. + footerButton?.isLoading = true + formView.prefillEmailAddress(emailAddress) + + let phoneNumber = dataSource.manifest.accountholderPhoneNumber ?? dataSource.elementsSessionContext?.prefillDetails?.formattedPhoneNumber + formView.prefillPhoneNumber(phoneNumber) + } else { + // Slightly delay opening the keyboard to avoid a janky animation. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in + self?.formView.beginEditingEmailAddressField() + } + } + + setContinueWithLinkButtonDisabledState() + } + + private func showLoadingView(_ show: Bool) { + if show && loadingView.superview == nil { + view.addAndPinSubviewToSafeArea(loadingView) + } + + loadingView.isHidden = !show + view.bringSubviewToFront(loadingView) + } + + private func didSelectContinueWithLink() { + if formView.phoneNumber.isEmpty { + lookupAccount(with: formView.email) + } else { + createAccount() + } + } + + private func lookupAccount(with emailAddress: String) { + formView.emailTextField.showLoadingView(true) + footerButton?.isLoading = true + + dataSource + .lookup(emailAddress: emailAddress) + .observe { [weak self, weak formView, weak footerButton] result in + formView?.emailTextField.showLoadingView(false) + footerButton?.isLoading = false + + guard let self else { return } + switch result { + case .success(let response): + if response.exists { + if response.consumerSession != nil { + self.delegate?.linkLoginViewController(self, foundReturningUserWith: response) + } else { + self.delegate?.linkLoginViewController( + self, + didReceiveTerminalError: FinancialConnectionsSheetError.unknown( + debugDescription: "No consumer session returned from lookupConsumerSession for emailAddress: \(emailAddress)" + ) + ) + } + } else { + formView?.showAndEditPhoneNumberFieldIfNeeded() + } + case .failure(let error): + self.delegate?.linkLoginViewController(self, didReceiveTerminalError: error) + } + } + } + + private func createAccount() { + footerButton?.isLoading = true + + dataSource.signUp( + emailAddress: formView.email, + phoneNumber: formView.phoneNumber, + country: formView.countryCode + ) + .chained { [weak self] signUpResponse -> Future in + guard let self else { return + Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "data source deallocated")) + } + + self.delegate?.linkLoginViewController(self, receivedLinkSignUpResponse: signUpResponse) + return self.dataSource.attachToAccountAndSynchronize(with: signUpResponse) + } + .observe { [weak self] result in + guard let self else { return } + self.footerButton?.isLoading = false + + switch result { + case .success(let response): + self.delegate?.linkLoginViewController(self, signedUpAttachedAndSynchronized: response) + case .failure(let error): + self.delegate?.linkLoginViewController(self, didReceiveTerminalError: error) + } + } + } + + private func didSelectURLInTextFromBackend(_ url: URL) { + AuthFlowHelpers.handleURLInTextFromBackend( + url: url, + pane: .linkLogin, + analyticsClient: dataSource.analyticsClient, + handleURL: { _, _ in /* Stripe scheme URLs are not expected. */ } + ) + } + + private func setContinueWithLinkButtonDisabledState() { + let isEmailValid = formView.emailTextField.isEmailValid + + if formView.phoneTextField.isHidden { + footerButton?.isEnabled = isEmailValid + } else { + let isPhoneNumberValid = formView.phoneTextField.isPhoneNumberValid + footerButton?.isEnabled = isEmailValid && isPhoneNumberValid + } + } +} + +extension LinkLoginViewController: LinkSignupFormViewDelegate { + func linkSignupFormView(_ view: LinkSignupFormView, didEnterValidEmailAddress emailAddress: String) { + lookupAccount(with: emailAddress) + } + + func linkSignupFormViewDidUpdateFields(_ view: LinkSignupFormView) { + setContinueWithLinkButtonDisabledState() + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryDataSource.swift new file mode 100644 index 00000000..f6894166 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryDataSource.swift @@ -0,0 +1,54 @@ +// +// ManualEntryDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/24/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol ManualEntryDataSource: AnyObject { + + var manifest: FinancialConnectionsSessionManifest { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + + func attachBankAccountToLinkAccountSession(routingNumber: String, accountNumber: String) -> Future< + FinancialConnectionsPaymentAccountResource + > +} + +final class ManualEntryDataSourceImplementation: ManualEntryDataSource { + + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let manifest: FinancialConnectionsSessionManifest + let analyticsClient: FinancialConnectionsAnalyticsClient + private let consumerSessionClientSecret: String? + + init( + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + manifest: FinancialConnectionsSessionManifest, + analyticsClient: FinancialConnectionsAnalyticsClient, + consumerSessionClientSecret: String? + ) { + self.apiClient = apiClient + self.clientSecret = clientSecret + self.manifest = manifest + self.analyticsClient = analyticsClient + self.consumerSessionClientSecret = consumerSessionClientSecret + } + + func attachBankAccountToLinkAccountSession( + routingNumber: String, + accountNumber: String + ) -> Future { + return apiClient.attachBankAccountToLinkAccountSession( + clientSecret: clientSecret, + accountNumber: accountNumber, + routingNumber: routingNumber, + consumerSessionClientSecret: consumerSessionClientSecret + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryErrorView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryErrorView.swift new file mode 100644 index 00000000..25ad0330 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryErrorView.swift @@ -0,0 +1,30 @@ +// +// ManualEntryErrorView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/31/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class ManualEntryErrorView: UIView { + + init(text: String) { + super.init(frame: .zero) + let errorLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textFeedbackCritical, + linkColor: .textFeedbackCritical + ) + errorLabel.setText(text) + addAndPinSubview(errorLabel) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryFormView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryFormView.swift new file mode 100644 index 00000000..b120cf11 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryFormView.swift @@ -0,0 +1,239 @@ +// +// ManualEntryFormView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/24/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import SwiftUI +import UIKit + +protocol ManualEntryFormViewDelegate: AnyObject { + func manualEntryFormViewTextDidChange(_ view: ManualEntryFormView) + func manualEntryFormViewShouldSubmit(_ view: ManualEntryFormView) +} + +final class ManualEntryFormView: UIView { + + enum TestModeValues { + static let routingNumber = "110000000" + static let accountNumber = "000123456789" + } + + weak var delegate: ManualEntryFormViewDelegate? + private lazy var textFieldStackView: UIStackView = { + let textFieldVerticalStackView = UIStackView( + arrangedSubviews: [ + routingNumberTextField, + accountNumberTextField, + accountNumberConfirmationTextField, + ] + ) + textFieldVerticalStackView.axis = .vertical + textFieldVerticalStackView.spacing = 16 + return textFieldVerticalStackView + }() + private var errorView: UIView? + private lazy var routingNumberTextField: RoundedTextField = { + let routingNumberTextField = RoundedTextField( + placeholder: STPLocalizedString( + "Routing number", + "The title of a user-input-field that appears when a user is manually entering their bank account information. It instructs user to type the routing number." + ), + showDoneToolbar: true, + theme: theme + ) + routingNumberTextField.textField.keyboardType = .numberPad + routingNumberTextField.delegate = self + routingNumberTextField.textField.accessibilityIdentifier = "manual_entry_routing_number_text_field" + return routingNumberTextField + }() + private lazy var accountNumberTextField: RoundedTextField = { + let accountNumberTextField = RoundedTextField( + placeholder: STPLocalizedString( + "Account number", + "The title of a user-input-field that appears when a user is manually entering their bank account information. It instructs user to type the account number." + ), + showDoneToolbar: true, + theme: theme + ) + accountNumberTextField.textField.keyboardType = .numberPad + accountNumberTextField.delegate = self + accountNumberTextField.textField.accessibilityIdentifier = "manual_entry_account_number_text_field" + return accountNumberTextField + }() + private lazy var accountNumberConfirmationTextField: RoundedTextField = { + let accountNumberConfirmationTextField = RoundedTextField( + placeholder: STPLocalizedString( + "Confirm account number", + "The title of a user-input-field that appears when a user is manually entering their bank account information. It instructs user to re-type the account number to confirm it." + ), + showDoneToolbar: true, + theme: theme + ) + accountNumberConfirmationTextField.textField.keyboardType = .numberPad + accountNumberConfirmationTextField.delegate = self + accountNumberConfirmationTextField.textField.accessibilityIdentifier = "manual_entry_account_number_confirmation_text_field" + return accountNumberConfirmationTextField + }() + + private let theme: FinancialConnectionsTheme + private var didEndEditingOnceRoutingNumberTextField = false + private var didEndEditingOnceAccountNumberTextField = false + private var didEndEditingOnceAccountNumberConfirmationTextField = false + + var routingAndAccountNumber: (routingNumber: String, accountNumber: String)? { + guard + ManualEntryValidator.validateRoutingNumber(routingNumberTextField.text) == nil + && ManualEntryValidator.validateAccountNumber(accountNumberTextField.text) == nil + && ManualEntryValidator.validateAccountNumberConfirmation( + accountNumberConfirmationTextField.text, + accountNumber: accountNumberTextField.text + ) == nil + else { + return nil + } + return (routingNumberTextField.text, accountNumberTextField.text) + } + + init(isTestMode: Bool, theme: FinancialConnectionsTheme) { + self.theme = theme + super.init(frame: .zero) + + let contentVerticalStackView = UIStackView() + + if isTestMode { + let testModeBannerView = TestModeAutofillBannerView( + context: .account, + theme: theme, + didTapAutofill: applyTestModeValues + ) + contentVerticalStackView.addArrangedSubview(testModeBannerView) + } + + contentVerticalStackView.addArrangedSubview(textFieldStackView) + + contentVerticalStackView.axis = .vertical + contentVerticalStackView.spacing = 16 + addAndPinSubview(contentVerticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func textFieldTextDidChange() { + delegate?.manualEntryFormViewTextDidChange(self) + updateTextFieldErrorStates() + } + + private func updateTextFieldErrorStates() { + // we only show errors if user has previously ended editing the field + + if didEndEditingOnceRoutingNumberTextField { + routingNumberTextField.errorText = ManualEntryValidator.validateRoutingNumber(routingNumberTextField.text) + } + + if didEndEditingOnceAccountNumberTextField { + accountNumberTextField.errorText = ManualEntryValidator.validateAccountNumber(accountNumberTextField.text) + } + + if didEndEditingOnceAccountNumberConfirmationTextField { + accountNumberConfirmationTextField.errorText = ManualEntryValidator.validateAccountNumberConfirmation( + accountNumberConfirmationTextField.text, + accountNumber: accountNumberTextField.text + ) + } + } + + func setError(text: String?) { + if let text = text { + let errorLabel = AttributedTextView( + font: .label(.medium), + boldFont: .label(.mediumEmphasized), + linkFont: .label(.medium), + textColor: .textFeedbackCritical, + linkColor: .textFeedbackCritical, + alignment: .center + ) + errorLabel.setText(text) + let paddingStackView = UIStackView( + arrangedSubviews: [ + errorLabel + ] + ) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 0, + bottom: 8, + trailing: 0 + ) + textFieldStackView.addArrangedSubview(paddingStackView) + self.errorView = paddingStackView + } else { + errorView?.removeFromSuperview() + errorView = nil + } + } + + private func applyTestModeValues() { + routingNumberTextField.text = TestModeValues.routingNumber + accountNumberTextField.text = TestModeValues.accountNumber + accountNumberConfirmationTextField.text = TestModeValues.accountNumber + + delegate?.manualEntryFormViewShouldSubmit(self) + } +} + +// MARK: - RoundedTextFieldDelegate + +extension ManualEntryFormView: RoundedTextFieldDelegate { + + func roundedTextField( + _ textField: RoundedTextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + let currentText = textField.textField.text ?? "" + guard let currentTextChangeRange = Range(range, in: currentText) else { + return false + } + let updatedText = currentText.replacingCharacters(in: currentTextChangeRange, with: string) + + // don't allow the user to type more characters than possible + if textField === routingNumberTextField { + return updatedText.count <= ManualEntryValidator.routingNumberLength + } else if textField === accountNumberTextField + || textField === accountNumberConfirmationTextField + { + return updatedText.count <= ManualEntryValidator.accountNumberMaxLength + } + + assertionFailure("we should never have an unhandled case") + return true + } + + func roundedTextField(_ textField: RoundedTextField, textDidChange text: String) { + textFieldTextDidChange() + } + + func roundedTextFieldUserDidPressReturnKey(_ textField: RoundedTextField) { + // no-op + } + + func roundedTextFieldDidEndEditing(_ textField: RoundedTextField) { + if textField === routingNumberTextField { + didEndEditingOnceRoutingNumberTextField = true + } else if textField === accountNumberTextField { + didEndEditingOnceAccountNumberTextField = true + } else if textField === accountNumberConfirmationTextField { + didEndEditingOnceAccountNumberConfirmationTextField = true + } else { + assertionFailure("we should always be able to reference a textfield") + } + updateTextFieldErrorStates() + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryValidator.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryValidator.swift new file mode 100644 index 00000000..e713d329 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryValidator.swift @@ -0,0 +1,121 @@ +// +// ManualEntryValidator.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/25/22. +// + +import Foundation + +final class ManualEntryValidator { + + static let routingNumberLength = 9 + static let accountNumberMaxLength = 17 + + static func validateRoutingNumber(_ routingNumber: String) -> String? { + if routingNumber.isEmpty { + return STPLocalizedString( + "Routing number is required.", + "An error message that appears when a user is manually entering their bank account information. This error message appears when the user left the 'Routing number' field blank." + ) + } else if !isStringDigits(routingNumber, withExactLength: routingNumberLength) { + return String( + format: STPLocalizedString( + "Please enter %d digits for your routing number.", + "An error message that appears when a user is manually entering their bank account information. %d is replaced with the routing number length (usually 9)." + ), + routingNumberLength + ) + } else if !isUSRoutingNumber(routingNumber) { + return STPLocalizedString( + "Invalid routing number.", + "An error message that appears when a user is manually entering their bank account information." + ) + } else { + return nil + } + } + + static func validateAccountNumber(_ accountNumber: String) -> String? { + if accountNumber.isEmpty { + return STPLocalizedString( + "Account number is required.", + "An error message that appears when a user is manually entering their bank account information. This error message appears when the user left the 'Account number' field blank." + ) + } else if !isStringDigits(accountNumber, withMaxLength: accountNumberMaxLength) { + return String( + format: STPLocalizedString( + "Invalid bank account number: must be at most %d digits long, containing only numbers.", + "An error message that appears when a user is manually entering their bank account information. %d is replaced with the account number length (usually 17)." + ), + accountNumberMaxLength + ) + } else { + return nil + } + } + + static func validateAccountNumberConfirmation( + _ accountNumberConfirmation: String, + accountNumber: String + ) -> String? { + if accountNumberConfirmation.isEmpty { + return STPLocalizedString( + "Confirm the account number.", + "An error message that appears when a user is manually entering their bank account information. This error message appears when the user left the 'Confirm account number' field blank." + ) + } else if accountNumberConfirmation != accountNumber { + return STPLocalizedString( + "Your account numbers don't match.", + "An error message that appears when a user is manually entering their bank account information. This error message tells the user that the account number they typed doesn't match a previously typed account number." + ) + } else { + return nil + } + } + + private static func isStringDigits( + _ string: String, + withMaxLength maxLength: Int + ) -> Bool { + let regex = "^\\d{1,\(maxLength)}$" + return string.range(of: regex, options: [.regularExpression]) != nil + } + + private static func isStringDigits( + _ string: String, + withExactLength exactLength: Int + ) -> Bool { + let regex = "^\\d{\(exactLength)}$" + return string.range(of: regex, options: [.regularExpression]) != nil + } + + private static func isUSRoutingNumber(_ routingNumber: String) -> Bool { + func usRoutingFactor(_ index: Int) -> Int { + let mod3 = index % 3 + if mod3 == 0 { + return 3 + } else if mod3 == 1 { + return 7 + } else { + return 1 + } + } + + if routingNumber.range(of: #"^\d{9}$"#, options: [.regularExpression]) != nil { + let total = routingNumber.enumerated().reduce(0) { partialResult, indexAndCharacter in + let index = indexAndCharacter.offset + let character = String(indexAndCharacter.element) + + // the character cast can't fail because we ensure that + // all characters are digits with the regex + assert(Int(character) != nil) + + return partialResult + (Int(character) ?? 1) * usRoutingFactor(index) + } + return total % 10 == 0 + } else { + return false + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryViewController.swift new file mode 100644 index 00000000..cddeaa90 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ManualEntry/ManualEntryViewController.swift @@ -0,0 +1,179 @@ +// +// ManualEntryViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/23/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol ManualEntryViewControllerDelegate: AnyObject { + func manualEntryViewController( + _ viewController: ManualEntryViewController, + didRequestToContinueWithPaymentAccountResource paymentAccountResource: + FinancialConnectionsPaymentAccountResource, + accountNumberLast4: String + ) +} + +final class ManualEntryViewController: UIViewController { + + private let dataSource: ManualEntryDataSource + weak var delegate: ManualEntryViewControllerDelegate? + + private lazy var manualEntryFormView: ManualEntryFormView = { + let manualEntryFormView = ManualEntryFormView( + isTestMode: dataSource.manifest.isTestMode, + theme: dataSource.manifest.theme + ) + manualEntryFormView.delegate = self + return manualEntryFormView + }() + + private var footerButton: StripeUICore.Button? + private var paneLayoutView: PaneLayoutView? + + init(dataSource: ManualEntryDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + + let footerView = PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: STPLocalizedString( + "Submit", + "The submit button for a screen that allows a user to manually enter their bank account information." + ), + accessibilityIdentifier: "manual_entry_continue_button", + action: didSelectContinue + ), + theme: dataSource.manifest.theme + ) + self.footerButton = footerView.primaryButton + + self.paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: nil, + title: STPLocalizedString( + "Enter bank details", + "The title of a screen that allows a user to manually enter their bank account information." + ), + subtitle: { + let checkingOnly = !dataSource.manifest.product.hasSuffix("onboarding") + if checkingOnly { + if dataSource.manifest.manualEntryUsesMicrodeposits { + return STPLocalizedString( + "Your bank information will be verified via micro-deposits to your account, typically within 1-2 business days. Only checking accounts are supported.", + "The subtitle/description in a screen that allows a user to manually enter their bank account information." + ) + } else { + return STPLocalizedString( + "Only checking accounts are supported.", + "The subtitle/description in a screen that allows a user to manually enter their bank account information." + ) + } + } else { // checking or savings + if dataSource.manifest.manualEntryUsesMicrodeposits { + return STPLocalizedString( + "Your bank information will be verified with micro-deposits to your account. This typically takes 1-2 business days.", + "The subtitle/description in a screen that allows a user to manually enter their bank account information." + ) + } else { + return STPLocalizedString( + "Checking and savings accounts are supported.", + "The subtitle/description in a screen that allows a user to manually enter their bank account information." + ) + } + } + }(), + contentView: manualEntryFormView + ), + footerView: footerView.footerView, + keepFooterAboveKeyboard: true + ) + paneLayoutView?.addTo(view: view) + #if !canImport(CompositorServices) + paneLayoutView?.scrollView.keyboardDismissMode = .onDrag + #endif + + adjustContinueButtonStateIfNeeded() + + dataSource + .analyticsClient + .logPaneLoaded(pane: .manualEntry) + } + + private func didSelectContinue() { + guard let routingAndAccountNumber = manualEntryFormView.routingAndAccountNumber else { + assertionFailure("user should never be able to press continue if we have no routing/account number") + return + } + manualEntryFormView.setError(text: nil) // clear previous error + + footerButton?.isLoading = true + dataSource.attachBankAccountToLinkAccountSession( + routingNumber: routingAndAccountNumber.routingNumber, + accountNumber: routingAndAccountNumber.accountNumber + ).observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let resource): + // note: we are not stopping the footer button loading in the `success` + // case because we will transition to a different screen + // so we want to avoid slight animation 'blip' by stopping + self.delegate? + .manualEntryViewController( + self, + didRequestToContinueWithPaymentAccountResource: resource, + accountNumberLast4: String(routingAndAccountNumber.accountNumber.suffix(4)) + ) + case .failure(let error): + self.footerButton?.isLoading = false + + let errorText: String + if let stripeError = error as? StripeError, case .apiError(let apiError) = stripeError { + errorText = apiError.message ?? stripeError.localizedDescription + } else { + errorText = error.localizedDescription + } + self.manualEntryFormView.setError(text: errorText) + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "ManualEntryAttachBankAccountToLinkAccountSessionError", + pane: .manualEntry + ) + } + } + } + + private func adjustContinueButtonStateIfNeeded() { + footerButton?.isEnabled = (manualEntryFormView.routingAndAccountNumber != nil) + } +} + +// MARK: - ManualEntryFormViewDelegate + +extension ManualEntryViewController: ManualEntryFormViewDelegate { + + func manualEntryFormViewTextDidChange(_ view: ManualEntryFormView) { + adjustContinueButtonStateIfNeeded() + } + + func manualEntryFormViewShouldSubmit(_ view: ManualEntryFormView) { + adjustContinueButtonStateIfNeeded() + didSelectContinue() + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift new file mode 100644 index 00000000..09b16aab --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift @@ -0,0 +1,1593 @@ +// +// NativeFlowController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/6/22. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol NativeFlowControllerDelegate: AnyObject { + + func nativeFlowController( + _ nativeFlowController: NativeFlowController, + didFinish result: HostControllerResult + ) + + func nativeFlowController( + _ nativeFlowController: NativeFlowController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +class NativeFlowController { + + private let dataManager: NativeFlowDataManager + private let navigationController: FinancialConnectionsNavigationController + weak var delegate: NativeFlowControllerDelegate? + + private lazy var navigationBarCloseBarButtonItem: UIBarButtonItem = { + let item = UIBarButtonItem( + image: Image.close.makeImage(template: false), + style: .plain, + target: self, + action: #selector(didSelectNavigationBarCloseButton) + ) + item.tintColor = .iconDefault + item.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5) + return item + }() + + init( + dataManager: NativeFlowDataManager, + navigationController: FinancialConnectionsNavigationController + ) { + self.dataManager = dataManager + self.navigationController = navigationController + navigationController.analyticsClient = dataManager.analyticsClient + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationDidEnterBackground), + name: UIApplication.didEnterBackgroundNotification, + object: nil + ) + } + + func startFlow() { + assert(navigationController.analyticsClient != nil) + let pane = dataManager.manifest.nextPane + guard + let viewController = CreatePaneViewController( + pane: pane, + nativeFlowController: self, + dataManager: dataManager + ) + else { + assertionFailure( + "We should always get a view controller for the first pane: \(dataManager.manifest.nextPane)" + ) + showTerminalError() + return + } + if pane == .manualEntry && dataManager.manifest.manualEntryMode == .custom { + // if we ever activate "manual entry only" mode (ex. due to an incident) + // then also handle "custom manual entry mode" + closeAuthFlow(customManualEntry: true) + } else { + setNavigationControllerViewControllers([viewController], animated: false) + } + } + + @objc private func didSelectNavigationBarCloseButton() { + FeedbackGeneratorAdapter.buttonTapped() + dataManager.analyticsClient.log( + eventName: "click.nav_bar.close", + pane: FinancialConnectionsAnalyticsClient + .paneFromViewController(navigationController.topViewController) + ) + + let showConfirmationAlert = !( + navigationController.topViewController is ConsentViewController + || navigationController.topViewController is SuccessViewController + || navigationController.topViewController is TerminalErrorViewController + || ((navigationController.topViewController as? ErrorViewController)?.isTerminal == true) + ) + + let finishClosingAuthFlow = { [weak self] in + self?.closeAuthFlow() + } + if showConfirmationAlert { + let closeConfirmationViewController = CloseConfirmationViewController( + theme: dataManager.manifest.theme, + didSelectClose: { + finishClosingAuthFlow() + } + ) + closeConfirmationViewController.present(on: navigationController) + } else { + finishClosingAuthFlow() + } + } + + @objc private func applicationWillEnterForeground() { + dataManager + .analyticsClient + .log( + eventName: "mobile.app_entered_foreground", + pane: FinancialConnectionsAnalyticsClient + .paneFromViewController(navigationController.topViewController) + ) + } + + @objc private func applicationDidEnterBackground() { + dataManager + .analyticsClient + .log( + eventName: "mobile.app_entered_background", + pane: FinancialConnectionsAnalyticsClient + .paneFromViewController(navigationController.topViewController) + ) + } +} + +// MARK: - Core Navigation Helpers + +extension NativeFlowController { + + private func setNavigationControllerViewControllers( + _ viewControllers: [UIViewController], + animated: Bool = true + ) { + dismissVisibleSheetsIfNeeded { [weak self] in + guard let self else { return } + viewControllers.forEach { viewController in + FinancialConnectionsNavigationController.configureNavigationItemForNative( + viewController.navigationItem, + closeItem: self.navigationBarCloseBarButtonItem, + shouldHideLogo: ShouldHideLogoInNavigationBar( + forViewController: viewController, + reducedBranding: self.dataManager.reducedBranding, + merchantLogo: self.dataManager.merchantLogo + ), + theme: self.dataManager.manifest.theme, + isTestMode: self.dataManager.manifest.isTestMode + ) + } + self.navigationController.setViewControllers(viewControllers, animated: animated) + } + } + + private func pushPane( + _ pane: FinancialConnectionsSessionManifest.NextPane, + parameters: CreatePaneParameters? = nil, + animated: Bool, + // useful for cases where we want to prevent the user from navigating back + // + // keeping this logic in `pushPane` is helpful because we want to + // reuse `skipSuccessPane` and `manualEntryMode == .custom` logic + clearNavigationStack: Bool = false + ) { + if pane == .success && dataManager.manifest.skipSuccessPane == true { + closeAuthFlow(error: nil) + } else if pane == .manualEntry && dataManager.manifest.manualEntryMode == .custom { + closeAuthFlow(customManualEntry: true) + } else { + let paneViewController = CreatePaneViewController( + pane: pane, + parameters: parameters, + nativeFlowController: self, + dataManager: dataManager + ) + if clearNavigationStack, let paneViewController = paneViewController { + setNavigationControllerViewControllers([paneViewController], animated: animated) + } else { + pushViewController(paneViewController, animated: animated) + } + } + } + + private func pushViewController(_ viewController: UIViewController?, animated: Bool) { + dismissVisibleSheetsIfNeeded { [weak self] in + guard let self else { return } + if let viewController = viewController { + FinancialConnectionsNavigationController.configureNavigationItemForNative( + viewController.navigationItem, + closeItem: self.navigationBarCloseBarButtonItem, + shouldHideLogo: ShouldHideLogoInNavigationBar( + forViewController: viewController, + reducedBranding: self.dataManager.reducedBranding, + merchantLogo: self.dataManager.merchantLogo + ), + theme: self.dataManager.manifest.theme, + isTestMode: self.dataManager.manifest.isTestMode + ) + self.navigationController.pushViewController(viewController, animated: animated) + } else { + // when we can't find a view controller to present, + // show a terminal error + self.showTerminalError() + } + } + } + + private func presentPaneAsSheet( + _ pane: FinancialConnectionsSessionManifest.NextPane, + parameters: CreatePaneParameters? = nil + ) { + let paneViewController = CreatePaneViewController( + pane: pane, + parameters: parameters, + nativeFlowController: self, + dataManager: dataManager, + panePresentationStyle: .sheet + ) + guard let paneViewController = paneViewController as? SheetViewController else { + assertionFailure("expected the pane to always be a sheet if `presentAsSheet` is used") + pushPane(pane, animated: true) + return + } + paneViewController.present(on: navigationController) + } + + private func dismissVisibleSheetsIfNeeded( + animated: Bool = true, + completionHandler: @escaping () -> Void + ) { + if let viewController = navigationController.presentedViewController { + viewController.dismiss( + animated: animated, + completion: { [weak self] in + // recursively dismiss any presented VC until + // there are none + // + // this is likely not needed, but it's there as + // an extra safe-guard + self?.dismissVisibleSheetsIfNeeded(completionHandler: completionHandler) + } + ) + } else { + completionHandler() + } + } + + private func dismissCurrentPane(animated: Bool) { + if + let sheetViewController = navigationController.presentedViewController as? SheetViewController, + sheetViewController.panePresentationStyle == .sheet + { + sheetViewController.dismiss(animated: animated) + } else { + navigationController.popViewController(animated: animated) + } + } +} + +// MARK: - Other Helpers + +extension NativeFlowController { + + private func didSelectAnotherBank() { + if dataManager.manifest.disableLinkMoreAccounts { + closeAuthFlow(error: nil) + } else { + startResetFlow() + } + } + + private func startResetFlow() { + guard + let resetFlowViewController = CreatePaneViewController( + pane: .resetFlow, + nativeFlowController: self, + dataManager: dataManager + ) + else { + assertionFailure( + "We should always get a view controller for \(FinancialConnectionsSessionManifest.NextPane.resetFlow)" + ) + showTerminalError() + return + } + + var viewControllers: [UIViewController] = [] + if let consentViewController = navigationController.viewControllers.first as? ConsentViewController { + viewControllers.append(consentViewController) + } + viewControllers.append(resetFlowViewController) + + setNavigationControllerViewControllers(viewControllers, animated: true) + } + + private func showTerminalError(_ error: Error? = nil) { + let terminalError: Error + if let error = error { + terminalError = error + } else { + terminalError = FinancialConnectionsSheetError.unknown( + debugDescription: + "Unknown terminal error. It is likely that we couldn't find a view controller for a specific pane." + ) + } + dataManager.terminalError = terminalError // needs to be set to create `terminalError` pane + + guard + let terminalErrorViewController = CreatePaneViewController( + pane: .terminalError, + nativeFlowController: self, + dataManager: dataManager + ) + else { + assertionFailure( + "We should always get a view controller for \(FinancialConnectionsSessionManifest.NextPane.terminalError)" + ) + closeAuthFlow(error: terminalError) + return + } + setNavigationControllerViewControllers([terminalErrorViewController], animated: false) + } + + // There's at least four types of close cases: + // 1. User closes, and accounts are returned (or `paymentAccount` or `bankAccountToken`). That's a success. + // 2. User closes, no accounts are returned, and there's an error. That's a failure. + // 3. User closes, no accounts are returned, and there's no error. That's a cancel. + // 4. User closes, and fetching accounts returns an error. That's a failure. + private func closeAuthFlow( + customManualEntry: Bool = false, + error closeAuthFlowError: Error? = nil // user can also close AuthFlow while looking at an error screen + ) { + if customManualEntry { + // if we don't display manual entry pane, and instead skip it + // we still want to log that we initiated manual entry + delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .manualEntryInitiated) + ) + } + + let finishAuthSession: (HostControllerResult) -> Void = { [weak self] result in + guard let self = self else { return } + let updatedResult = result.updateWith(self.dataManager.manifest) + self.delegate?.nativeFlowController(self, didFinish: updatedResult) + } + + dataManager + .completeFinancialConnectionsSession( + terminalError: customManualEntry ? "user_initiated_with_custom_manual_entry" : nil + ) + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let session): + let eventType = "object" + if session.status == .cancelled + && session.statusDetails?.cancelled?.reason == .customManualEntry + { + self.logCompleteEvent( + type: eventType, + status: "custom_manual_entry" + ) + finishAuthSession(.failed(error: FinancialConnectionsCustomManualEntryRequiredError())) + } else { + if !session.accounts.data.isEmpty || session.paymentAccount != nil + || session.bankAccountToken != nil + { + if dataManager.manifest.isProductInstantDebits { + // For Instant Debits, create a payment method and complete with it. + createPaymentMethod(for: session) { result in + switch result { + case .success(let linkedBank): + self.delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent( + name: .success, + metadata: FinancialConnectionsEvent.Metadata( + manualEntry: session.paymentAccount?.isManualEntry ?? false + ) + ) + ) + self.logCompleteEvent( + type: eventType, + status: "completed", + numberOfLinkedAccounts: session.accounts.data.count + ) + finishAuthSession(.completed(.instantDebits(linkedBank))) + case .failure(let createPaymentError): + self.logCompleteEvent( + type: eventType, + status: "failed", + error: createPaymentError + ) + finishAuthSession(.failed(error: createPaymentError)) + } + } + } else { + // Otherwise, complete with the existing session details. + self.delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent( + name: .success, + metadata: FinancialConnectionsEvent.Metadata( + manualEntry: session.paymentAccount?.isManualEntry ?? false + ) + ) + ) + self.logCompleteEvent( + type: eventType, + status: "completed", + numberOfLinkedAccounts: session.accounts.data.count + ) + finishAuthSession(.completed(.financialConnections(session))) + } + } else if let closeAuthFlowError = closeAuthFlowError { + self.logCompleteEvent( + type: eventType, + status: "failed", + error: closeAuthFlowError + ) + finishAuthSession(.failed(error: closeAuthFlowError)) + } else { + if let terminalError = self.dataManager.terminalError { + self.logCompleteEvent( + type: eventType, + status: "failed", + error: terminalError + ) + finishAuthSession(.failed(error: terminalError)) + } else { + self.delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .cancel) + ) + self.logCompleteEvent( + type: eventType, + status: "canceled" + ) + finishAuthSession(.canceled) + } + } + } + case .failure(let completeFinancialConnectionsSessionError): + self.dataManager + .analyticsClient + .logUnexpectedError( + completeFinancialConnectionsSessionError, + errorName: "CompleteSessionError", + pane: FinancialConnectionsAnalyticsClient + .paneFromViewController(self.navigationController.topViewController) + ) + self.logCompleteEvent( + type: "error", + status: "failed", + error: completeFinancialConnectionsSessionError + ) + + if let closeAuthFlowError = closeAuthFlowError { + finishAuthSession(.failed(error: closeAuthFlowError)) + } else { + finishAuthSession(.failed(error: completeFinancialConnectionsSessionError)) + } + } + } + } + + private func createPaymentMethod( + for session: StripeAPI.FinancialConnectionsSession, + completion: @escaping (Result) -> Void + ) { + // Extract bank account ID from the session + let bankAccountId: String? + switch session.paymentAccount { + case .bankAccount(let account): + bankAccountId = account.id + case .linkedAccount(let account): + bankAccountId = account.id + default: + bankAccountId = nil + } + + // Validate bank account ID + guard let bankAccountId else { + let error = "InstantDebitsCompletionError: No bank account ID available when trying to create a payment method." + completion(.failure(FinancialConnectionsSheetError.unknown(debugDescription: error))) + return + } + + // Validate consumer session + guard let consumerSession = dataManager.consumerSession else { + let error = "InstantDebitsCompletionError: No consumer session available when trying to create a payment method." + completion(.failure(FinancialConnectionsSheetError.unknown(debugDescription: error))) + return + } + + // Bank account details extraction for the linked bank + var bankAccountDetails: BankAccountDetails? + let elementsSessionContext = dataManager.elementsSessionContext + let linkMode = elementsSessionContext?.linkMode + let email = elementsSessionContext?.billingDetails?.email ?? dataManager.consumerSession?.emailAddress + let phone = elementsSessionContext?.billingDetails?.phone + dataManager.createPaymentDetails( + consumerSessionClientSecret: consumerSession.clientSecret, + bankAccountId: bankAccountId, + billingAddress: elementsSessionContext?.billingAddress, + billingEmail: email + ) + .chained { [weak self] paymentDetails -> Future in + guard let self else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "data source deallocated")) + } + + bankAccountDetails = paymentDetails.redactedPaymentDetails.bankAccountDetails + + // Decide which API to call based on the payment mode + if let linkMode, linkMode.isPantherPayment { + return self.dataManager.apiClient.sharePaymentDetails( + consumerSessionClientSecret: consumerSession.clientSecret, + paymentDetailsId: paymentDetails.redactedPaymentDetails.id, + expectedPaymentMethodType: linkMode.expectedPaymentMethodType, + billingEmail: email, + billingPhone: phone + ) + .transformed { $0.paymentMethod } + } else { + return self.dataManager.apiClient.paymentMethods( + consumerSessionClientSecret: consumerSession.clientSecret, + paymentDetailsId: paymentDetails.redactedPaymentDetails.id, + billingDetails: elementsSessionContext?.billingDetails + ) + } + } + .observe { result in + switch result { + case .success(let paymentMethod): + let linkedBank = InstantDebitsLinkedBank( + paymentMethod: paymentMethod, + bankName: bankAccountDetails?.bankName, + last4: bankAccountDetails?.last4, + linkMode: linkMode + ) + completion(.success(linkedBank)) + case .failure(let error): + completion(.failure(error)) + } + } + } + + private func logCompleteEvent( + type: String, + status: String, + numberOfLinkedAccounts: Int? = nil, + error: Error? = nil + ) { + var parameters: [String: Any] = [ + "type": type, + "status": status, + ] + parameters["num_linked_accounts"] = numberOfLinkedAccounts + if let error = error { + if let stripeError = error as? StripeError, + case .apiError(let apiError) = stripeError + { + parameters["error_type"] = apiError.type.rawValue + parameters["error_message"] = apiError.message + parameters["code"] = apiError.code + } else { + parameters["error_type"] = (error as NSError).domain + parameters["error_message"] = (error as NSError).localizedDescription + parameters["code"] = (error as NSError).code + } + } + dataManager + .analyticsClient + .log( + eventName: "complete", + parameters: parameters, + pane: FinancialConnectionsAnalyticsClient + .paneFromViewController(navigationController.topViewController) + ) + } + + private func showErrorPane( + forError error: Error, + referrerPane: FinancialConnectionsSessionManifest.NextPane + ) { + // the error pane acts as a replacement + // for the current pane so we need to first + // dismiss the current pane + dismissCurrentPane(animated: false) + + dataManager.errorPaneError = error + dataManager.errorPaneReferrerPane = referrerPane + pushPane(.unexpectedError, animated: false) + } +} + +// MARK: - ConsentViewControllerDelegate + +extension NativeFlowController: ConsentViewControllerDelegate { + + func consentViewController( + _ viewController: ConsentViewController, + didConsentWithManifest manifest: FinancialConnectionsSessionManifest + ) { + delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .consentAcquired) + ) + + dataManager.manifest = manifest + + let nextPane = manifest.nextPane + if nextPane == .networkingLinkLoginWarmup { + presentPaneAsSheet(nextPane) + } else { + pushPane(nextPane, animated: true) + } + } + + func consentViewController( + _ viewController: ConsentViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + nextPaneOrDrawerOnSecondaryCta: String? + ) { + let parameters = CreatePaneParameters( + nextPaneOrDrawerOnSecondaryCta: nextPaneOrDrawerOnSecondaryCta + ) + if nextPane == .networkingLinkLoginWarmup { + presentPaneAsSheet(nextPane, parameters: parameters) + } else { + pushPane(nextPane, parameters: parameters, animated: true) + } + } +} + +// MARK: - InstitutionPickerViewControllerDelegate + +extension NativeFlowController: InstitutionPickerViewControllerDelegate { + + func institutionPickerViewController( + _ viewController: InstitutionPickerViewController, + didSelect institution: FinancialConnectionsInstitution + ) { + // necessary to pass on institution for `ErrorViewController` + dataManager.institution = institution + } + + func institutionPickerViewController( + _ viewController: InstitutionPickerViewController, + didFinishSelecting institution: FinancialConnectionsInstitution, + authSession: FinancialConnectionsAuthSession + ) { + delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent( + name: .institutionSelected, + metadata: FinancialConnectionsEvent.Metadata( + institutionName: institution.name + ) + ) + ) + dataManager.institution = institution + dataManager.authSession = authSession + + if authSession.isOauthNonOptional { + presentPaneAsSheet(.partnerAuth) + } else { + pushPane(.partnerAuth, animated: true) + } + } + + func institutionPickerViewControllerDidSelectManuallyAddYourAccount( + _ viewController: InstitutionPickerViewController + ) { + pushPane(.manualEntry, animated: true) + } + + func institutionPickerViewControllerDidSearch( + _ viewController: InstitutionPickerViewController + ) { + delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .searchInitiated) + ) + } + + func institutionPickerViewController( + _ viewController: InstitutionPickerViewController, + didReceiveError error: Error + ) { + showErrorPane(forError: error, referrerPane: .institutionPicker) + } +} + +// MARK: - PartnerAuthViewControllerDelegate + +extension NativeFlowController: PartnerAuthViewControllerDelegate { + + func partnerAuthViewControllerDidRequestToGoBack(_ viewController: PartnerAuthViewController) { + dataManager.authSession = nil // clear any lingering auth sessions + + switch viewController.panePresentationStyle { + case .sheet: + viewController.dismiss(animated: true) + case .fullscreen: + navigationController.popViewController(animated: true) + } + } + + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didCompleteWithAuthSession authSession: FinancialConnectionsAuthSession + ) { + delegate?.nativeFlowController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .institutionAuthorized) + ) + + dataManager.authSession = authSession + + // This is a weird thing to do, but effectively we don't want to + // animate for OAuth since we make the authorize call in that case + // and already have the same loading screen. + let shouldAnimate = !authSession.isOauthNonOptional + pushPane(.accountPicker, animated: shouldAnimate) + } + + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) { + delegate?.nativeFlowController(self, didReceiveEvent: event) + } + + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didReceiveError error: Error + ) { + dataManager.authSession = nil // clear any lingering auth sessions + + showErrorPane(forError: error, referrerPane: .partnerAuth) + } +} + +// MARK: - AccountPickerViewControllerDelegate + +extension NativeFlowController: AccountPickerViewControllerDelegate { + + func accountPickerViewController( + _ viewController: AccountPickerViewController, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount], + nextPane: FinancialConnectionsSessionManifest.NextPane, + customSuccessPaneMessage: String?, + saveToLinkWithStripeSucceeded: Bool? + ) { + dataManager.linkedAccounts = selectedAccounts + dataManager.customSuccessPaneSubCaption = customSuccessPaneMessage + if let saveToLinkWithStripeSucceeded { + dataManager.saveToLinkWithStripeSucceeded = saveToLinkWithStripeSucceeded + } + + // this prevents an unnecessary push transition when presenting `attachLinkedPaymentAccount` + // + // `attachLinkedPaymentAccount` looks the same as the last step of `accountPicker` + // so navigating to a "Linking account" loading screen can look buggy to the user + let isAnimated = (nextPane != .attachLinkedPaymentAccount) + pushPane(nextPane, animated: isAnimated) + } + + func accountPickerViewControllerDidSelectAnotherBank(_ viewController: AccountPickerViewController) { + didSelectAnotherBank() + } + + func accountPickerViewControllerDidSelectManualEntry(_ viewController: AccountPickerViewController) { + pushPane(.manualEntry, animated: true) + } + + func accountPickerViewController( + _ viewController: AccountPickerViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } + + func accountPickerViewController( + _ viewController: AccountPickerViewController, + didReceiveEvent event: StripeCore.FinancialConnectionsEvent + ) { + delegate?.nativeFlowController(self, didReceiveEvent: event) + } +} + +// MARK: - SuccessViewControllerDelegate + +extension NativeFlowController: SuccessViewControllerDelegate { + + func successViewControllerDidSelectDone(_ viewController: SuccessViewController) { + closeAuthFlow(error: nil) + } +} + +// MARK: - ManualEntryViewControllerDelegate + +extension NativeFlowController: ManualEntryViewControllerDelegate { + + func manualEntryViewController( + _ viewController: ManualEntryViewController, + didRequestToContinueWithPaymentAccountResource paymentAccountResource: + FinancialConnectionsPaymentAccountResource, + accountNumberLast4: String + ) { + // #ir-magnesium-presser; keeping accounts selected can lead to them being passed along + // to the Link signup/save call later in the flow. We don't need them anymore since we know + // they've failed us in some way at this point. + dataManager.linkedAccounts = nil + + dataManager.paymentAccountResource = paymentAccountResource + dataManager.accountNumberLast4 = accountNumberLast4 + + if dataManager.manifest.manualEntryUsesMicrodeposits { + dataManager.customSuccessPaneCaption = STPLocalizedString( + "Almost there", + "The title of the success screen that appears when a user manually entered their bank account information." + ) + dataManager.customSuccessPaneSubCaption = String( + format: STPLocalizedString( + "You can expect micro-deposits to account ••••%@ in 1-2 days and an email with further instructions.", + "The subtitle/description of the success screen that appears when a user manually entered their bank account information. It informs the user that their bank account information will have to be verified." + ), + accountNumberLast4 + ) + } + pushPane(paymentAccountResource.nextPane ?? .success, animated: true) + } +} + +// MARK: - ResetFlowViewControllerDelegate + +extension NativeFlowController: ResetFlowViewControllerDelegate { + + func resetFlowViewController( + _ viewController: ResetFlowViewController, + didSucceedWithManifest manifest: FinancialConnectionsSessionManifest + ) { + assert(navigationController.topViewController is ResetFlowViewController) + if navigationController.topViewController is ResetFlowViewController { + // remove ResetFlowViewController from the navigation stack + if navigationController.viewControllers.count == 1 { + // there's a chance that `ResetFlowViewController` + // is the only VC on the stack and `popViewController` + // will not work + // + // scenario: + // 1. be returning Link consumer + // 2. press "Not Now" from warm up pane + // 3. go through reset flow + // (ex. select down bank scheduled > select another bank) + navigationController.setViewControllers([], animated: false) + } else { + navigationController.popViewController(animated: false) + } + } + + // reset all the state because we are starting + // a new auth session + dataManager.resetState(withNewManifest: manifest) + + // go to the next pane (likely `institutionPicker`) + pushPane(manifest.nextPane, animated: false) + } + + func resetFlowViewController( + _ viewController: ResetFlowViewController, + didFailWithError error: Error + ) { + closeAuthFlow(error: error) + } +} + +// MARK: - NetworkingLinkSignupViewControllerDelegate + +extension NativeFlowController: NetworkingLinkSignupViewControllerDelegate { + + func networkingLinkSignupViewController( + _ viewController: NetworkingLinkSignupViewController, + foundReturningConsumerWithSession consumerSession: ConsumerSessionData + ) { + dataManager.consumerSession = consumerSession + pushPane(.networkingSaveToLinkVerification, animated: true) + } + + func networkingLinkSignupViewControllerDidFinish( + _ viewController: NetworkingLinkSignupViewController, + saveToLinkWithStripeSucceeded: Bool?, + customSuccessPaneMessage: String?, + withError error: Error? + ) { + if let customSuccessPaneMessage { + dataManager.customSuccessPaneSubCaption = customSuccessPaneMessage + } + if saveToLinkWithStripeSucceeded != nil { + dataManager.saveToLinkWithStripeSucceeded = saveToLinkWithStripeSucceeded + } + pushPane(.success, animated: true) + } + + func networkingLinkSignupViewController( + _ viewController: NetworkingLinkSignupViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } +} + +// MARK: - NetworkingLinkLoginWarmupViewControllerDelegate + +extension NativeFlowController: NetworkingLinkLoginWarmupViewControllerDelegate { + + func networkingLinkLoginWarmupViewControllerDidSelectContinue( + _ viewController: NetworkingLinkLoginWarmupViewController + ) { + pushPane(.networkingLinkVerification, animated: true) + } + + func networkingLinkLoginWarmupViewControllerDidSelectCancel( + _ viewController: NetworkingLinkLoginWarmupViewController + ) { + viewController.dismiss(animated: true) + } + + func networkingLinkLoginWarmupViewController( + _ viewController: NetworkingLinkLoginWarmupViewController, + didSelectSkipWithManifest manifest: FinancialConnectionsSessionManifest + ) { + dataManager.manifest = manifest + pushPane( + manifest.nextPane, + animated: true, + // skipping disables networking, which means + // we don't want the user to navigate back to + // the warm-up pane + clearNavigationStack: true + ) + } + + func networkingLinkLoginWarmupViewController( + _ viewController: NetworkingLinkLoginWarmupViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } +} + +// MARK: - TerminalErrorViewControllerDelegate + +extension NativeFlowController: TerminalErrorViewControllerDelegate { + + func terminalErrorViewController( + _ viewController: TerminalErrorViewController, + didCloseWithError error: Error + ) { + closeAuthFlow(error: error) + } + + func terminalErrorViewControllerDidSelectManualEntry(_ viewController: TerminalErrorViewController) { + pushPane(.manualEntry, animated: true) + } +} + +// MARK: - AttachLinkedPaymentAccountViewControllerDelegate + +extension NativeFlowController: AttachLinkedPaymentAccountViewControllerDelegate { + + func attachLinkedPaymentAccountViewController( + _ viewController: AttachLinkedPaymentAccountViewController, + didFinishWithPaymentAccountResource paymentAccountResource: FinancialConnectionsPaymentAccountResource, + saveToLinkWithStripeSucceeded: Bool? + ) { + if saveToLinkWithStripeSucceeded != nil { + dataManager.saveToLinkWithStripeSucceeded = saveToLinkWithStripeSucceeded + } + pushPane(paymentAccountResource.nextPane ?? .success, animated: true) + } + + func attachLinkedPaymentAccountViewControllerDidSelectAnotherBank( + _ viewController: AttachLinkedPaymentAccountViewController + ) { + didSelectAnotherBank() + } + + func attachLinkedPaymentAccountViewControllerDidSelectManualEntry( + _ viewController: AttachLinkedPaymentAccountViewController + ) { + pushPane(.manualEntry, animated: true) + } + + func attachLinkedPaymentAccountViewController( + _ viewController: AttachLinkedPaymentAccountViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) { + delegate?.nativeFlowController(self, didReceiveEvent: event) + } +} + +// MARK: - NetworkingLinkVerificationViewControllerDelegate + +extension NativeFlowController: NetworkingLinkVerificationViewControllerDelegate { + func networkingLinkVerificationViewController(_ viewController: NetworkingLinkVerificationViewController, didReceiveConsumerPublishableKey consumerPublishableKey: String) { + dataManager.consumerPublishableKey = consumerPublishableKey + } + + func networkingLinkVerificationViewController( + _ viewController: NetworkingLinkVerificationViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + consumerSession: ConsumerSessionData?, + preventBackNavigation: Bool + ) { + dataManager.consumerSession = consumerSession + pushPane(nextPane, animated: true, clearNavigationStack: preventBackNavigation) + } + + func networkingLinkVerificationViewController( + _ viewController: NetworkingLinkVerificationViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } +} + +// MARK: - LinkAccountPickerViewControllerDelegate + +extension NativeFlowController: LinkAccountPickerViewControllerDelegate { + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didSelectAccounts selectedAccounts: [FinancialConnectionsPartnerAccount] + ) { + dataManager.linkedAccounts = selectedAccounts + } + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + requestedPartnerAuthWithInstitution institution: FinancialConnectionsInstitution + ) { + dataManager.institution = institution + pushPane(.partnerAuth, animated: true) + } + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + hideBackButtonOnNextPane: Bool + ) { + pushPane(nextPane, animated: true, clearNavigationStack: hideBackButtonOnNextPane) + } + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + customSuccessPaneCaption: String, + customSuccessPaneSubCaption: String + ) { + dataManager.customSuccessPaneCaption = customSuccessPaneCaption + dataManager.customSuccessPaneSubCaption = customSuccessPaneSubCaption + pushPane(nextPane, animated: true) + } + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } + + func linkAccountPickerViewController( + _ viewController: LinkAccountPickerViewController, + didReceiveEvent event: StripeCore.FinancialConnectionsEvent + ) { + delegate?.nativeFlowController(self, didReceiveEvent: event) + } +} + +// MARK: - NetworkingSaveToLinkVerificationDelegate + +extension NativeFlowController: NetworkingSaveToLinkVerificationViewControllerDelegate { + + func networkingSaveToLinkVerificationViewController(_ viewController: NetworkingSaveToLinkVerificationViewController, didReceiveConsumerPublishableKey consumerPublishableKey: String) { + dataManager.consumerPublishableKey = consumerPublishableKey + } + + func networkingSaveToLinkVerificationViewControllerDidFinish( + _ viewController: NetworkingSaveToLinkVerificationViewController, + saveToLinkWithStripeSucceeded: Bool?, + customSuccessPaneMessage: String? + ) { + if saveToLinkWithStripeSucceeded != nil { + dataManager.saveToLinkWithStripeSucceeded = saveToLinkWithStripeSucceeded + } + dataManager.customSuccessPaneSubCaption = customSuccessPaneMessage + pushPane(.success, animated: true) + } + + func networkingSaveToLinkVerificationViewController( + _ viewController: NetworkingSaveToLinkVerificationViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } +} + +// MARK: - NetworkingLinkStepUpVerificationViewControllerDelegate + +extension NativeFlowController: NetworkingLinkStepUpVerificationViewControllerDelegate { + + func networkingLinkStepUpVerificationViewController(_ viewController: NetworkingLinkStepUpVerificationViewController, didReceiveConsumerPublishableKey consumerPublishableKey: String) { + dataManager.consumerPublishableKey = consumerPublishableKey + } + + func networkingLinkStepUpVerificationViewController( + _ viewController: NetworkingLinkStepUpVerificationViewController, + didCompleteVerificationWithInstitution institution: FinancialConnectionsInstitution?, + nextPane: FinancialConnectionsSessionManifest.NextPane, + customSuccessPaneCaption: String?, + customSuccessPaneSubCaption: String? + ) { + dataManager.institution = institution + dataManager.customSuccessPaneCaption = customSuccessPaneCaption + dataManager.customSuccessPaneSubCaption = customSuccessPaneSubCaption + pushPane(nextPane, animated: true) + } + + func networkingLinkStepUpVerificationViewController( + _ viewController: NetworkingLinkStepUpVerificationViewController, + didReceiveTerminalError error: Error + ) { + showTerminalError(error) + } + + func networkingLinkStepUpVerificationViewControllerEncounteredSoftError( + _ viewController: NetworkingLinkStepUpVerificationViewController + ) { + pushPane(.institutionPicker, animated: true) + } +} + +// MARK: - LinkLoginViewControllerDelegate + +extension NativeFlowController: LinkLoginViewControllerDelegate { + func linkLoginViewController( + _ viewController: LinkLoginViewController, + foundReturningUserWith lookupConsumerSessionResponse: LookupConsumerSessionResponse + ) { + dataManager.consumerPublishableKey = lookupConsumerSessionResponse.publishableKey + dataManager.consumerSession = lookupConsumerSessionResponse.consumerSession + pushPane(.networkingLinkVerification, animated: true) + } + + func linkLoginViewController( + _ viewController: LinkLoginViewController, + receivedLinkSignUpResponse linkSignUpResponse: LinkSignUpResponse + ) { + dataManager.consumerPublishableKey = linkSignUpResponse.publishableKey + dataManager.consumerSession = linkSignUpResponse.consumerSession + } + + func linkLoginViewController( + _ viewController: LinkLoginViewController, + signedUpAttachedAndSynchronized synchronizePayload: FinancialConnectionsSynchronize + ) { + dataManager.manifest = synchronizePayload.manifest + pushPane(synchronizePayload.manifest.nextPane, animated: true, clearNavigationStack: true) + } + + func linkLoginViewController( + _ viewController: LinkLoginViewController, + didReceiveTerminalError error: any Error + ) { + showTerminalError(error) + } +} + +// MARK: - ErrorViewControllerDelegate + +extension NativeFlowController: ErrorViewControllerDelegate { + func errorViewControllerDidSelectAnotherBank(_ viewController: ErrorViewController) { + didSelectAnotherBank() + } + + func errorViewControllerDidSelectManualEntry(_ viewController: ErrorViewController) { + pushPane(.manualEntry, animated: true) + } + + func errorViewController( + _ viewController: ErrorViewController, + didSelectCloseWithError error: Error + ) { + closeAuthFlow(error: error) + } +} + +// MARK: - Static Helpers + +private func CreatePaneViewController( + pane: FinancialConnectionsSessionManifest.NextPane, + parameters: CreatePaneParameters? = nil, + nativeFlowController: NativeFlowController, + dataManager: NativeFlowDataManager, + panePresentationStyle: PanePresentationStyle = .fullscreen +) -> UIViewController? { + let viewController: UIViewController? + switch pane { + case .accountPicker: + if let authSession = dataManager.authSession, let institution = dataManager.institution { + let accountPickerDataSource = AccountPickerDataSourceImplementation( + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + accountPickerPane: dataManager.accountPickerPane, + authSession: authSession, + manifest: dataManager.manifest, + institution: institution, + analyticsClient: dataManager.analyticsClient, + reduceManualEntryProminenceInErrors: dataManager.reduceManualEntryProminenceInErrors, + dataAccessNotice: dataManager.consentPaneModel?.dataAccessNotice, + consumerSessionClientSecret: dataManager.consumerSession?.clientSecret + ) + let accountPickerViewController = AccountPickerViewController(dataSource: accountPickerDataSource) + accountPickerViewController.delegate = nativeFlowController + viewController = accountPickerViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .attachLinkedPaymentAccount: + if let institution = dataManager.institution, + let linkedAccountId = dataManager.linkedAccounts?.first?.linkedAccountId + { + let dataSource = AttachLinkedPaymentAccountDataSourceImplementation( + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + manifest: dataManager.manifest, + institution: institution, + linkedAccountId: linkedAccountId, + analyticsClient: dataManager.analyticsClient, + authSessionId: dataManager.authSession?.id, + consumerSessionClientSecret: dataManager.consumerSession?.clientSecret, + reduceManualEntryProminenceInErrors: dataManager.reduceManualEntryProminenceInErrors + ) + let attachedLinkedPaymentAccountViewController = AttachLinkedPaymentAccountViewController( + dataSource: dataSource + ) + attachedLinkedPaymentAccountViewController.delegate = nativeFlowController + viewController = attachedLinkedPaymentAccountViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .bankAuthRepair: + assertionFailure("Not supported") + viewController = nil + case .consent: + if let consentPaneModel = dataManager.consentPaneModel { + let consentDataSource = ConsentDataSourceImplementation( + manifest: dataManager.manifest, + consent: consentPaneModel, + merchantLogo: dataManager.merchantLogo, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient + ) + let consentViewController = ConsentViewController(dataSource: consentDataSource) + consentViewController.delegate = nativeFlowController + viewController = consentViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .institutionPicker: + let dataSource = InstitutionAPIDataSource( + manifest: dataManager.manifest, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient + ) + let picker = InstitutionPickerViewController(dataSource: dataSource) + picker.delegate = nativeFlowController + viewController = picker + case .linkAccountPicker: + if let consumerSession = dataManager.consumerSession { + let linkAccountPickerDataSource = LinkAccountPickerDataSourceImplementation( + manifest: dataManager.manifest, + apiClient: dataManager.apiClient, + analyticsClient: dataManager.analyticsClient, + clientSecret: dataManager.clientSecret, + consumerSession: consumerSession, + dataAccessNotice: dataManager.consentPaneModel?.dataAccessNotice + ) + let linkAccountPickerViewController = LinkAccountPickerViewController( + dataSource: linkAccountPickerDataSource + ) + linkAccountPickerViewController.delegate = nativeFlowController + viewController = linkAccountPickerViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .linkConsent: + assertionFailure("Not supported") + viewController = nil + case .linkLogin: + let linkLoginDataSource = LinkLoginDataSourceImplementation( + manifest: dataManager.manifest, + analyticsClient: dataManager.analyticsClient, + clientSecret: dataManager.clientSecret, + returnURL: dataManager.returnURL, + apiClient: dataManager.apiClient, + elementsSessionContext: dataManager.elementsSessionContext + ) + let linkLoginViewController = LinkLoginViewController(dataSource: linkLoginDataSource) + linkLoginViewController.delegate = nativeFlowController + viewController = linkLoginViewController + case .manualEntry: + nativeFlowController.delegate?.nativeFlowController( + nativeFlowController, + didReceiveEvent: FinancialConnectionsEvent(name: .manualEntryInitiated) + ) + + let dataSource = ManualEntryDataSourceImplementation( + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + manifest: dataManager.manifest, + analyticsClient: dataManager.analyticsClient, + consumerSessionClientSecret: dataManager.consumerSession?.clientSecret + ) + let manualEntryViewController = ManualEntryViewController(dataSource: dataSource) + manualEntryViewController.delegate = nativeFlowController + viewController = manualEntryViewController + case .networkingLinkSignupPane: + let networkingLinkSignupDataSource = NetworkingLinkSignupDataSourceImplementation( + manifest: dataManager.manifest, + selectedAccounts: dataManager.linkedAccounts, + returnURL: dataManager.returnURL, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient, + elementsSessionContext: dataManager.elementsSessionContext + ) + let networkingLinkSignupViewController = NetworkingLinkSignupViewController( + dataSource: networkingLinkSignupDataSource + ) + networkingLinkSignupViewController.delegate = nativeFlowController + viewController = networkingLinkSignupViewController + case .networkingLinkVerification: + let accountholderCustomerEmailAddress = dataManager.manifest.accountholderCustomerEmailAddress + let consumerSessionEmailAddress = dataManager.consumerSession?.emailAddress + if let accountholderCustomerEmailAddress = consumerSessionEmailAddress ?? accountholderCustomerEmailAddress { + let networkingLinkVerificationDataSource = NetworkingLinkVerificationDataSourceImplementation( + accountholderCustomerEmailAddress: accountholderCustomerEmailAddress, + manifest: dataManager.manifest, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + returnURL: dataManager.returnURL, + analyticsClient: dataManager.analyticsClient + ) + let networkingLinkVerificationViewController = NetworkingLinkVerificationViewController(dataSource: networkingLinkVerificationDataSource) + networkingLinkVerificationViewController.delegate = nativeFlowController + viewController = networkingLinkVerificationViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .networkingSaveToLinkVerification: + if + let consumerSession = dataManager.consumerSession + { + let networkingSaveToLinkVerificationDataSource = NetworkingSaveToLinkVerificationDataSourceImplementation( + manifest: dataManager.manifest, + consumerSession: consumerSession, + selectedAccounts: dataManager.linkedAccounts, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient + ) + let networkingSaveToLinkVerificationViewController = NetworkingSaveToLinkVerificationViewController( + dataSource: networkingSaveToLinkVerificationDataSource + ) + networkingSaveToLinkVerificationViewController.delegate = nativeFlowController + viewController = networkingSaveToLinkVerificationViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .networkingLinkStepUpVerification: + if + let consumerSession = dataManager.consumerSession, + let selectedAccountIds = dataManager.linkedAccounts?.map({ $0.id }) + { + let networkingLinkStepUpVerificationDataSource = NetworkingLinkStepUpVerificationDataSourceImplementation( + consumerSession: consumerSession, + selectedAccountIds: selectedAccountIds, + manifest: dataManager.manifest, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient + ) + let networkingLinkStepUpVerificationViewController = NetworkingLinkStepUpVerificationViewController( + dataSource: networkingLinkStepUpVerificationDataSource + ) + networkingLinkStepUpVerificationViewController.delegate = nativeFlowController + viewController = networkingLinkStepUpVerificationViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .partnerAuth: + if let institution = dataManager.institution { + let partnerAuthDataSource = PartnerAuthDataSourceImplementation( + authSession: dataManager.authSession, + institution: institution, + manifest: dataManager.manifest, + returnURL: dataManager.returnURL, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient + ) + let partnerAuthViewController = PartnerAuthViewController( + dataSource: partnerAuthDataSource, + panePresentationStyle: panePresentationStyle + ) + partnerAuthViewController.delegate = nativeFlowController + viewController = partnerAuthViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .manualEntrySuccess: + fallthrough + case .success: + let successDataSource = SuccessDataSourceImplementation( + manifest: dataManager.manifest, + linkedAccountsCount: dataManager.linkedAccounts?.count ?? 0, + saveToLinkWithStripeSucceeded: dataManager.saveToLinkWithStripeSucceeded, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient, + customSuccessPaneCaption: dataManager.customSuccessPaneCaption, + customSuccessPaneSubCaption: dataManager.customSuccessPaneSubCaption + ) + let successViewController = SuccessViewController(dataSource: successDataSource) + successViewController.delegate = nativeFlowController + viewController = successViewController + case .unexpectedError: + if + let errorPaneError = dataManager.errorPaneError, + let errorPaneReferrerPane = dataManager.errorPaneReferrerPane + { + let errorDataSource = ErrorDataSource( + error: errorPaneError, + referrerPane: errorPaneReferrerPane, + manifest: dataManager.manifest, + reduceManualEntryProminenceInErrors: dataManager.reduceManualEntryProminenceInErrors, + analyticsClient: dataManager.analyticsClient, + institution: dataManager.institution + ) + let errorViewController = ErrorViewController(dataSource: errorDataSource) + errorViewController.delegate = nativeFlowController + viewController = errorViewController + } else { + // if backend returns `unexpected_error`, the parameters being NULL + // might be OK and we will go to terminal error + viewController = nil + } + case .authOptions: + assertionFailure("Not supported") + viewController = nil + case .networkingLinkLoginWarmup: + let networkingLinkWarmupDataSource = NetworkingLinkLoginWarmupDataSourceImplementation( + manifest: dataManager.manifest, + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + analyticsClient: dataManager.analyticsClient, + nextPaneOrDrawerOnSecondaryCta: parameters?.nextPaneOrDrawerOnSecondaryCta + ) + let networkingLinkWarmupViewController = NetworkingLinkLoginWarmupViewController( + dataSource: networkingLinkWarmupDataSource, + panePresentationStyle: panePresentationStyle + ) + networkingLinkWarmupViewController.delegate = nativeFlowController + viewController = networkingLinkWarmupViewController + + // client-side only panes below + case .resetFlow: + let resetFlowDataSource = ResetFlowDataSourceImplementation( + apiClient: dataManager.apiClient, + clientSecret: dataManager.clientSecret, + manifest: dataManager.manifest, + analyticsClient: dataManager.analyticsClient + ) + let resetFlowViewController = ResetFlowViewController( + dataSource: resetFlowDataSource + ) + resetFlowViewController.delegate = nativeFlowController + viewController = resetFlowViewController + case .terminalError: + if let terminalError = dataManager.terminalError { + let terminalErrorViewController = TerminalErrorViewController( + error: terminalError, + allowManualEntry: dataManager.manifest.allowManualEntry, + theme: dataManager.manifest.theme + ) + terminalErrorViewController.delegate = nativeFlowController + viewController = terminalErrorViewController + } else { + assertionFailure("Code logic error. Missing parameters for \(pane).") + viewController = nil + } + case .unparsable: + viewController = nil + } + + if let viewController = viewController { + // this assert should ensure that it's nearly impossible to miss + // adding new cases to `paneFromViewController` + assert( + FinancialConnectionsAnalyticsClient.paneFromViewController(viewController) == pane + // `manualEntrySuccess` is a special case where it maps to the + // same thing as `success` so this assert is not necessary + || pane == .manualEntrySuccess, + "Found a new view controller (\(viewController.self)) that needs to be added to `paneFromViewController`." + ) + + // this logging isn't perfect because one could call `CreatePaneViewController` + // and never use the view controller, but that is not the case today + // and it is difficult to imagine when that would be the case in the future + dataManager + .analyticsClient + .log( + eventName: "pane.launched", + parameters: { + var parameters: [String: Any] = [:] + parameters["referrer_pane"] = dataManager.lastPaneLaunched?.rawValue + return parameters + }(), + pane: pane + ) + dataManager.lastPaneLaunched = pane + } else { + dataManager + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError.unknown( + debugDescription: "Pane Not Found: either app state is invalid, or an unsupported pane was requested." + ), + errorName: "PaneNotFound", + pane: pane + ) + } + + return viewController +} + +private func ShouldHideLogoInNavigationBar( + forViewController viewController: UIViewController, + reducedBranding: Bool, + merchantLogo: [String]? +) -> Bool { + if viewController is ConsentViewController { + let willShowMerchantLogoInConsentScreen = (merchantLogo != nil) + if willShowMerchantLogoInConsentScreen { + // if we are going to show merchant logo in consent screen, + // do not show the logo in the navigation bar + return true + } else { + return reducedBranding + } + } else { + return reducedBranding + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowDataManager.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowDataManager.swift new file mode 100644 index 00000000..01511a82 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowDataManager.swift @@ -0,0 +1,173 @@ +// +// NativeFlowDataManager.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 6/7/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NativeFlowDataManager: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get set } + var reducedBranding: Bool { get } + var merchantLogo: [String]? { get } + var returnURL: String? { get } + var consentPaneModel: FinancialConnectionsConsent? { get } + var accountPickerPane: FinancialConnectionsAccountPickerPane? { get } + var apiClient: FinancialConnectionsAPIClient { get } + var clientSecret: String { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var elementsSessionContext: ElementsSessionContext? { get } + var reduceManualEntryProminenceInErrors: Bool { get } + + var institution: FinancialConnectionsInstitution? { get set } + var authSession: FinancialConnectionsAuthSession? { get set } + var linkedAccounts: [FinancialConnectionsPartnerAccount]? { get set } + var terminalError: Error? { get set } + var errorPaneError: Error? { get set } + var errorPaneReferrerPane: FinancialConnectionsSessionManifest.NextPane? { get set } + var paymentAccountResource: FinancialConnectionsPaymentAccountResource? { get set } + var accountNumberLast4: String? { get set } + var consumerSession: ConsumerSessionData? { get set } + var consumerPublishableKey: String? { get set } + var saveToLinkWithStripeSucceeded: Bool? { get set } + var lastPaneLaunched: FinancialConnectionsSessionManifest.NextPane? { get set } + var customSuccessPaneCaption: String? { get set } + var customSuccessPaneSubCaption: String? { get set } + + func createPaymentDetails( + consumerSessionClientSecret: String, + bankAccountId: String, + billingAddress: BillingAddress?, + billingEmail: String? + ) -> Future + func resetState(withNewManifest newManifest: FinancialConnectionsSessionManifest) + func completeFinancialConnectionsSession(terminalError: String?) -> Future +} + +class NativeFlowAPIDataManager: NativeFlowDataManager { + + var manifest: FinancialConnectionsSessionManifest { + didSet { + didUpdateManifest() + } + } + // don't expose `visualUpdate` because we don't want anyone to directly + // access `visualUpdate.merchantLogo`; we have custom logic for `merchantLogo` + private let visualUpdate: FinancialConnectionsSynchronize.VisualUpdate + var reducedBranding: Bool { + return visualUpdate.reducedBranding + } + var merchantLogo: [String]? { + let merchantLogo = visualUpdate.merchantLogo + if merchantLogo.isEmpty || merchantLogo.count == 2 || merchantLogo.count == 3 { + // show merchant logo inside of consent pane + return visualUpdate.merchantLogo + } else { + // if `merchantLogo.count > 3`, that is an invalid case + // + // we want to log experiment exposure regardless because + // if experiment is not working fine (ex. returns 1 or 4 logos) + // then the "cost" of those bugs should show up in the `treatment` data + return nil + } + } + var reduceManualEntryProminenceInErrors: Bool { + return visualUpdate.reduceManualEntryProminenceInErrors + } + let returnURL: String? + let consentPaneModel: FinancialConnectionsConsent? + let accountPickerPane: FinancialConnectionsAccountPickerPane? + let apiClient: FinancialConnectionsAPIClient + let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + let elementsSessionContext: ElementsSessionContext? + + var institution: FinancialConnectionsInstitution? + var authSession: FinancialConnectionsAuthSession? + var linkedAccounts: [FinancialConnectionsPartnerAccount]? + var terminalError: Error? + var errorPaneError: Error? + var errorPaneReferrerPane: FinancialConnectionsSessionManifest.NextPane? + var paymentAccountResource: FinancialConnectionsPaymentAccountResource? + var accountNumberLast4: String? + var saveToLinkWithStripeSucceeded: Bool? + var lastPaneLaunched: FinancialConnectionsSessionManifest.NextPane? + var customSuccessPaneCaption: String? + var customSuccessPaneSubCaption: String? + + var consumerSession: ConsumerSessionData? { + didSet { + apiClient.consumerSession = consumerSession + } + } + + var consumerPublishableKey: String? { + didSet { + apiClient.consumerPublishableKey = consumerPublishableKey + } + } + + init( + manifest: FinancialConnectionsSessionManifest, + visualUpdate: FinancialConnectionsSynchronize.VisualUpdate, + returnURL: String?, + consentPaneModel: FinancialConnectionsConsent?, + accountPickerPane: FinancialConnectionsAccountPickerPane?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient, + elementsSessionContext: ElementsSessionContext? + ) { + self.manifest = manifest + self.visualUpdate = visualUpdate + self.returnURL = returnURL + self.consentPaneModel = consentPaneModel + self.accountPickerPane = accountPickerPane + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + self.elementsSessionContext = elementsSessionContext + // Use server provided active AuthSession. + self.authSession = manifest.activeAuthSession + // If the server returns active institution use that, otherwise resort to initial institution. + self.institution = manifest.activeInstitution ?? manifest.initialInstitution + didUpdateManifest() + } + + func createPaymentDetails( + consumerSessionClientSecret: String, + bankAccountId: String, + billingAddress: BillingAddress?, + billingEmail: String? + ) -> Future { + apiClient.paymentDetails( + consumerSessionClientSecret: consumerSessionClientSecret, + bankAccountId: bankAccountId, + billingAddress: billingAddress, + billingEmail: billingEmail + ) + } + + func completeFinancialConnectionsSession(terminalError: String?) -> Future { + return apiClient.completeFinancialConnectionsSession( + clientSecret: clientSecret, + terminalError: terminalError + ) + } + + func resetState(withNewManifest newManifest: FinancialConnectionsSessionManifest) { + authSession = nil + institution = nil + paymentAccountResource = nil + accountNumberLast4 = nil + linkedAccounts = nil + manifest = newManifest + } + + private func didUpdateManifest() { + apiClient.isLinkWithStripe = manifest.isLinkWithStripe == true + analyticsClient.setAdditionalParameters(fromManifest: manifest) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupBodyView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupBodyView.swift new file mode 100644 index 00000000..e38f6783 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupBodyView.swift @@ -0,0 +1,110 @@ +// +// NetworkingLinkLoginWarmupBodyView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/6/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class NetworkingLinkLoginWarmupBodyView: HitTestView { + + init(email: String) { + super.init(frame: .zero) + let emailView = CreateEmailView(email: email) + addAndPinSubview(emailView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private func CreateEmailView( + email: String +) -> UIView { + + let emailLabel = AttributedLabel( + font: .body(.small), + textColor: .textDefault + ) + emailLabel.setText(email) + + let horizontalStack = UIStackView( + arrangedSubviews: [ + CreateAvatarView(email: email), + emailLabel, + ] + ) + horizontalStack.axis = .horizontal + horizontalStack.alignment = .center + horizontalStack.spacing = 12 + horizontalStack.isLayoutMarginsRelativeArrangement = true + horizontalStack.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 12, + leading: 16, + bottom: 12, + trailing: 16 + ) + horizontalStack.layer.borderColor = UIColor.borderDefault.cgColor + horizontalStack.layer.borderWidth = 1 + horizontalStack.layer.cornerRadius = 12 + return horizontalStack +} + +private func CreateAvatarView(email: String) -> UIView { + let diameter: CGFloat = 36 + + let circleView = UIView() + circleView.backgroundColor = .linkGreen200 + circleView.layer.cornerRadius = diameter / 2 + circleView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + circleView.heightAnchor.constraint(equalToConstant: diameter), + circleView.widthAnchor.constraint(equalToConstant: diameter), + ]) + + let letterLabel = AttributedLabel( + font: .body(.small), + textColor: .linkGreen900 + ) + letterLabel.setText(String(email.uppercased().first ?? "E")) + circleView.addSubview(letterLabel) + letterLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + letterLabel.centerXAnchor.constraint(equalTo: circleView.centerXAnchor), + letterLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor), + ]) + + return circleView +} + +#if DEBUG + +import SwiftUI + +private struct NetworkingLinkLoginWarmupBodyViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> NetworkingLinkLoginWarmupBodyView { + NetworkingLinkLoginWarmupBodyView(email: "test@stripe.com") + } + + func updateUIView(_ uiView: NetworkingLinkLoginWarmupBodyView, context: Context) {} +} + +struct NetworkingLinkLoginWarmupBodyView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + Spacer() + NetworkingLinkLoginWarmupBodyViewUIViewRepresentable() + .frame(maxHeight: 200) + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupDataSource.swift new file mode 100644 index 00000000..b1898fc3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupDataSource.swift @@ -0,0 +1,47 @@ +// +// NetworkingLinkLoginWarmupDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/6/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NetworkingLinkLoginWarmupDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + + func disableNetworking() -> Future +} + +final class NetworkingLinkLoginWarmupDataSourceImplementation: NetworkingLinkLoginWarmupDataSource { + + let manifest: FinancialConnectionsSessionManifest + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + private let nextPaneOrDrawerOnSecondaryCta: String? + + init( + manifest: FinancialConnectionsSessionManifest, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient, + nextPaneOrDrawerOnSecondaryCta: String? + ) { + self.manifest = manifest + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + self.nextPaneOrDrawerOnSecondaryCta = nextPaneOrDrawerOnSecondaryCta + } + + func disableNetworking() -> Future { + return apiClient.disableNetworking( + disabledReason: "returning_consumer_opt_out", + clientSuggestedNextPaneOnDisableNetworking: nextPaneOrDrawerOnSecondaryCta, + clientSecret: clientSecret + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupViewController.swift new file mode 100644 index 00000000..33942607 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkLoginWarmup/NetworkingLinkLoginWarmupViewController.swift @@ -0,0 +1,151 @@ +// +// NetworkingLinkLoginWarmupViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/6/23. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +protocol NetworkingLinkLoginWarmupViewControllerDelegate: AnyObject { + func networkingLinkLoginWarmupViewControllerDidSelectContinue( + _ viewController: NetworkingLinkLoginWarmupViewController + ) + func networkingLinkLoginWarmupViewControllerDidSelectCancel( + _ viewController: NetworkingLinkLoginWarmupViewController + ) + func networkingLinkLoginWarmupViewController( + _ viewController: NetworkingLinkLoginWarmupViewController, + didSelectSkipWithManifest manifest: FinancialConnectionsSessionManifest + ) + func networkingLinkLoginWarmupViewController(_ viewController: NetworkingLinkLoginWarmupViewController, didReceiveTerminalError error: Error) +} + +final class NetworkingLinkLoginWarmupViewController: SheetViewController { + + private let dataSource: NetworkingLinkLoginWarmupDataSource + weak var delegate: NetworkingLinkLoginWarmupViewControllerDelegate? + + init( + dataSource: NetworkingLinkLoginWarmupDataSource, + panePresentationStyle: PanePresentationStyle + ) { + self.dataSource = dataSource + super.init(panePresentationStyle: panePresentationStyle) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + let footerSecondaryButtonTitle: String + if dataSource.manifest.isProductInstantDebits { + footerSecondaryButtonTitle = STPLocalizedString( + "Cancel", + "A button title. This button, when pressed, will simply dismiss the warmup pane, as it is required to continue with Link in the Instant Debits flow." + ) + } else { + footerSecondaryButtonTitle = STPLocalizedString( + "Not now", + "A button title. This button, when pressed, will skip logging in the user with their e-mail to Link (one-click checkout provider)." + ) + } + setup( + withContentView: PaneLayoutView.createContentView( + iconView: RoundedIconView( + image: .image(.person), + style: .circle, + theme: dataSource.manifest.theme + ), + title: STPLocalizedString( + "Continue with Link", + "The title of a screen where users are informed that they can sign-in-to Link." + ), + subtitle: STPLocalizedString( + "Use information you previously saved with your Link account.", + "The subtitle/description of a screen where users are informed that they can sign-in-to Link." + ), + contentView: NetworkingLinkLoginWarmupBodyView( + // `accountholderCustomerEmailAddress` should always be non-null, and + // since the email is only used as a visual, it's not worth to throw an error + // if it is null + email: dataSource.manifest.accountholderCustomerEmailAddress ?? "you" + ) + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: STPLocalizedString( + "Continue with Link", + "A button title. This button, when pressed, will automatically log-in the user with their e-mail to Link (one-click checkout provider)." + ), + accessibilityIdentifier: "link_continue_button", + action: { [weak self] in + self?.didSelectContinue() + } + ), + secondaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: footerSecondaryButtonTitle, + action: { [weak self] in + self?.didSelectSkip() + } + ), + theme: dataSource.manifest.theme + ).footerView + ) + } + + private func didSelectContinue() { + dataSource.analyticsClient.log( + eventName: "click.continue", + pane: .networkingLinkLoginWarmup + ) + delegate?.networkingLinkLoginWarmupViewControllerDidSelectContinue(self) + } + + private func didSelectSkip() { + if dataSource.manifest.isProductInstantDebits { + guard let delegate else { + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError.unknown( + debugDescription: "Unexpected nil delegate in the NetworkLinkLoginWarmup pane when selecting Cancel." + ), + errorName: "InstantDebitsCancelError", + pane: .networkingLinkLoginWarmup + ) + return + } + delegate.networkingLinkLoginWarmupViewControllerDidSelectCancel(self) + } else { + dataSource.analyticsClient.log( + eventName: "click.skip_sign_in", + pane: .networkingLinkLoginWarmup + ) + dataSource.disableNetworking() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let manifest): + self.delegate?.networkingLinkLoginWarmupViewController( + self, + didSelectSkipWithManifest: manifest + ) + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "DisableNetworkingError", + pane: .networkingLinkLoginWarmup + ) + self.delegate?.networkingLinkLoginWarmupViewController(self, didReceiveTerminalError: error) + } + } + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/EmailTextField.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/EmailTextField.swift new file mode 100644 index 00000000..cd4734bf --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/EmailTextField.swift @@ -0,0 +1,218 @@ +// +// EmailTextField.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/30/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +protocol EmailTextFieldDelegate: AnyObject { + func emailTextField( + _ emailTextField: EmailTextField, + didChangeEmailAddress emailAddress: String, + isValid: Bool + ) + func emailTextFieldUserDidPressReturnKey(_ textField: EmailTextField) +} + +final class EmailTextField: UIView { + + private let theme: FinancialConnectionsTheme + fileprivate lazy var textField: RoundedTextField = { + let textField = RoundedTextField( + placeholder: STPLocalizedString( + "Email address", + "The title of a user-input-field that appears when a user is signing up to Link (a payment service). It instructs user to type an email address." + ), + showDoneToolbar: true, + theme: theme + ) + textField.textField.keyboardType = .emailAddress + textField.textField.textContentType = .emailAddress + textField.textField.autocapitalizationType = .none + textField + .containerHorizontalStackView + .addArrangedSubview(activityIndicator) + textField.delegate = self + textField.textField.accessibilityIdentifier = "email_text_field" + return textField + }() + private lazy var activityIndicator: ActivityIndicator = { + let activityIndicator = ActivityIndicator(size: .medium) + activityIndicator.setContentCompressionResistancePriority(.required, for: .horizontal) + activityIndicator.color = theme.spinnerColor + return activityIndicator + }() + fileprivate var didEndEditingOnce = false + + var text: String { + get { + textField.text + } + set { + textField.text = newValue + textDidChange() + } + } + var isEmailValid: Bool { + return STPEmailAddressValidator.stringIsValidEmailAddress(text) + } + + weak var delegate: EmailTextFieldDelegate? + + init(theme: FinancialConnectionsTheme) { + self.theme = theme + super.init(frame: .zero) + addAndPinSubview(textField) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func showLoadingView(_ show: Bool) { + if show { + activityIndicator.startAnimating() + } else { + activityIndicator.stopAnimating() + } + } + + override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } + + override func endEditing(_ force: Bool) -> Bool { + return textField.endEditing(force) + } + + private func textDidChange() { + textField.errorText = nil + if !isEmailValid { + // do not show error messages unless the user + // stopped editing the text field at least once + if didEndEditingOnce { + if text.isEmpty { + // no error message if empty + } else { + textField.errorText = STPLocalizedString("Your email address is invalid.", "An error message that instructs the user to keep typing their email address in a user-input field.") + } + } + } + + delegate?.emailTextField( + self, + didChangeEmailAddress: text, + isValid: isEmailValid + ) + } +} + +extension EmailTextField: RoundedTextFieldDelegate { + + func roundedTextField( + _ textField: RoundedTextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + return true + } + + func roundedTextField( + _ textField: RoundedTextField, + textDidChange text: String + ) { + textDidChange() + } + + func roundedTextFieldUserDidPressReturnKey(_ textField: RoundedTextField) { + delegate?.emailTextFieldUserDidPressReturnKey(self) + } + + func roundedTextFieldDidEndEditing(_ textField: RoundedTextField) { + didEndEditingOnce = true + // check whether we need to update error state + textDidChange() + } +} + +#if DEBUG + +import SwiftUI + +private struct EmailTextFieldUIViewRepresentable: UIViewRepresentable { + + let text: String + let isLoading: Bool + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> EmailTextField { + EmailTextField(theme: theme) + } + + func updateUIView( + _ emailTextField: EmailTextField, + context: Context + ) { + emailTextField.text = text + emailTextField.showLoadingView(isLoading) + emailTextField.didEndEditingOnce = true + emailTextField.roundedTextField( + emailTextField.textField, + textDidChange: text + ) + } +} + +struct EmailTextField_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + VStack(spacing: 16) { + EmailTextFieldUIViewRepresentable( + text: "", + isLoading: false, + theme: .light + ).frame(height: 56) + + EmailTextFieldUIViewRepresentable( + text: "test@test.com", + isLoading: false, + theme: .light + ).frame(height: 56) + + EmailTextFieldUIViewRepresentable( + text: "test@test-very-long-name-thats-very-long.com", + isLoading: true, + theme: .light + ).frame(height: 56) + + EmailTextFieldUIViewRepresentable( + text: "wrongemail@wronger", + isLoading: false, + theme: .light + ).frame(height: 90) + + EmailTextFieldUIViewRepresentable( + text: "light@theme.com", + isLoading: true, + theme: .light + ).frame(height: 56) + + EmailTextFieldUIViewRepresentable( + text: "linklight@theme.com", + isLoading: true, + theme: .linkLight + ).frame(height: 56) + + Spacer() + } + .padding() + .background(Color(UIColor.customBackgroundColor)) + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/LinkSignupFormView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/LinkSignupFormView.swift new file mode 100644 index 00000000..b37e8cb2 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/LinkSignupFormView.swift @@ -0,0 +1,225 @@ +// +// LinkSignupFormView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/24/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol LinkSignupFormViewDelegate: AnyObject { + func linkSignupFormView( + _ view: LinkSignupFormView, + didEnterValidEmailAddress emailAddress: String + ) + func linkSignupFormViewDidUpdateFields( + _ view: LinkSignupFormView + ) +} + +final class LinkSignupFormView: UIView { + + private let accountholderPhoneNumber: String? + private let theme: FinancialConnectionsTheme + weak var delegate: LinkSignupFormViewDelegate? + + private lazy var verticalStackView: UIStackView = { + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.addArrangedSubview(emailTextField) + verticalStackView.addArrangedSubview(phoneTextField) + return verticalStackView + }() + private(set) lazy var emailTextField: EmailTextField = { + let emailTextField = EmailTextField(theme: theme) + emailTextField.delegate = self + return emailTextField + }() + private(set) lazy var phoneTextField: PhoneTextField = { + let phoneTextField = PhoneTextField(defaultPhoneNumber: accountholderPhoneNumber, theme: theme) + phoneTextField.delegate = self + return phoneTextField + }() + private var debounceEmailTimer: Timer? + private var lastValidEmail: String? + + init(accountholderPhoneNumber: String?, theme: FinancialConnectionsTheme) { + self.theme = theme + self.accountholderPhoneNumber = accountholderPhoneNumber + super.init(frame: .zero) + addAndPinSubview(verticalStackView) + phoneTextField.isHidden = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // returns `true` if the phone number field was shown for the first time + func showPhoneNumberFieldIfNeeded() -> Bool { + let isPhoneNumberFieldHidden = phoneTextField.isHidden + guard isPhoneNumberFieldHidden else { + return false // phone number field is already shown + } + phoneTextField.isHidden = false + return true // phone number is shown for the first time + } + + func showAndEditPhoneNumberFieldIfNeeded() { + let didShowPhoneNumberFieldForTheFirstTime = showPhoneNumberFieldIfNeeded() + // in case user needs to slowly re-type the e-mail, + // we want to only jump to the phone number the + // first time they enter the e-mail + if didShowPhoneNumberFieldForTheFirstTime { + let didPrefillPhoneNumber = (phoneTextField.phoneNumber?.number ?? "").count > 1 + if !didPrefillPhoneNumber { + // this disables the "Phone" label animating (we don't want that animation here) + UIView.performWithoutAnimation { + // auto-focus the non-prefilled phone field + beginEditingPhoneNumberField() + } + } else { + // user is done with e-mail AND phone number, so dismiss the keyboard + // so they can see the "Save to Link" button + endEditingEmailAddressField() + } + } + } + + func prefillEmailAddress(_ emailAddress: String?) { + guard let emailAddress = emailAddress, !emailAddress.isEmpty else { + return + } + emailTextField.text = emailAddress + } + + func prefillPhoneNumber(_ phoneNumber: String?) { + guard let phoneNumber, !phoneNumber.isEmpty else { + return + } + phoneTextField.text = phoneNumber + } + + func beginEditingEmailAddressField() { + _ = emailTextField.becomeFirstResponder() + } + + func endEditingEmailAddressField() { + _ = emailTextField.endEditing(true) + } + + func beginEditingPhoneNumberField() { + _ = phoneTextField.becomeFirstResponder() + } +} + +// MARK: - EmailTextFieldDelegate + +extension LinkSignupFormView: EmailTextFieldDelegate { + + func emailTextField( + _ emailTextField: EmailTextField, + didChangeEmailAddress emailAddress: String, + isValid: Bool + ) { + if isValid { + debounceEmailTimer?.invalidate() + debounceEmailTimer = Timer.scheduledTimer( + // TODO(kgaidis): discuss this logic w/ team; Stripe.js is constant 0.3 + // + // a valid e-mail will transition the user to the phone number + // field (sometimes prematurely), so we increase debounce if + // if there's a high chance the e-mail is not yet finished + // being typed (high chance of not finishing == not .com suffix) + withTimeInterval: emailAddress.hasSuffix(".com") ? 0.3 : 1.0, + repeats: false + ) { [weak self] _ in + guard let self = self else { return } + self.delegate?.linkSignupFormViewDidUpdateFields(self) + + if + // make sure the email inputted is still valid + // even after the debounce + self.emailTextField.isEmailValid, + // `lastValidEmail` ensures that we only + // fire the delegate ONCE per unique valid email + emailAddress != self.lastValidEmail + { + self.lastValidEmail = emailAddress + self.delegate?.linkSignupFormView( + self, + didEnterValidEmailAddress: emailAddress + ) + } + } + } else { + // errors are displayed automatically by the component + delegate?.linkSignupFormViewDidUpdateFields(self) + lastValidEmail = nil + } + } + + func emailTextFieldUserDidPressReturnKey(_ textField: EmailTextField) { + _ = textField.endEditing(true) + // move keyboard to phone field if phone is not valid, + // otherwise just dismiss it + if !phoneTextField.isHidden, !phoneTextField.isPhoneNumberValid { + _ = phoneTextField.becomeFirstResponder() + } + } +} + +extension LinkSignupFormView { + var email: String { + emailTextField.text + } + + var phoneNumber: String { + phoneTextField.phoneNumber?.string(as: .e164) ?? "" + } + + var countryCode: String { + phoneTextField.phoneNumber?.countryCode ?? "US" + } +} + +// MARK: - PhoneTextFieldDelegate + +extension LinkSignupFormView: PhoneTextFieldDelegate { + func phoneTextField( + _ phoneTextField: PhoneTextField, + didChangePhoneNumber phoneNumber: PhoneNumber? + ) { + delegate?.linkSignupFormViewDidUpdateFields(self) + } +} + +#if DEBUG + +import SwiftUI + +private struct NetworkingLinkSignupBodyFormViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> LinkSignupFormView { + LinkSignupFormView(accountholderPhoneNumber: nil, theme: .light) + } + + func updateUIView(_ uiView: LinkSignupFormView, context: Context) {} +} + +struct NetworkingLinkSignupBodyFormView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + NetworkingLinkSignupBodyFormViewUIViewRepresentable() + .frame(maxHeight: 200) + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupBodyView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupBodyView.swift new file mode 100644 index 00000000..6e2ebc45 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupBodyView.swift @@ -0,0 +1,151 @@ +// +// NetworkingLinkSignupContentView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/24/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class NetworkingLinkSignupBodyView: UIView { + + init( + bulletPoints: [FinancialConnectionsBulletPoint], + formView: UIView, + didSelectURL: @escaping (URL) -> Void + ) { + super.init(frame: .zero) + let verticalStackView = UIStackView( + arrangedSubviews: [ + CreateMultipleBulletPointView( + bulletPoints: bulletPoints, + didSelectURL: didSelectURL + ), + formView, + ] + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 24 + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private func CreateMultipleBulletPointView( + bulletPoints: [FinancialConnectionsBulletPoint], + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + bulletPoints.forEach { bulletPoint in + let bulletPointView = CreateBulletPointView( + title: bulletPoint.title, + content: bulletPoint.content, + iconUrl: bulletPoint.icon?.default, + action: didSelectURL + ) + verticalStackView.addArrangedSubview(bulletPointView) + } + return verticalStackView +} + +private func CreateBulletPointView( + title: String?, + content: String?, + iconUrl: String?, + action: @escaping (URL) -> Void +) -> UIView { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .iconDefault + imageView.setImage(with: iconUrl) + imageView.translatesAutoresizingMaskIntoConstraints = false + let imageDiameter: CGFloat = 20 + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: imageDiameter), + imageView.heightAnchor.constraint(equalToConstant: imageDiameter), + ]) + + let labelView = BulletPointLabelView( + title: title, + content: content, + didSelectURL: action + ) + + let horizontalStackView = HitTestStackView( + arrangedSubviews: [ + { + // add extra padding to `imageView` to align + // the text + image better + let extraPaddingView = UIStackView(arrangedSubviews: [imageView]) + extraPaddingView.isLayoutMarginsRelativeArrangement = true + extraPaddingView.directionalLayoutMargins = NSDirectionalEdgeInsets( + // center the image in the middle of the first line height + top: max(0, (labelView.topLineHeight - imageDiameter) / 2), + leading: 0, + bottom: 0, + trailing: 0 + ) + return extraPaddingView + }(), + labelView, + ] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 16 + horizontalStackView.alignment = .top + return horizontalStackView +} + +#if DEBUG + +import SwiftUI + +private struct NetworkingLinkSignupBodyViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> NetworkingLinkSignupBodyView { + NetworkingLinkSignupBodyView( + bulletPoints: [ + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: + "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--reserve-primary-3x.png" + ), + content: + "Connect your account faster on [Merchant] and thousands of sites." + ), + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: + "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--reserve-primary-3x.png" + ), + content: "Link with Stripe encrypts your data and never shares your login details." + ), + ], + formView: UIView(), + didSelectURL: { _ in } + ) + } + + func updateUIView(_ uiView: NetworkingLinkSignupBodyView, context: Context) {} +} + +struct NetworkingLinkSignupBodyView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + NetworkingLinkSignupBodyViewUIViewRepresentable() + .frame(maxHeight: 200) + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupDataSource.swift new file mode 100644 index 00000000..4e09d144 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupDataSource.swift @@ -0,0 +1,88 @@ +// +// NetworkingLinkSignupDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/17/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NetworkingLinkSignupDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var elementsSessionContext: ElementsSessionContext? { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + + func synchronize() -> Future + func lookup(emailAddress: String) -> Future + func saveToLink( + emailAddress: String, + phoneNumber: String, + countryCode: String + ) -> Future +} + +final class NetworkingLinkSignupDataSourceImplementation: NetworkingLinkSignupDataSource { + + let manifest: FinancialConnectionsSessionManifest + let elementsSessionContext: ElementsSessionContext? + private let selectedAccounts: [FinancialConnectionsPartnerAccount]? + private let returnURL: String? + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + + init( + manifest: FinancialConnectionsSessionManifest, + selectedAccounts: [FinancialConnectionsPartnerAccount]?, + returnURL: String?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient, + elementsSessionContext: ElementsSessionContext? + ) { + self.manifest = manifest + self.selectedAccounts = selectedAccounts + self.returnURL = returnURL + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + self.elementsSessionContext = elementsSessionContext + } + + func synchronize() -> Future { + return apiClient.synchronize( + clientSecret: clientSecret, + returnURL: returnURL + ) + .chained { synchronize in + if let networkingLinkSignup = synchronize.text?.networkingLinkSignupPane { + return Promise(value: networkingLinkSignup) + } else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "no networkingLinkSignup data attached")) + } + } + } + + func lookup(emailAddress: String) -> Future { + return apiClient.consumerSessionLookup(emailAddress: emailAddress, clientSecret: clientSecret) + } + + func saveToLink( + emailAddress: String, + phoneNumber: String, + countryCode: String + ) -> Future { + return apiClient.saveAccountsToNetworkAndLink( + shouldPollAccounts: !manifest.shouldAttachLinkedPaymentMethod, + selectedAccounts: selectedAccounts, + emailAddress: emailAddress, + phoneNumber: phoneNumber, + country: countryCode, // ex. "US" + consumerSessionClientSecret: nil, + clientSecret: clientSecret + ).chained { (_, customSuccessPaneMessage) in + return Promise(value: customSuccessPaneMessage) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupFooterView.swift new file mode 100644 index 00000000..653c5785 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupFooterView.swift @@ -0,0 +1,164 @@ +// +// NetworkingLinkSignupFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/17/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class NetworkingLinkSignupFooterView: HitTestView { + + private let aboveCtaText: String + private let saveToLinkButtonText: String + private let notNowButtonText: String + private let theme: FinancialConnectionsTheme + private let didSelectSaveToLink: () -> Void + private let didSelectNotNow: () -> Void + private let didSelectURL: (URL) -> Void + + private lazy var footerVerticalStackView: UIStackView = { + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.isLayoutMarginsRelativeArrangement = true + verticalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 24, + bottom: 16, + trailing: 24 + ) + verticalStackView.addArrangedSubview(aboveCtaLabel) + verticalStackView.addArrangedSubview(buttonVerticalStack) + return verticalStackView + }() + + private lazy var aboveCtaLabel: AttributedTextView = { + let termsAndPrivacyPolicyLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + termsAndPrivacyPolicyLabel.setText( + aboveCtaText, + action: didSelectURL + ) + return termsAndPrivacyPolicyLabel + }() + + private lazy var buttonVerticalStack: UIStackView = { + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 8 + verticalStackView.addArrangedSubview(saveToLinkButton) + verticalStackView.addArrangedSubview(notNowButton) + return verticalStackView + }() + + private lazy var saveToLinkButton: StripeUICore.Button = { + let saveToLinkButton = Button.primary(theme: theme) + saveToLinkButton.title = saveToLinkButtonText + saveToLinkButton.accessibilityIdentifier = "networking_link_signup_footer_view.save_to_link_button" + saveToLinkButton.addTarget(self, action: #selector(didSelectSaveToLinkButton), for: .touchUpInside) + saveToLinkButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + saveToLinkButton.heightAnchor.constraint(equalToConstant: 56) + ]) + return saveToLinkButton + }() + + private lazy var notNowButton: StripeUICore.Button = { + let saveToLinkButton = Button.secondary() + saveToLinkButton.title = notNowButtonText + saveToLinkButton.addTarget(self, action: #selector(didSelectNotNowButton), for: .touchUpInside) + saveToLinkButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + saveToLinkButton.heightAnchor.constraint(equalToConstant: 56) + ]) + return saveToLinkButton + }() + + init( + aboveCtaText: String, + saveToLinkButtonText: String, + notNowButtonText: String, + theme: FinancialConnectionsTheme, + didSelectSaveToLink: @escaping () -> Void, + didSelectNotNow: @escaping () -> Void, + didSelectURL: @escaping (URL) -> Void + ) { + self.aboveCtaText = aboveCtaText + self.saveToLinkButtonText = saveToLinkButtonText + self.notNowButtonText = notNowButtonText + self.theme = theme + self.didSelectSaveToLink = didSelectSaveToLink + self.didSelectNotNow = didSelectNotNow + self.didSelectURL = didSelectURL + super.init(frame: .zero) + backgroundColor = .customBackgroundColor + addAndPinSubview(footerVerticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func enableSaveToLinkButton(_ enable: Bool) { + saveToLinkButton.isEnabled = enable + } + + @objc private func didSelectSaveToLinkButton() { + didSelectSaveToLink() + } + + @objc private func didSelectNotNowButton() { + didSelectNotNow() + } + + func setIsLoading(_ isLoading: Bool) { + saveToLinkButton.isLoading = isLoading + } +} + +#if DEBUG + +import SwiftUI + +private struct NetworkingLinkSignupFooterViewUIViewRepresentable: UIViewRepresentable { + + func makeUIView(context: Context) -> NetworkingLinkSignupFooterView { + NetworkingLinkSignupFooterView( + aboveCtaText: "By saving your account to Link, you agree to Link’s [Terms](https://link.co/terms) and [Privacy Policy](https://link.co/privacy)", + saveToLinkButtonText: "Save to Link", + notNowButtonText: "Not now", + theme: .light, + didSelectSaveToLink: {}, + didSelectNotNow: {}, + didSelectURL: { _ in } + ) + } + + func updateUIView(_ uiView: NetworkingLinkSignupFooterView, context: Context) { + uiView.sizeToFit() + } +} + +@available(iOS 14.0, *) +struct NetworkingLinkSignupFooterView_Previews: PreviewProvider { + static var previews: some View { + VStack { + NetworkingLinkSignupFooterViewUIViewRepresentable() + .frame(maxHeight: 200) + Spacer() + } + .padding() + .frame(maxWidth: .infinity) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupViewController.swift new file mode 100644 index 00000000..79c3e850 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/NetworkingLinkSignupViewController.swift @@ -0,0 +1,338 @@ +// +// NetworkingLinkSignupViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/17/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol NetworkingLinkSignupViewControllerDelegate: AnyObject { + func networkingLinkSignupViewController( + _ viewController: NetworkingLinkSignupViewController, + foundReturningConsumerWithSession consumerSession: ConsumerSessionData + ) + func networkingLinkSignupViewControllerDidFinish( + _ viewController: NetworkingLinkSignupViewController, + // nil == we did not perform saveToLink + saveToLinkWithStripeSucceeded: Bool?, + customSuccessPaneMessage: String?, + withError error: Error? + ) + func networkingLinkSignupViewController( + _ viewController: NetworkingLinkSignupViewController, + didReceiveTerminalError error: Error + ) +} + +final class NetworkingLinkSignupViewController: UIViewController { + + private let dataSource: NetworkingLinkSignupDataSource + weak var delegate: NetworkingLinkSignupViewControllerDelegate? + + private lazy var loadingView: SpinnerView = { + return SpinnerView(theme: dataSource.manifest.theme) + }() + private lazy var formView: LinkSignupFormView = { + let formView = LinkSignupFormView( + accountholderPhoneNumber: dataSource.manifest.accountholderPhoneNumber, + theme: dataSource.manifest.theme + ) + formView.delegate = self + return formView + }() + private var footerView: NetworkingLinkSignupFooterView? + private var viewDidAppear: Bool = false + private var willNavigateToReturningConsumer = false + + init(dataSource: NetworkingLinkSignupDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.hidesBackButton = true + view.backgroundColor = .customBackgroundColor + + showLoadingView(true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // delay executing logic until `viewDidAppear` because + // of janky keyboard animations + if !viewDidAppear { + viewDidAppear = true + dataSource.synchronize() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let networkingLinkSignup): + self.showContent(networkingLinkSignup: networkingLinkSignup) + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "NetworkingLinkSignupSynchronizeError", + pane: .networkingLinkSignupPane + ) + self.delegate?.networkingLinkSignupViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: nil, + customSuccessPaneMessage: nil, + withError: error + ) + } + self.showLoadingView(false) // first set to `true` from `viewDidLoad` + } + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + if willNavigateToReturningConsumer { + willNavigateToReturningConsumer = false + // in case a user decides to go back from verification pane, + // we clear the email so they can re-enter + formView.emailTextField.text = "" + } + } + + private func showContent(networkingLinkSignup: FinancialConnectionsNetworkingLinkSignup) { + let footerView = NetworkingLinkSignupFooterView( + aboveCtaText: networkingLinkSignup.aboveCta, + saveToLinkButtonText: networkingLinkSignup.cta, + notNowButtonText: networkingLinkSignup.skipCta, + theme: dataSource.manifest.theme, + didSelectSaveToLink: { [weak self] in + self?.didSelectSaveToLink() + }, + didSelectNotNow: { [weak self] in + guard let self = self else { + return + } + self.dataSource.analyticsClient + .log( + eventName: "click.not_now", + pane: .networkingLinkSignupPane + ) + self.delegate?.networkingLinkSignupViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: nil, + customSuccessPaneMessage: nil, + withError: nil + ) + }, + didSelectURL: { [weak self] url in + self?.didSelectURLInTextFromBackend( + url, + legalDetailsNotice: networkingLinkSignup.legalDetailsNotice + ) + } + ) + self.footerView = footerView + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: nil, + title: networkingLinkSignup.title, + subtitle: nil, + contentView: NetworkingLinkSignupBodyView( + bulletPoints: networkingLinkSignup.body.bullets, + formView: formView, + didSelectURL: { [weak self] url in + self?.didSelectURLInTextFromBackend( + url, + legalDetailsNotice: networkingLinkSignup.legalDetailsNotice + ) + } + ) + ), + footerView: footerView + ) + paneLayoutView.addTo(view: view) + + #if !canImport(CompositorServices) + // if user drags, dismiss keyboard so the CTA buttons can be shown + paneLayoutView.scrollView.keyboardDismissMode = .onDrag + #endif + + let emailAddress = dataSource.manifest.accountholderCustomerEmailAddress ?? dataSource.elementsSessionContext?.prefillDetails?.email + if let emailAddress, !emailAddress.isEmpty { + formView.prefillEmailAddress(emailAddress) + + let phoneNumber = dataSource.manifest.accountholderPhoneNumber ?? dataSource.elementsSessionContext?.prefillDetails?.formattedPhoneNumber + formView.prefillPhoneNumber(phoneNumber) + } else { + // Slightly delay opening the keyboard to avoid a janky animation. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in + self?.formView.beginEditingEmailAddressField() + } + } + + assert(self.footerView != nil, "footer view should be initialized as part of displaying content") + + // disable CTA if needed + adjustSaveToLinkButtonDisabledState() + } + + private func showLoadingView(_ show: Bool) { + if show && loadingView.superview == nil { + // first-time we are showing this, so add the view to hierarchy + view.addAndPinSubviewToSafeArea(loadingView) + } + + loadingView.isHidden = !show + view.bringSubviewToFront(loadingView) // defensive programming to avoid loadingView being hiddden + } + + private func didSelectSaveToLink() { + footerView?.setIsLoading(true) + dataSource + .analyticsClient + .log( + eventName: "click.save_to_link", + pane: .networkingLinkSignupPane + ) + + dataSource.saveToLink( + emailAddress: formView.email, + phoneNumber: formView.phoneNumber, + countryCode: formView.countryCode + ) + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let customSuccessPaneMessage): + self.delegate?.networkingLinkSignupViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: true, + customSuccessPaneMessage: customSuccessPaneMessage, + withError: nil + ) + case .failure(let error): + // on error, we still go to success pane, but show a small error + // notice above the done button of the success pane + self.delegate?.networkingLinkSignupViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: false, + customSuccessPaneMessage: nil, + withError: error + ) + self.dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "SaveToLinkError", + pane: .networkingLinkSignupPane + ) + } + self.footerView?.setIsLoading(false) + } + } + + private func didSelectURLInTextFromBackend( + _ url: URL, + legalDetailsNotice: FinancialConnectionsLegalDetailsNotice? + ) { + AuthFlowHelpers.handleURLInTextFromBackend( + url: url, + pane: .networkingLinkSignupPane, + analyticsClient: dataSource.analyticsClient, + handleURL: { urlHost, _ in + if urlHost == "legal-details-notice", let legalDetailsNotice { + let legalDetailsNoticeViewController = LegalDetailsNoticeViewController( + legalDetailsNotice: legalDetailsNotice, + theme: dataSource.manifest.theme, + didSelectUrl: { [weak self] url in + self?.didSelectURLInTextFromBackend( + url, + legalDetailsNotice: legalDetailsNotice + ) + } + ) + legalDetailsNoticeViewController.present(on: self) + } + } + ) + } + + private func adjustSaveToLinkButtonDisabledState() { + let isEmailValid = formView.emailTextField.isEmailValid + let isPhoneNumberValid = formView.phoneTextField.isPhoneNumberValid + footerView?.enableSaveToLinkButton(isEmailValid && isPhoneNumberValid) + } + + private func foundReturningConsumer(withSession consumerSession: ConsumerSessionData) { + willNavigateToReturningConsumer = true + delegate?.networkingLinkSignupViewController( + self, + foundReturningConsumerWithSession: consumerSession + ) + } +} + +extension NetworkingLinkSignupViewController: LinkSignupFormViewDelegate { + + func linkSignupFormView( + _ bodyFormView: LinkSignupFormView, + didEnterValidEmailAddress emailAddress: String + ) { + bodyFormView.emailTextField.showLoadingView(true) + dataSource + .lookup(emailAddress: emailAddress) + .observe { [weak self, weak bodyFormView] result in + guard let self = self else { return } + switch result { + case .success(let response): + if response.exists { + self.dataSource.analyticsClient.log( + eventName: "networking.returning_consumer", + pane: .networkingLinkSignupPane + ) + if let consumerSession = response.consumerSession { + // TODO(kgaidis): check whether its fair to assume that we will always have a consumer sesion here + self.foundReturningConsumer(withSession: consumerSession) + } else { + self.delegate?.networkingLinkSignupViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: nil, + customSuccessPaneMessage: nil, + withError: FinancialConnectionsSheetError.unknown( + debugDescription: "No consumer session returned from lookupConsumerSession for emailAddress: \(emailAddress)" + ) + ) + } + } else { + self.dataSource.analyticsClient.log( + eventName: "networking.new_consumer", + pane: .networkingLinkSignupPane + ) + + self.formView.showAndEditPhoneNumberFieldIfNeeded() + } + case .failure(let error): + self.dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "LookupConsumerSessionError", + pane: .networkingLinkSignupPane + ) + self.delegate?.networkingLinkSignupViewController( + self, + didReceiveTerminalError: error + ) + } + bodyFormView?.emailTextField.showLoadingView(false) + } + } + + func linkSignupFormViewDidUpdateFields(_ view: LinkSignupFormView) { + adjustSaveToLinkButtonDisabledState() + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneCountryCodePickerView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneCountryCodePickerView.swift new file mode 100644 index 00000000..b023e7b4 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneCountryCodePickerView.swift @@ -0,0 +1,193 @@ +// +// PhoneCountryCodePickerView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/30/24. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol PhoneCountryCodePickerViewDelegate: AnyObject { + func phoneCountryCodePickerView( + _ pickerView: PhoneCountryCodePickerView, + didSelectCountryCode countryCode: String + ) +} + +final class PhoneCountryCodePickerView: UIView, UIPickerViewDelegate, UIPickerViewDataSource { + + private let height: CGFloat = 250 + + private lazy var pickerView: UIPickerView = { + let pickerView = UIPickerView() + pickerView.dataSource = self + pickerView.delegate = self + return pickerView + }() + private let rowItems: [CountryCodeRowItem] + private var selectedRow: Int { + didSet { + delegate?.phoneCountryCodePickerView( + self, + didSelectCountryCode: selectedCountryCode + ) + } + } + var selectedCountryCode: String { + return rowItems[selectedRow].countryCode + } + + weak var delegate: PhoneCountryCodePickerViewDelegate? + + init(defaultCountryCode: String?) { + let locale = Locale.current + let rowItems = CreateCountryCodeRowItems(locale: locale) + let defaultCountryCode = defaultCountryCode ?? locale.stp_regionCode ?? "US" + self.rowItems = rowItems + self.selectedRow = IndexInCountryCodeRowItems( + rowItems, + forCountryCode: defaultCountryCode + ) ?? 0 + super.init(frame: .zero) + clipsToBounds = true + addSubview(pickerView) + + translatesAutoresizingMaskIntoConstraints = false + pickerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + heightAnchor.constraint(equalToConstant: height), + pickerView.widthAnchor.constraint(equalTo: widthAnchor), + // UIPickerView defines its own height so, to avoid breaking + // constraints/layout, we center it within the view, but + // `clipsToBounds` its edges + pickerView.heightAnchor.constraint(greaterThanOrEqualToConstant: height), + pickerView.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + + // adjusted pickerview to selected row + if pickerView.selectedRow(inComponent: 0) != selectedRow { + pickerView.reloadComponent(0) + selectRow(selectedRow) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // fixes a bug where height was not respected when put + // into `textField.inputView` unless this was overridden + override var intrinsicContentSize: CGSize { + var intrinsicContentSize = super.intrinsicContentSize + intrinsicContentSize.height = height + return intrinsicContentSize + } + + func selectCountryCode(_ countryCode: String) { + guard let row = IndexInCountryCodeRowItems( + rowItems, + forCountryCode: countryCode + ) else { + return + } + selectRow(row) + selectedRow = row + } + + private func selectRow(_ row: Int) { + pickerView.selectRow(row, inComponent: 0, animated: false) + } + + // MARK: - UIPickerViewDataSource + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView( + _ pickerView: UIPickerView, + numberOfRowsInComponent component: Int + ) -> Int { + return rowItems.count + } + + // MARK: - UIPickerViewDelegate + + func pickerView( + _ pickerView: UIPickerView, + titleForRow row: Int, + forComponent component: Int + ) -> String? { + let rowItem = rowItems[row] + return rowItem.displayTitle + } + + func pickerView( + _ pickerView: UIPickerView, + didSelectRow row: Int, + inComponent component: Int + ) { + selectedRow = row + } +} + +private func IndexInCountryCodeRowItems( + _ countryCodeRowItems: [CountryCodeRowItem], + forCountryCode countryCode: String +) -> Int? { + return countryCodeRowItems.firstIndex( + where: { $0.countryCode == countryCode } + ) +} + +private struct CountryCodeRowItem { + let displayTitle: String + let countryCode: String +} + +private func CreateCountryCodeRowItems(locale: Locale) -> [CountryCodeRowItem] { + let countryCodes = locale.sortedByTheirLocalizedNames( + PhoneNumber.Metadata.allMetadata.map({ $0.regionCode }) + ) + return countryCodes.map { countryCode in + let countryFlag = String.countryFlagEmoji(for: countryCode) ?? "" // 🇺🇸 + let countryName = locale.localizedString(forRegionCode: countryCode) ?? countryCode // United States + let phonePrefix = PhoneNumber.Metadata.metadata(for: countryCode)?.prefix ?? "" // +1 + return CountryCodeRowItem( + displayTitle: "\(countryFlag) \(countryName) (\(phonePrefix))", + countryCode: countryCode + ) + } +} + +#if DEBUG + +import SwiftUI + +private struct PhoneCountryCodePickerViewUIViewRepresentable: UIViewRepresentable { + func makeUIView(context: Context) -> PhoneCountryCodePickerView { + PhoneCountryCodePickerView(defaultCountryCode: nil) + } + + func updateUIView( + _ phoneCountryCodePickerView: PhoneCountryCodePickerView, + context: Context + ) {} +} + +struct PhoneCountryCodePickerView_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + VStack(spacing: 0) { + PhoneCountryCodePickerViewUIViewRepresentable() + Spacer() + } + .padding(.horizontal, 40) + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneCountryCodeSelectorView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneCountryCodeSelectorView.swift new file mode 100644 index 00000000..733bcf6c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneCountryCodeSelectorView.swift @@ -0,0 +1,209 @@ +// +// PhoneCountryCodeSelectorView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/30/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +protocol PhoneCountryCodeSelectorViewDelegate: AnyObject { + func phoneCountryCodeSelectorView( + _ selectorView: PhoneCountryCodeSelectorView, + didSelectCountryCode countryCode: String + ) +} + +final class PhoneCountryCodeSelectorView: UIView { + + private lazy var flagLabel: AttributedLabel = { + let flagLabel = AttributedLabel( + font: .label(.large), + textColor: .textDefault + ) + return flagLabel + }() + private lazy var countryCodeLabel: AttributedLabel = { + let flagLabel = AttributedLabel( + font: .label(.large), + textColor: .textDefault + ) + return flagLabel + }() + // to show the `pickerView` as a keyboard, we need an + // "invisible" UITextField for the user to tap on + private lazy var invisbleTextField: UITextField = { + let textField = UnselectableTextField() + textField.autocorrectionType = .no + textField.tintColor = .clear + textField.borderStyle = .none + textField.backgroundColor = .clear + textField.inputView = pickerView + textField.inputAccessoryView = keyboardToolbar + return textField + }() + private lazy var keyboardToolbar: DoneButtonToolbar = { + var theme: ElementsAppearance = .default + theme.colors = { + var colors = ElementsAppearance.Color() + colors.primary = self.theme.primaryColor + colors.secondaryText = .textSubdued + return colors + }() + let keyboardToolbar = DoneButtonToolbar( + delegate: self, + showCancelButton: false, + theme: theme + ) + return keyboardToolbar + }() + private let pickerView: PhoneCountryCodePickerView + private let theme: FinancialConnectionsTheme + var selectedCountryCode: String { + return pickerView.selectedCountryCode + } + + weak var delegate: PhoneCountryCodeSelectorViewDelegate? + + init(defaultCountryCode: String?, theme: FinancialConnectionsTheme) { + self.pickerView = PhoneCountryCodePickerView(defaultCountryCode: defaultCountryCode) + self.theme = theme + super.init(frame: .zero) + pickerView.delegate = self + + backgroundColor = .backgroundOffset + layer.cornerRadius = 8 + clipsToBounds = true + accessibilityIdentifier = "phone_country_code_selector" + + let horizontalStackView = UIStackView( + arrangedSubviews: [ + flagLabel, + countryCodeLabel, + ] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 8 + horizontalStackView.isLayoutMarginsRelativeArrangement = true + horizontalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 12, + leading: 12, + bottom: 12, + trailing: 12 + ) + addAndPinSubview(horizontalStackView) + addAndPinSubview(invisbleTextField) + + // this will update the view based off whatever the + // default is in the picker view + updateLabelsBasedOffSelectedCountryCode() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func endEditing(_ force: Bool) -> Bool { + return invisbleTextField.endEditing(force) + } + + func selectCountryCode(_ countryCode: String) { + // this will fire `PhoneCountryCodePickerViewDelegate` + pickerView.selectCountryCode(countryCode) + } + + private func updateLabelsBasedOffSelectedCountryCode() { + flagLabel.setText(String.countryFlagEmoji(for: selectedCountryCode) ?? "🇺🇸") + countryCodeLabel.setText(PhoneNumber.Metadata.metadata(for: selectedCountryCode)?.prefix ?? "") + } +} + +// MARK: - PhoneCountryCodePickerViewDelegate + +extension PhoneCountryCodeSelectorView: PhoneCountryCodePickerViewDelegate { + + func phoneCountryCodePickerView( + _ pickerView: PhoneCountryCodePickerView, + didSelectCountryCode countryCode: String + ) { + updateLabelsBasedOffSelectedCountryCode() + delegate?.phoneCountryCodeSelectorView(self, didSelectCountryCode: countryCode) + } +} + +// MARK: - DoneButtonToolbarDelegate + +extension PhoneCountryCodeSelectorView: DoneButtonToolbarDelegate { + func didTapDone(_ toolbar: DoneButtonToolbar) { + invisbleTextField.endEditing(true) + } +} + +private class UnselectableTextField: UITextField { + override func caretRect(for position: UITextPosition) -> CGRect { + return .zero + } + + override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { + return [] + } + + override func canPerformAction( + _ action: Selector, + withSender sender: Any? + ) -> Bool { + if action == #selector(UIResponderStandardEditActions.paste(_:)) { + return false + } + return super.canPerformAction(action, withSender: sender) + } +} + +#if DEBUG + +import SwiftUI + +private struct PhoneCountryCodeSelectorViewUIViewRepresentable: UIViewRepresentable { + + let defaultCountryCode: String? + + func makeUIView(context: Context) -> PhoneCountryCodeSelectorView { + PhoneCountryCodeSelectorView(defaultCountryCode: defaultCountryCode, theme: .light) + } + + func updateUIView( + _ PhoneCountryCodeSelectorView: PhoneCountryCodeSelectorView, + context: Context + ) {} +} + +struct PhoneCountryCodeSelectorView_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + VStack(spacing: 16) { + PhoneCountryCodeSelectorViewUIViewRepresentable( + defaultCountryCode: "" + ) + .frame(width: 85, height: 48) + + PhoneCountryCodeSelectorViewUIViewRepresentable( + defaultCountryCode: "US" + ) + .frame(width: 72, height: 48) + + PhoneCountryCodeSelectorViewUIViewRepresentable( + defaultCountryCode: "GB" + ) + .frame(width: 85, height: 48) + + Spacer() + } + .padding() + .background(Color(UIColor.customBackgroundColor)) + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneTextField.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneTextField.swift new file mode 100644 index 00000000..74839010 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkSignupPane/PhoneTextField.swift @@ -0,0 +1,270 @@ +// +// PhoneTextField.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/30/24. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol PhoneTextFieldDelegate: AnyObject { + func phoneTextField( + _ phoneTextField: PhoneTextField, + didChangePhoneNumber phoneNumber: PhoneNumber? + ) +} + +final class PhoneTextField: UIView { + + fileprivate lazy var textField: RoundedTextField = { + let textField = RoundedTextField( + placeholder: STPLocalizedString("Phone number", "The title of a user-input-field that appears when a user is signing up to Link (a payment service). It instructs user to type a phone number."), + showDoneToolbar: true, + theme: theme + ) + textField.textField.keyboardType = .phonePad + textField.textField.textContentType = .telephoneNumber + textField.textField.autocapitalizationType = .none + textField + .containerHorizontalStackView + .insertArrangedSubview(countryCodeSelectorView, at: 0) + textField + .containerHorizontalStackView + .directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 4, + leading: 4, + bottom: 4, + trailing: 16 + ) + textField.delegate = self + textField.textField.accessibilityIdentifier = "phone_text_field" + return textField + }() + private let countryCodeSelectorView: PhoneCountryCodeSelectorView + private let theme: FinancialConnectionsTheme + // we will only start validating as user + // types once editing ends + fileprivate var didEndEditingOnce = false + + var text: String { + get { + textField.text + } + set { + textField.text = newValue + phoneNumberDidChange() + } + } + var phoneNumber: PhoneNumber? { + return PhoneNumber(number: text, countryCode: countryCodeSelectorView.selectedCountryCode) + } + var isPhoneNumberValid: Bool { + if text.isEmpty { + // empty phone number + return false + } else if let phoneNumber { + return phoneNumber.isComplete + } else { + // Assume user has entered a format or for a region the SDK doesn't know about. + // Return valid as long as it's non-empty and let the server decide. + return true + } + } + + weak var delegate: PhoneTextFieldDelegate? + + init(defaultPhoneNumber: String?, theme: FinancialConnectionsTheme) { + var defaultPhoneNumber = defaultPhoneNumber + var defaultCountryCode: String? + if let _defaultPhoneNumber = defaultPhoneNumber, let e164PhoneNumber = PhoneNumber.fromE164(_defaultPhoneNumber) { + defaultPhoneNumber = e164PhoneNumber.number + defaultCountryCode = e164PhoneNumber.countryCode + } + self.countryCodeSelectorView = PhoneCountryCodeSelectorView( + defaultCountryCode: defaultCountryCode, + theme: theme + ) + self.theme = theme + super.init(frame: .zero) + countryCodeSelectorView.delegate = self + addAndPinSubview(textField) + text = defaultPhoneNumber ?? "" + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } + + override func endEditing(_ force: Bool) -> Bool { + return textField.endEditing(force) + } + + private func phoneNumberDidChange() { + // if we notice that `text` starts with a prefix + // (ex. due to autofill, or copy-paste), then we will extract + // the prefix + if + // we noticed that user started input with a prefix + text.hasPrefix("+"), + let e164PhoneNumber = PhoneNumber.fromE164( + // `fromE164` only accepts a format like "+14005006000" + // so remove everything except digits and "+" + text.stp_stringByRemovingCharacters( + from: CharacterSet.stp_asciiDigit.union( + CharacterSet(charactersIn: "+") + ).inverted + ) + ) + { + // (IMPORTANT!) this will call `phoneNumberDidChange` again + text = e164PhoneNumber + .number + // the "+" should already be removed at this point but + // we add this extra code as defensive programming + // + // it ensures that we will not enter a infinite + // loop because to enter this code the text needs + // to start with a "+" (`text.hasPrefix("+")`) + .stp_stringByRemovingCharacters( + from: CharacterSet(charactersIn: "+") + ) + + // (IMPORTANT!) this will call `phoneNumberDidChange` again + // + // its important that it comes after setting `text` + // because otherwise there will be an infinite loop + countryCodeSelectorView.selectCountryCode(e164PhoneNumber.countryCode) + + // Setting `text` will cause this function to be + // called again so its safe to return + return + } + + // format the text (ex. "401500" -> "(401) 500") + textField.text = phoneNumber?.string(as: .national) ?? text + + textField.errorText = nil + if !isPhoneNumberValid { + // only show error messages once + // user cleared + if didEndEditingOnce { + if text.isEmpty { + // no error message if empty + } else { + textField.errorText = STPLocalizedString("Your mobile phone number is incomplete.", "An error message that instructs the user to keep typing their phone number in a user-input field.") + } + } + } + + delegate?.phoneTextField(self, didChangePhoneNumber: phoneNumber) + } +} + +// MARK: - RoundedTextFieldDelegate + +extension PhoneTextField: RoundedTextFieldDelegate { + + func roundedTextField( + _ textField: RoundedTextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + return true + } + + func roundedTextField( + _ textField: RoundedTextField, + textDidChange text: String + ) { + phoneNumberDidChange() + } + + func roundedTextFieldUserDidPressReturnKey(_ textField: RoundedTextField) { + // no-op + } + + func roundedTextFieldDidEndEditing( + _ textField: RoundedTextField + ) { + didEndEditingOnce = true + phoneNumberDidChange() // activate error checking + } +} + +// MARK: - PhoneCountryCodeSelectorViewDelegate + +extension PhoneTextField: PhoneCountryCodeSelectorViewDelegate { + + func phoneCountryCodeSelectorView( + _ selectorView: PhoneCountryCodeSelectorView, + didSelectCountryCode countryCode: String + ) { + phoneNumberDidChange() + } +} + +#if DEBUG + +import SwiftUI + +private struct PhoneTextFieldUIViewRepresentable: UIViewRepresentable { + + let defaultPhoneNumber: String + + func makeUIView(context: Context) -> PhoneTextField { + PhoneTextField(defaultPhoneNumber: defaultPhoneNumber, theme: .light) + } + + func updateUIView( + _ phoneTextField: PhoneTextField, + context: Context + ) { + // activate the error-view if needed + phoneTextField.didEndEditingOnce = true + phoneTextField.roundedTextField( + phoneTextField.textField, + textDidChange: defaultPhoneNumber + ) + } +} + +struct PhoneTextField_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + VStack(spacing: 16) { + PhoneTextFieldUIViewRepresentable( + defaultPhoneNumber: "" + ).frame(height: 56) + + PhoneTextFieldUIViewRepresentable( + defaultPhoneNumber: "4015006000" + ).frame(height: 56) + + PhoneTextFieldUIViewRepresentable( + defaultPhoneNumber: "401500600" + ).frame(height: 56) + + PhoneTextFieldUIViewRepresentable( + defaultPhoneNumber: "40150060003435" + ).frame(height: 56) + + PhoneTextFieldUIViewRepresentable( + defaultPhoneNumber: "+442079460321" + ).frame(height: 56) + + Spacer() + } + .padding() + .background(Color(UIColor.customBackgroundColor)) + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationBodyView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationBodyView.swift new file mode 100644 index 00000000..aa0c2597 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationBodyView.swift @@ -0,0 +1,131 @@ +// +// NetworkingLinkStepUpVerificationBodyView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/16/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class NetworkingLinkStepUpVerificationBodyView: UIView { + + private let theme: FinancialConnectionsTheme + private let didSelectResendCode: () -> Void + + // `UIStackView` is used only for padding + private lazy var footnoteStackView: UIStackView = { + let footnoteStackView = UIStackView() + footnoteStackView.axis = .vertical + footnoteStackView.alignment = .center + footnoteStackView.isLayoutMarginsRelativeArrangement = true + footnoteStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 0, + bottom: 8, + trailing: 0 + ) + return footnoteStackView + }() + + init( + theme: FinancialConnectionsTheme, + otpView: UIView, + didSelectResendCode: @escaping () -> Void + ) { + self.theme = theme + self.didSelectResendCode = didSelectResendCode + super.init(frame: .zero) + let verticalStackView = UIStackView( + arrangedSubviews: [ + otpView, + footnoteStackView, + ] + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + addAndPinSubview(verticalStackView) + + showResendCodeLabel(true) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func showResendCodeLabel(_ show: Bool) { + // clear all previous state + footnoteStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + + if show { + footnoteStackView.addArrangedSubview( + CreateResendCodeLabel( + theme: theme, + didSelect: didSelectResendCode + ) + ) + } + } +} + +private func CreateResendCodeLabel( + theme: FinancialConnectionsTheme, + didSelect: @escaping () -> Void +) -> UIView { + let resendCodeLabel = AttributedTextView( + font: .label(.medium), + boldFont: .label(.mediumEmphasized), + linkFont: .label(.mediumEmphasized), + textColor: theme.textActionColor, + showLinkUnderline: false + ) + let text = STPLocalizedString( + "Resend code", + "The title of a button that allows a user to request a one-time-password (OTP) again in case they did not receive it." + ) + resendCodeLabel.setText( + // we add a fake link to fire the `action` closure + "[\(text)](https://www.just-fire-action.com)", + action: { _ in + didSelect() + } + ) + return resendCodeLabel +} + +#if DEBUG + +import SwiftUI + +private struct NetworkingLinkStepUpVerificationBodyViewUIViewRepresentable: UIViewRepresentable { + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> NetworkingLinkStepUpVerificationBodyView { + NetworkingLinkStepUpVerificationBodyView( + theme: theme, + otpView: UIView(), + didSelectResendCode: {} + ) + } + + func updateUIView(_ uiView: NetworkingLinkStepUpVerificationBodyView, context: Context) {} +} + +struct NetworkingLinkStepUpVerificationBodyView_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + Spacer() + NetworkingLinkStepUpVerificationBodyViewUIViewRepresentable(theme: .light) + .frame(maxHeight: 100) + .padding() + NetworkingLinkStepUpVerificationBodyViewUIViewRepresentable(theme: .linkLight) + .frame(maxHeight: 100) + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationDataSource.swift new file mode 100644 index 00000000..c06f805d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationDataSource.swift @@ -0,0 +1,86 @@ +// +// NetworkingLinkStepUpVerificationDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/16/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NetworkingLinkStepUpVerificationDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var consumerSession: ConsumerSessionData { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var networkingOTPDataSource: NetworkingOTPDataSource { get } + + func markLinkStepUpAuthenticationVerified() -> Future + func selectNetworkedAccount() -> Future +} + +final class NetworkingLinkStepUpVerificationDataSourceImplementation: NetworkingLinkStepUpVerificationDataSource { + + private(set) var consumerSession: ConsumerSessionData + private let selectedAccountIds: [String] + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let manifest: FinancialConnectionsSessionManifest + let analyticsClient: FinancialConnectionsAnalyticsClient + let networkingOTPDataSource: NetworkingOTPDataSource + + init( + consumerSession: ConsumerSessionData, + selectedAccountIds: [String], + manifest: FinancialConnectionsSessionManifest, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.consumerSession = consumerSession + self.selectedAccountIds = selectedAccountIds + self.manifest = manifest + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + let networkingOTPDataSource = NetworkingOTPDataSourceImplementation( + otpType: "EMAIL", + emailAddress: consumerSession.emailAddress, + customEmailType: "NETWORKED_CONNECTIONS_OTP_EMAIL", + connectionsMerchantName: manifest.businessName, + pane: .networkingLinkStepUpVerification, + consumerSession: consumerSession, + apiClient: apiClient, + clientSecret: clientSecret, + analyticsClient: analyticsClient, + isTestMode: manifest.isTestMode, + theme: manifest.theme + ) + self.networkingOTPDataSource = networkingOTPDataSource + networkingOTPDataSource.delegate = self + } + + func markLinkStepUpAuthenticationVerified() -> Future { + return apiClient.markLinkStepUpAuthenticationVerified(clientSecret: clientSecret) + } + + func selectNetworkedAccount() -> Future { + return apiClient.selectNetworkedAccounts( + selectedAccountIds: selectedAccountIds, + clientSecret: clientSecret, + consumerSessionClientSecret: consumerSession.clientSecret, + consentAcquired: nil + ) + } +} + +// MARK: - NetworkingOTPDataSourceDelegate + +extension NetworkingLinkStepUpVerificationDataSourceImplementation: NetworkingOTPDataSourceDelegate { + + func networkingOTPDataSource( + _ dataSource: NetworkingOTPDataSource, + didUpdateConsumerSession consumerSession: ConsumerSessionData + ) { + self.consumerSession = consumerSession + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationViewController.swift new file mode 100644 index 00000000..01c6254e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkStepUpVerification/NetworkingLinkStepUpVerificationViewController.swift @@ -0,0 +1,294 @@ +// +// NetworkingLinkStepUpVerificationViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/16/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol NetworkingLinkStepUpVerificationViewControllerDelegate: AnyObject { + func networkingLinkStepUpVerificationViewController( + _ viewController: NetworkingLinkStepUpVerificationViewController, + didReceiveConsumerPublishableKey consumerPublishableKey: String + ) + func networkingLinkStepUpVerificationViewController( + _ viewController: NetworkingLinkStepUpVerificationViewController, + didCompleteVerificationWithInstitution institution: FinancialConnectionsInstitution?, + nextPane: FinancialConnectionsSessionManifest.NextPane, + customSuccessPaneCaption: String?, + customSuccessPaneSubCaption: String? + ) + func networkingLinkStepUpVerificationViewController( + _ viewController: NetworkingLinkStepUpVerificationViewController, + didReceiveTerminalError error: Error + ) + func networkingLinkStepUpVerificationViewControllerEncounteredSoftError( + _ viewController: NetworkingLinkStepUpVerificationViewController + ) +} + +final class NetworkingLinkStepUpVerificationViewController: UIViewController { + + private let dataSource: NetworkingLinkStepUpVerificationDataSource + weak var delegate: NetworkingLinkStepUpVerificationViewControllerDelegate? + + private lazy var fullScreenLoadingView: UIView = { + return SpinnerView(theme: dataSource.manifest.theme) + }() + private lazy var bodyView: NetworkingLinkStepUpVerificationBodyView = { + let bodyView = NetworkingLinkStepUpVerificationBodyView( + theme: dataSource.manifest.theme, + otpView: otpView, + didSelectResendCode: { [weak self] in + self?.didSelectResendCode() + } + ) + return bodyView + }() + private lazy var otpView: NetworkingOTPView = { + let otpView = NetworkingOTPView(dataSource: dataSource.networkingOTPDataSource) + otpView.delegate = self + return otpView + }() + // used to track whether we show loading view when calling `lookupConsumerAndStartVerification` + private var didShowContent: Bool = false + + init(dataSource: NetworkingLinkStepUpVerificationDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + + otpView.startVerification() + } + + private func handleFailure(error: Error, errorName: String) { + dataSource.analyticsClient.log( + eventName: "networking.verification.step_up.error", + parameters: [ + "error": errorName + ], + pane: .networkingLinkStepUpVerification + ) + dataSource.analyticsClient.logUnexpectedError( + error, + errorName: errorName, + pane: .networkingLinkStepUpVerification + ) + delegate?.networkingLinkStepUpVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + + private func showContent() { + didShowContent = true + + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: nil, + title: STPLocalizedString( + "Verify your email", + "The title of a screen where users are asked to enter a one-time-password (OTP) that they received in their email." + ), + subtitle: String( + format: STPLocalizedString( + "Enter the code sent to %@. We periodically request this extra step to keep your account safe.", + "The subtitle/description of a screen where users are asked to enter a one-time-password (OTP) that they received in their email. '%@' is replaced with an email, for example, 'test@test.com'." + ), dataSource.consumerSession.emailAddress + ), + contentView: bodyView + ), + footerView: nil + ) + paneLayoutView.addTo(view: view) + } + + private func showFullScreenLoadingView(_ show: Bool) { + if show && fullScreenLoadingView.superview == nil { + // first-time we are showing this, so add the view to hierarchy + view.addAndPinSubview(fullScreenLoadingView) + } + + fullScreenLoadingView.isHidden = !show + view.bringSubviewToFront(fullScreenLoadingView) // defensive programming to avoid loadingView being hiddden + } + + private func showSmallLoadingView(_ showLoadingView: Bool) { + bodyView.showResendCodeLabel(!showLoadingView) + otpView.showLoadingView(showLoadingView) + } + + private func didSelectResendCode() { + otpView.startVerification() + } +} + +// MARK: - NetworkingOTPViewDelegate + +extension NetworkingLinkStepUpVerificationViewController: NetworkingOTPViewDelegate { + + func networkingOTPView(_ view: NetworkingOTPView, didGetConsumerPublishableKey consumerPublishableKey: String) { + delegate?.networkingLinkStepUpVerificationViewController(self, didReceiveConsumerPublishableKey: consumerPublishableKey) + } + + func networkingOTPViewWillStartConsumerLookup(_ view: NetworkingOTPView) { + if !didShowContent { + showFullScreenLoadingView(true) + } else { + showSmallLoadingView(true) + } + } + + func networkingOTPViewConsumerNotFound(_ view: NetworkingOTPView) { + // side-note: it is redundant to call `showLoadingView` & `showSmallLoadingView` because + // usually only one needs to be hidden, but this keeps the code simple + showFullScreenLoadingView(false) + showSmallLoadingView(false) + + dataSource.analyticsClient.log( + eventName: "networking.verification.step_up.error", + parameters: [ + "error": "ConsumerNotFoundError", + ], + pane: .networkingLinkStepUpVerification + ) + delegate?.networkingLinkStepUpVerificationViewControllerEncounteredSoftError(self) + } + + func networkingOTPView(_ view: NetworkingOTPView, didFailConsumerLookup error: Error) { + // side-note: it is redundant to call both (`showLoadingView` & `isResendingCode`) because + // only one needs to be hidden (depends on the state), but this keeps the code simple + showFullScreenLoadingView(false) + showSmallLoadingView(false) + + handleFailure(error: error, errorName: "LookupConsumerSessionError") + } + + func networkingOTPViewWillStartVerification(_ view: NetworkingOTPView) { + // no-op + } + + func networkingOTPView(_ view: NetworkingOTPView, didStartVerification consumerSession: ConsumerSessionData) { + // it's important to call this BEFORE we call `showContent` because of `didShowContent` + if !didShowContent { + showFullScreenLoadingView(false) + } else { + showSmallLoadingView(false) + } + + showContent() + } + + func networkingOTPView(_ view: NetworkingOTPView, didFailToStartVerification error: Error) { + // side-note: it is redundant to call `showLoadingView` & `showSmallLoadingView` because + // usually only one needs to be hidden, but this keeps the code simple + showFullScreenLoadingView(false) + showSmallLoadingView(false) + + handleFailure(error: error, errorName: "StartVerificationSessionError") + } + + func networkingOTPViewWillConfirmVerification(_ view: NetworkingOTPView) { + showSmallLoadingView(true) + } + + func networkingOTPViewDidConfirmVerification(_ view: NetworkingOTPView) { + showSmallLoadingView(true) + dataSource.markLinkStepUpAuthenticationVerified() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success: + self.dataSource + .analyticsClient + .log( + eventName: "networking.verification.step_up.success", + pane: .networkingLinkStepUpVerification + ) + self.dataSource.selectNetworkedAccount() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let response): + self.dataSource + .analyticsClient + .log( + eventName: "click.link_accounts", + pane: .networkingLinkStepUpVerification + ) + + let nextPane = response.nextPane ?? .success + let successPane = response.displayText?.text?.succcessPane + self.delegate?.networkingLinkStepUpVerificationViewController( + self, + // networking manual entry will not return an institution + didCompleteVerificationWithInstitution: response.data.first, + nextPane: nextPane, + customSuccessPaneCaption: successPane?.caption, + customSuccessPaneSubCaption: successPane?.subCaption + ) + + // only hide loading view after animation + // to next screen has completed + DispatchQueue.main.asyncAfter( + deadline: .now() + 1.0 + ) { [weak self] in + self?.showSmallLoadingView(false) + } + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "SelectNetworkedAccountError", + pane: .networkingLinkStepUpVerification + ) + self.delegate?.networkingLinkStepUpVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + } + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "MarkLinkStepUpAuthenticationVerifiedError", + pane: .networkingLinkStepUpVerification + ) + self.delegate?.networkingLinkStepUpVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + } + } + + func networkingOTPView( + _ view: NetworkingOTPView, + didFailToConfirmVerification error: Error, + isTerminal: Bool + ) { + showSmallLoadingView(false) + + if isTerminal { + delegate?.networkingLinkStepUpVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkVerification/NetworkingLinkVerificationDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkVerification/NetworkingLinkVerificationDataSource.swift new file mode 100644 index 00000000..0de88b4b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkVerification/NetworkingLinkVerificationDataSource.swift @@ -0,0 +1,123 @@ +// +// NetworkingLinkVerificationDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/7/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NetworkingLinkVerificationDataSource: AnyObject { + var accountholderCustomerEmailAddress: String { get } + var manifest: FinancialConnectionsSessionManifest { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var consumerSession: ConsumerSessionData? { get } + var networkingOTPDataSource: NetworkingOTPDataSource { get } + + func markLinkVerified() -> Future + func fetchNetworkedAccounts() -> Future + func attachConsumerToLinkAccountAndSynchronize() -> Future +} + +final class NetworkingLinkVerificationDataSourceImplementation: NetworkingLinkVerificationDataSource { + + let accountholderCustomerEmailAddress: String + let manifest: FinancialConnectionsSessionManifest + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + private let returnURL: String? + let analyticsClient: FinancialConnectionsAnalyticsClient + let networkingOTPDataSource: NetworkingOTPDataSource + + private(set) var consumerSession: ConsumerSessionData? { + didSet { + apiClient.consumerSession = consumerSession + } + } + + init( + accountholderCustomerEmailAddress: String, + manifest: FinancialConnectionsSessionManifest, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + returnURL: String?, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.accountholderCustomerEmailAddress = accountholderCustomerEmailAddress + self.manifest = manifest + self.apiClient = apiClient + self.clientSecret = clientSecret + self.returnURL = returnURL + self.analyticsClient = analyticsClient + let networkingOTPDataSource = NetworkingOTPDataSourceImplementation( + otpType: "SMS", + emailAddress: accountholderCustomerEmailAddress, + customEmailType: nil, + connectionsMerchantName: nil, + pane: .networkingLinkVerification, + consumerSession: nil, + apiClient: apiClient, + clientSecret: clientSecret, + analyticsClient: analyticsClient, + isTestMode: manifest.isTestMode, + theme: manifest.theme + ) + self.networkingOTPDataSource = networkingOTPDataSource + networkingOTPDataSource.delegate = self + } + + func markLinkVerified() -> Future { + return apiClient.markLinkVerified(clientSecret: clientSecret) + } + + func fetchNetworkedAccounts() -> Future { + guard let consumerSessionClientSecret = consumerSession?.clientSecret else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "invalid confirmVerificationSession state: no consumerSessionClientSecret")) + } + return apiClient.fetchNetworkedAccounts( + clientSecret: clientSecret, + consumerSessionClientSecret: consumerSessionClientSecret + ) + } + + func attachConsumerToLinkAccountAndSynchronize() -> Future { + guard manifest.isProductInstantDebits else { + return Promise(error: FinancialConnectionsSheetError.unknown( + debugDescription: "Invalid \(#function) state: should only be used in instant debits flow" + )) + } + + guard let consumerSessionClientSecret = consumerSession?.clientSecret else { + return Promise(error: FinancialConnectionsSheetError.unknown( + debugDescription: "Invalid \(#function) state: no consumerSessionClientSecret" + )) + } + + return apiClient.attachLinkConsumerToLinkAccountSession( + linkAccountSession: clientSecret, + consumerSessionClientSecret: consumerSessionClientSecret + ) + .chained { [weak self] _ in + guard let self else { + return Promise(error: FinancialConnectionsSheetError.unknown( + debugDescription: "Data source deallocated" + )) + } + + return self.apiClient.synchronize( + clientSecret: self.clientSecret, + returnURL: self.returnURL + ) + } + } +} + +// MARK: - NetworkingOTPDataSourceDelegate + +extension NetworkingLinkVerificationDataSourceImplementation: NetworkingOTPDataSourceDelegate { + + func networkingOTPDataSource(_ dataSource: NetworkingOTPDataSource, didUpdateConsumerSession consumerSession: ConsumerSessionData) { + self.consumerSession = consumerSession + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkVerification/NetworkingLinkVerificationViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkVerification/NetworkingLinkVerificationViewController.swift new file mode 100644 index 00000000..69fb612c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingLinkVerification/NetworkingLinkVerificationViewController.swift @@ -0,0 +1,269 @@ +// +// NetworkingLinkVerificationViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/7/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol NetworkingLinkVerificationViewControllerDelegate: AnyObject { + func networkingLinkVerificationViewController( + _ viewController: NetworkingLinkVerificationViewController, + didReceiveConsumerPublishableKey consumerPublishableKey: String + ) + func networkingLinkVerificationViewController( + _ viewController: NetworkingLinkVerificationViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane, + consumerSession: ConsumerSessionData?, + preventBackNavigation: Bool + ) + func networkingLinkVerificationViewController( + _ viewController: NetworkingLinkVerificationViewController, + didReceiveTerminalError error: Error + ) +} + +final class NetworkingLinkVerificationViewController: UIViewController { + + private let dataSource: NetworkingLinkVerificationDataSource + weak var delegate: NetworkingLinkVerificationViewControllerDelegate? + + private lazy var loadingView: UIView = { + return SpinnerView(theme: dataSource.manifest.theme) + }() + private lazy var otpView: NetworkingOTPView = { + let otpView = NetworkingOTPView(dataSource: dataSource.networkingOTPDataSource) + otpView.delegate = self + return otpView + }() + + init(dataSource: NetworkingLinkVerificationDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + otpView.lookupConsumerAndStartVerification() + } + + private func showContent(redactedPhoneNumber: String) { + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: nil, + title: STPLocalizedString( + "Confirm it's you", + "The title of a screen where users are informed that they can sign-in-to Link." + ), + subtitle: String(format: STPLocalizedString( + "Enter the code sent to %@", + "The subtitle/description of a screen where users are informed that they have received a One-Type-Password (OTP) to their phone. '%@' gets replaced by a redacted phone number." + ), AuthFlowHelpers.formatRedactedPhoneNumber(redactedPhoneNumber)), + contentView: otpView + ), + footerView: nil + ) + paneLayoutView.addTo(view: view) + } + + private func showLoadingView(_ show: Bool) { + if show && loadingView.superview == nil { + // first-time we are showing this, so add the view to hierarchy + view.addAndPinSubviewToSafeArea(loadingView) + } + + loadingView.isHidden = !show + view.bringSubviewToFront(loadingView) // defensive programming to avoid loadingView being hiddden + } + + private func requestNextPane(_ pane: FinancialConnectionsSessionManifest.NextPane, preventBackNavigation: Bool) { + if let consumerSession = dataSource.consumerSession { + delegate?.networkingLinkVerificationViewController( + self, + didRequestNextPane: pane, + consumerSession: consumerSession, + preventBackNavigation: preventBackNavigation + ) + } else { + assertionFailure("logic error: did not have consumerSession") + delegate?.networkingLinkVerificationViewController(self, didReceiveTerminalError: FinancialConnectionsSheetError.unknown(debugDescription: "logic error: did not have consumerSession")) + } + } + + private func attachConsumerToLinkAccountAndSynchronize(from view: NetworkingOTPView) { + view.showLoadingView(true) + + dataSource + .attachConsumerToLinkAccountAndSynchronize() + .observe { [weak self] result in + guard let self else { return } + self.hideOTPLoadingViewAfterDelay(view) + + switch result { + case .success: + self.requestNextPane(.linkAccountPicker, preventBackNavigation: true) + case .failure(let error): + self.delegate?.networkingLinkVerificationViewController(self, didReceiveTerminalError: error) + } + } + } + + private func hideOTPLoadingViewAfterDelay(_ view: NetworkingOTPView) { + // only hide loading view after animation + // to next screen has completed + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak view] in + view?.showLoadingView(false) + } + } +} + +// MARK: - NetworkingOTPViewDelegate + +extension NetworkingLinkVerificationViewController: NetworkingOTPViewDelegate { + func networkingOTPView(_ view: NetworkingOTPView, didGetConsumerPublishableKey consumerPublishableKey: String) { + delegate?.networkingLinkVerificationViewController(self, didReceiveConsumerPublishableKey: consumerPublishableKey) + } + + func networkingOTPViewWillStartConsumerLookup(_ view: NetworkingOTPView) { + showLoadingView(true) + } + + func networkingOTPViewConsumerNotFound(_ view: NetworkingOTPView) { + dataSource.analyticsClient.log( + eventName: "networking.verification.error", + parameters: [ + "error": "ConsumerNotFoundError" + ], + pane: .networkingLinkVerification + ) + delegate?.networkingLinkVerificationViewController( + self, + didRequestNextPane: .institutionPicker, + consumerSession: nil, + preventBackNavigation: false + ) + showLoadingView(false) // started in networkingOTPViewWillStartConsumerLookup + } + + func networkingOTPView(_ view: NetworkingOTPView, didFailConsumerLookup error: Error) { + dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "LookupConsumerSessionError", + pane: .networkingLinkVerification + ) + dataSource.analyticsClient.log( + eventName: "networking.verification.error", + parameters: [ + "error": "LookupConsumerSession" + ], + pane: .networkingLinkVerification + ) + delegate?.networkingLinkVerificationViewController(self, didReceiveTerminalError: error) + showLoadingView(false) // started in networkingOTPViewWillStartConsumerLookup + } + + func networkingOTPViewWillStartVerification(_ view: NetworkingOTPView) { + // no-op + } + + func networkingOTPView(_ view: NetworkingOTPView, didStartVerification consumerSession: ConsumerSessionData) { + showLoadingView(false) // started in networkingOTPViewWillStartConsumerLookup + showContent(redactedPhoneNumber: consumerSession.redactedFormattedPhoneNumber) + } + + func networkingOTPView(_ view: NetworkingOTPView, didFailToStartVerification error: Error) { + showLoadingView(false) // started in networkingOTPViewWillStartConsumerLookup + + dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "StartVerificationSessionError", + pane: .networkingLinkVerification + ) + dataSource.analyticsClient.log( + eventName: "networking.verification.error", + parameters: [ + "error": "StartVerificationSession" + ], + pane: .networkingLinkVerification + ) + delegate?.networkingLinkVerificationViewController(self, didReceiveTerminalError: error) + } + + func networkingOTPViewWillConfirmVerification(_ view: NetworkingOTPView) { + // no-op + } + + func networkingOTPViewDidConfirmVerification(_ view: NetworkingOTPView) { + if dataSource.manifest.isProductInstantDebits { + attachConsumerToLinkAccountAndSynchronize(from: view) + return + } + + view.showLoadingView(true) + dataSource.markLinkVerified() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success: + if self.dataSource.manifest.isProductInstantDebits { + self.attachConsumerToLinkAccountAndSynchronize(from: view) + } else { + self.dataSource.analyticsClient.log( + eventName: "networking.verification.success", + pane: .networkingLinkVerification + ) + self.requestNextPane(.linkAccountPicker, preventBackNavigation: false) + } + case .failure(let error): + self.dataSource + .analyticsClient + .log( + eventName: "networking.verification.error", + parameters: [ + "error": "MarkLinkVerifiedError", + ], + pane: .networkingLinkVerification + ) + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "MarkLinkVerifiedError", + pane: .networkingLinkVerification + ) + + let nextPane: FinancialConnectionsSessionManifest.NextPane + if self.dataSource.manifest.initialInstitution != nil { + nextPane = .partnerAuth + } else { + nextPane = .institutionPicker + } + self.requestNextPane(nextPane, preventBackNavigation: false) + } + + self.hideOTPLoadingViewAfterDelay(view) + } + } + + func networkingOTPView( + _ view: NetworkingOTPView, + didFailToConfirmVerification error: Error, + isTerminal: Bool + ) { + if isTerminal { + delegate?.networkingLinkVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingSaveToLinkVerification/NetworkingSaveToLinkVerificationDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingSaveToLinkVerification/NetworkingSaveToLinkVerificationDataSource.swift new file mode 100644 index 00000000..26d7feab --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingSaveToLinkVerification/NetworkingSaveToLinkVerificationDataSource.swift @@ -0,0 +1,114 @@ +// +// NetworkingSaveToLinkVerificationDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/14/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NetworkingSaveToLinkVerificationDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var consumerSession: ConsumerSessionData { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var networkingOTPDataSource: NetworkingOTPDataSource { get } + + func startVerificationSession() -> Future + func markLinkVerified() -> Future + func saveToLink() -> Future +} + +final class NetworkingSaveToLinkVerificationDataSourceImplementation: NetworkingSaveToLinkVerificationDataSource { + + let manifest: FinancialConnectionsSessionManifest + private(set) var consumerSession: ConsumerSessionData + private let selectedAccounts: [FinancialConnectionsPartnerAccount]? + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + let networkingOTPDataSource: NetworkingOTPDataSource + + init( + manifest: FinancialConnectionsSessionManifest, + consumerSession: ConsumerSessionData, + selectedAccounts: [FinancialConnectionsPartnerAccount]?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.manifest = manifest + self.consumerSession = consumerSession + self.selectedAccounts = selectedAccounts + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + let networkingOTPDataSource = NetworkingOTPDataSourceImplementation( + otpType: "SMS", + emailAddress: consumerSession.emailAddress, + customEmailType: nil, + connectionsMerchantName: nil, + pane: .networkingSaveToLinkVerification, + consumerSession: consumerSession, + apiClient: apiClient, + clientSecret: clientSecret, + analyticsClient: analyticsClient, + isTestMode: manifest.isTestMode, + theme: manifest.theme + ) + self.networkingOTPDataSource = networkingOTPDataSource + networkingOTPDataSource.delegate = self + } + + func startVerificationSession() -> Future { + apiClient + .consumerSessionLookup( + emailAddress: consumerSession.emailAddress, + clientSecret: clientSecret + ) + .chained { [weak self] (lookupConsumerSessionResponse: LookupConsumerSessionResponse) in + guard let self = self else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "data source deallocated")) + } + if let consumerSession = lookupConsumerSessionResponse.consumerSession { + self.consumerSession = consumerSession + return self.apiClient.consumerSessionStartVerification( + otpType: "SMS", + customEmailType: nil, + connectionsMerchantName: nil, + consumerSessionClientSecret: consumerSession.clientSecret + ) + } else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "invalid consumerSessionLookup response: no consumerSession.clientSecret")) + } + } + } + + func markLinkVerified() -> Future { + return apiClient.markLinkVerified(clientSecret: clientSecret) + } + + func saveToLink() -> Future { + return apiClient.saveAccountsToNetworkAndLink( + shouldPollAccounts: !manifest.shouldAttachLinkedPaymentMethod, + selectedAccounts: selectedAccounts, + emailAddress: nil, + phoneNumber: nil, + country: nil, + consumerSessionClientSecret: consumerSession.clientSecret, + clientSecret: clientSecret + ) + .chained { (_, customSuccessPaneMessage) in + return Promise(value: customSuccessPaneMessage) + } + } +} + +// MARK: - NetworkingOTPDataSourceDelegate + +extension NetworkingSaveToLinkVerificationDataSourceImplementation: NetworkingOTPDataSourceDelegate { + + func networkingOTPDataSource(_ dataSource: NetworkingOTPDataSource, didUpdateConsumerSession consumerSession: ConsumerSessionData) { + self.consumerSession = consumerSession + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingSaveToLinkVerification/NetworkingSaveToLinkVerificationViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingSaveToLinkVerification/NetworkingSaveToLinkVerificationViewController.swift new file mode 100644 index 00000000..fe051839 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NetworkingSaveToLinkVerification/NetworkingSaveToLinkVerificationViewController.swift @@ -0,0 +1,244 @@ +// +// NetworkingSaveToLinkVerification.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/14/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol NetworkingSaveToLinkVerificationViewControllerDelegate: AnyObject { + func networkingSaveToLinkVerificationViewController( + _ viewController: NetworkingSaveToLinkVerificationViewController, + didReceiveConsumerPublishableKey consumerPublishableKey: String + ) + func networkingSaveToLinkVerificationViewControllerDidFinish( + _ viewController: NetworkingSaveToLinkVerificationViewController, + saveToLinkWithStripeSucceeded: Bool?, + customSuccessPaneMessage: String? + ) + func networkingSaveToLinkVerificationViewController( + _ viewController: NetworkingSaveToLinkVerificationViewController, + didReceiveTerminalError error: Error + ) +} + +final class NetworkingSaveToLinkVerificationViewController: UIViewController { + + private let dataSource: NetworkingSaveToLinkVerificationDataSource + weak var delegate: NetworkingSaveToLinkVerificationViewControllerDelegate? + + private lazy var loadingView: SpinnerView = { + return SpinnerView(theme: dataSource.manifest.theme) + }() + private lazy var otpView: NetworkingOTPView = { + let otpView = NetworkingOTPView(dataSource: dataSource.networkingOTPDataSource) + otpView.delegate = self + return otpView + }() + + init(dataSource: NetworkingSaveToLinkVerificationDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + + otpView.startVerification() + } + + private func showContent(redactedPhoneNumber: String) { + // if we automatically moved to this pane due to + // prefilled email, we shot the "not now" button + let showNotNowButton = (dataSource.manifest.accountholderCustomerEmailAddress != nil) + let paneLayoutView = PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: nil, + title: STPLocalizedString( + "Confirm it's you", + "The title of a screen where users are informed that they can sign-in-to Link." + ), + subtitle: String(format: STPLocalizedString( + "Enter the code sent to %@.", + "The subtitle/description of a screen where users are informed that they have received a One-Type-Password (OTP) to their phone. '%@' gets replaced by a redacted phone number." + ), AuthFlowHelpers.formatRedactedPhoneNumber(redactedPhoneNumber)), + contentView: otpView + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: nil, + secondaryButtonConfiguration: showNotNowButton ? PaneLayoutView.ButtonConfiguration( + title: STPLocalizedString("Not now", "Title of a button that allows users to skip the current screen."), + action: { [weak self] in + guard let self = self else { return } + self.dataSource + .analyticsClient + .log(eventName: "click.not_now", pane: .networkingSaveToLinkVerification) + self.delegate?.networkingSaveToLinkVerificationViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: nil, + customSuccessPaneMessage: nil + ) + } + ) : nil, + theme: dataSource.manifest.theme + ).footerView + ) + paneLayoutView.addTo(view: view) + } + + private func showLoadingView(_ show: Bool) { + if show && loadingView.superview == nil { + // first-time we are showing this, so add the view to hierarchy + view.addAndPinSubviewToSafeArea(loadingView) + } + + loadingView.isHidden = !show + view.bringSubviewToFront(loadingView) // defensive programming to avoid loadingView being hiddden + } + + private func markLinkVerified( + saveToLinkSucceeded: Bool, + customSuccessPaneMessage: String? + ) { + dataSource.markLinkVerified() + .observe { [weak self] result in + guard let self else { return } + switch result { + case .success: + self.delegate?.networkingSaveToLinkVerificationViewControllerDidFinish( + self, + saveToLinkWithStripeSucceeded: saveToLinkSucceeded, + customSuccessPaneMessage: customSuccessPaneMessage + ) + case .failure(let error): + self.delegate?.networkingSaveToLinkVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + + // only hide loading view after animation + // to next screen has completed + DispatchQueue.main.asyncAfter( + deadline: .now() + 1.0 + ) { [weak self] in + self?.otpView.showLoadingView(false) + } + } + } +} + +// MARK: - NetworkingOTPViewDelegate + +extension NetworkingSaveToLinkVerificationViewController: NetworkingOTPViewDelegate { + + func networkingOTPViewWillStartVerification(_ view: NetworkingOTPView) { + showLoadingView(true) + } + + func networkingOTPView(_ view: NetworkingOTPView, didStartVerification consumerSession: ConsumerSessionData) { + showLoadingView(false) + showContent(redactedPhoneNumber: consumerSession.redactedFormattedPhoneNumber) + } + + func networkingOTPView(_ view: NetworkingOTPView, didGetConsumerPublishableKey consumerPublishableKey: String) { + delegate?.networkingSaveToLinkVerificationViewController(self, didReceiveConsumerPublishableKey: consumerPublishableKey) + } + + func networkingOTPView(_ view: NetworkingOTPView, didFailToStartVerification error: Error) { + showLoadingView(false) + dataSource.analyticsClient.log( + eventName: "networking.verification.error", + parameters: [ + "error": "StartVerificationSessionError" + ], + pane: .networkingSaveToLinkVerification + ) + dataSource.analyticsClient.logUnexpectedError( + error, + errorName: "StartVerificationSessionError", + pane: .networkingSaveToLinkVerification + ) + delegate?.networkingSaveToLinkVerificationViewController(self, didReceiveTerminalError: error) + } + + func networkingOTPViewWillConfirmVerification(_ view: NetworkingOTPView) { + // no-op + } + + func networkingOTPViewDidConfirmVerification(_ view: NetworkingOTPView) { + view.showLoadingView(true) + dataSource.saveToLink() + .observe { [weak self] result in + guard let self else { return } + let saveToLinkSucceeded: Bool + let customSuccessPaneMessage: String? + switch result { + case .success(let _customSuccessPaneMessage): + customSuccessPaneMessage = _customSuccessPaneMessage + self.dataSource + .analyticsClient + .log( + eventName: "networking.verification.success", + pane: .networkingSaveToLinkVerification + ) + saveToLinkSucceeded = true + case .failure(let error): + customSuccessPaneMessage = nil + self.dataSource + .analyticsClient + .log( + eventName: "networking.verification.error", + pane: .networkingSaveToLinkVerification + ) + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "SaveToLinkError", + pane: .networkingSaveToLinkVerification + ) + saveToLinkSucceeded = false + } + + self.markLinkVerified( + saveToLinkSucceeded: saveToLinkSucceeded, + customSuccessPaneMessage: customSuccessPaneMessage + ) + } + } + + func networkingOTPView( + _ view: NetworkingOTPView, + didFailToConfirmVerification error: Error, + isTerminal: Bool + ) { + if isTerminal { + delegate?.networkingSaveToLinkVerificationViewController( + self, + didReceiveTerminalError: error + ) + } + } + + func networkingOTPViewWillStartConsumerLookup(_ view: NetworkingOTPView) { + assertionFailure("we shouldn't call `lookup` for NetworkingSaveToLink") + } + + func networkingOTPViewConsumerNotFound(_ view: NetworkingOTPView) { + assertionFailure("we shouldn't call `lookup` for NetworkingSaveToLink") + } + + func networkingOTPView(_ view: NetworkingOTPView, didFailConsumerLookup error: Error) { + assertionFailure("we shouldn't call `lookup` for NetworkingSaveToLink") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthDataSource.swift new file mode 100644 index 00000000..98d3356a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthDataSource.swift @@ -0,0 +1,197 @@ +// +// PartnerAuthDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/8/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol PartnerAuthDataSource: AnyObject { + var institution: FinancialConnectionsInstitution { get } + var manifest: FinancialConnectionsSessionManifest { get } + var returnURL: String? { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var pendingAuthSession: FinancialConnectionsAuthSession? { get } + var disableAuthSessionRetrieval: Bool { get } + + func createAuthSession() -> Future + func authorizeAuthSession(_ authSession: FinancialConnectionsAuthSession) -> Future + func cancelPendingAuthSessionIfNeeded() + func recordAuthSessionEvent(eventName: String, authSessionId: String) + func clearReturnURL(authSession: FinancialConnectionsAuthSession, authURL: String) -> Future + func retrieveAuthSession(_ authSession: FinancialConnectionsAuthSession) -> Future +} + +final class PartnerAuthDataSourceImplementation: PartnerAuthDataSource { + + let institution: FinancialConnectionsInstitution + let manifest: FinancialConnectionsSessionManifest + let returnURL: String? + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + var disableAuthSessionRetrieval: Bool { + return manifest.features?["bank_connections_disable_defensive_auth_session_retrieval_on_complete"] == true + } + + // a "pending" auth session is a session which has started + // BUT the session is still yet-to-be authorized + // + // in other words, a `pendingAuthSession` is up for being + // cancelled unless the user successfully authorizes + private(set) var pendingAuthSession: FinancialConnectionsAuthSession? + + init( + authSession: FinancialConnectionsAuthSession?, + institution: FinancialConnectionsInstitution, + manifest: FinancialConnectionsSessionManifest, + returnURL: String?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.pendingAuthSession = authSession + self.institution = institution + self.manifest = manifest + self.returnURL = returnURL + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + } + + func createAuthSession() -> Future { + return apiClient.createAuthSession( + clientSecret: clientSecret, + institutionId: institution.id + ).chained { [weak self] (authSession: FinancialConnectionsAuthSession) in + self?.pendingAuthSession = authSession + return Promise(value: authSession) + } + } + + func clearReturnURL(authSession: FinancialConnectionsAuthSession, authURL: String) -> Future { + let promise = Promise() + + apiClient + .synchronize( + clientSecret: clientSecret, + returnURL: nil + ) + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success: + let copiedSession = FinancialConnectionsAuthSession(id: authSession.id, + flow: authSession.flow, + institutionSkipAccountSelection: authSession.institutionSkipAccountSelection, + nextPane: authSession.nextPane, + showPartnerDisclosure: authSession.showPartnerDisclosure, + skipAccountSelection: authSession.skipAccountSelection, + url: authURL, + isOauth: authSession.isOauth, + display: authSession.display) + self.pendingAuthSession = copiedSession + promise.fullfill(with: .success(copiedSession)) + case .failure(let error): + self.analyticsClient + .logUnexpectedError( + error, + errorName: "SynchronizeClearReturnURLError", + pane: .partnerAuth + ) + promise.reject(with: error) + } + } + + return promise + } + + func cancelPendingAuthSessionIfNeeded() { + guard let pendingAuthSession = pendingAuthSession else { + return + } + self.pendingAuthSession = nil + cancelAuthSession(pendingAuthSession) + .observe { _ in + // we ignore the result because its not important + } + } + + private func cancelAuthSession(_ authSession: FinancialConnectionsAuthSession) -> Future< + FinancialConnectionsAuthSession + > { + return apiClient.cancelAuthSession( + clientSecret: clientSecret, + authSessionId: authSession.id + ) + } + + func authorizeAuthSession(_ authSession: FinancialConnectionsAuthSession) -> Future + { + return apiClient.fetchAuthSessionOAuthResults( + clientSecret: clientSecret, + authSessionId: authSession.id + ) + .chained( + on: DispatchQueue.main, + using: { [weak self] mixedOAuthParameters in + guard let self = self else { + return Promise( + error: FinancialConnectionsSheetError.unknown( + debugDescription: "\(PartnerAuthDataSourceImplementation.self) deallocated." + ) + ) + } + return self.apiClient.authorizeAuthSession( + clientSecret: self.clientSecret, + authSessionId: authSession.id, + publicToken: mixedOAuthParameters.publicToken + ) + } + ) + } + + func recordAuthSessionEvent( + eventName: String, + authSessionId: String + ) { + guard ShouldRecordAuthSessionEvent() else { + // on Stripe SDK Core analytics client we don't send events + // for simulator or tests, so don't send these either... + return + } + + apiClient.recordAuthSessionEvent( + clientSecret: clientSecret, + authSessionId: authSessionId, + eventNamespace: "partner-auth-lifecycle", + eventName: eventName + ) + .observe { _ in + // we don't do anything with the event response + } + } + + func retrieveAuthSession( + _ authSession: FinancialConnectionsAuthSession + ) -> Future { + return apiClient.retrieveAuthSession( + clientSecret: clientSecret, + authSessionId: authSession.id + ).chained { [weak self] (authSession: FinancialConnectionsAuthSession) in + // update the `pendingAuthSession` with the latest from the server + self?.pendingAuthSession = authSession + return Promise(value: authSession) + } + } +} + +private func ShouldRecordAuthSessionEvent() -> Bool { + #if targetEnvironment(simulator) + return false + #else + return NSClassFromString("XCTest") == nil + #endif +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift new file mode 100644 index 00000000..0addc699 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift @@ -0,0 +1,824 @@ +// +// PartnerAuthViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/25/22. +// + +import AuthenticationServices +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol PartnerAuthViewControllerDelegate: AnyObject { + func partnerAuthViewControllerDidRequestToGoBack(_ viewController: PartnerAuthViewController) + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didCompleteWithAuthSession authSession: FinancialConnectionsAuthSession + ) + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didReceiveError error: Error + ) +} + +final class PartnerAuthViewController: SheetViewController { + + /** + Unfortunately there is a need for this state-full parameter. When we get url callback the app might not be in foreground state. + If we then authorize the auth session will fail as you can't do background networking without special permission. + */ + private var unprocessedReturnURL: URL? + private var subscribedToURLNotifications = false + private var subscribedToAppActiveNotifications = false + + private let dataSource: PartnerAuthDataSource + private var institution: FinancialConnectionsInstitution { + return dataSource.institution + } + private var webAuthenticationSession: ASWebAuthenticationSession? + private var lastHandledAuthenticationSessionReturnUrl: URL? + weak var delegate: PartnerAuthViewControllerDelegate? + + private var prepaneViews: PrepaneViews? + private var continueStateViews: ContinueStateViews? + private var loadingView: UIView? + private var legacyLoadingView: UIView? + private var showLegacyBrowserOnViewDidAppear = false + + init( + dataSource: PartnerAuthDataSource, + panePresentationStyle: PanePresentationStyle + ) { + self.dataSource = dataSource + super.init(panePresentationStyle: panePresentationStyle) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + dataSource + .analyticsClient + .logPaneLoaded(pane: .partnerAuth) + + if let authSession = dataSource.pendingAuthSession { + if authSession.isOauthNonOptional { + createdAuthSession(authSession) + } else { + // for legacy (non-oauth), start showing the loading indicator, + // and wait until `viewDidAppear` gets called + insertLegacyLoadingView() + showLegacyBrowserOnViewDidAppear = true + } + } else { + assert( + panePresentationStyle == .fullscreen, + "partner auth initialized without an auth session is expected to be full screen" + ) + createAuthSession() + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + if showLegacyBrowserOnViewDidAppear { + showLegacyBrowserOnViewDidAppear = false + // wait until `viewDidAppear` gets called for legacy (non-oauth) because + // calling `createdAuthSession` WHILE the VC is animating causes an + // animation glitch due to ASWebAuthenticationSession browser animation + // happening simultaneously + if + let authSession = dataSource.pendingAuthSession, + !authSession.isOauthNonOptional + { + createdAuthSession(authSession) + } + } + } + + private func createAuthSession() { + assertMainQueue() + + showLoadingView(true) + dataSource + .createAuthSession() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + // order is important so be careful of moving + self.showLoadingView(false) + switch result { + case .success(let authSession): + self.createdAuthSession(authSession) + case .failure(let error): + self.showErrorView(error) + } + } + } + + private func createdAuthSession(_ authSession: FinancialConnectionsAuthSession) { + dataSource.recordAuthSessionEvent( + eventName: "launched", + authSessionId: authSession.id + ) + + if authSession.isOauthNonOptional, let prepaneModel = authSession.display?.text?.oauthPrepane { + prepaneViews = nil // the `deinit` of prepane views will remove views + let prepaneViews = PrepaneViews( + prepaneModel: prepaneModel, + isRepairSession: false, // TODO(kgaidis): change this for repair sessions + panePresentationStyle: panePresentationStyle, + theme: dataSource.manifest.theme, + didSelectURL: { [weak self] url in + self?.didSelectURLInTextFromBackend(url) + }, + didSelectContinue: { [weak self] in + guard let self = self else { return } + self.dataSource.analyticsClient.log( + eventName: "click.prepane.continue", + parameters: [ + "requires_native_redirect": authSession.requiresNativeRedirect, + ], + pane: .partnerAuth + ) + + if authSession.requiresNativeRedirect { + self.openInstitutionAuthenticationNativeRedirect(authSession: authSession) + } else { + self.openInstitutionAuthenticationWebView(authSession: authSession) + } + }, + didSelectCancel: { [weak self] in + guard let self = self else { return } + self.delegate?.partnerAuthViewControllerDidRequestToGoBack(self) + } + ) + self.prepaneViews = prepaneViews + + setup( + withContentView: prepaneViews.contentStackView, + footerView: prepaneViews.footerView + ) + + dataSource.recordAuthSessionEvent( + eventName: "loaded", + authSessionId: authSession.id + ) + } else { + insertLegacyLoadingView() + + openInstitutionAuthenticationWebView(authSession: authSession) + } + } + + private func insertLegacyLoadingView() { + legacyLoadingView?.removeFromSuperview() + legacyLoadingView = nil + + // a legacy (non-oauth) institution will have a blank background + // during presenting + dismissing of the Web View, so + // add a loading spinner to fill some of the blank space + // + // note that this is purposefully separate from `showLoadingView` + // function because it avoids animation glitches where + // `showLoadingView(false)` can remove the loading view + let loadingView = SpinnerView(theme: dataSource.manifest.theme) + self.legacyLoadingView = loadingView + view.addAndPinSubviewToSafeArea(loadingView) + } + + private func showErrorView(_ error: Error) { + delegate?.partnerAuthViewController(self, didReceiveError: error) + } + + private func handleAuthSessionCompletionWithStatus( + _ status: String, + _ authSession: FinancialConnectionsAuthSession + ) { + if status == "success" { + self.dataSource.recordAuthSessionEvent( + eventName: "success", + authSessionId: authSession.id + ) + + if authSession.isOauthNonOptional { + // for OAuth flows, we need to fetch OAuth results + self.authorizeAuthSession(authSession) + } else { + // for legacy flows (non-OAuth), we do not need to fetch OAuth results, or call authorize + self.didComplete(withAuthSession: authSession) + } + } else if status == "failure" { + self.dataSource.recordAuthSessionEvent( + eventName: "failure", + authSessionId: authSession.id + ) + + // cancel current auth session + self.dataSource.cancelPendingAuthSessionIfNeeded() + + // show a terminal error + self.showErrorView( + FinancialConnectionsSheetError.unknown( + debugDescription: "Shim returned a failure." + ) + ) + } else { // assume `status == cancel` + self.checkIfAuthSessionWasSuccessful( + authSession: authSession, + completionHandler: { [weak self] isSuccess in + guard let self = self else { return } + if !isSuccess { + self.handleAuthSessionCancel(authSession, nil) + } + } + ) + } + } + + private func handleAuthSessionCancel( + _ authSession: FinancialConnectionsAuthSession, + _ error: Error? + ) { + if authSession.isOauthNonOptional { + // on "manual cancels" (for OAuth) we log retry event: + dataSource.recordAuthSessionEvent( + eventName: "retry", + authSessionId: authSession.id + ) + } else { + // on "manual cancels" (for Legacy) we log cancel event: + dataSource.recordAuthSessionEvent( + eventName: "cancel", + authSessionId: authSession.id + ) + } + + // cancel current auth session because something went wrong + dataSource.cancelPendingAuthSessionIfNeeded() + + if authSession.isOauthNonOptional { + // for OAuth institutions, we remain on the pre-pane, + // but create a brand new auth session + createAuthSession() + } else { + // for legacy (non-OAuth) institutions, we navigate back to InstitutionPickerViewController + navigateBack() + } + } + + private func openInstitutionAuthenticationNativeRedirect( + authSession: FinancialConnectionsAuthSession + ) { + guard + let urlString = authSession.url?.droppingNativeRedirectPrefix(), + let url = URL(string: urlString) + else { + self.showErrorView( + FinancialConnectionsSheetError.unknown( + debugDescription: "Malformed auth session url." + ) + ) + return + } + let continueStateViews = ContinueStateViews( + institutionImageUrl: institution.icon?.default, + theme: dataSource.manifest.theme, + didSelectContinue: { [weak self] in + guard let self else { return } + self.dataSource.analyticsClient.log( + eventName: "click.apptoapp.continue", + pane: .partnerAuth + ) + self.openInstitutionAuthenticationNativeRedirect(authSession: authSession) + }, + didSelectCancel: { [weak self] in + guard let self else { return } + self.delegate?.partnerAuthViewControllerDidRequestToGoBack(self) + } + ) + self.continueStateViews = continueStateViews + setup( + withContentView: continueStateViews.contentView, + footerView: continueStateViews.footerView + ) + + subscribeToURLAndAppActiveNotifications() + UIApplication.shared.open( + url, + options: [.universalLinksOnly: true] + ) { (didOpenBankingApp) in + guard !didOpenBankingApp else { + // we pass control to the bank app + return + } + // if we get here, it means the banking app is not installed + self.clearStateAndUnsubscribeFromNotifications(removeContinueStateView: true) + + self.showLoadingView(true) + self.dataSource + .clearReturnURL(authSession: authSession, authURL: urlString) + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + // order is important so be careful of moving + self.showLoadingView(false) + switch result { + case .success(let authSession): + self.openInstitutionAuthenticationWebView(authSession: authSession) + case .failure(let error): + self.showErrorView(error) + } + } + } + } + + private func openInstitutionAuthenticationWebView(authSession: FinancialConnectionsAuthSession) { + guard let urlString = authSession.url, let url = URL(string: urlString) else { + assertionFailure("Expected to get a URL back from authorization session.") + dataSource + .analyticsClient + .logUnexpectedError( + FinancialConnectionsSheetError.unknown( + debugDescription: "Invalid or NULL URL returned from auth session" + ), + errorName: "InvalidAuthSessionURL", + pane: .partnerAuth + ) + // navigate back to institution picker so user can try again + navigateBack() + return + } + + lastHandledAuthenticationSessionReturnUrl = nil + let webAuthenticationSession = ASWebAuthenticationSession( + url: url, + callbackURLScheme: "stripe", + // note that `error` is NOT related to our backend + // sending errors, it's only related to `ASWebAuthenticationSession` + completionHandler: { [weak self] returnUrl, error in + guard let self = self else { return } + if self.lastHandledAuthenticationSessionReturnUrl != nil + && self.lastHandledAuthenticationSessionReturnUrl == returnUrl + { + // for unknown reason, `ASWebAuthenticationSession` can _sometimes_ + // call the `completionHandler` twice + // + // we use `returnUrl`, instead of a `Bool`, in the case that + // this completion handler can sometimes return different URL's + self.dataSource.recordAuthSessionEvent( + eventName: "ios_double_return", + authSessionId: authSession.id + ) + return + } + self.lastHandledAuthenticationSessionReturnUrl = returnUrl + + if let returnUrl = returnUrl, + returnUrl.scheme == "stripe", + let urlComponsents = URLComponents(url: returnUrl, resolvingAgainstBaseURL: true), + let status = urlComponsents.queryItems?.first(where: { $0.name == "status" })?.value + { + self.logUrlReceived(returnUrl, status: status, authSessionId: authSession.id) + self.handleAuthSessionCompletionWithStatus(status, authSession) + } + // we did NOT get a `status` back from the backend, + // so assume a "cancel" + else { + self.logUrlReceived(returnUrl, status: nil, authSessionId: authSession.id) + + if let error = error { + if + (error as NSError).domain == ASWebAuthenticationSessionErrorDomain, + (error as NSError).code == ASWebAuthenticationSessionError.canceledLogin.rawValue + { + self.dataSource + .analyticsClient + .log( + eventName: "secure_webview_cancel", + pane: .partnerAuth + ) + } else { + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "ASWebAuthenticationSessionError", + pane: .partnerAuth + ) + } + } + + self.checkIfAuthSessionWasSuccessful( + authSession: authSession, + completionHandler: { [weak self] isSuccess in + guard let self = self else { return } + if !isSuccess { + self.handleAuthSessionCancel(authSession, error) + } + } + ) + } + + self.webAuthenticationSession = nil + } + ) + self.webAuthenticationSession = webAuthenticationSession + + webAuthenticationSession.presentationContextProvider = self + webAuthenticationSession.prefersEphemeralWebBrowserSession = true + + if #available(iOS 13.4, *) { + if !webAuthenticationSession.canStart { + dataSource.recordAuthSessionEvent( + eventName: "ios-browser-cant-start", + authSessionId: authSession.id + ) + // navigate back to bank picker so user can try again + // + // this may be an odd way to handle an issue, but trying again + // is potentially better than forcing user to close the whole + // auth session + navigateBack() + return // skip starting + } + } + + if !webAuthenticationSession.start() { + dataSource.recordAuthSessionEvent( + eventName: "ios-browser-did-not-start", + authSessionId: authSession.id + ) + // navigate back to bank picker so user can try again + // + // this may be an odd way to handle an issue, but trying again + // is potentially better than forcing user to close the whole + // auth session + navigateBack() + } else { + // we successfully launched the secure web browser + dataSource + .analyticsClient + .log( + eventName: "auth_session.opened", + parameters: [ + "browser": "ASWebAuthenticationSession", + "auth_session_id": authSession.id, + "flow": authSession.flow?.rawValue ?? "null", + ], + pane: .partnerAuth + ) + + if authSession.isOauthNonOptional { + dataSource.recordAuthSessionEvent( + eventName: "oauth-launched", + authSessionId: authSession.id + ) + } else { + dataSource.recordAuthSessionEvent( + eventName: "legacy-launched", + authSessionId: authSession.id + ) + } + } + } + + private func authorizeAuthSession(_ authSession: FinancialConnectionsAuthSession) { + showLoadingView(true) + dataSource + .authorizeAuthSession(authSession) + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let authSession): + self.didComplete(withAuthSession: authSession) + + // hide the loading view after a delay to prevent + // the screen from flashing _while_ the transition + // to the next screen takes place + // + // note that it should be impossible to view this screen + // after a successful `authorizeAuthSession`, so + // calling `showEstablishingConnectionLoadingView(false)` is + // defensive programming anyway + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + self?.showLoadingView(false) + } + case .failure(let error): + self.showLoadingView(false) // important to come BEFORE showing error view so we avoid showing back button + self.showErrorView(error) + assert(self.navigationItem.hidesBackButton) + } + } + } + + private func navigateBack() { + delegate?.partnerAuthViewControllerDidRequestToGoBack(self) + } + + private func showLoadingView(_ show: Bool) { + loadingView?.removeFromSuperview() + loadingView = nil + + // there's a chance we don't have the data yet to display a + // prepane-based loading view, so we have extra handling + // to handle both states + if prepaneViews != nil || continueStateViews != nil { + prepaneViews?.showLoadingView(show) + continueStateViews?.showLoadingView(show) + } else { + if show { + let loadingView = SpinnerView(theme: dataSource.manifest.theme) + self.loadingView = loadingView + view.addAndPinSubviewToSafeArea(loadingView) + } + } + navigationItem.hidesBackButton = show + } + + private func didSelectURLInTextFromBackend(_ url: URL) { + AuthFlowHelpers.handleURLInTextFromBackend( + url: url, + pane: .partnerAuth, + analyticsClient: dataSource.analyticsClient, + handleURL: { urlHost, _ in + if urlHost == "data-access-notice" { + if let dataAccessNoticeModel = dataSource.pendingAuthSession?.display?.text?.oauthPrepane?.dataAccessNotice { + let dataAccessNoticeViewController = DataAccessNoticeViewController( + dataAccessNotice: dataAccessNoticeModel, + theme: dataSource.manifest.theme, + didSelectUrl: { [weak self] url in + self?.didSelectURLInTextFromBackend(url) + } + ) + dataAccessNoticeViewController.present(on: self) + } + } + } + ) + } + + // There are edge-cases where redirect links don't work properly. + // Check the auth session in-case the auth session was successful. + private func checkIfAuthSessionWasSuccessful( + authSession: FinancialConnectionsAuthSession, + completionHandler: @escaping (_ isSuccess: Bool) -> Void + ) { + guard !dataSource.disableAuthSessionRetrieval else { + // if auth session retrieval is disabled, go to the default case + completionHandler(false) + return + } + + showLoadingView(true) + dataSource + .retrieveAuthSession(authSession) + .observe { [weak self] result in + guard let self = self else { return } + self.showLoadingView(false) + + self.dataSource + .analyticsClient + .log( + eventName: "auth_session.retrieved", + parameters: [ + "auth_session_id": authSession.id, + "next_pane": (try? result.get())?.nextPane.rawValue ?? "null", + ], + pane: .partnerAuth + ) + + switch result { + case .success(let authSession): + if authSession.nextPane != .partnerAuth { + completionHandler(true) + self.dataSource.recordAuthSessionEvent( + eventName: "success", + authSessionId: authSession.id + ) + // abstract auth handles calling `authorize` + self.didComplete(withAuthSession: authSession) + } else { + completionHandler(false) + } + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "RetrieveAuthSessionError", + pane: .partnerAuth + ) + completionHandler(false) + } + } + } + + private func logUrlReceived( + _ url: URL?, + status: String?, + authSessionId: String + ) { + dataSource + .analyticsClient + .log( + eventName: "auth_session.url_received", + parameters: [ + "status": status ?? "null", + "url": url?.absoluteString ?? "null", + "auth_session_id": authSessionId, + ], + pane: .partnerAuth + ) + } + + // Defensive programming to avoid completing the same auth session twice. + // + // There's been a series of odd edge-cases where the same auth session + // could complete twice, so this acts as future-proofing that this + // never happens again. + private var lastCompletedAuthSessionId: String? + private func didComplete(withAuthSession authSession: FinancialConnectionsAuthSession) { + if lastCompletedAuthSessionId != authSession.id { + lastCompletedAuthSessionId = authSession.id + delegate?.partnerAuthViewController(self, didCompleteWithAuthSession: authSession) + } else { + dataSource + .analyticsClient.log( + eventName: "ios_double_complete_attempt", + parameters: [ + "auth_session_id": authSession.id, + ], + pane: .partnerAuth + ) + } + } +} + +// MARK: - STPURLCallbackListener + +extension PartnerAuthViewController: STPURLCallbackListener { + + private func handleAuthSessionCompletionFromNativeRedirect(_ url: URL) { + assertMainQueue() + + guard let authSession = dataSource.pendingAuthSession else { + return + } + guard var urlComponsents = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + dataSource.recordAuthSessionEvent( + eventName: "native-app-to-app-failed-to-resolve-url", + authSessionId: authSession.id + ) + return + } + urlComponsents.query = url.fragment + + if + let status = urlComponsents.queryItems?.first(where: { $0.name == "code" })?.value, + let authSessionId = urlComponsents.queryItems?.first(where: { $0.name == "authSessionId" })?.value, + authSessionId == dataSource.pendingAuthSession?.id + { + logUrlReceived(url, status: status, authSessionId: authSession.id) + handleAuthSessionCompletionWithStatus(status, authSession) + } else { + logUrlReceived(url, status: nil, authSessionId: authSession.id) + handleAuthSessionCancel(authSession, nil) + } + } + + func handleURLCallback(_ url: URL) -> Bool { + DispatchQueue.main.async { + self.unprocessedReturnURL = url + self.handleAuthSessionCompletionFromNativeRedirectIfNeeded() + } + return true + } +} + +// MARK: - Authentication restart helpers + +private extension PartnerAuthViewController { + + private func subscribeToURLAndAppActiveNotifications() { + assertMainQueue() + + subscribeToURLNotifications() + if !subscribedToAppActiveNotifications { + subscribedToAppActiveNotifications = true + NotificationCenter.default.addObserver( + self, + selector: #selector(handleDidBecomeActiveNotification), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + } + } + + private func subscribeToURLNotifications() { + assertMainQueue() + + guard let returnURL = dataSource.returnURL, + let url = URL(string: returnURL) + else { + return + } + if !subscribedToURLNotifications { + subscribedToURLNotifications = true + STPURLCallbackHandler.shared().register( + self, + for: url + ) + } + } + + private func unsubscribeFromNotifications() { + assertMainQueue() + + NotificationCenter.default.removeObserver( + self, + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + STPURLCallbackHandler.shared().unregisterListener(self) + subscribedToURLNotifications = false + subscribedToAppActiveNotifications = false + } + + @objc func handleDidBecomeActiveNotification() { + DispatchQueue.main.async { + self.handleAuthSessionCompletionFromNativeRedirectIfNeeded() + } + } + + private func clearStateAndUnsubscribeFromNotifications( + removeContinueStateView: Bool + ) { + unprocessedReturnURL = nil + unsubscribeFromNotifications() + + if removeContinueStateView { + continueStateViews = nil + if let authSession = dataSource.pendingAuthSession { + // re-create the views + createdAuthSession(authSession) + } + } + } + + private func handleAuthSessionCompletionFromNativeRedirectIfNeeded() { + assertMainQueue() + + guard UIApplication.shared.applicationState == .active else { + /** + When we get url callback the app might not be in foreground state. + If we then proceed with authorization network request might fail as we will be doing background networking without special permission.. + */ + return + } + if let url = unprocessedReturnURL { + if let authSession = dataSource.pendingAuthSession { + dataSource.recordAuthSessionEvent( + eventName: "native-app-to-app-redirect-url-received", + authSessionId: authSession.id + ) + } + clearStateAndUnsubscribeFromNotifications(removeContinueStateView: false) + handleAuthSessionCompletionFromNativeRedirect(url) + } else if let authSession = dataSource.pendingAuthSession { + self.checkIfAuthSessionWasSuccessful( + authSession: authSession, + completionHandler: { [weak self] isSuccess in + if isSuccess { + self?.clearStateAndUnsubscribeFromNotifications( + // on success, we will move away from + // the screen, so there's no reason + // to remove the continue state now + removeContinueStateView: false + ) + } else { + // the default case is to not do anything + // user can press "Continue" to re-start + // app-to-app + } + } + ) + } + } +} + +// MARK: - ASWebAuthenticationPresentationContextProviding + +/// :nodoc: +extension PartnerAuthViewController: ASWebAuthenticationPresentationContextProviding { + + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return self.view.window ?? ASPresentationAnchor() + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PrepaneImageView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PrepaneImageView.swift new file mode 100644 index 00000000..161a3087 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PrepaneImageView.swift @@ -0,0 +1,142 @@ +// +// PrepaneImageView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/10/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit +import WebKit + +final class PrepaneImageView: UIView { + + private let centeringView: UIView + + init(imageURLString: String) { + // first we load an image (or GIF) into a WebView + let imageView = GIFImageView(gifUrlString: imageURLString) + // the WebView is surrounded by a background that imitates the GIF presented inside of a phone + let phoneBackgroundView = CreatePhoneBackgroundView(imageView: imageView) + // we center the phone+gif in the middle + let centeringView = CreateCenteringView(centeredView: phoneBackgroundView) + self.centeringView = centeringView + super.init(frame: .zero) + + // background color of the whole view + centeringView.backgroundColor = .backgroundContainer + + addAndPinSubview( + centeringView, + // the insets expand the view beyond the bounds + // to stretch the `PrepaneImageView` for the + // full width of the pane + insets: NSDirectionalEdgeInsets( + top: 0, + leading: -Constants.Layout.defaultHorizontalMargin, + bottom: 0, + trailing: -Constants.Layout.defaultHorizontalMargin + ) + ) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + // Clip-to-bounds the top and bottom, but leave + // left/right unclipped. + // + // This code is like setting `clipsToBounds = true`, + // except for just top/bottom. + let path = UIBezierPath( + rect: CGRect( + x: (bounds.width - centeringView.frame.width) / 2, + y: 0, + width: centeringView.frame.width, + height: bounds.height + ) + ).cgPath + let maskLayer = CAShapeLayer() + maskLayer.path = path + layer.mask = maskLayer + } +} + +private func CreatePhoneBackgroundView(imageView: UIView) -> UIView { + let containerView = UIView() + let borderWidth: CGFloat = 8 + imageView.layer.borderWidth = borderWidth + imageView.layer.borderColor = UIColor.backgroundOffset.cgColor + imageView.layer.shadowRadius = 10 + imageView.layer.shadowColor = UIColor.borderDefault.cgColor + imageView.layer.shadowOpacity = 1.0 + containerView.addAndPinSubview( + imageView, + insets: NSDirectionalEdgeInsets( + top: -borderWidth, + leading: 0, + bottom: -borderWidth, + trailing: 0 + ) + ) + return containerView +} + +private func CreateCenteringView(centeredView: UIView) -> UIView { + let leftSpacerView = UIView() + leftSpacerView.setContentHuggingPriority(.defaultLow, for: .horizontal) + + let rightSpacerView = UIView() + rightSpacerView.setContentHuggingPriority(.defaultLow, for: .horizontal) + + let horizontalStackView = UIStackView( + arrangedSubviews: [leftSpacerView, centeredView, rightSpacerView] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.distribution = .equalCentering + horizontalStackView.alignment = .center + return horizontalStackView +} + +private final class GIFImageView: UIView, WKNavigationDelegate { + + private let webView = WKWebView() + + override var intrinsicContentSize: CGSize { + return CGSize(width: 256, height: 264) + } + + init(gifUrlString: String) { + super.init(frame: .zero) + let htmlString = + """ + + + + + + + + + + + """ + + webView.scrollView.isScrollEnabled = false + webView.isUserInteractionEnabled = false + webView.loadHTMLString(htmlString, baseURL: nil) + webView.backgroundColor = .customBackgroundColor + addAndPinSubview(webView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PrepaneViews.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PrepaneViews.swift new file mode 100644 index 00000000..5200780a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PrepaneViews.swift @@ -0,0 +1,271 @@ +// +// PrepaneViewss.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/6/24. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +// A container that encapsulates all the subviews necessary +// to create a prepane view. Helps to avoid bloat in +// `PartnerAuthViewController`. +final class PrepaneViews { + + private let didSelectContinue: () -> Void + private let didSelectCancel: () -> Void + + let contentStackView: UIStackView = { + let contentStackView = UIStackView() + contentStackView.axis = .vertical + contentStackView.spacing = 0 + return contentStackView + }() + private let headerView: UIView + private let bodyView: UIView + private var primaryButton: StripeUICore.Button? + private var secondaryButton: StripeUICore.Button? + let footerView: UIView? + + init( + prepaneModel: FinancialConnectionsOAuthPrepane, + isRepairSession: Bool, + panePresentationStyle: PanePresentationStyle, + theme: FinancialConnectionsTheme, + didSelectURL: @escaping (URL) -> Void, + didSelectContinue: @escaping () -> Void, + didSelectCancel: @escaping () -> Void + ) { + self.didSelectContinue = didSelectContinue + self.didSelectCancel = didSelectCancel + self.headerView = PaneLayoutView.createHeaderView( + iconView: { + if let institutionIconUrl = prepaneModel.institutionIcon?.default { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institutionIconUrl) + return institutionIconView + } else { + return nil + } + }(), + title: prepaneModel.title, + isSheet: (panePresentationStyle == .sheet) + ) + self.bodyView = PaneLayoutView.createBodyView( + text: prepaneModel.subtitle, + contentView: CreateContentView( + prepaneBodyModel: prepaneModel.body, + didSelectURL: didSelectURL + ) + ) + + contentStackView.addArrangedSubview(headerView) + + let footerViewTuple = PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: prepaneModel.cta.text, + accessibilityIdentifier: "prepane_continue_button", + action: didSelectContinue + ), + secondaryButtonConfiguration: { + if isRepairSession { + return nil + } else { + return PaneLayoutView.ButtonConfiguration( + title: { + switch panePresentationStyle { + case .fullscreen: + return STPLocalizedString( + "Choose a different bank", + "Title of a button. It acts as a back button to go back to choosing a different bank instead of the currently selected one." + ) + case .sheet: + return "Cancel" // TODO: when Financial Connections starts supporting localization, change this to `String.Localized.cancel` + } + }(), + accessibilityIdentifier: "prepane_cancel_button", + action: didSelectCancel + ) + } + }(), + theme: theme + ) + self.footerView = footerViewTuple.footerView + self.primaryButton = footerViewTuple.primaryButton + self.secondaryButton = footerViewTuple.secondaryButton + + contentStackView.addArrangedSubview(bodyView) + + showLoadingView(false) + } + + deinit { + contentStackView.removeFromSuperview() + footerView?.removeFromSuperview() + } + + func showLoadingView(_ show: Bool) { + primaryButton?.isLoading = show + secondaryButton?.isEnabled = !show + } + + @objc fileprivate func didSelectContinueButton() { + didSelectContinue() + } +} + +private func CreateContentView( + prepaneBodyModel: FinancialConnectionsOAuthPrepane.OauthPrepaneBody, + didSelectURL: @escaping (URL) -> Void +) -> UIView? { + guard + let entries = prepaneBodyModel.entries, + !entries.isEmpty + else { + // returning an empty `UIStackView` added unnecessary + // padding, so returning `nil` removes the extra padding + return nil + } + + let verticalStackView = UIStackView() + verticalStackView.spacing = 22 + verticalStackView.axis = .vertical + + entries.forEach { entry in + switch entry.content { + case .text(let text): + let label = AttributedTextView( + font: .label(.large), + boldFont: .label(.largeEmphasized), + linkFont: .label(.largeEmphasized), + textColor: .textDefault + ) + label.setText(text, action: didSelectURL) + verticalStackView.addArrangedSubview(label) + case .image(let image): + if let imageUrl = image.default { + let prepaneImageView = PrepaneImageView(imageURLString: imageUrl) + verticalStackView.addArrangedSubview(prepaneImageView) + } + case .unparsable: + break // we encountered an unknown type, so just skip + } + } + + return verticalStackView +} + +#if DEBUG + +import SwiftUI + +private class PrepanePreviewView: UIView { + + let prepaneViews = PrepaneViews( + prepaneModel: FinancialConnectionsOAuthPrepane( + institutionIcon: nil, + title: "Log in to Capital One and grant the right permissions", + subtitle: "Next, you'll be promted to log in and connect your accounts.", + body: FinancialConnectionsOAuthPrepane.OauthPrepaneBody( + entries: [ + .init( + content: .text("Be sure to select **Account Number & Routing Number**.") + ), + .init( + content: .image( + FinancialConnectionsImage( + default: "https://js.stripe.com/v3/f0620405e3235ff4736f6876f4d3d045.gif" + ) + ) + ), + .init( + content: .text( + "We will only share the [requested data](https://www.stripe.com) with [Merchant] even if your bank grants Stripe access to more." + ) + ), + ] + ), + partnerNotice: FinancialConnectionsOAuthPrepane.OauthPrepanePartnerNotice( + partnerIcon: nil, + text: + "Stripe works with partners like [Partner Name] to reliability offer access to thousands of financial institutions. [Learn more](https://www.stripe.com)" + ), + cta: FinancialConnectionsOAuthPrepane.OauthPrepaneCTA( + text: "Continue", + icon: nil + ), + dataAccessNotice: FinancialConnectionsDataAccessNotice( + icon: nil, + title: "", + connectedAccountNotice: nil, + subtitle: nil, + body: FinancialConnectionsDataAccessNotice.Body(bullets: []), + disclaimer: nil, + cta: "OK" + ) + ), + isRepairSession: false, + panePresentationStyle: .sheet, + theme: .light, + didSelectURL: { _ in }, + didSelectContinue: {}, + didSelectCancel: {} + ) + + init() { + super.init(frame: .zero) + let paneLayoutView = PaneLayoutView( + contentView: prepaneViews.contentStackView, + footerView: prepaneViews.footerView + ) + paneLayoutView.addTo(view: self) + backgroundColor = .customBackgroundColor + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private struct PrepaneViewsUIViewRepresentable: UIViewRepresentable { + + let isLoading: Bool + + func makeUIView(context: Context) -> PrepanePreviewView { + PrepanePreviewView() + } + + func updateUIView(_ prepanePreviewView: PrepanePreviewView, context: Context) { + prepanePreviewView.prepaneViews.showLoadingView(isLoading) + } +} + +@available(iOS 14.0, *) +struct PrepaneViews_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + VStack { + PrepaneViewsUIViewRepresentable(isLoading: false) + } + .frame(maxWidth: .infinity) + .background(Color.purple.opacity(0.1)) + .navigationTitle("stripe") + .navigationBarTitleDisplayMode(.inline) + } + + NavigationView { + VStack { + PrepaneViewsUIViewRepresentable(isLoading: true) + } + .frame(maxWidth: .infinity) + .background(Color.purple.opacity(0.1)) + .navigationTitle("stripe") + .navigationBarTitleDisplayMode(.inline) + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ResetFlow/ResetFlowDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ResetFlow/ResetFlowDataSource.swift new file mode 100644 index 00000000..4a1c3911 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ResetFlow/ResetFlowDataSource.swift @@ -0,0 +1,40 @@ +// +// ResetFlowDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/2/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol ResetFlowDataSource: AnyObject { + var manifest: FinancialConnectionsSessionManifest { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + + func markLinkingMoreAccounts() -> Promise +} + +final class ResetFlowDataSourceImplementation: ResetFlowDataSource { + + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let manifest: FinancialConnectionsSessionManifest + let analyticsClient: FinancialConnectionsAnalyticsClient + + init( + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + manifest: FinancialConnectionsSessionManifest, + analyticsClient: FinancialConnectionsAnalyticsClient + ) { + self.apiClient = apiClient + self.clientSecret = clientSecret + self.manifest = manifest + self.analyticsClient = analyticsClient + } + + func markLinkingMoreAccounts() -> Promise { + return apiClient.markLinkingMoreAccounts(clientSecret: clientSecret) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ResetFlow/ResetFlowViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ResetFlow/ResetFlowViewController.swift new file mode 100644 index 00000000..531437d3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/ResetFlow/ResetFlowViewController.swift @@ -0,0 +1,72 @@ +// +// LinkMoreAccounts.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/2/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol ResetFlowViewControllerDelegate: AnyObject { + func resetFlowViewController( + _ viewController: ResetFlowViewController, + didSucceedWithManifest manifest: FinancialConnectionsSessionManifest + ) + func resetFlowViewController( + _ viewController: ResetFlowViewController, + didFailWithError error: Error + ) +} + +// Used in at least two scenarios: +// 1) User presses "Link another account" in Consent Pane +// 2) User selects "Select another bank" in an Error screen from Institution Picker +final class ResetFlowViewController: UIViewController { + + private let dataSource: ResetFlowDataSource + + weak var delegate: ResetFlowViewControllerDelegate? + + init(dataSource: ResetFlowDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + navigationItem.hidesBackButton = true + + dataSource + .analyticsClient + .logPaneLoaded(pane: .resetFlow) + + let loadingView = SpinnerView(theme: dataSource.manifest.theme) + view.addAndPinSubviewToSafeArea(loadingView) + + dataSource.markLinkingMoreAccounts() + .observe(on: .main) { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let manifest): + self.delegate?.resetFlowViewController(self, didSucceedWithManifest: manifest) + case .failure(let error): + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "ResetFlowLinkMoreAccountsError", + pane: .resetFlow + ) + self.delegate?.resetFlowViewController(self, didFailWithError: error) + } + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AccountPickerRowLabelView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AccountPickerRowLabelView.swift new file mode 100644 index 00000000..f88486cf --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AccountPickerRowLabelView.swift @@ -0,0 +1,99 @@ +// +// AccountPickerRowLabelView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/5/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class AccountPickerRowLabelView: UIView { + + private lazy var verticalLabelStackView: UIStackView = { + let labelStackView = UIStackView() + labelStackView.axis = .vertical + labelStackView.spacing = 0 + labelStackView.alignment = .leading + return labelStackView + }() + private lazy var titleLabel: AttributedLabel = { + return AttributedLabel( + font: .label(.largeEmphasized), + textColor: .textDefault + ) + }() + private lazy var horizontalSubtitleStackView: UIStackView = { + let horizontalSubtitleStackView = UIStackView() + horizontalSubtitleStackView.axis = .horizontal + horizontalSubtitleStackView.spacing = 8 + return horizontalSubtitleStackView + }() + private lazy var subtitleLabel: AttributedLabel = { + return AttributedLabel( + font: .label(.medium), + textColor: .textSubdued + ) + }() + private lazy var subtitleBalanceView: UIView = { + let paddingView = UIStackView(arrangedSubviews: [subtitleBalanceLabel]) + paddingView.axis = .vertical + paddingView.isLayoutMarginsRelativeArrangement = true + paddingView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 2, + leading: 6, + bottom: 2, + trailing: 6 + ) + paddingView.backgroundColor = .backgroundOffset + paddingView.layer.cornerRadius = 4 + return paddingView + }() + private lazy var subtitleBalanceLabel: AttributedLabel = { + let trailingTitleLabel = AttributedLabel( + font: .label(.small), + textColor: .textSubdued + ) + return trailingTitleLabel + }() + + init() { + super.init(frame: .zero) + verticalLabelStackView.addArrangedSubview(titleLabel) + addAndPinSubview(verticalLabelStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func set( + title: String, + subtitle: String?, + underlineSubtitle: Bool = false, + balanceString: String? = nil + ) { + titleLabel.text = title + + horizontalSubtitleStackView.removeFromSuperview() + subtitleLabel.removeFromSuperview() + subtitleBalanceView.removeFromSuperview() + if let subtitle = subtitle { + subtitleLabel.setText( + subtitle, + underline: underlineSubtitle + ) + horizontalSubtitleStackView.addArrangedSubview(subtitleLabel) + } + + if let balanceString = balanceString { + subtitleBalanceLabel.text = balanceString + horizontalSubtitleStackView.addArrangedSubview(subtitleBalanceView) + } + + if (subtitle != nil) || (balanceString != nil) { + verticalLabelStackView.addArrangedSubview(horizontalSubtitleStackView) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AccountPickerRowView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AccountPickerRowView.swift new file mode 100644 index 00000000..75d88645 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AccountPickerRowView.swift @@ -0,0 +1,289 @@ +// +// AccountPickerRowView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/5/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class AccountPickerRowView: UIView { + + private let theme: FinancialConnectionsTheme + private let didSelect: () -> Void + private var isSelected: Bool = false { + didSet { + layer.cornerRadius = 12 + if isSelected { + layer.borderColor = theme.borderColor.cgColor + layer.borderWidth = 2 + let shadowWidthOffset: CGFloat = 0 + layer.shadowPath = CGPath( + roundedRect: CGRect(x: shadowWidthOffset / 2, y: 0, width: bounds.width - shadowWidthOffset, height: bounds.height), + cornerWidth: layer.cornerRadius, + cornerHeight: layer.cornerRadius, + transform: nil + ) + layer.shadowColor = UIColor.black.cgColor + layer.shadowRadius = 1.5 / UIScreen.main.nativeScale + layer.shadowOpacity = 0.23 + layer.shadowOffset = CGSize( + width: 0, + height: 1 / UIScreen.main.nativeScale + ) + } else { + layer.borderColor = UIColor.borderDefault.cgColor + layer.borderWidth = 1 + layer.shadowOpacity = 0 + } + checkboxView.isSelected = isSelected + } + } + private lazy var horizontalStackView: UIStackView = { + return CreateHorizontalStackView( + arrangedSubviews: [ + labelView, + checkboxView, + ] + ) + }() + private lazy var institutionIconView: InstitutionIconView = { + return InstitutionIconView() + }() + private lazy var checkboxView: CheckboxView = { + let selectionView = CheckboxView(theme: theme) + selectionView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + selectionView.widthAnchor.constraint(equalToConstant: 16), + selectionView.heightAnchor.constraint(equalToConstant: 16), + ]) + return selectionView + }() + private lazy var labelView: AccountPickerRowLabelView = { + return AccountPickerRowLabelView() + }() + + init( + isDisabled: Bool, + isFaded: Bool, + theme: FinancialConnectionsTheme, + didSelect: @escaping () -> Void + ) { + self.theme = theme + self.didSelect = didSelect + super.init(frame: .zero) + + // necessary so the shadow does not appear under text + backgroundColor = .customBackgroundColor + + if isFaded { + horizontalStackView.alpha = 0.25 + } + addAndPinSubviewToSafeArea(horizontalStackView) + + if !isDisabled { + let tapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(didTapView) + ) + addGestureRecognizer(tapGestureRecognizer) + } + + isSelected = false // activate the setter to draw border + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + // `isSelected` controls the shadow, which is driven + // by the layout, so this refreshes + // shadow layout + let isSelected = self.isSelected + self.isSelected = isSelected + } + + func set( + institutionIconUrl: String? = nil, + title: String, + subtitle: String?, + underlineSubtitle: Bool = false, + balanceString: String? = nil, + isSelected: Bool + ) { + if let institutionIconUrl = institutionIconUrl { + let needToInsertInstitutionIconView = (institutionIconView.superview == nil) + if needToInsertInstitutionIconView { + horizontalStackView.insertArrangedSubview(institutionIconView, at: 0) + } + institutionIconView.setImageUrl(institutionIconUrl) + } else { + institutionIconView.removeFromSuperview() + } + + labelView.set( + title: title, + subtitle: subtitle, + underlineSubtitle: underlineSubtitle, + balanceString: balanceString + ) + set(isSelected: isSelected) + } + + func set(isSelected: Bool) { + self.isSelected = isSelected + } + + @objc private func didTapView() { + self.didSelect() + } +} + +private func CreateHorizontalStackView(arrangedSubviews: [UIView]) -> UIStackView { + let horizontalStackView = UIStackView(arrangedSubviews: arrangedSubviews) + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 12 + horizontalStackView.alignment = .center + horizontalStackView.isLayoutMarginsRelativeArrangement = true + horizontalStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 16, + bottom: 16, + trailing: 16 + ) + return horizontalStackView +} + +#if DEBUG + +import SwiftUI + +private struct AccountPickerRowViewUIViewRepresentable: UIViewRepresentable { + + let institutionIconUrl: String? + let title: String + let subtitle: String? + let balanceString: String? + let isSelected: Bool + let isDisabled: Bool + let isFaded: Bool + let theme: FinancialConnectionsTheme + + init( + institutionIconUrl: String? = nil, + title: String, + subtitle: String?, + balanceString: String?, + isSelected: Bool, + isDisabled: Bool, + isFaded: Bool, + theme: FinancialConnectionsTheme = .light + ) { + self.institutionIconUrl = institutionIconUrl + self.title = title + self.subtitle = subtitle + self.balanceString = balanceString + self.isSelected = isSelected + self.isDisabled = isDisabled + self.isFaded = isFaded + self.theme = theme + } + + func makeUIView(context: Context) -> AccountPickerRowView { + let view = AccountPickerRowView( + isDisabled: isDisabled, + isFaded: isFaded, + theme: theme, + didSelect: {} + ) + view.set( + institutionIconUrl: institutionIconUrl, + title: title, + subtitle: subtitle, + balanceString: balanceString, + isSelected: isSelected + ) + return view + } + + func updateUIView( + _ uiView: AccountPickerRowView, + context: Context + ) { + uiView.set( + institutionIconUrl: institutionIconUrl, + title: title, + subtitle: subtitle, + balanceString: balanceString, + isSelected: isSelected + ) + } +} + +struct AccountPickerRowView_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + ScrollView { + VStack(spacing: 16) { + AccountPickerRowViewUIViewRepresentable( + institutionIconUrl: "https://b.stripecdn.com/connections-statics-srv/assets/BrandIcon--stripe-4x.png", + title: "Joint Checking Very Long Name To Truncate", + subtitle: "••••6789", + balanceString: nil, + isSelected: true, + isDisabled: false, + isFaded: false + ).frame(height: 88) + AccountPickerRowViewUIViewRepresentable( + title: "Joint Checking Very Long Name To Truncate", + subtitle: "••••6789", + balanceString: nil, + isSelected: true, + isDisabled: false, + isFaded: false + ).frame(height: 76) + AccountPickerRowViewUIViewRepresentable( + title: "Link Light Theme", + subtitle: "••••6789", + balanceString: nil, + isSelected: true, + isDisabled: false, + isFaded: false, + theme: .linkLight + ).frame(height: 76) + AccountPickerRowViewUIViewRepresentable( + title: "Joint Checking Very Long Name To Truncate", + subtitle: "••••6789", + balanceString: "$3285.53", + isSelected: false, + isDisabled: false, + isFaded: false + ).frame(height: 76) + AccountPickerRowViewUIViewRepresentable( + title: "Joint Checking", + subtitle: nil, + balanceString: "$3285.53", + isSelected: false, + isDisabled: false, + isFaded: false + ).frame(height: 76) + AccountPickerRowViewUIViewRepresentable( + title: "Joint Checking", + subtitle: "Not available", + balanceString: nil, + isSelected: false, + isDisabled: true, + isFaded: true + ).frame(height: 76) + }.padding() + } + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AttributedLabel.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AttributedLabel.swift new file mode 100644 index 00000000..067c9349 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AttributedLabel.swift @@ -0,0 +1,97 @@ +// +// Label.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 5/8/23. +// + +import Foundation +import UIKit + +// Prefer `AttributedLabel` over `AttributedTextView` for single-line text. +final class AttributedLabel: UILabel { + + private let customFont: FinancialConnectionsFont + private let customTextColor: UIColor + private var customTextAlignment: NSTextAlignment? + + // one can accidentally forget to call `setText` instead of `text` so + // this makes it convenient to use `AttributedLabel` + override var text: String? { + didSet { + setText(text ?? "") + } + } + + override var textAlignment: NSTextAlignment { + didSet { + self.customTextAlignment = textAlignment + } + } + + init(font: FinancialConnectionsFont, textColor: UIColor) { + self.customFont = font + self.customTextColor = textColor + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // UILabel with custom `lineHeight` via `NSParagraphStyle` was not properly + // centering the text, so here we adjust it to be centered. + override func drawText(in rect: CGRect) { + guard + let attributedText = self.attributedText, + attributedText.length > 0, // `attributes(at:effectiveRange)` crashes if empty string + let font = attributedText.attributes(at: 0, effectiveRange: nil)[.font] as? UIFont, + let paragraphStyle = attributedText.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle + else { + super.drawText(in: rect) + return + } + let uiFontLineHeight = font.lineHeight + let paragraphStyleLineHeight = paragraphStyle.minimumLineHeight + assert(paragraphStyle.minimumLineHeight == paragraphStyle.maximumLineHeight, "we are assuming that minimum and maximum are the same") + + if paragraphStyleLineHeight > uiFontLineHeight { + let lineHeightDifference = (paragraphStyle.minimumLineHeight - uiFontLineHeight) + let newRect = CGRect( + x: rect.origin.x, + y: rect.origin.y - lineHeightDifference / 2, + width: rect.width, + height: rect.height + ) + super.drawText(in: newRect) + } else { + super.drawText(in: rect) + } + } + + func setText(_ text: String, underline: Bool = false) { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.minimumLineHeight = customFont.lineHeight + paragraphStyle.maximumLineHeight = customFont.lineHeight + if let customTextAlignment = customTextAlignment { + paragraphStyle.alignment = customTextAlignment + } + + let string = NSMutableAttributedString( + string: text, + attributes: { + var attributes: [NSAttributedString.Key: Any] = [ + .paragraphStyle: paragraphStyle, + .font: customFont.uiFont, + .foregroundColor: customTextColor, + ] + if underline { + attributes[.underlineColor] = customTextColor + attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue + } + return attributes + }() + ) + attributedText = string + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AttributedTextView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AttributedTextView.swift new file mode 100644 index 00000000..ac0ab676 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AttributedTextView.swift @@ -0,0 +1,245 @@ +// +// AttributedTextView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 5/2/23. +// + +import Foundation +import SafariServices +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +// Adds support for markdown links and markdown bold. +// +// `AttributedTextView` is also the `UITextView` version of `AttributedLabel`. +final class AttributedTextView: HitTestView { + + private struct LinkDescriptor { + let range: NSRange + let urlString: String + let action: (URL) -> Void + } + + private let font: FinancialConnectionsFont + private let boldFont: FinancialConnectionsFont + private let linkFont: FinancialConnectionsFont + private let textColor: UIColor + private let alignment: NSTextAlignment? + private let textView: IncreasedHitTestTextView + private var linkURLStringToAction: [String: (URL) -> Void] = [:] + + init( + font: FinancialConnectionsFont, + boldFont: FinancialConnectionsFont, + linkFont: FinancialConnectionsFont, + textColor: UIColor, + // links are the same color as the text by default + linkColor: UIColor? = nil, + showLinkUnderline: Bool = true, + alignment: NSTextAlignment? = nil + ) { + let linkColor = linkColor ?? textColor + let textContainer = NSTextContainer(size: .zero) + let layoutManager = VerticalCenterLayoutManager() + layoutManager.addTextContainer(textContainer) + let textStorage = NSTextStorage() + textStorage.addLayoutManager(layoutManager) + self.textView = IncreasedHitTestTextView( + frame: .zero, + textContainer: textContainer + ) + self.font = font + self.boldFont = boldFont + self.linkFont = linkFont + self.textColor = textColor + self.alignment = alignment + super.init(frame: .zero) + textView.isScrollEnabled = false + textView.delaysContentTouches = false + textView.isEditable = false + textView.isSelectable = true + textView.backgroundColor = UIColor.clear + // Get rid of the extra padding added by default to UITextViews + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0.0 + textView.linkTextAttributes = { + var linkTextAttributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: linkColor + ] + if showLinkUnderline { + linkTextAttributes[.underlineStyle] = NSUnderlineStyle.single.rawValue + } + return linkTextAttributes + }() + textView.delegate = self + // remove clipping so when user selects an attributed + // link, the selection area does not get clipped + textView.clipsToBounds = false + addAndPinSubview(textView) + + // enable faster tap recognizing + if let gestureRecognizers = textView.gestureRecognizers { + for gestureRecognizer in gestureRecognizers { + if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer, + tapGestureRecognizer.numberOfTapsRequired == 2 + { + // double-tap gesture recognizer causes a delay + // to single-tap gesture recognizer so we + // disable it + tapGestureRecognizer.isEnabled = false + } + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Helper that automatically handles extracting links and, optionally, opening it via `SFSafariViewController` + func setText( + _ text: String, + action: @escaping ((URL) -> Void) = { url in + SFSafariViewController.present(url: url) + } + ) { + let textLinks = text.extractLinks() + setText( + textLinks.linklessString, + links: textLinks.links.map { + AttributedTextView.LinkDescriptor( + range: $0.range, + urlString: $0.urlString, + action: action + ) + } + ) + } + + private func setText( + _ text: String, + links: [LinkDescriptor] + ) { + let paragraphStyle = NSMutableParagraphStyle() + if let alignment { + paragraphStyle.alignment = alignment + } + paragraphStyle.minimumLineHeight = font.lineHeight + paragraphStyle.maximumLineHeight = font.lineHeight + let string = NSMutableAttributedString( + string: text, + attributes: [ + .paragraphStyle: paragraphStyle, + .font: font.uiFont, + .foregroundColor: textColor, + ] + ) + + // apply link attributes + for link in links { + string.addAttribute(.link, value: link.urlString, range: link.range) + + // setting font in `linkTextAttributes` does not work + string.addAttribute(.font, value: linkFont.uiFont, range: link.range) + + linkURLStringToAction[link.urlString] = link.action + } + + // apply bold attributes + string.addBoldFontAttributesByMarkdownRules(boldFont: boldFont.uiFont) + + textView.attributedText = string + } +} + +// MARK: + +extension AttributedTextView: UITextViewDelegate { + + #if !canImport(CompositorServices) + func textView( + _ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction + ) -> Bool { + if let linkAction = linkURLStringToAction[URL.absoluteString] { + FeedbackGeneratorAdapter.buttonTapped() + linkAction(URL) + return false + } else { + assertionFailure("Expected every URL to have an action defined. keys:\(linkURLStringToAction); url:\(URL)") + } + return true + } + #endif + + func textViewDidChangeSelection(_ textView: UITextView) { + // disable the ability to select/copy the text as a way to improve UX + textView.selectedTextRange = nil + } +} + +private class IncreasedHitTestTextView: UITextView { + + // increase the area of NSAttributedString taps + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + // Note that increasing size here does NOT help to + // increase NSAttributedString implementation of how + // large a tap area is. As a result, this function + // can return `true` and the link-tap may still + // not happen. + let largerBounds = bounds.insetBy(dx: -20, dy: -20) + return largerBounds.contains(point) + } +} + +// UITextView with custom `lineHeight` via `NSParagraphStyle` was not properly +// centering the text, so here we adjust it to be centered. +private class VerticalCenterLayoutManager: NSLayoutManager { + override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) { + let range = characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil) + guard + let attributedString = textStorage?.attributedSubstring(from: range), + attributedString.length > 0, // `attributes(at:effectiveRange)` crashes if empty string + let font = attributedString.attributes(at: 0, effectiveRange: nil)[.font] as? UIFont, + let paragraphStyle = attributedString.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle + else { + super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) + return + } + let uiFontLineHeight = font.lineHeight + let paragraphStyleLineHeight = paragraphStyle.minimumLineHeight + assert(paragraphStyle.minimumLineHeight == paragraphStyle.maximumLineHeight, "we are assuming that minimum and maximum are the same") + if paragraphStyleLineHeight > uiFontLineHeight { + let lineHeightDifference = (paragraphStyleLineHeight - uiFontLineHeight) + let newOrigin = CGPoint( + x: origin.x, + y: origin.y - lineHeightDifference / 2 + ) + super.drawGlyphs(forGlyphRange: glyphsToShow, at: newOrigin) + } else { + super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin) + } + } + + override func underlineGlyphRange( + _ glyphRange: NSRange, + underlineType: NSUnderlineStyle, + lineFragmentRect: CGRect, + lineFragmentGlyphRange: NSRange, + containerOrigin: CGPoint + ) { + var lineFragmentRect = lineFragmentRect + lineFragmentRect.origin.y += 1.5 // move the underline down more + super.underlineGlyphRange( + glyphRange, + underlineType: underlineType, + lineFragmentRect: lineFragmentRect, + lineFragmentGlyphRange: lineFragmentGlyphRange, + containerOrigin: containerOrigin + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AuthFlowHelpers.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AuthFlowHelpers.swift new file mode 100644 index 00000000..7095c367 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AuthFlowHelpers.swift @@ -0,0 +1,109 @@ +// +// AuthFlowHelpers.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/5/22. +// + +import Foundation +import SafariServices +@_spi(STP) import StripeCore + +final class AuthFlowHelpers { + + private init() {} // only static functions used + + static func formatUrlString(_ urlString: String?) -> String? { + guard var urlString = urlString else { + return nil + } + if urlString.hasPrefix("https://") { + urlString.removeFirst("https://".count) + } + if urlString.hasPrefix("http://") { + urlString.removeFirst("http://".count) + } + if urlString.hasPrefix("www.") { + urlString.removeFirst("www.".count) + } + if urlString.hasSuffix("/") { + urlString.removeLast() + } + return urlString + } + + static func handleURLInTextFromBackend( + url: URL, + pane: FinancialConnectionsSessionManifest.NextPane, + analyticsClient: FinancialConnectionsAnalyticsClient, + handleURL: (_ urlHost: String?, _ nextPaneOrDrawerOnSecondaryCta: String?) -> Void + ) { + let internalLinkToPaneId: [String: String] = [ + "manual-entry": "manual_entry" + ] + let urlParameters = URLComponents(url: url, resolvingAgainstBaseURL: true) + if + let urlParameters, + let eventName = urlParameters.queryItems?.first( + where: { $0.name == "eventName" } + )?.value + { + analyticsClient + .log( + eventName: eventName, + pane: pane + ) + } + + var nextPaneOrDrawerOnSecondaryCta: String? + if + let urlParameters, + let _nextPaneOrDrawerOnSecondaryCta = urlParameters.queryItems?.first( + where: { $0.name == "nextPaneOrDrawerOnSecondaryCta" } + )?.value + { + nextPaneOrDrawerOnSecondaryCta = internalLinkToPaneId[_nextPaneOrDrawerOnSecondaryCta] + } + + if url.scheme == "stripe" { + handleURL(url.host, nextPaneOrDrawerOnSecondaryCta) + } else { + SFSafariViewController.present(url: url) + } + } + + static func networkingOTPErrorMessage( + fromError error: Error, + otpType: String + ) -> String? { + if + let error = error as? StripeError, + case .apiError(let apiError) = error + { + if apiError.code == "consumer_verification_code_invalid" { + return STPLocalizedString("Hmm, that code didn’t work. Double check it and try again.", "Error message when one-time-passcode (OTP) is invalid.") + } else if + apiError.code == "consumer_session_expired" + || apiError.code == "consumer_verification_expired" + || apiError.code == "consumer_verification_max_attempts_exceeded" + { + let leadingMessage = STPLocalizedString("It looks like the verification code you provided is not valid anymore.", "The leading text in an error message that explains that the one-type-passcode (OTP) the user provided is invalid. This is leading text embedded inside of larger text: 'It looks like the verification code you provided is not valid anymore. Try again, or contact us.'") + let trailingMessage = (otpType == "EMAIL") ? STPLocalizedString("Click “Resend code” and try again, or %@.", "Text as part of an error message that shows up when user entered an invalid one-time-passcode (OTP). '%@' will be replaced by text with a link: 'contact us'") : STPLocalizedString("Try again, or %@.", "Text as part of an error message that shows up when user entered an invalid one-time-passcode (OTP). '%@' will be replaced by text with a link: 'contact us'") + + let contactUsText = STPLocalizedString("contact us", "A link/button inside of text that can be tapped to visit a support website. This link will be embedded inside of larger text: 'It looks like the verification code you provided is not valid anymore. Try again, or contact us.'") + let contactUsUrlString = "https://support.link.co/contact/email?skipVerification=true" + let contactUsWithUrlText = "[\(contactUsText)](\(contactUsUrlString))" + + return leadingMessage + " " + String(format: trailingMessage, contactUsWithUrlText) + } else { + return nil + } + } else { + return nil + } + } + + static func formatRedactedPhoneNumber(_ redactedPhoneNumber: String) -> String { + return redactedPhoneNumber.replacingOccurrences(of: "*", with: "•") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AutoResizableUIView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AutoResizableUIView.swift new file mode 100644 index 00000000..68c70b70 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/AutoResizableUIView.swift @@ -0,0 +1,81 @@ +// +// AutoResizableUIView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/11/24. +// + +import Foundation +import SwiftUI +import UIKit + +// +// These are only used for SwiftUI previews so no need to expose outside of DEBUG. +// +#if DEBUG + +/// This helps to auto-resize UIView's placed in SwiftUI. Otherwise, +/// the UIView's tend to stretch the full height of the screen. +/// +/// If wrapping the `UIView` with `AutoResizableUIView` doesn't help, +/// also call `applyAutoResizableUIViewModifier` to your SwiftUI view. +class AutoResizableUIView: UIView { + var contentView: ContentView + + init(contentView: ContentView) { + self.contentView = contentView + super.init(frame: .zero) + backgroundColor = .clear + addSubview(contentView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var height: CGFloat = .zero { + didSet { + invalidateIntrinsicContentSize() + } + } + + override var intrinsicContentSize: CGSize { + CGSize( + width: UIView.noIntrinsicMetric, + height: height + ) + } + + override var frame: CGRect { + didSet { + guard frame != oldValue else { + return + } + contentView.frame = bounds + contentView.layoutIfNeeded() + + let targetFrameSize = CGSize( + width: frame.width, + height: UIView.layoutFittingCompressedSize.height + ) + height = contentView.systemLayoutSizeFitting( + targetFrameSize, + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel + ).height + } + } +} + +extension View { + /// Helper for use with `AutoResizableUIView`. Apply it to the SwitUI view that's + /// being created via `UIViewRepresentable`. + func applyAutoResizableUIViewModifier() -> some View { + fixedSize( + horizontal: false, + vertical: true + ) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/BulletPointLabelView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/BulletPointLabelView.swift new file mode 100644 index 00000000..0cf3ad28 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/BulletPointLabelView.swift @@ -0,0 +1,63 @@ +// +// BulletPointLabelView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class BulletPointLabelView: HitTestView { + + private(set) var topPadding: CGFloat = 0 + private(set) var topLineHeight: CGFloat = 0 + + init( + title: String?, + content: String?, + didSelectURL: @escaping (URL) -> Void + ) { + super.init(frame: .zero) + let verticalLabelStackView = HitTestStackView() + verticalLabelStackView.axis = .vertical + verticalLabelStackView.spacing = 0 + if let title = title { + let displayingOnlyTitle = (content == nil) + let font: FinancialConnectionsFont = displayingOnlyTitle ? .body(.medium) : .body(.mediumEmphasized) + let primaryLabel = AttributedTextView( + font: font, + boldFont: font, + linkFont: font, + textColor: .textDefault + ) + primaryLabel.setText(title, action: didSelectURL) + verticalLabelStackView.addArrangedSubview(primaryLabel) + topPadding = font.topPadding + topLineHeight = font.lineHeight + } + if let content = content { + let displayingOnlyContent = (title == nil) + let font: FinancialConnectionsFont = displayingOnlyContent ? .body(.medium) : .body(.small) + let subtitleLabel = AttributedTextView( + font: font, + boldFont: displayingOnlyContent ? .body(.medium) : .body(.smallEmphasized), + linkFont: font, + textColor: .textSubdued + ) + subtitleLabel.setText(content, action: didSelectURL) + verticalLabelStackView.addArrangedSubview(subtitleLabel) + if displayingOnlyContent { + topPadding = font.topPadding + topLineHeight = font.lineHeight + } + } + addAndPinSubview(verticalLabelStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/Button+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/Button+Extensions.swift new file mode 100644 index 00000000..415c6c25 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/Button+Extensions.swift @@ -0,0 +1,154 @@ +// +// Button+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/30/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +extension StripeUICore.Button { + static func primary(theme: FinancialConnectionsTheme) -> StripeUICore.Button { + let button = Button(configuration: .financialConnectionsPrimary(theme: theme)) + button.layer.shadowColor = UIColor.black.cgColor + button.layer.shadowRadius = 5 / UIScreen.main.nativeScale + button.layer.shadowOpacity = 0.25 + button.layer.shadowOffset = CGSize( + width: 0, + height: 2 / UIScreen.main.nativeScale + ) + ButtonFeedbackGeneratorHandler.attach(toButton: button) + return button + } + + static func secondary() -> StripeUICore.Button { + let button = Button(configuration: .financialConnectionsSecondary) + ButtonFeedbackGeneratorHandler.attach(toButton: button) + return button + } +} + +extension StripeUICore.Button.Configuration { + + fileprivate static func financialConnectionsPrimary(theme: FinancialConnectionsTheme) -> StripeUICore.Button.Configuration { + var primaryButtonConfiguration = Button.Configuration.primary() + primaryButtonConfiguration.font = FinancialConnectionsFont.label(.largeEmphasized).uiFont + primaryButtonConfiguration.cornerRadius = 12.0 + // default + primaryButtonConfiguration.backgroundColor = theme.primaryColor + primaryButtonConfiguration.foregroundColor = theme.primaryAccentColor + // disabled + primaryButtonConfiguration.disabledBackgroundColor = theme.primaryColor + primaryButtonConfiguration.disabledForegroundColor = theme.primaryAccentColor.withAlphaComponent(0.4) + // pressed + primaryButtonConfiguration.colorTransforms.highlightedBackground = .darken(amount: 0.23) // this tries to simulate `brand600` + primaryButtonConfiguration.colorTransforms.highlightedForeground = nil + return primaryButtonConfiguration + } + + fileprivate static var financialConnectionsSecondary: StripeUICore.Button.Configuration { + var secondaryButtonConfiguration = Button.Configuration.secondary() + secondaryButtonConfiguration.font = FinancialConnectionsFont.label(.largeEmphasized).uiFont + secondaryButtonConfiguration.cornerRadius = 12.0 + // default + secondaryButtonConfiguration.foregroundColor = .textDefault + secondaryButtonConfiguration.backgroundColor = .neutral25 + // disabled + secondaryButtonConfiguration.disabledForegroundColor = .textDefault.withAlphaComponent(0.4) + secondaryButtonConfiguration.disabledBackgroundColor = .neutral25 + // pressed + secondaryButtonConfiguration.colorTransforms.highlightedBackground = .darken(amount: 0.04) // this tries to simulate `neutral100` + secondaryButtonConfiguration.colorTransforms.highlightedForeground = nil + return secondaryButtonConfiguration + } +} + +// attaches haptic feedback to a button press +private final class ButtonFeedbackGeneratorHandler: NSObject { + + @objc private func didTouchUpInside() { + FeedbackGeneratorAdapter.buttonTapped() + } + + // `associatedObjectKey` is a unique address when accessed + // via `&`, so we just map a key ("random address") to + // a value (or "instance variable") `buttonFeedbackGeneratorHandler` + // so we can retain it to fire `didTouchUpInside` func + private static var associatedObjectKey: UInt8 = 0 + static func attach(toButton button: UIControl) { + let buttonFeedbackGeneratorHandler = ButtonFeedbackGeneratorHandler() + objc_setAssociatedObject( + button, + &associatedObjectKey, + buttonFeedbackGeneratorHandler, + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + button.addTarget( + buttonFeedbackGeneratorHandler, + action: #selector(didTouchUpInside), + for: .touchUpInside + ) + } +} + +#if DEBUG + +import SwiftUI + +private struct PrimaryButtonViewRepresentable: UIViewRepresentable { + let theme: FinancialConnectionsTheme + let enabled: Bool + + func makeUIView(context: Context) -> StripeUICore.Button { + let button = StripeUICore.Button.primary(theme: theme) + button.title = "primary | \(theme.rawValue) | \(enabled ? "enabled" : "disabled")" + return button + } + + func updateUIView(_ uiView: StripeUICore.Button, context: Context) { + uiView.isEnabled = enabled + } +} + +private struct SecondaryButtonViewRepresentable: UIViewRepresentable { + let enabled: Bool + + func makeUIView(context: Context) -> StripeUICore.Button { + let button = StripeUICore.Button.secondary() + button.title = "secondary | \(enabled ? "enabled" : "disabled")" + return button + } + + func updateUIView(_ uiView: StripeUICore.Button, context: Context) { + uiView.isEnabled = enabled + } +} + +struct ButtonViewRepresentable_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 20) { + PrimaryButtonViewRepresentable(theme: .light, enabled: true) + .frame(height: 64) + .padding() + PrimaryButtonViewRepresentable(theme: .light, enabled: false) + .frame(height: 64) + .padding() + PrimaryButtonViewRepresentable(theme: .linkLight, enabled: true) + .frame(height: 64) + .padding() + PrimaryButtonViewRepresentable(theme: .linkLight, enabled: false) + .frame(height: 64) + .padding() + SecondaryButtonViewRepresentable(enabled: true) + .frame(height: 64) + .padding() + SecondaryButtonViewRepresentable(enabled: false) + .frame(height: 64) + .padding() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/CloseConfirmationViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/CloseConfirmationViewController.swift new file mode 100644 index 00000000..661d69f7 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/CloseConfirmationViewController.swift @@ -0,0 +1,79 @@ +// +// CloseConfirmationViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 12/19/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class CloseConfirmationViewController: SheetViewController { + + private let theme: FinancialConnectionsTheme + private let didSelectClose: () -> Void + + init(theme: FinancialConnectionsTheme, didSelectClose: @escaping () -> Void) { + self.theme = theme + self.didSelectClose = didSelectClose + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setup( + withContentView: PaneLayoutView.createContentView( + iconView: RoundedIconView( + image: .image(.panel_arrow_right), + style: .circle, + theme: theme + ), + title: STPLocalizedString( + "Exit without connecting?", + "The title of a sheet that appears when the user attempts to exit the bank linking screen." + ), + subtitle: STPLocalizedString( + "You haven't finished linking your bank account and all progress will be lost.", + "The subtitle/description of a sheet that appears when the user attempts to exit the bank linking screen." + ), + contentView: nil, + isSheet: true + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: "Cancel", // TODO: when Financial Connections starts supporting localization, change this to `String.Localized.cancel` + action: { [weak self] in + self?.dismiss(animated: true) + } + ), + secondaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: STPLocalizedString( + "Yes, exit", + "A button title. The user encounters it as part of a confirmation sheet when trying to exit a screen. Pressing it will exit the screen, and cancel the process of connecting the users bank account." + ), + accessibilityIdentifier: "close_confirmation_ok", + action: { [weak self] in + guard let self = self else { return } + let didSelectClose = self.didSelectClose + self.dismiss( + animated: true, + completion: { + // call `didSelectClose` AFTER we dismiss the + // sheet to ensure we don't have bugs where + // a view controller is in process of dismissing + // while we are trying to present/dismiss another + didSelectClose() + } + ) + } + ), + theme: theme + ).footerView + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/Constants.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/Constants.swift new file mode 100644 index 00000000..4ab407a7 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/Constants.swift @@ -0,0 +1,17 @@ +// +// Constants.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/15/24. +// + +import Foundation + +struct Constants { + private init() {} + struct Layout { + private init() {} + static let defaultHorizontalMargin: CGFloat = 24.0 + static let defaultVerticalPadding: CGFloat = 16.0 + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/CreatePaneParameters.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/CreatePaneParameters.swift new file mode 100644 index 00000000..2da9dfbf --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/CreatePaneParameters.swift @@ -0,0 +1,20 @@ +// +// CreatePaneParameters.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/22/24. +// + +import Foundation + +// A bag of extra parameters we can pass to `CreatePaneViewController` function +// that creates new pane view controllers. This avoids preserving state in +// `NativeFlowDataManager` where the state might be outdated after a specific +// pane push. +struct CreatePaneParameters { + let nextPaneOrDrawerOnSecondaryCta: String? + + init(nextPaneOrDrawerOnSecondaryCta: String? = nil) { + self.nextPaneOrDrawerOnSecondaryCta = nextPaneOrDrawerOnSecondaryCta + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/DataAccessNoticeViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/DataAccessNoticeViewController.swift new file mode 100644 index 00000000..fef87273 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/DataAccessNoticeViewController.swift @@ -0,0 +1,292 @@ +// +// DataAccessNoticeViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/3/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class DataAccessNoticeViewController: SheetViewController { + + private let dataAccessNotice: FinancialConnectionsDataAccessNotice + private let theme: FinancialConnectionsTheme + private let didSelectUrl: (URL) -> Void + + init( + dataAccessNotice: FinancialConnectionsDataAccessNotice, + theme: FinancialConnectionsTheme, + didSelectUrl: @escaping (URL) -> Void + ) { + self.dataAccessNotice = dataAccessNotice + self.theme = theme + self.didSelectUrl = didSelectUrl + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let firstSubtitle: String? + let contentView: UIView + if let connectedAccountNotice = dataAccessNotice.connectedAccountNotice { + firstSubtitle = connectedAccountNotice.subtitle + contentView = CreateConnectedAccountContentView( + connectedAccountBulletItems: connectedAccountNotice.body.bullets, + secondSubtitle: dataAccessNotice.subtitle, + merchantBulletItems: dataAccessNotice.body.bullets, + didSelectURL: didSelectUrl + ) + } else { + firstSubtitle = dataAccessNotice.subtitle + contentView = CreateMultiBulletinView( + bulletItems: dataAccessNotice.body.bullets, + didSelectURL: didSelectUrl + ) + } + + setup( + withContentView: PaneLayoutView.createContentView( + iconView: RoundedIconView( + image: .imageUrl(dataAccessNotice.icon?.default), + style: .circle, + theme: theme + ), + title: dataAccessNotice.title, + subtitle: firstSubtitle, + contentView: contentView, + isSheet: true + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: dataAccessNotice.cta, + action: { [weak self] in + guard let self = self else { return } + self.dismiss(animated: true) + } + ), + secondaryButtonConfiguration: nil, + topText: dataAccessNotice.disclaimer, + theme: theme, + didSelectURL: didSelectUrl + ).footerView + ) + } +} + +private func CreateConnectedAccountContentView( + connectedAccountBulletItems: [FinancialConnectionsBulletPoint], + secondSubtitle: String?, + merchantBulletItems: [FinancialConnectionsBulletPoint], + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 24 + verticalStackView.addArrangedSubview( + CreateMultiBulletinView( + bulletItems: connectedAccountBulletItems, + didSelectURL: didSelectURL + ) + ) + if let secondSubtitle = secondSubtitle { + let secondSubtitleLabel = AttributedTextView( + font: .body(.medium), + boldFont: .body(.mediumEmphasized), + linkFont: .body(.mediumEmphasized), + textColor: .textDefault + ) + secondSubtitleLabel.setText(secondSubtitle) + verticalStackView.addArrangedSubview(secondSubtitleLabel) + } + verticalStackView.addArrangedSubview( + CreateMultiBulletinView( + bulletItems: merchantBulletItems, + didSelectURL: didSelectURL + ) + ) + return verticalStackView +} + +private func CreateMultiBulletinView( + bulletItems: [FinancialConnectionsBulletPoint], + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let verticalStackView = HitTestStackView( + arrangedSubviews: { + var subviews: [UIView] = [] + bulletItems.forEach { bulletItem in + subviews.append( + CreateSingleBulletinView( + title: bulletItem.title, + subtitle: bulletItem.content, + iconUrl: bulletItem.icon?.default, + didSelectURL: didSelectURL + ) + ) + } + return subviews + }() + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + return verticalStackView +} + +private func CreateSingleBulletinView( + title: String?, + subtitle: String?, + iconUrl: String?, + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .iconDefault + if let iconUrl = iconUrl { + imageView.setImage(with: iconUrl, useAlwaysTemplateRenderingMode: true) + } else { + imageView.image = Image.bullet.makeImage().withRenderingMode(.alwaysTemplate) + } + imageView.translatesAutoresizingMaskIntoConstraints = false + let imageDiameter: CGFloat = 20 + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: imageDiameter), + imageView.heightAnchor.constraint(equalToConstant: imageDiameter), + ]) + + let bulletPointLabelView = BulletPointLabelView( + title: title, + content: subtitle, + didSelectURL: didSelectURL + ) + let horizontalStackView = HitTestStackView( + arrangedSubviews: [ + { + // add padding to the icon so its better aligned with text + let paddingStackView = UIStackView(arrangedSubviews: [imageView]) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + // center the image in the middle of the first line height + top: max(0, (bulletPointLabelView.topLineHeight - imageDiameter) / 2), + leading: 0, + bottom: 0, + trailing: 0 + ) + return paddingStackView + }(), + bulletPointLabelView, + ] + ) + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 16 + horizontalStackView.alignment = .top + return horizontalStackView +} + +#if DEBUG + +import SwiftUI + +private struct DataAccessNoticeViewControllerRepresentable: UIViewControllerRepresentable { + let dataAccessNotice: FinancialConnectionsDataAccessNotice + + func makeUIViewController(context: Context) -> DataAccessNoticeViewController { + DataAccessNoticeViewController( + dataAccessNotice: dataAccessNotice, + theme: .light, + didSelectUrl: { _ in }) + } + + func updateUIViewController( + _ viewController: DataAccessNoticeViewController, + context: Context + ) {} +} + +struct DataAccessNoticeViewController_Previews: PreviewProvider { + static var previews: some View { + DataAccessNoticeViewControllerRepresentable( + dataAccessNotice: FinancialConnectionsDataAccessNotice( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--platform-stripeBrand-3x.png" + ), + title: "Data sharing", + connectedAccountNotice: nil, + subtitle: "[Merchant] will have access to the following data and related insights:", + body: FinancialConnectionsDataAccessNotice.Body( + bullets: [ + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--bank-primary-3x.png" + ), + title: "Account details" + ), + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--balance-primary-3x.png" + ), + title: "Balances" + ), + ] + ), + disclaimer: "Learn about [data shared with Stripe](https://test.com) and [how to disconnect](https://test.com)", + cta: "OK" + ) + ) + + DataAccessNoticeViewControllerRepresentable( + dataAccessNotice: FinancialConnectionsDataAccessNotice( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--platform-stripeBrand-3x.png" + ), + title: "Data sharing", + connectedAccountNotice: FinancialConnectionsDataAccessNotice.ConnectedAccountNotice( + subtitle: "[Connected account] will have access to the following data and related insights:", + body: FinancialConnectionsDataAccessNotice.Body( + bullets: [ + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--bank-primary-3x.png" + ), + title: "[C] Account details" + ), + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--balance-primary-3x.png" + ), + title: "[C] Balances" + ), + ] + ) + ), + subtitle: "[Merchant] will have access to the following data and related insights:", + body: FinancialConnectionsDataAccessNotice.Body( + bullets: [ + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--bank-primary-3x.png" + ), + title: "Account details" + ), + FinancialConnectionsBulletPoint( + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--balance-primary-3x.png" + ), + title: "Balances" + ), + ] + ), + disclaimer: "Learn about [data shared with Stripe](https://test.com) and [how to disconnect](https://test.com)", + cta: "OK" + ) + ) + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/FeedbackGeneratorAdapter.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/FeedbackGeneratorAdapter.swift new file mode 100644 index 00000000..828d5e9e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/FeedbackGeneratorAdapter.swift @@ -0,0 +1,30 @@ +// +// FeedbackGeneratorAdapter.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 3/6/24. +// + +import Foundation +import UIKit + +final class FeedbackGeneratorAdapter { + + private init() {} + + static func buttonTapped() { + UIImpactFeedbackGenerator(style: .light).impactOccurred() + } + + static func selectionChanged() { + UISelectionFeedbackGenerator().selectionChanged() + } + + static func errorOccurred() { + UINotificationFeedbackGenerator().notificationOccurred(.error) + } + + static func successOccurred() { + UINotificationFeedbackGenerator().notificationOccurred(.success) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoBodyView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoBodyView.swift new file mode 100644 index 00000000..47e0c92d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoBodyView.swift @@ -0,0 +1,339 @@ +// +// GenericInfoBodyView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/11/24. +// + +import Foundation +import UIKit + +func GenericInfoBodyView( + body: FinancialConnectionsGenericInfoScreen.Body?, + didSelectURL: @escaping (URL) -> Void +) -> UIView? { + guard let body, !body.entries.isEmpty else { + return nil + } + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 0 + for entry in body.entries { + let entryView: UIView? + switch entry { + case .text(let textBodyEntry): + entryView = TextBodyEntryView( + textBodyEntry, + didSelectURL: didSelectURL + ) + case .image(let imageBodyEntry): + entryView = ImageBodyEntryView(imageBodyEntry) + case .bullets(let bulletsBodyEntry): + entryView = BulletsBodyEntryView( + bulletsBodyEntry, + didSelectURL: didSelectURL + ) + case .unparasable: + entryView = nil // skip + } + if let entryView { + verticalStackView.addArrangedSubview(entryView) + } + } + // check `isEmpty` in case we were not able to handle any entry type + return verticalStackView.arrangedSubviews.isEmpty ? nil : verticalStackView +} + +// MARK: - Text + +private func TextBodyEntryView( + _ textBodyEntry: FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry, + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let font: FinancialConnectionsFont + let boldFont: FinancialConnectionsFont + let textColor: UIColor + switch textBodyEntry.size { + case .xsmall: + font = .body(.extraSmall) + boldFont = .body(.extraSmallEmphasized) + textColor = .textSubdued + case .small: + font = .body(.small) + boldFont = .body(.smallEmphasized) + textColor = .textSubdued + case .medium: fallthrough + case .unparsable: fallthrough + case .none: + font = .body(.medium) + boldFont = .body(.mediumEmphasized) + textColor = .textDefault + } + let textView = AttributedTextView( + font: font, + boldFont: boldFont, + linkFont: font, + textColor: textColor, + alignment: { + switch textBodyEntry.alignment { + case .center: + return .center + case .right: + return .right + case .left: fallthrough + case .unparsable: fallthrough + case .none: + return .left + } + }() + ) + textView.setText( + textBodyEntry.text, + action: didSelectURL + ) + return textView +} + +// MARK: - Image + +private func ImageBodyEntryView( + _ imageBodyEntry: FinancialConnectionsGenericInfoScreen.Body.ImageBodyEntry +) -> UIView? { + guard let imageUrlString = imageBodyEntry.image.default else { + return nil + } + let imageView = AutoResizableImageView() + imageView.setImage(with: imageUrlString) + return imageView +} + +// `UIImageView` that will autoresize itself to be +// full width, but maintain aspect ratio height +private class AutoResizableImageView: UIImageView { + + override var intrinsicContentSize: CGSize { + if let image, image.size.width > 0 { + let height = image.size.height * (bounds.width / image.size.width) + return CGSize( + width: UIView.noIntrinsicMetric, + height: height + ) + } else { + return CGSize( + width: UIView.noIntrinsicMetric, + height: 200 // give some height with assumption that an image will load + ) + } + } + + override var image: UIImage? { + didSet { + invalidateIntrinsicContentSize() + setNeedsLayout() + layoutIfNeeded() + } + } +} + +// MARK: - Bullets + +private func BulletsBodyEntryView( + _ bulletsBodyEntry: FinancialConnectionsGenericInfoScreen.Body.BulletsBodyEntry, + didSelectURL: @escaping (URL) -> Void +) -> UIView? { + guard !bulletsBodyEntry.bullets.isEmpty else { + return nil + } + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + bulletsBodyEntry.bullets.forEach { genericBulletPoint in + verticalStackView.addArrangedSubview( + SingleBulletPointView( + title: genericBulletPoint.title, + content: genericBulletPoint.content, + iconUrlString: genericBulletPoint.icon?.default, + didSelectUrl: didSelectURL + ) + ) + } + return verticalStackView +} + +private func SingleBulletPointView( + title: String?, + content: String?, + iconUrlString: String?, + didSelectUrl: @escaping (URL) -> Void +) -> UIView { + let horizontalStackView = HitTestStackView() + let labelView = BulletPointLabelView( + title: title, + content: content, + didSelectURL: didSelectUrl + ) + if let iconUrlString { + horizontalStackView.addArrangedSubview( + { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.setImage(with: iconUrlString) + imageView.translatesAutoresizingMaskIntoConstraints = false + let imageDiameter: CGFloat = 20 + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: imageDiameter), + imageView.heightAnchor.constraint(equalToConstant: imageDiameter), + ]) + // add padding to the `imageView` so the + // image is aligned with the label + let paddingStackView = UIStackView( + arrangedSubviews: [imageView] + ) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + // center the image in the middle of the first line height + top: max(0, (labelView.topLineHeight - imageDiameter) / 2), + leading: 0, + bottom: 0, + trailing: 0 + ) + return paddingStackView + }() + ) + } + horizontalStackView.addArrangedSubview(labelView) + horizontalStackView.axis = .horizontal + horizontalStackView.spacing = 16 + horizontalStackView.alignment = .top + return horizontalStackView +} + +// MARK: - SwiftUI Preview + +#if DEBUG + +import SwiftUI + +@available(iOS 14.0, *) +private struct GenericInfoBodyViewUIViewRepresentable: UIViewRepresentable { + + let body: FinancialConnectionsGenericInfoScreen.Body + + func makeUIView(context: Context) -> UIView { + return AutoResizableUIView( + contentView: GenericInfoBodyView( + body: body, + didSelectURL: { _ in } + )! + ) + } + + func updateUIView(_ uiView: UIView, context: Context) { + uiView.sizeToFit() + } +} + +@available(iOS 14.0, *) +struct GenericInfoBodyView_Previews: PreviewProvider { + static var previews: some View { + VStack { + GenericInfoBodyViewUIViewRepresentable( + body: FinancialConnectionsGenericInfoScreen.Body( + entries: [ + .text( + FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry( + id: "", + text: "Text - Alignment(nil) - Size (nil)", + alignment: nil, + size: nil + ) + ), + .text( + FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry( + id: "", + text: "Text - Alignment(left) - Size (xsmall)", + alignment: .left, + size: .xsmall + ) + ), + .text( + FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry( + id: "", + text: "Text - Alignment(center) - Size (small)", + alignment: .center, + size: .small + ) + ), + .text( + FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry( + id: "", + text: "Text - Alignment(right) - Size (medium)", + alignment: .right, + size: .medium + ) + ), + .text( + FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry( + id: "", + text: "vvv Image Item Expected Below vvv", + alignment: .center, + size: nil + ) + ), + .image( + FinancialConnectionsGenericInfoScreen.Body.ImageBodyEntry( + id: "", + image: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/BrandIcon--stripe-4x.png" + ), + alt: "" + ) + ), + .text( + FinancialConnectionsGenericInfoScreen.Body.TextBodyEntry( + id: "", + text: "^^^ Image Item Expected Above ^^^", + alignment: .center, + size: nil + ) + ), + .bullets( + FinancialConnectionsGenericInfoScreen.Body.BulletsBodyEntry( + id: "", + bullets: [ + .init( + id: "", + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--lock-primary-3x.png" + ), + title: "Bullet Title", + content: "Bullet Content" + ), + .init( + id: "String", + icon: nil, + title: "Bullet Title", + content: nil + ), + .init( + id: "", + icon: FinancialConnectionsImage( + default: "https://b.stripecdn.com/connections-statics-srv/assets/SailIcon--lock-primary-3x.png" + ), + title: nil, + content: "Stripe will allow Goldilocks to access only the [data requested](https://www.stripe.com). We never share your login details with them." + ), + ] + ) + ), + ] + ) + ) + .applyAutoResizableUIViewModifier() + .padding() + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoFooterView.swift new file mode 100644 index 00000000..825329a1 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoFooterView.swift @@ -0,0 +1,110 @@ +// +// GenericInfoFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/2/24. +// + +import Foundation +import UIKit + +func GenericInfoFooterView( + footer: FinancialConnectionsGenericInfoScreen.Footer?, + theme: FinancialConnectionsTheme, + didSelectPrimaryButton: (() -> Void)?, + didSelectSecondaryButton: (() -> Void)?, + didSelectURL: @escaping (URL) -> Void +) -> UIView? { + guard let footer else { + return nil + } + let primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration? + if let primaryCta = footer.primaryCta, let didSelectPrimaryButton { + primaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: primaryCta.label, + accessibilityIdentifier: "generic_info_primary_button", + action: didSelectPrimaryButton + ) + } else { + primaryButtonConfiguration = nil + } + let secondaryButtonConfiguration: PaneLayoutView.ButtonConfiguration? + if let secondaryCta = footer.secondaryCta, let didSelectSecondaryButton { + secondaryButtonConfiguration = PaneLayoutView.ButtonConfiguration( + title: secondaryCta.label, + accessibilityIdentifier: "generic_info_secondary_button", + action: didSelectSecondaryButton + ) + } else { + secondaryButtonConfiguration = nil + } + return PaneLayoutView.createFooterView( + primaryButtonConfiguration: primaryButtonConfiguration, + secondaryButtonConfiguration: secondaryButtonConfiguration, + topText: footer.disclaimer, + theme: theme, + bottomText: footer.belowCta, + didSelectURL: didSelectURL + ).footerView +} + +#if DEBUG + +import SwiftUI + +@available(iOS 14.0, *) +private struct GenericInfoFooterViewUIViewRepresentable: UIViewRepresentable { + + let footer: FinancialConnectionsGenericInfoScreen.Footer + + func makeUIView(context: Context) -> UIView { + GenericInfoFooterView( + footer: footer, + theme: .light, + didSelectPrimaryButton: {}, + didSelectSecondaryButton: {}, + didSelectURL: { _ in } + )! + } + + func updateUIView(_ uiView: UIView, context: Context) { + uiView.sizeToFit() + } +} + +@available(iOS 14.0, *) +struct GenericInfoFooterView_Previews: PreviewProvider { + static var previews: some View { + VStack { + GenericInfoFooterViewUIViewRepresentable( + footer: FinancialConnectionsGenericInfoScreen.Footer( + disclaimer: "Disclaimer Text", + primaryCta: FinancialConnectionsGenericInfoScreen + .Footer + .GenericInfoAction( + id: UUID().uuidString, + label: "Primary CTA", + icon: nil, + action: "primary_cta_action", + testId: nil + ), + secondaryCta: FinancialConnectionsGenericInfoScreen + .Footer + .GenericInfoAction( + id: UUID().uuidString, + label: "Secondary CTA", + icon: nil, + action: "secondary_cta_action", + testId: nil + ), + belowCta: "[Below CTA](stripe://link_here)" + ) + ) + .frame(maxHeight: 228) + .background(Color.red.opacity(0.1)) + } + .padding() + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoViewController.swift new file mode 100644 index 00000000..9a9a4b35 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/GenericInfoScreen/GenericInfoViewController.swift @@ -0,0 +1,109 @@ +// +// GenericInfoViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 7/2/24. +// + +import Foundation +import UIKit + +final class GenericInfoViewController: SheetViewController { + + private let genericInfoScreen: FinancialConnectionsGenericInfoScreen + private let theme: FinancialConnectionsTheme + private let iconView: UIView? + private let didSelectPrimaryButton: (_ genericInfoViewController: GenericInfoViewController) -> Void + private let didSelectSecondaryButton: ((_ genericInfoViewController: GenericInfoViewController) -> Void)? + private let didSelectURL: (URL) -> Void + private let willDismissSheet: (() -> Void)? + + init( + genericInfoScreen: FinancialConnectionsGenericInfoScreen, + theme: FinancialConnectionsTheme, + panePresentationStyle: PanePresentationStyle, + iconView: UIView? = nil, + didSelectPrimaryButton: @escaping (_ genericInfoViewController: GenericInfoViewController) -> Void, + didSelectSecondaryButton: ((_ genericInfoViewController: GenericInfoViewController) -> Void)? = nil, + didSelectURL: @escaping (URL) -> Void, + willDismissSheet: (() -> Void)? = nil + ) { + self.genericInfoScreen = genericInfoScreen + self.theme = theme + self.iconView = iconView + self.didSelectPrimaryButton = didSelectPrimaryButton + self.didSelectSecondaryButton = didSelectSecondaryButton + self.didSelectURL = didSelectURL + self.willDismissSheet = willDismissSheet + super.init(panePresentationStyle: panePresentationStyle) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setup( + withContentView: PaneLayoutView.createContentView( + iconView: iconView ?? { + if let imageUrl = genericInfoScreen.header?.icon?.default { + return RoundedIconView( + image: .imageUrl(imageUrl), + style: .circle, + theme: theme + ) + } else { + return nil + } + }(), + title: genericInfoScreen.header?.title, + subtitle: genericInfoScreen.header?.subtitle, + headerAlignment: { + let headerAlignment = genericInfoScreen.header?.alignment + switch headerAlignment { + case .center: + return .center + case .right: + return .trailing + case .left: fallthrough + case .unparsable: fallthrough + case .none: + return .leading + } + }(), + contentView: GenericInfoBodyView( + body: genericInfoScreen.body, + didSelectURL: didSelectURL + ), + isSheet: (panePresentationStyle == .sheet) + ), + footerView: GenericInfoFooterView( + footer: genericInfoScreen.footer, + theme: theme, + didSelectPrimaryButton: { [weak self] in + guard let self else { return } + didSelectPrimaryButton(self) + }, + didSelectSecondaryButton: { + if let didSelectSecondaryButton { + return { [weak self] in + guard let self else { return } + didSelectSecondaryButton(self) + } + } else { + return nil + } + }(), + didSelectURL: didSelectURL + ) + ) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if isBeingDismissed, let willDismissSheet { + willDismissSheet() + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/HitTestStackView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/HitTestStackView.swift new file mode 100644 index 00000000..385944ed --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/HitTestStackView.swift @@ -0,0 +1,25 @@ +// +// HitTestStackView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/16/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +// A `UIStackView` that considers the touch area +// of subviews first because the subviews might have +// increased tap area. +class HitTestStackView: UIStackView { + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + for subview in subviews { + let subviewPoint = subview.convert(point, from: self) + if subview.point(inside: subviewPoint, with: event) { + return true + } + } + return super.point(inside: point, with: event) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/HitTestView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/HitTestView.swift new file mode 100644 index 00000000..97b49e2e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/HitTestView.swift @@ -0,0 +1,25 @@ +// +// HitTestView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/16/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +// A `UIView` that considers the touch area +// of subviews first because the subviews might have +// increased tap area. +class HitTestView: UIView { + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + for subview in subviews { + let subviewPoint = subview.convert(point, from: self) + if subview.point(inside: subviewPoint, with: event) { + return true + } + } + return super.point(inside: point, with: event) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/InstitutionIconView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/InstitutionIconView.swift new file mode 100644 index 00000000..7d0c78aa --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/InstitutionIconView.swift @@ -0,0 +1,94 @@ +// +// InstitutionIconView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/27/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class InstitutionIconView: UIView { + + private lazy var institutionImageView: UIImageView = { + let iconImageView = UIImageView() + return iconImageView + }() + + init() { + super.init(frame: .zero) + let diameter: CGFloat = 56 + let cornerRadius: CGFloat = 12 + translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + widthAnchor.constraint(equalToConstant: diameter), + heightAnchor.constraint(equalToConstant: diameter), + ]) + + addAndPinSubview(institutionImageView) + institutionImageView.layer.cornerRadius = cornerRadius + institutionImageView.clipsToBounds = true + + layer.shadowColor = UIColor.textDefault.cgColor + layer.shadowOpacity = 0.3 + layer.shadowRadius = 1 + layer.shadowOffset = CGSize( + width: 0, + height: 1 + ) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setImageUrl(_ imageUrl: String?) { + institutionImageView.setImage( + with: imageUrl, + placeholder: Image.brandicon_default.makeImage() + ) + } +} + +#if DEBUG + +import SwiftUI + +private struct InstitutionIconViewUIViewRepresentable: UIViewRepresentable { + + private let institution: FinancialConnectionsInstitution = FinancialConnectionsInstitution( + id: "123", + name: "Chase", + url: nil, + icon: nil, + logo: nil + ) + + func makeUIView(context: Context) -> InstitutionIconView { + InstitutionIconView() + } + + func updateUIView(_ institutionIconView: InstitutionIconView, context: Context) { + institutionIconView.setImageUrl(institution.icon?.default) + } +} + +struct InstitutionIconView_Previews: PreviewProvider { + static var previews: some View { + VStack { + VStack(spacing: 20) { + InstitutionIconViewUIViewRepresentable() + .frame(width: 56, height: 56) + + Spacer() + } + .frame(width: 100, height: 300) + .padding() + + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/LegalDetailsNoticeViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/LegalDetailsNoticeViewController.swift new file mode 100644 index 00000000..f9af9694 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/LegalDetailsNoticeViewController.swift @@ -0,0 +1,138 @@ +// +// LegalDetailsNoticeViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/3/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class LegalDetailsNoticeViewController: SheetViewController { + + private let legalDetailsNotice: FinancialConnectionsLegalDetailsNotice + private let theme: FinancialConnectionsTheme + private let didSelectUrl: (URL) -> Void + + init( + legalDetailsNotice: FinancialConnectionsLegalDetailsNotice, + theme: FinancialConnectionsTheme, + didSelectUrl: @escaping (URL) -> Void + ) { + self.legalDetailsNotice = legalDetailsNotice + self.theme = theme + self.didSelectUrl = didSelectUrl + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setup( + withContentView: PaneLayoutView.createContentView( + iconView: RoundedIconView( + image: .imageUrl(legalDetailsNotice.icon?.default), + style: .circle, + theme: theme + ), + title: legalDetailsNotice.title, + subtitle: legalDetailsNotice.subtitle, + contentView: CreateMultiLinkView( + linkItems: legalDetailsNotice.body.links, + theme: theme, + didSelectURL: didSelectUrl + ), + isSheet: true + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: legalDetailsNotice.cta, + action: { [weak self] in + guard let self = self else { return } + self.dismiss(animated: true) + } + ), + secondaryButtonConfiguration: nil, + topText: legalDetailsNotice.disclaimer, + theme: theme, + didSelectURL: didSelectUrl + ).footerView + ) + } +} + +private func CreateMultiLinkView( + linkItems: [FinancialConnectionsLegalDetailsNotice.Body.Link], + theme: FinancialConnectionsTheme, + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let verticalStackView = HitTestStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 16 + verticalStackView.addArrangedSubview(CreateSeparatorView()) + linkItems.forEach { linkItem in + verticalStackView.addArrangedSubview( + CreateSingleLinkView( + title: linkItem.title, + content: linkItem.content, + theme: theme, + didSelectURL: didSelectURL + ) + ) + verticalStackView.addArrangedSubview(CreateSeparatorView()) + } + return verticalStackView +} + +private func CreateSingleLinkView( + title: String, + content: String?, + theme: FinancialConnectionsTheme, + didSelectURL: @escaping (URL) -> Void +) -> UIView { + let verticalLabelStackView = HitTestStackView() + verticalLabelStackView.axis = .vertical + verticalLabelStackView.spacing = 0 + + let titleLabelFont: FinancialConnectionsFont = .label(.largeEmphasized) + let titleLabel = AttributedTextView( + font: titleLabelFont, + boldFont: titleLabelFont, + linkFont: titleLabelFont, + textColor: .textDefault, + linkColor: theme.textActionColor, + showLinkUnderline: false + ) + titleLabel.setText(title, action: didSelectURL) + verticalLabelStackView.addArrangedSubview(titleLabel) + + if let content = content { + let contentFont: FinancialConnectionsFont = .label(.medium) + let contentLabel = AttributedTextView( + font: contentFont, + boldFont: contentFont, + linkFont: contentFont, + textColor: .textSubdued, + linkColor: theme.textActionColor, + showLinkUnderline: false + ) + contentLabel.setText(content, action: didSelectURL) + verticalLabelStackView.addArrangedSubview(contentLabel) + } + + return verticalLabelStackView +} + +private func CreateSeparatorView() -> UIView { + let separatorView = UIView() + separatorView.backgroundColor = .borderDefault + separatorView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + separatorView.heightAnchor.constraint(equalToConstant: 1.0 / UIScreen.main.nativeScale) + ]) + return separatorView +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPDataSource.swift new file mode 100644 index 00000000..b7d1944b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPDataSource.swift @@ -0,0 +1,114 @@ +// +// NetworkingOTPDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/28/23. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol NetworkingOTPDataSourceDelegate: AnyObject { + func networkingOTPDataSource(_ dataSource: NetworkingOTPDataSource, didUpdateConsumerSession consumerSession: ConsumerSessionData) +} + +protocol NetworkingOTPDataSource: AnyObject { + var otpType: String { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var isTestMode: Bool { get } + var theme: FinancialConnectionsTheme { get } + var pane: FinancialConnectionsSessionManifest.NextPane { get } + + func lookupConsumerSession() -> Future + func startVerificationSession() -> Future + func confirmVerificationSession(otpCode: String) -> Future +} + +final class NetworkingOTPDataSourceImplementation: NetworkingOTPDataSource { + + let otpType: String + private let emailAddress: String + private let customEmailType: String? + private let connectionsMerchantName: String? + private var consumerSession: ConsumerSessionData? { + didSet { + if let consumerSession = consumerSession { + delegate?.networkingOTPDataSource(self, didUpdateConsumerSession: consumerSession) + } + } + } + let pane: FinancialConnectionsSessionManifest.NextPane + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + let isTestMode: Bool + let theme: FinancialConnectionsTheme + weak var delegate: NetworkingOTPDataSourceDelegate? + + init( + otpType: String, + emailAddress: String, + customEmailType: String?, + connectionsMerchantName: String?, + pane: FinancialConnectionsSessionManifest.NextPane, + consumerSession: ConsumerSessionData?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient, + isTestMode: Bool, + theme: FinancialConnectionsTheme + ) { + self.otpType = otpType + self.emailAddress = emailAddress + self.customEmailType = customEmailType + self.connectionsMerchantName = connectionsMerchantName + self.pane = pane + self.consumerSession = consumerSession + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + self.isTestMode = isTestMode + self.theme = theme + } + + func lookupConsumerSession() -> Future { + apiClient + .consumerSessionLookup( + emailAddress: emailAddress, + clientSecret: clientSecret + ) + .chained { [weak self] lookupConsumerSessionResponse in + self?.consumerSession = lookupConsumerSessionResponse.consumerSession + return Promise(value: lookupConsumerSessionResponse) + } + } + + func startVerificationSession() -> Future { + guard let consumerSessionClientSecret = consumerSession?.clientSecret else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "invalid startVerificationSession call: no consumerSession.clientSecret")) + } + return apiClient.consumerSessionStartVerification( + otpType: otpType, + customEmailType: customEmailType, + connectionsMerchantName: connectionsMerchantName, + consumerSessionClientSecret: consumerSessionClientSecret + ).chained { [weak self] consumerSessionResponse in + self?.consumerSession = consumerSessionResponse.consumerSession + return Promise(value: consumerSessionResponse) + } + } + + func confirmVerificationSession(otpCode: String) -> Future { + guard let consumerSessionClientSecret = consumerSession?.clientSecret else { + return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "invalid confirmVerificationSession state: no consumerSessionClientSecret")) + } + return apiClient.consumerSessionConfirmVerification( + otpCode: otpCode, + otpType: otpType, + consumerSessionClientSecret: consumerSessionClientSecret + ).chained { [weak self] consumerSessionResponse in + self?.consumerSession = consumerSessionResponse.consumerSession + return Promise(value: consumerSessionResponse) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPView.swift new file mode 100644 index 00000000..17912a2b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPView.swift @@ -0,0 +1,292 @@ +// +// NetworkingOTPView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/28/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol NetworkingOTPViewDelegate: AnyObject { + func networkingOTPViewWillStartConsumerLookup(_ view: NetworkingOTPView) + func networkingOTPViewConsumerNotFound(_ view: NetworkingOTPView) + func networkingOTPView(_ view: NetworkingOTPView, didFailConsumerLookup error: Error) + + func networkingOTPViewWillStartVerification(_ view: NetworkingOTPView) + func networkingOTPView(_ view: NetworkingOTPView, didStartVerification consumerSession: ConsumerSessionData) + func networkingOTPView(_ view: NetworkingOTPView, didGetConsumerPublishableKey consumerPublishableKey: String) + func networkingOTPView(_ view: NetworkingOTPView, didFailToStartVerification error: Error) + + func networkingOTPViewWillConfirmVerification(_ view: NetworkingOTPView) + func networkingOTPViewDidConfirmVerification(_ view: NetworkingOTPView) + func networkingOTPView( + _ view: NetworkingOTPView, + didFailToConfirmVerification error: Error, + isTerminal: Bool + ) +} + +final class NetworkingOTPView: UIView { + + enum TestModeValues { + static let otp = "000000" + } + + private let dataSource: NetworkingOTPDataSource + weak var delegate: NetworkingOTPViewDelegate? + + private lazy var verticalStackView: UIStackView = { + let otpVerticalStackView = UIStackView() + + if dataSource.isTestMode { + let testModeBanner = TestModeAutofillBannerView( + context: .otp, + theme: dataSource.theme, + didTapAutofill: applyTestModeValue + ) + otpVerticalStackView.addArrangedSubview(testModeBanner) + } + + otpVerticalStackView.addArrangedSubview(otpTextField) + + otpVerticalStackView.axis = .vertical + otpVerticalStackView.spacing = 16 + return otpVerticalStackView + }() + private(set) lazy var otpTextField: OneTimeCodeTextField = { + let otpTextField = OneTimeCodeTextField( + configuration: OneTimeCodeTextField.Configuration( + itemSpacing: 8, + enableDigitGrouping: false, + font: UIFont.systemFont(ofSize: 28, weight: .regular), + itemCornerRadius: 12, + itemHeight: 58 + ), + theme: theme + ) + otpTextField.tintColor = dataSource.theme.primaryColor + otpTextField.addTarget(self, action: #selector(otpTextFieldDidChange), for: .valueChanged) + return otpTextField + }() + private lazy var theme: ElementsAppearance = { + var theme: ElementsAppearance = .default + theme.colors = { + var colors = ElementsAppearance.Color() + colors.border = .borderDefault + colors.componentBackground = .customBackgroundColor + colors.textFieldText = .textDefault + colors.danger = .textFeedbackCritical + return colors + }() + return theme + }() + private var lastFooterView: UIView? + + init(dataSource: NetworkingOTPDataSource) { + self.dataSource = dataSource + super.init(frame: .zero) + addAndPinSubview(verticalStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func otpTextFieldDidChange() { + showErrorText(nil) // clear the error + + if otpTextField.isComplete { + userDidEnterValidOTPCode(otpTextField.value) + } + } + + func showLoadingView(_ show: Bool) { + lastFooterView?.removeFromSuperview() + lastFooterView = nil + + if show { + let activityIndicator = ActivityIndicator(size: .medium) + activityIndicator.color = dataSource.theme.primaryColor + activityIndicator.startAnimating() + let loadingView = UIStackView( + arrangedSubviews: [activityIndicator] + ) + loadingView.isLayoutMarginsRelativeArrangement = true + loadingView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 0, + bottom: 8, + trailing: 0 + ) + self.lastFooterView = loadingView + verticalStackView.addArrangedSubview(loadingView) + } + } + + private func showErrorText(_ errorText: String?) { + lastFooterView?.removeFromSuperview() + lastFooterView = nil + + if let errorText = errorText { + let errorLabel = AttributedTextView( + font: .label(.medium), + boldFont: .label(.mediumEmphasized), + linkFont: .label(.medium), + textColor: .textFeedbackCritical, + linkColor: .textFeedbackCritical, + alignment: .center + ) + errorLabel.setText(errorText) + let errorView = UIStackView( + arrangedSubviews: [errorLabel] + ) + errorView.isLayoutMarginsRelativeArrangement = true + errorView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 0, + bottom: 0, + trailing: 0 + ) + self.lastFooterView = errorView + verticalStackView.addArrangedSubview(errorView) + } + } + + func lookupConsumerAndStartVerification() { + delegate?.networkingOTPViewWillStartConsumerLookup(self) + dataSource.lookupConsumerSession() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let lookupConsumerSessionResponse): + if lookupConsumerSessionResponse.exists { + if let consumerPublishableKey = lookupConsumerSessionResponse.publishableKey { + self.delegate?.networkingOTPView(self, didGetConsumerPublishableKey: consumerPublishableKey) + } + self.startVerification() + } else { + self.delegate?.networkingOTPViewConsumerNotFound(self) + } + case .failure(let error): + self.delegate?.networkingOTPView(self, didFailConsumerLookup: error) + } + } + } + + func startVerification() { + delegate?.networkingOTPViewWillStartVerification(self) + dataSource.startVerificationSession() + .observe { [weak self] result in + guard let self = self else { return } + switch result { + case .success(let consumerSessionResponse): + self.delegate?.networkingOTPView(self, didStartVerification: consumerSessionResponse.consumerSession) + + // call this AFTER the delegate to ensure that the delegate-handler + // adds the OTP view to the view-hierarchy + self.otpTextField.becomeFirstResponder() + case .failure(let error): + self.delegate?.networkingOTPView(self, didFailToStartVerification: error) + } + } + } + + private func userDidEnterValidOTPCode(_ otpCode: String) { + otpTextField.resignFirstResponder() + showLoadingView(true) + delegate?.networkingOTPViewWillConfirmVerification(self) + + dataSource.confirmVerificationSession(otpCode: otpCode) + .observe { [weak self] result in + guard let self = self else { return } + self.showLoadingView(false) + + switch result { + case .success: + self.delegate?.networkingOTPViewDidConfirmVerification(self) + case .failure(let error): + let isTerminal: Bool + if let errorMessage = AuthFlowHelpers.networkingOTPErrorMessage(fromError: error, otpType: self.dataSource.otpType) { + self.dataSource + .analyticsClient + .logExpectedError( + error, + errorName: "ConfirmVerificationSessionError", + pane: self.dataSource.pane + ) + + self.otpTextField.performInvalidCodeAnimation(shouldClearValue: false) + self.showErrorText(errorMessage) + isTerminal = false + } else { + self.dataSource + .analyticsClient + .logUnexpectedError( + error, + errorName: "ConfirmVerificationSessionError", + pane: self.dataSource.pane + ) + isTerminal = true + } + self.delegate?.networkingOTPView( + self, + didFailToConfirmVerification: error, + isTerminal: isTerminal + ) + } + } + } + + private func applyTestModeValue() { + otpTextField.value = TestModeValues.otp + otpTextFieldDidChange() + } +} + +#if DEBUG + +import SwiftUI + +private struct NetowrkingOTPViewRepresentable: UIViewRepresentable { + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> NetworkingOTPView { + NetworkingOTPView(dataSource: NetworkingOTPDataSourceImplementation( + otpType: "", + emailAddress: "", + customEmailType: nil, + connectionsMerchantName: nil, + pane: .networkingLinkVerification, + consumerSession: nil, + apiClient: FinancialConnectionsAPIClient(apiClient: .shared), + clientSecret: "", + analyticsClient: FinancialConnectionsAnalyticsClient(), + isTestMode: false, + theme: theme + )) + } + + func updateUIView(_ uiView: NetworkingOTPView, context: Context) { + uiView.otpTextField.value = "123" + uiView.otpTextField.becomeFirstResponder() + } +} + +struct NetowrkingOTPView_Previews: PreviewProvider { + static var previews: some View { + NetowrkingOTPViewRepresentable(theme: .light) + .frame(height: 58) + .padding() + .previewDisplayName("Light theme") + + NetowrkingOTPViewRepresentable(theme: .linkLight) + .frame(height: 58) + .padding() + .previewDisplayName("Link Light theme") + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/PaneLayoutView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/PaneLayoutView.swift new file mode 100644 index 00000000..3987ddf8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/PaneLayoutView.swift @@ -0,0 +1,232 @@ +// +// PaneLayoutView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/12/22. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +/// Reusable view that separates panes into two parts: +/// 1. A scroll view for content +/// 2. A footer that is "locked" and does not get affected by scroll view +/// +/// Purposefully NOT a `UIView` subclass because it should only be used via +/// `addToView` helper function. +final class PaneLayoutView { + + private weak var scrollViewContentView: UIView? + private let paneLayoutView: UIView + let scrollView: UIScrollView + + private var footerView: UIView? + private var footerViewBottomConstraint: NSLayoutConstraint? + private weak var presentingView: UIView? + private let keepFooterAboveKeyboard: Bool + + /// Whether or not the sheet is currently presented as a form sheet (which only happens on iPad). + /// Unfortunately, the best way to know this is to check if the sheet's width is not equal to the window's width. + private var isPresentedAsFormSheet: Bool { + guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return false } + guard let presentingView else { return false } + return window.frame.width != presentingView.frame.width + } + + /// Only move the footer view when the sheet is not presented as a form sheet. + /// Form sheet positions are already adjusted when the keyboard is shown. + private var shouldMoveFooterViewAboveKeyboard: Bool { + !isPresentedAsFormSheet + } + + /// Creates a PaneLayoutView with the provided content view and footer view. + /// In order to keep the footer view above the keyboard; + /// - Set `keepFooterAboveKeyboard: true`. + /// - Hold onto this instance of `PaneLayoutView` on the view controller presenting it. + /// This is required to prevent the keyboard observer notifications be removed. + init(contentView: UIView, footerView: UIView?, keepFooterAboveKeyboard: Bool = false) { + self.scrollViewContentView = contentView + self.footerView = footerView + self.keepFooterAboveKeyboard = keepFooterAboveKeyboard + + let scrollView = AutomaticShadowScrollView() + self.scrollView = scrollView + scrollView.addAndPinSubview(contentView) + + let verticalStackView = HitTestStackView( + arrangedSubviews: [ + scrollView + ] + ) + if let footerView = footerView { + verticalStackView.addArrangedSubview(footerView) + } + verticalStackView.spacing = 0 + verticalStackView.axis = .vertical + self.paneLayoutView = verticalStackView + } + + /// Adds this `PaneLayoutView` to the provided view. + func addTo(view: UIView) { + // This function encapsulates an error-prone sequence where we + // must add `paneLayoutView` (and all it's subviews) to the `view` + // BEFORE we can add a constraint for `UIScrollView` content + self.presentingView = view + view.addAndPinSubviewToSafeArea(paneLayoutView) + scrollViewContentView?.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor).isActive = true + + // Fit the scroll view height to be the size of the + // scroll view contents + // + // For exampple, this is needed for `SheetViewController` + // to automatically re-size the sheet to the size of contents + let scrollViewHeightConstraint = scrollView.heightAnchor.constraint( + equalTo: scrollView.contentLayoutGuide.heightAnchor) + scrollViewHeightConstraint.priority = .fittingSizeLevel + scrollViewHeightConstraint.isActive = true + + if keepFooterAboveKeyboard { + setupKeyboardObservers() + } + } + + func createView() -> UIView { + let containerView = UIView() + addTo(view: containerView) + return containerView + } + + private func setupKeyboardObservers() { + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillShow), + name: UIResponder.keyboardWillShowNotification, + object: nil + ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillHide), + name: UIResponder.keyboardWillHideNotification, + object: nil + ) + } + + @objc private func keyboardWillShow(_ notification: Notification) { + guard shouldMoveFooterViewAboveKeyboard else { return } + guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { + return + } + + // Only move the footer view if the keyboard height is above 200px + // This is to prevent false-positives of the keyboard being shown. + guard keyboardSize.height > 200 else { return } + animateAlongsideKeyboard(notification) { [weak self] in + self?.updateFooterViewConstraints(keyboardHeight: keyboardSize.height) + } + } + + @objc private func keyboardWillHide(_ notification: Notification) { + animateAlongsideKeyboard(notification) { [weak self] in + self?.updateFooterViewConstraints(keyboardHeight: 0) + } + } + + private func updateFooterViewConstraints(keyboardHeight: CGFloat) { + guard let presentingView, let footerView else { return } + let adjustedKeyboardHeight: CGFloat + if keyboardHeight > 0 { + // Removes additional padding applied to footer view when showing above the keyboard. + adjustedKeyboardHeight = keyboardHeight - (Constants.Layout.defaultVerticalPadding * 2) + } else { + adjustedKeyboardHeight = keyboardHeight + } + + if let existingConstraint = footerViewBottomConstraint { + existingConstraint.constant = -adjustedKeyboardHeight + } else { + footerViewBottomConstraint = footerView.bottomAnchor.constraint( + equalTo: presentingView.safeAreaLayoutGuide.bottomAnchor, + constant: -adjustedKeyboardHeight + ) + footerViewBottomConstraint?.isActive = true + } + paneLayoutView.layoutIfNeeded() + } + + private func animateAlongsideKeyboard( + _ notification: Notification, + animations: @escaping () -> Void + ) { + let userInfo = notification.userInfo + guard let duration = userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber else { + animations() + return + } + + let animationOption: UIView.AnimationOptions + if let keyboardAnimationCurve = userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int, + let curve = UIView.AnimationCurve(rawValue: keyboardAnimationCurve)?.rawValue { + animationOption = UIView.AnimationOptions(rawValue: ((UInt(curve << 16)))) + } else { + animationOption = .curveEaseInOut + } + + UIView.animate( + withDuration: duration.doubleValue, + delay: 0, + options: [animationOption], + animations: animations + ) + } +} + +// Automatically adds a shadow to the bottom +// if the content is scrollable +private class AutomaticShadowScrollView: UIScrollView { + + private var shadowView: UIView? + + override func layoutSubviews() { + super.layoutSubviews() + + let canScroll = contentSize.height > bounds.height + if canScroll && shadowView == nil { + let shadowView = UIView() + self.shadowView = shadowView + shadowView.layer.shadowColor = UIColor.textDefault.cgColor + shadowView.layer.shadowOpacity = 0.77 + shadowView.layer.shadowOffset = CGSize(width: 0, height: -4) + shadowView.layer.shadowRadius = 10 + // if the background color is clear, iOS will + // not draw a shadow + shadowView.backgroundColor = UIColor.customBackgroundColor + addSubview(shadowView) + } else if !canScroll { + shadowView?.removeFromSuperview() + shadowView = nil + } + + if let shadowView { + // smaller shadow width "smoothens" the shadow + // around the leading/trailing edges + let x = Constants.Layout.defaultHorizontalMargin / 2 + // move the `shadowView` to keep being at the bottom of visible bounds + shadowView.frame = CGRect( + x: x, + y: contentOffset.y + bounds.height, + width: bounds.width - (2 * x), + height: 1 + ) + + // slowly fade the `shadowView` as user scrolls to bottom + // + // the fade will only activate when we reach `startFadingDistanceToBottom` + let distanceToBottom = contentSize.height - (contentOffset.y + bounds.size.height) + let startFadingDistanceToBottom: CGFloat = 24 + let remainingFadeDistance = max(0, min(startFadingDistanceToBottom, distanceToBottom)) + shadowView.alpha = remainingFadeDistance / startFadingDistanceToBottom + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/PaneLayoutView/PaneLayoutView+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/PaneLayoutView/PaneLayoutView+Extensions.swift new file mode 100644 index 00000000..72f7eef7 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/PaneLayoutView/PaneLayoutView+Extensions.swift @@ -0,0 +1,283 @@ +// +// PaneLayoutView+Header.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 12/22/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import SwiftUI +import UIKit + +extension PaneLayoutView { + + @available(iOSApplicationExtension, unavailable) + static func createContentView( + iconView: UIView?, + title: String?, + subtitle: String?, + headerAlignment: UIStackView.Alignment = .leading, + contentView: UIView?, + isSheet: Bool = false + ) -> UIView { + let verticalStackView = UIStackView() + verticalStackView.axis = .vertical + verticalStackView.spacing = 0 + if iconView != nil || title != nil { + let headerView = createHeaderView( + iconView: iconView, + title: title, + alignment: headerAlignment, + isSheet: isSheet + ) + verticalStackView.addArrangedSubview(headerView) + } + if subtitle != nil || contentView != nil { + let bodyView = createBodyView( + text: subtitle, + contentView: contentView + ) + verticalStackView.addArrangedSubview(bodyView) + } + return verticalStackView + } + + @available(iOSApplicationExtension, unavailable) + static func createHeaderView( + iconView: UIView?, + title: String?, + alignment: UIStackView.Alignment = .leading, + isSheet: Bool = false + ) -> UIView { + let headerStackView = HitTestStackView() + headerStackView.axis = .vertical + headerStackView.spacing = 16 + headerStackView.alignment = alignment + if let iconView = iconView { + headerStackView.addArrangedSubview(iconView) + } + + if let title = title { + let titleFont: FinancialConnectionsFont = isSheet ? .heading(.large) : .heading(.extraLarge) + let titleLabel = AttributedTextView( + font: titleFont, + boldFont: titleFont, + linkFont: titleFont, + textColor: .textDefault + ) + titleLabel.setText(title) + headerStackView.addArrangedSubview(titleLabel) + } + + let paddingStackView = HitTestStackView( + arrangedSubviews: [ + headerStackView + ] + ) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: isSheet ? 0 : 16, // the sheet handle adds some padding + leading: Constants.Layout.defaultHorizontalMargin, + // if there is a subtitle in the "body/content view," + // we will add extra "8" padding + bottom: 16, + trailing: Constants.Layout.defaultHorizontalMargin + ) + return paddingStackView + } + + @available(iOSApplicationExtension, unavailable) + static func createBodyView( + text: String?, + contentView: UIView? + ) -> UIView { + let willShowDescriptionText = (text != nil) + + let paddingStackView = HitTestStackView() + paddingStackView.axis = .vertical + // add 24 spacing between the text and `contentView` + paddingStackView.spacing = willShowDescriptionText ? 24 : 0 + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + // when we don't show text, add extra 8 spacing + // to create 24 spacing between "content" and "header" + // where 16 spacing is already added in `createHeaderView` + top: willShowDescriptionText ? 0 : 8, + leading: Constants.Layout.defaultHorizontalMargin, + bottom: 8, + trailing: Constants.Layout.defaultHorizontalMargin + ) + + if let text = text { + let textLabel = AttributedTextView( + font: .body(.medium), + boldFont: .body(.mediumEmphasized), + linkFont: .body(.mediumEmphasized), + textColor: .textDefault + ) + textLabel.setText(text) + paddingStackView.addArrangedSubview(textLabel) + } + + if let contentView = contentView { + paddingStackView.addArrangedSubview(contentView) + } + + return paddingStackView + } + + struct ButtonConfiguration { + let title: String + let accessibilityIdentifier: String? + let action: () -> Void + + init( + title: String, + accessibilityIdentifier: String? = nil, + action: @escaping () -> Void + ) { + self.title = title + self.accessibilityIdentifier = accessibilityIdentifier + self.action = action + } + } + + @available(iOSApplicationExtension, unavailable) + static func createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration?, + secondaryButtonConfiguration: PaneLayoutView.ButtonConfiguration? = nil, + topText: String? = nil, + theme: FinancialConnectionsTheme, + bottomText: String? = nil, + didSelectURL: ((URL) -> Void)? = nil + ) -> (footerView: UIView?, primaryButton: StripeUICore.Button?, secondaryButton: StripeUICore.Button?) { + guard + primaryButtonConfiguration != nil || secondaryButtonConfiguration != nil + else { + return (nil, nil, nil) // display no footer + } + let footerStackView = FooterStackView( + didSelectPrimaryButton: primaryButtonConfiguration?.action, + didSelectSecondaryButton: secondaryButtonConfiguration?.action + ) + footerStackView.axis = .vertical + footerStackView.spacing = 8 + + if let topText = topText { + let topTextLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + topTextLabel.setText( + topText, + action: didSelectURL ?? { _ in } + ) + footerStackView.addArrangedSubview(topTextLabel) + footerStackView.setCustomSpacing(16, after: topTextLabel) + } + + var primaryButtonReference: StripeUICore.Button? + if let primaryButtonConfiguration = primaryButtonConfiguration { + let primaryButton = Button.primary(theme: theme) + primaryButtonReference = primaryButton + primaryButton.title = primaryButtonConfiguration.title + primaryButton.accessibilityIdentifier = primaryButtonConfiguration.accessibilityIdentifier + primaryButton.addTarget( + footerStackView, + action: #selector(FooterStackView.didSelectPrimaryButton), + for: .touchUpInside + ) + primaryButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + primaryButton.heightAnchor.constraint(equalToConstant: 56) + ]) + footerStackView.addArrangedSubview(primaryButton) + } + + var secondaryButtonReference: StripeUICore.Button? + if let secondaryButtonConfiguration = secondaryButtonConfiguration { + let secondaryButton = Button.secondary() + secondaryButtonReference = secondaryButton + secondaryButton.title = secondaryButtonConfiguration.title + secondaryButton.accessibilityIdentifier = secondaryButtonConfiguration.accessibilityIdentifier + secondaryButton.addTarget( + footerStackView, + action: #selector(FooterStackView.didSelectSecondaryButton), + for: .touchUpInside + ) + secondaryButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + secondaryButton.heightAnchor.constraint(equalToConstant: 56) + ]) + footerStackView.addArrangedSubview(secondaryButton) + } + + if let bottomText { + let bottomTextLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textDefault, + alignment: .center + ) + bottomTextLabel.setText( + bottomText, + action: didSelectURL ?? { _ in } + ) + if let lastView = footerStackView.arrangedSubviews.last { + footerStackView.setCustomSpacing(24, after: lastView) + } + footerStackView.addArrangedSubview(bottomTextLabel) + } + + let paddingStackView = HitTestStackView( + arrangedSubviews: [ + footerStackView + ] + ) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: Constants.Layout.defaultVerticalPadding, + leading: Constants.Layout.defaultHorizontalMargin, + bottom: Constants.Layout.defaultVerticalPadding, + trailing: Constants.Layout.defaultHorizontalMargin + ) + return (paddingStackView, primaryButtonReference, secondaryButtonReference) + } +} + +private class FooterStackView: UIStackView { + + private let didSelectPrimaryButtonHandler: (() -> Void)? + private let didSelectSecondaryButtonHandler: (() -> Void)? + + init( + didSelectPrimaryButton: (() -> Void)?, + didSelectSecondaryButton: (() -> Void)? + ) { + self.didSelectPrimaryButtonHandler = didSelectPrimaryButton + self.didSelectSecondaryButtonHandler = didSelectSecondaryButton + super.init(frame: .zero) + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func didSelectPrimaryButton() { + didSelectPrimaryButtonHandler?() + } + + @objc func didSelectSecondaryButton() { + didSelectSecondaryButtonHandler?() + } +} + +protocol FooterViewActions: NSObjectProtocol { + func didSelectPrimaryButton() + func didSelectSecondaryButton() +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/RoundedIconView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/RoundedIconView.swift new file mode 100644 index 00000000..f744dfed --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/RoundedIconView.swift @@ -0,0 +1,136 @@ +// +// RoundedIconView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/29/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class RoundedIconView: UIView { + + enum ImageType { + /// A local image. Images live in `/Resources/Images`, and defined in `/Source/Helpers/Image.swift`. + case image(Image) + + /// A remote image from a given URL, and an optional local image to use as a placeholder. + case imageUrl(String?, placeholder: Image? = nil) + } + + enum Style { + case rounded + case circle + } + + init(image: ImageType, style: Style, theme: FinancialConnectionsTheme) { + super.init(frame: .zero) + let diameter: CGFloat = 56 + let cornerRadius: CGFloat + switch style { + case .rounded: + cornerRadius = 12 + case .circle: + cornerRadius = diameter / 2 + } + + backgroundColor = theme.iconBackgroundColor + layer.cornerRadius = cornerRadius + + translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + widthAnchor.constraint(equalToConstant: diameter), + heightAnchor.constraint(equalToConstant: diameter), + ]) + + let iconImageView = UIImageView() + iconImageView.tintColor = theme.iconTintColor + switch image { + case .image(let image): + iconImageView.image = image.makeImage(template: true) + case .imageUrl(let imageUrl, let placeholder): + iconImageView.setImage( + with: imageUrl, + placeholder: placeholder?.makeImage(template: true), + useAlwaysTemplateRenderingMode: true + ) + } + addSubview(iconImageView) + iconImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + iconImageView.widthAnchor.constraint(equalToConstant: 20), + iconImageView.heightAnchor.constraint(equalToConstant: 20), + iconImageView.centerXAnchor.constraint(equalTo: centerXAnchor), + iconImageView.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG + +import SwiftUI + +private struct RoundedIconViewUIViewRepresentable: UIViewRepresentable { + + let image: Image + let style: RoundedIconView.Style + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> RoundedIconView { + RoundedIconView( + image: .image(image), + style: style, + theme: theme + ) + } + + func updateUIView(_ institutionIconView: RoundedIconView, context: Context) {} +} + +struct RoundedIconView_Previews: PreviewProvider { + static var previews: some View { + VStack { + VStack(spacing: 20) { + Spacer().frame(height: 30) + + RoundedIconViewUIViewRepresentable( + image: .search, + style: .rounded, + theme: .light + ) + .frame(width: 56, height: 56) + + RoundedIconViewUIViewRepresentable( + image: .search, + style: .rounded, + theme: .linkLight + ) + .frame(width: 56, height: 56) + + RoundedIconViewUIViewRepresentable( + image: .cancel_circle, + style: .circle, + theme: .light + ) + .frame(width: 56, height: 56) + + RoundedIconViewUIViewRepresentable( + image: .cancel_circle, + style: .circle, + theme: .linkLight + ) + .frame(width: 56, height: 56) + + Spacer() + } + Spacer() + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/RoundedTextField.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/RoundedTextField.swift new file mode 100644 index 00000000..6a8cc06b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/RoundedTextField.swift @@ -0,0 +1,756 @@ +// +// RoundedTextField.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/30/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +protocol RoundedTextFieldDelegate: AnyObject { + func roundedTextField( + _ textField: RoundedTextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool + func roundedTextField( + _ textField: RoundedTextField, + textDidChange text: String + ) + func roundedTextFieldUserDidPressReturnKey(_ textField: RoundedTextField) + func roundedTextFieldDidEndEditing(_ textField: RoundedTextField) +} + +final class RoundedTextField: UIView { + + private let showDoneToolbar: Bool + private let theme: FinancialConnectionsTheme + + // Used to optionally add an error message + // at the bottom of the text field + private lazy var verticalStackView: UIStackView = { + let verticalStackView = UIStackView( + arrangedSubviews: [ + containerHorizontalStackView, + ] + ) + verticalStackView.axis = .vertical + verticalStackView.spacing = 6 + return verticalStackView + }() + lazy var containerHorizontalStackView: UIStackView = { + let containerStackView = UIStackView( + arrangedSubviews: [ + textFieldContainerView + ] + ) + containerStackView.backgroundColor = .customBackgroundColor + containerStackView.axis = .horizontal + containerStackView.spacing = 12 + containerStackView.isLayoutMarginsRelativeArrangement = true + containerStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 8, + leading: 16, + bottom: 8, + trailing: 16 + ) + containerStackView.layer.cornerRadius = 12 + containerStackView.layer.shadowColor = UIColor.black.cgColor + containerStackView.layer.shadowRadius = 2 / UIScreen.main.nativeScale + containerStackView.layer.shadowOpacity = 0.1 + containerStackView.layer.shadowOffset = CGSize( + width: 0, + height: 1 / UIScreen.main.nativeScale + ) + return containerStackView + }() + lazy var textFieldContainerView: UIView = { + let textFieldStackView = UIStackView( + arrangedSubviews: [ + textField + ] + ) + return textFieldStackView + }() + private(set) lazy var textField: UITextField = { + let textField = IncreasedHitTestTextField() + textField.font = FinancialConnectionsFont.label(.large).uiFont + textField.textColor = .textDefault + textField.defaultPlaceholderColor = .textSubdued + textField.floatingPlaceholderColor = .textSubdued + textField.placeholderLabel.font = textField.font + textField.tintColor = textField.textColor + textField.delegate = self + if showDoneToolbar { + textField.inputAccessoryView = keyboardToolbar + } + textField.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + // not ideal, but height constraint fixes an odd bug + // where on landscape the text field gets compressed + textField.heightAnchor.constraint( + greaterThanOrEqualToConstant: FloatingPlaceholderTextField.LayoutConstants.defaultHeight + ), + ]) + return textField + }() + private var currentFooterView: UIView? + private lazy var keyboardToolbar: DoneButtonToolbar = { + var theme: ElementsAppearance = .default + theme.colors = { + var colors = ElementsAppearance.Color() + colors.primary = self.theme.primaryColor + colors.secondaryText = .textSubdued + return colors + }() + let keyboardToolbar = DoneButtonToolbar( + delegate: self, + showCancelButton: false, + theme: theme + ) + return keyboardToolbar + }() + + var text: String { + get { + return textField.text ?? "" + } + set { + textField.text = newValue + } + } + private var footerText: String? { + didSet { + didUpdateFooterText() + } + } + var errorText: String? { + didSet { + didUpdateFooterText() + } + } + weak var delegate: RoundedTextFieldDelegate? + + init( + placeholder: String, + footerText: String? = nil, + showDoneToolbar: Bool = false, + theme: FinancialConnectionsTheme + ) { + self.showDoneToolbar = showDoneToolbar + self.theme = theme + super.init(frame: .zero) + addAndPinSubview(verticalStackView) + textField.placeholder = placeholder + self.footerText = footerText + didUpdateFooterText() // simulate `didSet`. it not get called in `init` + updateBorder(highlighted: false) + textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } + + override func endEditing(_ force: Bool) -> Bool { + return textField.endEditing(force) + } + + private func didUpdateFooterText() { + currentFooterView?.removeFromSuperview() + currentFooterView = nil + + let footerTextLabel: UIView? + if let errorText = errorText, footerText != nil { + footerTextLabel = CreateErrorLabel(text: errorText) + } else if let errorText = errorText { + footerTextLabel = CreateErrorLabel(text: errorText) + } else if let footerText = footerText { + let footerLabel = AttributedLabel( + font: .label(.large), + textColor: .textDefault + ) + footerLabel.text = footerText + footerTextLabel = footerLabel + } else { // no text + footerTextLabel = nil + } + if let footerTextLabel = footerTextLabel { + verticalStackView.addArrangedSubview(footerTextLabel) + currentFooterView = footerTextLabel + } + + updateBorder(highlighted: textField.isFirstResponder) + } + + private func updateBorder(highlighted: Bool) { + let highlighted = textField.isFirstResponder + + if errorText != nil && !highlighted { + containerHorizontalStackView.layer.borderColor = UIColor.textFeedbackCritical.cgColor + containerHorizontalStackView.layer.borderWidth = 2.0 + } else { + if highlighted { + containerHorizontalStackView.layer.borderColor = theme.textFieldFocusedColor.cgColor + containerHorizontalStackView.layer.borderWidth = 2.0 + } else { + containerHorizontalStackView.layer.borderColor = UIColor.borderNeutral.cgColor + containerHorizontalStackView.layer.borderWidth = 1.0 + } + } + } + + @IBAction private func textFieldDidChange() { + delegate?.roundedTextField(self, textDidChange: text) + } +} + +// MARK: - UITextFieldDelegate + +extension RoundedTextField: UITextFieldDelegate { + + func textFieldDidBeginEditing(_ textField: UITextField) { + updateBorder(highlighted: true) + } + + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + return delegate?.roundedTextField( + self, + shouldChangeCharactersIn: range, + replacementString: string + ) ?? true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + delegate?.roundedTextFieldUserDidPressReturnKey(self) + + // the return value (whether true or false) seems to be a no-op + // in all practical test cases + return false + } + + func textFieldDidEndEditing(_ textField: UITextField) { + updateBorder(highlighted: false) + delegate?.roundedTextFieldDidEndEditing(self) + } +} + +// MARK: - DoneButtonToolbarDelegate + +extension RoundedTextField: DoneButtonToolbarDelegate { + func didTapDone(_ toolbar: DoneButtonToolbar) { + textField.endEditing(true) + } +} + +private func CreateErrorLabel(text: String) -> UIView { + let errorLabel = AttributedTextView( + font: .label(.small), + boldFont: .label(.smallEmphasized), + linkFont: .label(.small), + textColor: .textFeedbackCritical, + linkColor: .textFeedbackCritical + ) + errorLabel.setText(text) + return errorLabel +} + +private class IncreasedHitTestTextField: FloatingPlaceholderTextField { + // increase the area of TextField taps + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let largerBounds = bounds.insetBy(dx: -16, dy: -16) + return largerBounds.contains(point) + } +} + +private class FloatingPlaceholderTextField: UITextField { + + fileprivate struct LayoutConstants { + static let defaultHeight: CGFloat = 40 + + static let horizontalMargin: CGFloat = 0 + static let horizontalSpacing: CGFloat = 4 + + static let floatingPlaceholderScale: CGFloat = 0.75 + } + + private(set) lazy var placeholderLabel: UILabel = { + let label = UILabel() + label.textColor = defaultPlaceholderColor + label.adjustsFontForContentSizeCategory = true + return label + }() + private var lastAnimator: UIViewPropertyAnimator? + private var changingFirstResponderStatus = false + var defaultPlaceholderColor: UIColor = .textSubdued + var floatingPlaceholderColor: UIColor = .textSubdued + private var placeholderColor: UIColor { + get { + return placeholderLabel.textColor + } + set { + placeholderLabel.textColor = newValue + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + + private func setupSubviews() { + // even though the default font value for UITextFields is body, on iOS 13 at least they do not respect + // the font size settings. Resetting here fixes + font = UIFont.preferredFont(forTextStyle: .body) + adjustsFontForContentSizeCategory = true + + placeholderLabel.font = font + placeholderLabel.textAlignment = textAlignment + placeholderColor = defaultPlaceholderColor + addSubview(placeholderLabel) + } + + private func floatingPlaceholderHeight() -> CGFloat { + let placeholderLabelHeight = placeholderLabel.textRect( + forBounds: CGRect( + x: 0, + y: 0, + width: CGFloat.greatestFiniteMagnitude, + height: CGFloat.greatestFiniteMagnitude + ), + limitedToNumberOfLines: 1 + ).height + return placeholderLabelHeight + * FloatingPlaceholderTextField.LayoutConstants.floatingPlaceholderScale + } + + private func contentPadding() -> UIEdgeInsets { + + let floatingPlaceholderLabelHeight = floatingPlaceholderHeight() + let availableHeight = bounds.height + + // + // |----------------------------------------------------| + // |_______|vMargin_____________________________________| + // | | | + // |_______|floatingPlacholderLabelHeight_______________| + // | | | + // | | | availableHeight + // | | | + // |_______|textEntryHeight_____________________________| + // |_______|vMargin_____________________________________| + // + // vMargin is calculated as follows: + // + // We want the text content to be vertically centered, giving the equation: + // (floatingPlaceholderLabelHeight + textEntryHeight)/2 = availableHeight/2 + // + // We want the distance from the top to the midpoint of the floating placeholder to + // be the same as the distance from the bottom to the center of the text entry rect, + // but scaled by floatingPlaceholderScale giving: + // floatingPlaceholderScale * (textEntryHeight/2 + vMargin) = floatingPlacholderLabelHeight/2 + vMargin + // + + let vMargin = + floatingPlaceholderLabelHeight > 0 + ? max( + 0, + FloatingPlaceholderTextField.LayoutConstants.floatingPlaceholderScale + * (availableHeight - floatingPlaceholderLabelHeight + - (floatingPlaceholderLabelHeight + / FloatingPlaceholderTextField.LayoutConstants + .floatingPlaceholderScale)) + / CGFloat(2) + ) : 0 + + var leftMargin = FloatingPlaceholderTextField.LayoutConstants.horizontalMargin + if leftView != nil, + leftViewMode == .always + { + leftMargin = + leftMargin + self.leftViewRect(forBounds: bounds).width + + FloatingPlaceholderTextField.LayoutConstants.horizontalSpacing + } + + var rightMargin = FloatingPlaceholderTextField.LayoutConstants.horizontalMargin + if rightView != nil, + rightViewMode == .always + { + rightMargin = + rightMargin + self.rightViewRect(forBounds: bounds).width + + FloatingPlaceholderTextField.LayoutConstants.horizontalSpacing + } + + let isRTL = traitCollection.layoutDirection == .rightToLeft + + return UIEdgeInsets( + top: vMargin, + left: isRTL ? rightMargin : leftMargin, + bottom: vMargin, + right: isRTL ? leftMargin : rightMargin + ) + } + + private func textEntryFieldInset() -> UIEdgeInsets { + var inset = contentPadding() + if isEditing || !(text?.isEmpty ?? true) { + // contentPadding pads the top to the floating placeholder so for text + // entry we need to offset past that + let floatingPlaceholderLabelHeight = floatingPlaceholderHeight() + inset.top = inset.top + floatingPlaceholderLabelHeight + } + return inset + } + + private func textEntryFrame() -> CGRect { + return bounds.inset(by: textEntryFieldInset()) + } + + private func layoutPlaceholder(animated: Bool) { + guard !(placeholder?.isEmpty ?? true) else { + return + } + layoutIfNeeded() + + var placeholderFrame = textEntryFrame() + placeholderFrame.size.width = min( + placeholderFrame.size.width, + placeholderLabel.textRect(forBounds: placeholderFrame, limitedToNumberOfLines: 1).width + ) + if traitCollection.layoutDirection == .rightToLeft { + placeholderFrame.origin.x = textEntryFrame().maxX - placeholderFrame.width + } + var placeholderTransform = CGAffineTransform.identity + var placeholderColor: UIColor = defaultPlaceholderColor + + let minimized = isEditing || !(text?.isEmpty ?? true) + + if minimized { + let scale = FloatingPlaceholderTextField.LayoutConstants.floatingPlaceholderScale + + placeholderFrame.origin.y = self.contentPadding().top + if traitCollection.layoutDirection == .rightToLeft { + // shift origin to the right by the amount the text is compressed horizontally + placeholderFrame.origin.x = + placeholderFrame.origin.x + (1 - scale) * placeholderFrame.width + } + // scaling the width here leads to a clean up and down animation + placeholderFrame.size.width = placeholderFrame.width * scale + placeholderFrame.size.height = + placeholderLabel.textRect(forBounds: placeholderFrame, limitedToNumberOfLines: 1) + .height * scale + placeholderTransform = placeholderTransform.scaledBy(x: scale, y: scale) + placeholderColor = floatingPlaceholderColor + } + + if animated { + // Stop any in-flight animations + lastAnimator?.stopAnimation(true) + let params = UISpringTimingParameters( + mass: 1.0, + dampingRatio: 0.93, + frequencyResponse: 0.22 + ) + let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) + animator.isInterruptible = true + animator.addAnimations { + self.placeholderLabel.transform = placeholderTransform + self.placeholderLabel.frame = placeholderFrame + if !minimized { + // when we are animating back to center, change color immediately + self.placeholderColor = placeholderColor + } + } + animator.addCompletion { (_) in + if minimized { + // when animating away from center, change color at end of animation + self.placeholderColor = placeholderColor + } + } + animator.startAnimation() + self.lastAnimator = animator + } else { + placeholderLabel.transform = placeholderTransform + placeholderLabel.frame = placeholderFrame + self.placeholderColor = placeholderColor + } + } + + // MARK: - UITextField Overrides + + override var placeholder: String? { + get { + return placeholderLabel.text + } + set { + placeholderLabel.text = newValue + self.accessibilityLabel = newValue + invalidateIntrinsicContentSize() + setNeedsLayout() + } + } + + override var attributedPlaceholder: NSAttributedString? { + get { + return placeholderLabel.attributedText + } + set { + placeholderLabel.attributedText = newValue + self.accessibilityLabel = newValue?.string + invalidateIntrinsicContentSize() + setNeedsLayout() + } + } + + override var font: UIFont? { + didSet { + placeholderLabel.font = font + } + } + + override var textAlignment: NSTextAlignment { + didSet { + placeholderLabel.textAlignment = textAlignment + } + } + + override var leftViewMode: UITextField.ViewMode { + get { + return super.leftViewMode + } + set { + if newValue != .always && newValue != .never { + assert(false, "Only .always or .never are supported") + super.leftViewMode = .never + } else { + super.leftViewMode = newValue + } + } + } + + override var rightViewMode: UITextField.ViewMode { + get { + return super.rightViewMode + } + set { + if newValue != .always && newValue != .never { + assert(false, "Only .always or .never are supported") + super.rightViewMode = .never + } else { + super.rightViewMode = newValue + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + // internally, becoming first responder triggers a layout which we want to suppress + // so we can animate + if !changingFirstResponderStatus { + layoutPlaceholder(animated: false) + } + } + + override func becomeFirstResponder() -> Bool { + changingFirstResponderStatus = true + let ret = super.becomeFirstResponder() + layoutPlaceholder(animated: true) + changingFirstResponderStatus = false + return ret + } + + override func resignFirstResponder() -> Bool { + changingFirstResponderStatus = true + let ret = super.resignFirstResponder() + layoutPlaceholder(animated: true) + changingFirstResponderStatus = false + return ret + } + + override func textRect(forBounds bounds: CGRect) -> CGRect { + // N.B. The bounds passed here are not the same as self.bounds + // which is why we don't just use textEntryFrame() + return bounds.inset(by: textEntryFieldInset()) + } + + override func placeholderRect(forBounds bounds: CGRect) -> CGRect { + // N.B. The bounds passed here are not the same as self.bounds + // which is why we don't just use textEntryFrame() + return bounds.inset(by: textEntryFieldInset()) + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + // N.B. The bounds passed here are not the same as self.bounds + // which is why we don't just use textEntryFrame() + return bounds.inset(by: textEntryFieldInset()) + } + + override func leftViewRect(forBounds bounds: CGRect) -> CGRect { + var leftViewRect = super.leftViewRect(forBounds: bounds) + leftViewRect.origin.x = + leftViewRect.origin.x + FloatingPlaceholderTextField.LayoutConstants.horizontalMargin + return leftViewRect + } + + override func rightViewRect(forBounds bounds: CGRect) -> CGRect { + var rightViewRect = super.rightViewRect(forBounds: bounds) + rightViewRect.origin.x = + rightViewRect.origin.x + - FloatingPlaceholderTextField.LayoutConstants.horizontalMargin + return rightViewRect + } + + override var intrinsicContentSize: CGSize { + let height = UIFontMetrics.default.scaledValue( + for: FloatingPlaceholderTextField.LayoutConstants.defaultHeight + ) + let contentPadding = self.contentPadding() + return CGSize( + width: placeholderLabel.intrinsicContentSize.width + contentPadding.left + + contentPadding.right, + height: height + ) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + var size = super.sizeThatFits(size) + size.height = max(size.height, intrinsicContentSize.height) + size.width = max(size.width, intrinsicContentSize.width) + return size + } + + override func systemLayoutSizeFitting( + _ targetSize: CGSize, + withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, + verticalFittingPriority: UILayoutPriority + ) -> CGSize { + var size = super.systemLayoutSizeFitting( + targetSize, + withHorizontalFittingPriority: horizontalFittingPriority, + verticalFittingPriority: verticalFittingPriority + ) + size.width = max(size.width, intrinsicContentSize.width) + return size + } +} + +#if DEBUG + +import SwiftUI + +private struct RoundedTextFieldUIViewRepresentable: UIViewRepresentable { + + let placeholder: String + let footerText: String? + let errorText: String? + let isFocused: Bool + let theme: FinancialConnectionsTheme + + func makeUIView(context: Context) -> RoundedTextField { + RoundedTextField( + placeholder: placeholder, + footerText: footerText, + theme: theme + ) + } + + func updateUIView(_ uiView: RoundedTextField, context: Context) { + if isFocused { + _ = uiView.becomeFirstResponder() + } + + uiView.errorText = errorText + } +} + +struct RoundedTextField_Previews: PreviewProvider { + static var previews: some View { + if #available(iOS 14.0, *) { + VStack(spacing: 16) { + RoundedTextFieldUIViewRepresentable( + placeholder: "Routing number", + footerText: nil, + errorText: nil, + isFocused: false, + theme: .light + ) + .frame(height: 56) + RoundedTextFieldUIViewRepresentable( + placeholder: "Account number", + footerText: "Your account can be checkings or savings.", + errorText: nil, + isFocused: false, + theme: .light + ) + .frame(height: 80) + RoundedTextFieldUIViewRepresentable( + placeholder: "Confirm account number", + footerText: nil, + errorText: nil, + isFocused: false, + theme: .light + ) + .frame(height: 56) + RoundedTextFieldUIViewRepresentable( + placeholder: "Routing number", + footerText: nil, + errorText: "Routing number is required.", + isFocused: false, + theme: .light + ) + .frame(height: 80) + RoundedTextFieldUIViewRepresentable( + placeholder: "Account number", + footerText: "Your account can be checkings or savings.", + errorText: "Account number is required.", + isFocused: false, + theme: .light + ) + .frame(height: 80) + Spacer() + } + .padding() + .background(Color(UIColor.customBackgroundColor)) + + // Use separate devices to showcase highlighted state + RoundedTextFieldUIViewRepresentable( + placeholder: "Light theme", + footerText: nil, + errorText: nil, + isFocused: true, + theme: .light + ) + .frame(height: 56) + .padding() + .previewDisplayName("Focused - Light theme") + + RoundedTextFieldUIViewRepresentable( + placeholder: "Link Light theme", + footerText: nil, + errorText: nil, + isFocused: true, + theme: .linkLight + ) + .frame(height: 56) + .padding() + .previewDisplayName("Focused - Link Light theme") + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SFSafariViewController+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SFSafariViewController+Extensions.swift new file mode 100644 index 00000000..585e8b08 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SFSafariViewController+Extensions.swift @@ -0,0 +1,24 @@ +// +// SFSafariViewController+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 6/16/22. +// + +import Foundation +import SafariServices + +extension SFSafariViewController { + + static func present(url: URL) { + guard + url.scheme == "http" || url.scheme == "https", + let topMostViewController = UIViewController.topMostViewController() + else { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + return + } + let safariViewController = SFSafariViewController(url: url) + topMostViewController.present(safariViewController, animated: true, completion: nil) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SheetViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SheetViewController.swift new file mode 100644 index 00000000..1dbfe426 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SheetViewController.swift @@ -0,0 +1,584 @@ +// +// SheetViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 12/18/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +enum PanePresentationStyle { + case fullscreen + case sheet +} + +extension Notification.Name { + static let sheetViewControllerWillDismiss = Notification.Name("FinancialConnectionsSheetViewControllerWillDismiss") +} + +class SheetViewController: UIViewController { + + private static let cornerRadius: CGFloat = 20 + + // Used to toggle between sheet-specific logic and fullscreen. + // + // Due to `SheetViewController` being a subclass, and auth flow + // design constraints of dynamically presenting panes either + // as sheets or fullscreen, we need this to handle both states. + let panePresentationStyle: PanePresentationStyle + + // The `contentView` represents the area of the sheet + // where content is displayed. It's about 80% of the + // screen and does NOT contain the dark overlay at the top. + private let contentView = UIView(frame: UIScreen.main.bounds) + + // The `contentStackView` automatically resizes between the + // bottom of `contentView` and the top of `contentView`. + private lazy var contentStackView: UIStackView = { + let contentStackView = UIStackView() + contentStackView.axis = .vertical + contentStackView.spacing = 0 + contentStackView.layer.cornerRadius = Self.cornerRadius + // only round the corners of top left and top right corners + contentStackView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + contentStackView.clipsToBounds = true + if panePresentationStyle == .sheet { + contentStackView.addArrangedSubview(handleView) + } + return contentStackView + }() + + // The sheet/drawer handle at the top of the sheet + private lazy var handleView: UIView = { + let handleView = CreateCustomSheetHandleView() + handleView.backgroundColor = .customBackgroundColor + return handleView + }() + + private var contentViewMinY: CGFloat = 0 + private var performedSheetPresentationAnimation = false + private var dismissAnimationInitialSpringVelocityY: CGFloat = 0 + + private var paneViewContainerView: UIView? + private var paneView: PaneLayoutView? + private var sheetTopConstraint: NSLayoutConstraint? + + private lazy var darkAreaTapGestureRecognizer: UITapGestureRecognizer = { + let tapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(didTapDarkArea) + ) + tapGestureRecognizer.delegate = self + return tapGestureRecognizer + }() + + init(panePresentationStyle: PanePresentationStyle = .sheet) { + self.panePresentationStyle = panePresentationStyle + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = panePresentationStyle == .sheet ? .clear : .customBackgroundColor + + if panePresentationStyle == .sheet { + view.addSubview(contentView) + + contentStackView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(contentStackView) + + let sheetTopConstraint = contentStackView.topAnchor.constraint( + // keep the `contentStackView` flexible to resize + greaterThanOrEqualTo: contentView.topAnchor, + constant: 0 + ) + self.sheetTopConstraint = sheetTopConstraint + NSLayoutConstraint.activate([ + sheetTopConstraint, + contentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + contentStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + contentStackView.bottomAnchor.constraint(equalTo: contentView.safeAreaLayoutGuide.bottomAnchor), + ]) + + Self.addBottomExtensionView(toView: contentView) + + let panGestureRecognizer = UIPanGestureRecognizer( + target: self, + action: #selector(handlePanGesture(_:)) + ) + view.addGestureRecognizer(panGestureRecognizer) + + view.addGestureRecognizer(darkAreaTapGestureRecognizer) + } + // non-sheet logic + else { + view.addAndPinSubview(contentView) + contentView.addAndPinSubview(contentStackView) + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if isBeingDismissed { + NotificationCenter.default.post(name: .sheetViewControllerWillDismiss, object: self) + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if panePresentationStyle == .sheet { + // Note that using `UIDevice.current.orientation.isLandscape` + // performed worse (/ was buggy) when testing on device + let isLandscapePhone = UIDevice.current.userInterfaceIdiom == .phone && (UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + var contentViewMinY = view.window?.safeAreaInsets.top ?? 0 + // estimated iOS value of how far default sheet + // stretches beyond safeAreaInset.top + contentViewMinY += isLandscapePhone ? 0 : 10 + contentViewMinY += UINavigationController().navigationBar.bounds.height + contentViewMinY += isLandscapePhone ? 0 : 24 + let didChangeContentViewMinY = (self.contentViewMinY != contentViewMinY) + self.contentViewMinY = contentViewMinY + + // we only want `contentView.frame` to be adjusted + // if view changes (ex. first presentation or rotation) + // otherwise, there could be layout/animation glitches + if didChangeContentViewMinY { + var contentViewFrame = view.bounds + contentViewFrame.size.height -= contentViewMinY + contentViewFrame.origin.y = view.bounds.height - contentViewFrame.height + contentView.frame = contentViewFrame + + // fixes a bug where rotations wouldn't properly + // resize the sheet + sheetTopConstraint?.isActive = false + sheetTopConstraint?.isActive = true + + // animate the sheet from top to bottom + if !performedSheetPresentationAnimation { + performedSheetPresentationAnimation = true + + var initialFrame = contentViewFrame + initialFrame.origin.y += contentViewFrame.height + let finalFrame = contentViewFrame + + contentView.frame = initialFrame + UIView.animate( + withDuration: sheetAnimationDuration, + delay: 0, + options: .curveEaseOut, + animations: { + self.contentView.frame = finalFrame + }, + completion: { _ in } + ) + } + } + } else { + // non-sheet layout is handled by auto-layout + } + } + + func setup(withContentView contentView: UIView, footerView: UIView?) { + self.paneViewContainerView?.removeFromSuperview() + self.paneViewContainerView = nil + self.paneView = nil + + let paneLayoutView = PaneLayoutView(contentView: contentView, footerView: footerView) + let paneContainerView = UIView() + paneContainerView.backgroundColor = .customBackgroundColor + paneLayoutView.addTo(view: paneContainerView) + contentStackView.addArrangedSubview(paneContainerView) + + self.paneView = paneLayoutView + self.paneViewContainerView = paneContainerView + } + + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + if panePresentationStyle == .sheet { + // animate dismiss animation + UIView.animate( + withDuration: sheetAnimationDuration, + delay: 0, + usingSpringWithDamping: 1.0, + initialSpringVelocity: abs(dismissAnimationInitialSpringVelocityY)/max(1, view.bounds.height), + options: [.curveEaseOut] + ) { + self.contentView.frame = CGRect( + x: 0, + y: self.view.bounds.height, + width: self.contentView.bounds.width, + height: self.contentView.bounds.height + ) + self.removeContentViewSnapshot() + } + } + + super.dismiss(animated: flag, completion: completion) + } + + @objc private func handlePanGesture(_ recognizer: UIPanGestureRecognizer) { + let touchPoint = recognizer.location(in: view) + let translation = recognizer.translation(in: view) + let velocity = recognizer.velocity(in: view) + + if recognizer.state == .began { + addContentViewSnapshot() + } else if recognizer.state == .changed { + contentView.frame = CGRect( + x: 0, + y: { + // panning down + if translation.y > 0 { + return contentViewMinY + translation.y + } + // panning up + else { + return contentViewMinY + -self.dampenValue(abs(translation.y)) + } + }(), + width: contentView.bounds.width, + height: contentView.bounds.height + ) + self.contentViewSnapshot?.frame = self.contentView.frame + } else if recognizer.state == .ended { + + // panned above the sheet (the dark area by navigation bar) + if contentView.frame.minY < self.contentViewMinY { + UIView.animate( + withDuration: sheetAnimationDuration, + delay: 0, + options: .curveEaseOut + ) { + self.contentView.frame = CGRect( + x: 0, + y: self.contentViewMinY, + width: self.contentView.bounds.width, + height: self.contentView.bounds.height + ) + self.removeContentViewSnapshot() + } + } + // panned inside the sheet area + else { + let middleOfSheetY = view.bounds.height - (contentStackView.bounds.height / 2) + let velocityThreshold: CGFloat = 500 // fast velocity + let didPanDownHalfWay = ( + touchPoint.y > middleOfSheetY + // ensure the user is NOT quickly panning up + // (indicating that they want the sheet to be opened) + && velocity.y > -velocityThreshold + ) + let isQuicklyPanningDown = (velocity.y > velocityThreshold) + + // Sheet will be closed + if didPanDownHalfWay || isQuicklyPanningDown { + dismissAnimationInitialSpringVelocityY = velocity.y + dismiss(animated: true) + } + // Sheet will remain open + else { + UIView.animate( + withDuration: sheetAnimationDuration, + delay: 0, + usingSpringWithDamping: 0.9, + // the abs on velocity is important as + // velocity when going up is negative + initialSpringVelocity: abs(velocity.y)/max(1, view.bounds.height), + options: [.curveEaseOut] + ) { + self.contentView.frame = CGRect( + x: 0, + y: self.contentViewMinY, + width: self.contentView.bounds.width, + height: self.contentView.bounds.height + ) + self.removeContentViewSnapshot() + } + } + } + } + } + + private var contentViewSnapshot: UIView? + private func addContentViewSnapshot() { + // the `contentViewSnapshot` fixes various issues with just + // animating `contentView`: + // 1. during UIViewController dismiss animation, the UITextView + // text disappears for uknown reason + // 2. on phones with safe area at the bottom, the safe area + // reduces as one drags the sheet up, but this causes the + // sheet stack view to resize, and the sheet to not + // properly follow the finger until the distance of + // safeAreaInset.bottom is traveled + let contentViewSnapshot = contentView.snapshotView(afterScreenUpdates: false) + contentViewSnapshot?.clipsToBounds = false + contentViewSnapshot?.frame = contentView.frame + if let contentViewSnapshot = contentViewSnapshot { + // the `superview` should always be the UIViewController + // `view` but we just do it here in case that is not true + contentView.superview?.addSubview(contentViewSnapshot) + contentView.isHidden = true + Self.addBottomExtensionView(toView: contentViewSnapshot) + } + self.contentViewSnapshot = contentViewSnapshot + } + + private func removeContentViewSnapshot() { + self.contentViewSnapshot?.frame = self.contentView.frame + self.contentViewSnapshot?.removeFromSuperview() + self.contentViewSnapshot = nil + self.contentView.isHidden = false + } + + private func dampenValue( + _ value: CGFloat, + dampingFactor: CGFloat = 0.05 + ) -> CGFloat { + guard dampingFactor > 0, value >= 0 else { + return value + } + return round((1 - exp(-dampingFactor * value)) / dampingFactor) + } + + @objc private func didTapDarkArea() { + dismiss(animated: true) + } + + // Adds extra padding at the bottom of the sheet so + // there is no blank space - instead, it looks like a + // continous sheet + private static func addBottomExtensionView(toView view: UIView) { + view.clipsToBounds = false + let extensionBottomView = UIView() + extensionBottomView.backgroundColor = .customBackgroundColor + view.insertSubview(extensionBottomView, at: 0) + extensionBottomView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + extensionBottomView.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.bottomAnchor, + // if we put this at "0" there will be a "glitchy gap" + // while moving the drawer, so we set it to a higher + // value to fix this gap + // + // it needs to be smaller than the bottom padding of + // the footer view + constant: -4 + ), + extensionBottomView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + extensionBottomView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + // the value is estimated...ideally it should cover bottom safe area insets, + // and some extra to account for "pulling" the sheet up beyond the size + // of it on screen + extensionBottomView.heightAnchor.constraint(equalToConstant: 100), + ]) + } + + // MARK: - Presenting + + fileprivate let transitionDelegate = SheetTransitioningDelegate() + func present(on viewController: UIViewController) { + modalPresentationStyle = .custom + transitioningDelegate = transitionDelegate + viewController.present(self, animated: true) + } +} + +// MARK: - UIGestureRecognizerDelegate + +extension SheetViewController: UIGestureRecognizerDelegate { + + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer === darkAreaTapGestureRecognizer { + let point = gestureRecognizer.location(in: view) + let drawerTopY = view.convert(handleView.frame.origin, from: handleView.superview).y + if point.y < drawerTopY { + return true + } else { + return false + } + } else { + return true + } + } +} + +private func CreateCustomSheetHandleView() -> UIView { + let topPadding: CGFloat = 12 + let bottomPadding: CGFloat = 8 + let handleHeight: CGFloat = 4 + + let containerView = UIView() + containerView.backgroundColor = .customBackgroundColor + containerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + containerView.heightAnchor.constraint(equalToConstant: topPadding + handleHeight + bottomPadding), + ]) + + let handleView = UIView() + handleView.backgroundColor = UIColor.neutral200 + handleView.layer.cornerRadius = 2 + containerView.addSubview(handleView) + handleView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + handleView.widthAnchor.constraint(equalToConstant: 32), + handleView.heightAnchor.constraint(equalToConstant: handleHeight), + handleView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), + handleView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: topPadding), + handleView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -bottomPadding), + ]) + return containerView +} + +private class SheetTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { + + private let transitionAnimator = SheetTransitionAnimator() + + func animationController( + forPresented presented: UIViewController, + presenting: UIViewController, + source: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + transitionAnimator.isPresenting = true + return transitionAnimator + } + + func animationController( + forDismissed dismissed: UIViewController + ) -> UIViewControllerAnimatedTransitioning? { + transitionAnimator.isPresenting = false + return transitionAnimator + } +} + +private let sheetAnimationDuration: TimeInterval = 0.3 + +private class SheetTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { + + private let backgroundDimmingView = UIView() + + var isPresenting: Bool = true + + override init() { + super.init() + backgroundDimmingView.backgroundColor = .black.withAlphaComponent(0.5) + } + + func transitionDuration( + using transitionContext: UIViewControllerContextTransitioning? + ) -> TimeInterval { + return sheetAnimationDuration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard + let toViewController = transitionContext.viewController(forKey: .to), + let fromViewController = transitionContext.viewController(forKey: .from) + else { + transitionContext.completeTransition(false) + return + } + let containerView = transitionContext.containerView + + if isPresenting { + // iPad + if UIDevice.current.userInterfaceIdiom == .pad { + // the `sheetContainerView` is a + let sheetContainerView: UIView = { + // the `fromViewController` has the right frame + // (a sheet frame) so we use that to get the + // right sheet size + let origin = fromViewController.view.convert( + fromViewController.view.frame.origin, + to: fromViewController.view.window + ) + let size = fromViewController.view.frame.size + + let sheetContainerView = UIView( + frame: CGRect( + origin: origin, + size: size + ) + ) + sheetContainerView.backgroundColor = .clear + return sheetContainerView + }() + sheetContainerView.clipsToBounds = true + sheetContainerView.layer.cornerRadius = { + let defaultCornerRadius: CGFloat = 10 + let fromSuperView = fromViewController.view.superview + if (fromSuperView?.layer.cornerRadius ?? 0) > 0 { + return fromSuperView?.layer.cornerRadius ?? defaultCornerRadius + } else { + return defaultCornerRadius + } + }() as CGFloat + + // rotate if iPad rotates + containerView.addSubview(sheetContainerView) + sheetContainerView.autoresizingMask = [ + .flexibleLeftMargin, + .flexibleTopMargin, + .flexibleRightMargin, + .flexibleBottomMargin, + ] + + // WARNING: Do not use autolayout because it + // breaks the custom presentation + // when other VC is presented on top + sheetContainerView.addSubview(backgroundDimmingView) + backgroundDimmingView.frame = sheetContainerView.bounds + backgroundDimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + sheetContainerView.addSubview(toViewController.view) + toViewController.view.frame = sheetContainerView.bounds + toViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + // iPhone + else { + // WARNING: Do not use autolayout because it + // breaks the custom presentation + // when other VC is presented on top + containerView.addSubview(backgroundDimmingView) + backgroundDimmingView.frame = containerView.bounds + backgroundDimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + containerView.addSubview(toViewController.view) + toViewController.view.frame = containerView.bounds + toViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + backgroundDimmingView.alpha = 0.0 + UIView.animate( + withDuration: transitionDuration(using: transitionContext), + animations: { + self.backgroundDimmingView.alpha = 1.0 + }, + completion: { _ in + transitionContext.completeTransition(true) + } + ) + } + // dismissing the view controller + else { + backgroundDimmingView.alpha = 1.0 + UIView.animate( + withDuration: transitionDuration(using: transitionContext), + animations: { + self.backgroundDimmingView.alpha = 0.0 + }, + completion: { _ in + self.backgroundDimmingView.removeFromSuperview() + fromViewController.view.removeFromSuperview() + transitionContext.completeTransition(true) + } + ) + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/ShimmeringView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/ShimmeringView.swift new file mode 100644 index 00000000..42c8ed38 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/ShimmeringView.swift @@ -0,0 +1,40 @@ +// +// ShimmeringView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/24/24. +// + +import Foundation +import UIKit + +class ShimmeringView: UIView { + + override init(frame: CGRect) { + super.init(frame: frame) + startShimmering() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func startShimmering() { + self.alpha = 1.0 + + UIView.animate( + withDuration: 1.0, + delay: 1.0, + options: [.autoreverse, .repeat, .allowUserInteraction], + animations: { + self.alpha = 0.3 + }, + completion: nil + ) + } + + func stopShimmering() { + layer.removeAllAnimations() + self.alpha = 1.0 + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SpinnerView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SpinnerView.swift new file mode 100644 index 00000000..d9064e21 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/SpinnerView.swift @@ -0,0 +1,79 @@ +// +// SpinnerView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 1/9/24. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +final class SpinnerView: UIView { + + private let theme: FinancialConnectionsTheme? + private lazy var imageView: UIImageView = { + let imageView = UIImageView(image: Image.spinner.makeImage(template: true)) + imageView.tintColor = theme.spinnerColor + return imageView + }() + private let animationKey = "animation_key" + + init(theme: FinancialConnectionsTheme?, shouldStartAnimating: Bool = true) { + self.theme = theme + super.init(frame: .zero) + addSubview(imageView) + imageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + imageView.centerXAnchor.constraint(equalTo: centerXAnchor), + imageView.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + + if shouldStartAnimating { + startAnimating() + } + } + + func startAnimating() { + let rotatingAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotatingAnimation.byValue = 2 * Float.pi + rotatingAnimation.duration = 0.7 + rotatingAnimation.isAdditive = true + rotatingAnimation.repeatCount = .infinity + imageView.layer.add(rotatingAnimation, forKey: animationKey) + } + + func stopAnimating() { + imageView.layer.removeAnimation(forKey: animationKey) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +#if DEBUG + +import SwiftUI + +private struct SpinnerViewUIViewRepresentable: UIViewRepresentable { + let theme: FinancialConnectionsTheme? + + func makeUIView(context: Context) -> SpinnerView { + SpinnerView(theme: theme) + } + + func updateUIView(_ uiView: SpinnerView, context: Context) {} +} + +struct SpinnerView_Previews: PreviewProvider { + static var previews: some View { + VStack { + SpinnerViewUIViewRepresentable(theme: .light) + SpinnerViewUIViewRepresentable(theme: .linkLight) + SpinnerViewUIViewRepresentable(theme: nil) + } + } +} + +#endif diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/StripeSchemeAddress.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/StripeSchemeAddress.swift new file mode 100644 index 00000000..e1ea670d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/StripeSchemeAddress.swift @@ -0,0 +1,16 @@ +// +// StripeSchemeAddress.swift +// StripeFinancialConnections +// +// Created by Mat Schmid on 2024-08-07. +// + +import Foundation + +enum StripeSchemeAddress: String { + case manualEntry = "manual-entry" + case dataAccessNotice = "data-access-notice" + case legalDatailsNotice = "legal-details-notice" + case linkAccountPicker = "link-account-picker" + case linkLogin = "link-login" +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/TimeInterval+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/TimeInterval+Extensions.swift new file mode 100644 index 00000000..53b35bbb --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/TimeInterval+Extensions.swift @@ -0,0 +1,16 @@ +// +// TimeInterval+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/28/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension TimeInterval { + + var milliseconds: Int { + return Int(self * 1_000) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIImage+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIImage+Extensions.swift new file mode 100644 index 00000000..337c83ec --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIImage+Extensions.swift @@ -0,0 +1,27 @@ +// +// UIImage+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 11/17/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +extension UIImage { + + // create a new image with transparent insets around it + func withInsets(_ insets: UIEdgeInsets) -> UIImage? { + let newSize = CGSize( + width: size.width + insets.left * scale + insets.right * scale, + height: size.height + insets.top * scale + insets.bottom * scale + ) + UIGraphicsBeginImageContextWithOptions(newSize, false, scale) + let origin = CGPoint(x: insets.left * scale, y: insets.top * scale) + self.draw(at: origin) + let newImage = UIGraphicsGetImageFromCurrentImageContext()?.withRenderingMode(renderingMode) + UIGraphicsEndImageContext() + return newImage + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIImageView+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIImageView+Extensions.swift new file mode 100644 index 00000000..12e4532c --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIImageView+Extensions.swift @@ -0,0 +1,79 @@ +// +// UIImageView+Extensions.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 10/11/22. +// + +import Foundation +import UIKit + +extension UIImageView { + + func setImage( + with urlString: String?, + placeholder: UIImage? = nil, + useAlwaysTemplateRenderingMode: Bool = false, + completionHandler: ((_ didDownloadImage: Bool) -> Void)? = nil + ) { + if let placeholder = placeholder { + image = placeholder + } + + guard let urlString = urlString else { + completionHandler?(false) + return + } + + // We use `tag` to ensure that if we call `setImage(with:)` multiple times, + // we ONLY set the image from the `urlString` for the last `urlString` passed. + // + // This avoids async bugs where an older image could override a newer image. + tag = urlString.hashValue + DownloadImage(urlString: urlString) { [weak self] image in + if let image = image { + DispatchQueue.main.async { + if self?.tag == urlString.hashValue { + if useAlwaysTemplateRenderingMode { + // this ensures that if `UIImageView.tintColor` is set, + // the image will be re-colored according to `tintColor` + self?.image = image.withRenderingMode(.alwaysTemplate) + } else { + self?.image = image + } + completionHandler?(true) + } + } + } else { + DispatchQueue.main.async { + if self?.tag == urlString.hashValue { + completionHandler?(false) + } + } + } + } + } +} + +private func DownloadImage( + urlString: String, + completionHandler: @escaping (UIImage?) -> Void +) { + guard let url = URL(string: urlString) else { + completionHandler(nil) + return + } + URLSession.shared.dataTask(with: url) { data, response, _ in + guard let response = response as? HTTPURLResponse else { + assertionFailure("we always expect to get back `HTTPURLResponse`") + completionHandler(nil) + return + } + if response.statusCode == 200, let data = data, let image = UIImage(data: data) { + completionHandler(image) + } else { + completionHandler(nil) + } + } + .resume() +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UITableView+Extensions.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UITableView+Extensions.swift new file mode 100644 index 00000000..8adb7a10 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UITableView+Extensions.swift @@ -0,0 +1,31 @@ +// +// UITableView+Extensions.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +extension UITableView { + /** + Sets `tableHeaderView` of the table view by first changing the header frame size to system compressed size. + + This prevents various layout issues where we try to set a header before we have a size setup on table view. + */ + func setTableHeaderViewWithCompressedFrameSize(_ header: UIView) { + header.frame.size = header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + self.tableHeaderView = header + } + + /** + Sets `tableFooterView` of the table view by first changing the footer frame size to system compressed size. + + This prevents various layout issues where we try to set a footer before we have a size setup on table view. + */ + func setTableFooterViewWithCompressedFrameSize(_ footer: UIView) { + footer.frame.size = footer.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + self.tableFooterView = footer + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIViewController+KeyboardAvoiding.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIViewController+KeyboardAvoiding.swift new file mode 100644 index 00000000..a70d624a --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/UIViewController+KeyboardAvoiding.swift @@ -0,0 +1,179 @@ +// +// UIViewController+Stripe_KeyboardAvoiding.swift +// Stripe +// +// Created by Jack Flintermann on 4/15/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import UIKit + +typealias STPKeyboardFrameBlock = (CGRect, UIView?) -> Void +extension UIViewController { + + func stp_beginObservingKeyboardAndInsettingScrollView( + _ scrollView: UIScrollView?, + onChange block: STPKeyboardFrameBlock? + ) { + if let existing = stp_keyboardDetectingViewController() { + existing.removeFromParent() + existing.view.removeFromSuperview() + existing.didMove(toParent: nil) + } + let keyboardAvoiding = STPKeyboardDetectingViewController( + keyboardFrameBlock: block, + scrollView: scrollView + ) + addChild(keyboardAvoiding) + view.addSubview(keyboardAvoiding.view) + keyboardAvoiding.didMove(toParent: self) + } + + private func stp_keyboardDetectingViewController() -> STPKeyboardDetectingViewController? { + return + (children as NSArray).filtered( + using: NSPredicate(block: { viewController, _ in + return viewController is STPKeyboardDetectingViewController + }) + ).first as? STPKeyboardDetectingViewController + } +} + +// This is a private class that is only a UIViewController subclass by virtue of the fact +// that that makes it easier to attach to another UIViewController as a child. +class STPKeyboardDetectingViewController: UIViewController { + var lastKeyboardFrame = CGRect.zero + weak var lastResponder: UIView? + var keyboardFrameBlock: STPKeyboardFrameBlock? + weak var managedScrollView: UIScrollView? + var currentBottomInsetChange: CGFloat = 0.0 + + init( + keyboardFrameBlock block: STPKeyboardFrameBlock?, + scrollView: UIScrollView? + ) { + keyboardFrameBlock = block + super.init(nibName: nil, bundle: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillChangeFrame(_:)), + name: UIResponder.keyboardWillChangeFrameNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(textFieldWillBeginEditing(_:)), + name: UITextField.textDidBeginEditingNotification, + object: nil + ) + managedScrollView = scrollView + currentBottomInsetChange = 0 + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func loadView() { + let view = UIView() + view.backgroundColor = UIColor.clear + view.autoresizingMask = [] + self.view = view + } + + @objc func textFieldWillBeginEditing(_ notification: Notification) { + guard let textField = notification.object as? UITextField, let parentView = parent?.view, + textField.isDescendant(of: parentView) + else { + return + } + if let keyboardFrameBlock = keyboardFrameBlock, + textField != lastResponder && !lastKeyboardFrame.isEmpty + { + UIView.animate( + withDuration: 0.3, + delay: 0, + options: .curveEaseOut, + animations: { + keyboardFrameBlock(self.lastKeyboardFrame, textField) + } + ) + } + } + + @objc func keyboardWillChangeFrame(_ notification: Notification) { + // As of iOS 8, this all takes place inside the necessary animation block + // https://twitter.com/SmileyKeith/status/684100833823174656 + guard + var keyboardFrame = + (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)? + .cgRectValue, + let window = view.window + else { + return + } + keyboardFrame = window.convert(keyboardFrame, from: nil) + + if managedScrollView != nil { + if !lastKeyboardFrame.equalTo(keyboardFrame) { + let responder = parent?.view.stp_findFirstResponder() + lastResponder = responder + doKeyboardChangeAnimation(withNewFrame: keyboardFrame) + } + } + } + + func doKeyboardChangeAnimation(withNewFrame keyboardFrame: CGRect) { + lastKeyboardFrame = keyboardFrame + + if let managedScrollView = managedScrollView { + let scrollView = managedScrollView + let scrollViewSuperView = managedScrollView.superview + + var contentInsets = scrollView.contentInset + var scrollIndicatorInsets: UIEdgeInsets = .zero + #if !TARGET_OS_MACCATALYST + scrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets + #else + scrollIndicatorInsets = scrollView.scrollIndicatorInsets + #endif + + let windowFrame = scrollViewSuperView?.convert( + scrollViewSuperView?.frame ?? CGRect.zero, + to: nil + ) + + let bottomIntersection = windowFrame?.intersection(keyboardFrame) + let bottomInsetDelta = + (bottomIntersection?.size.height ?? 0.0) - currentBottomInsetChange + contentInsets.bottom += bottomInsetDelta + scrollIndicatorInsets.bottom += bottomInsetDelta + currentBottomInsetChange += bottomInsetDelta + scrollView.contentInset = contentInsets + scrollView.scrollIndicatorInsets = scrollIndicatorInsets + } + + if let keyboardFrameBlock = keyboardFrameBlock { + keyboardFrameBlock(keyboardFrame, lastResponder) + } + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} + +private extension UIView { + func stp_findFirstResponder() -> UIView? { + if isFirstResponder { + return self + } + for subView in subviews { + let responder = subView.stp_findFirstResponder() + if let responder = responder { + return responder + } + } + return nil + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessDataSource.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessDataSource.swift new file mode 100644 index 00000000..77d530d1 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessDataSource.swift @@ -0,0 +1,55 @@ +// +// SuccessDataSource.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/12/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol SuccessDataSource: AnyObject { + + var manifest: FinancialConnectionsSessionManifest { get } + var linkedAccountsCount: Int { get } + var saveToLinkWithStripeSucceeded: Bool? { get } + var analyticsClient: FinancialConnectionsAnalyticsClient { get } + var showLinkMoreAccountsButton: Bool { get } + var customSuccessPaneCaption: String? { get } + var customSuccessPaneSubCaption: String? { get } +} + +final class SuccessDataSourceImplementation: SuccessDataSource { + + let manifest: FinancialConnectionsSessionManifest + let linkedAccountsCount: Int + let saveToLinkWithStripeSucceeded: Bool? + private let apiClient: FinancialConnectionsAPIClient + private let clientSecret: String + let analyticsClient: FinancialConnectionsAnalyticsClient + var customSuccessPaneCaption: String? + var customSuccessPaneSubCaption: String? + var showLinkMoreAccountsButton: Bool { + !manifest.singleAccount && !manifest.disableLinkMoreAccounts && !(manifest.isNetworkingUserFlow ?? false) + } + + init( + manifest: FinancialConnectionsSessionManifest, + linkedAccountsCount: Int, + saveToLinkWithStripeSucceeded: Bool?, + apiClient: FinancialConnectionsAPIClient, + clientSecret: String, + analyticsClient: FinancialConnectionsAnalyticsClient, + customSuccessPaneCaption: String?, + customSuccessPaneSubCaption: String? + ) { + self.manifest = manifest + self.linkedAccountsCount = linkedAccountsCount + self.saveToLinkWithStripeSucceeded = saveToLinkWithStripeSucceeded + self.apiClient = apiClient + self.clientSecret = clientSecret + self.analyticsClient = analyticsClient + self.customSuccessPaneCaption = customSuccessPaneCaption + self.customSuccessPaneSubCaption = customSuccessPaneSubCaption + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessFooterView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessFooterView.swift new file mode 100644 index 00000000..c63b31a0 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessFooterView.swift @@ -0,0 +1,62 @@ +// +// SuccessFooterView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/15/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class SuccessFooterView: UIView { + + private let theme: FinancialConnectionsTheme + private let didSelectDone: (SuccessFooterView) -> Void + + private lazy var doneButton: Button = { + let doneButton = Button.primary(theme: theme) + doneButton.title = "Done" // TODO: replace with UIButton.doneButtonTitle once the SDK is localized + doneButton.addTarget(self, action: #selector(didSelectDoneButton), for: .touchUpInside) + doneButton.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + doneButton.heightAnchor.constraint(equalToConstant: 56) + ]) + doneButton.accessibilityIdentifier = "success_done_button" + return doneButton + }() + + init( + theme: FinancialConnectionsTheme, + didSelectDone: @escaping (SuccessFooterView) -> Void + ) { + self.theme = theme + self.didSelectDone = didSelectDone + super.init(frame: .zero) + + let paddingStackView = UIStackView() + paddingStackView.axis = .vertical + paddingStackView.addArrangedSubview(doneButton) + paddingStackView.isLayoutMarginsRelativeArrangement = true + paddingStackView.directionalLayoutMargins = NSDirectionalEdgeInsets( + top: 16, + leading: 24, + bottom: 16, + trailing: 24 + ) + addAndPinSubviewToSafeArea(paddingStackView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func didSelectDoneButton() { + didSelectDone(self) + } + + func setIsLoading(_ isLoading: Bool) { + doneButton.isLoading = isLoading + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessViewController.swift new file mode 100644 index 00000000..94255328 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/Success/SuccessViewController.swift @@ -0,0 +1,239 @@ +// +// SuccessViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 8/12/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol SuccessViewControllerDelegate: AnyObject { + func successViewControllerDidSelectDone(_ viewController: SuccessViewController) +} + +final class SuccessViewController: UIViewController { + + private let dataSource: SuccessDataSource + weak var delegate: SuccessViewControllerDelegate? + + init(dataSource: SuccessDataSource) { + self.dataSource = dataSource + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + navigationItem.hidesBackButton = true + + let showSaveToLinkFailedNotice = (dataSource.saveToLinkWithStripeSucceeded == false) + + let contentView = UIView() + view.addSubview(contentView) + + let bodyView = CreateBodyView( + title: dataSource.customSuccessPaneCaption ?? STPLocalizedString( + "Success", + "The title of the success screen that appears when a user is done with the process of connecting their bank account to an application. Now that the bank account is connected (or linked), the user will be able to use the bank account for payments." + ), + subtitle: dataSource.customSuccessPaneSubCaption ?? CreateSubtitleText( + // manual entry has "0" linked accounts count + isLinkingOneAccount: (dataSource.linkedAccountsCount == 0 || dataSource.linkedAccountsCount == 1), + showSaveToLinkFailedNotice: showSaveToLinkFailedNotice + ), + theme: dataSource.manifest.theme + ) + contentView.addSubview(bodyView) + + let footerView = SuccessFooterView( + theme: dataSource.manifest.theme, + didSelectDone: { [weak self] footerView in + guard let self = self else { return } + // we NEVER set isLoading to `false` because + // we will always close the Auth Flow + footerView.setIsLoading(true) + self.dataSource + .analyticsClient + .log( + eventName: "click.done", + pane: .success + ) + self.delegate?.successViewControllerDidSelectDone(self) + } + ) + view.addSubview(footerView) + + contentView.translatesAutoresizingMaskIntoConstraints = false + bodyView.translatesAutoresizingMaskIntoConstraints = false + footerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + // constraint `contentView` to the top of `view` and top of `footerView` + contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + contentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 24), + contentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -24), + contentView.bottomAnchor.constraint(equalTo: footerView.topAnchor), + + // constraint `footerView` to the bottom of `view` and bottom of `contentView` + footerView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + footerView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + footerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + + // constraint `bodyView` to the center of `contentView` + bodyView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + bodyView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + // we assume `bodyView` will never be larger than `contentView` + // available space - as is in designs today + bodyView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + ]) + + dataSource + .analyticsClient + .logPaneLoaded(pane: .success) + + if showSaveToLinkFailedNotice { + dataSource + .analyticsClient + .log( + eventName: "networking.save_to_link_failed_notice", + pane: .success + ) + } + } + + private var didFireFeedbackGenerator = false + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if !didFireFeedbackGenerator { + didFireFeedbackGenerator = true + FeedbackGeneratorAdapter.successOccurred() + } + } +} + +private func CreateBodyView( + title: String, + subtitle: String?, + theme: FinancialConnectionsTheme +) -> UIView { + let titleLabel = AttributedLabel( + font: .heading(.extraLarge), + textColor: .textDefault + ) + titleLabel.setText(title) + let labelVerticalStackView = UIStackView( + arrangedSubviews: [titleLabel] + ) + labelVerticalStackView.axis = .vertical + labelVerticalStackView.spacing = 8 + labelVerticalStackView.alignment = .center + + if let subtitle = subtitle { + let subtitleLabel = AttributedTextView( + font: .body(.medium), + boldFont: .body(.mediumEmphasized), + linkFont: .body(.medium), + textColor: .textDefault, + alignment: .center + ) + subtitleLabel.setText(subtitle) + labelVerticalStackView.addArrangedSubview(subtitleLabel) + } + + let bodyVerticalStackView = UIStackView( + arrangedSubviews: [ + CreateIconView(theme: theme), + labelVerticalStackView, + ] + ) + bodyVerticalStackView.axis = .vertical + bodyVerticalStackView.spacing = 16 + bodyVerticalStackView.alignment = .center + + // animate the whole view body to move down-to-up, and appear + bodyVerticalStackView.alpha = 0.0 + bodyVerticalStackView.transform = CGAffineTransform(translationX: 0, y: 37) + UIView.animate( + withDuration: 0.35, + delay: 0.0, + options: [.curveEaseOut], + animations: { + bodyVerticalStackView.alpha = 1.0 + bodyVerticalStackView.transform = .identity + } + ) + return bodyVerticalStackView +} + +private func CreateIconView(theme: FinancialConnectionsTheme) -> UIView { + let iconContainerView = UIView() + iconContainerView.backgroundColor = theme.primaryColor + let iconRadius: CGFloat = 56 + iconContainerView.layer.cornerRadius = iconRadius/2 + iconContainerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + iconContainerView.widthAnchor.constraint(equalToConstant: iconRadius), + iconContainerView.heightAnchor.constraint(equalToConstant: iconRadius), + ]) + + let iconImageView = UIImageView() + iconImageView.image = Image.check.makeImage().withTintColor(theme.primaryAccentColor) + iconContainerView.addSubview(iconImageView) + iconImageView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + iconImageView.widthAnchor.constraint(equalToConstant: 20), + iconImageView.heightAnchor.constraint(equalToConstant: 20), + iconImageView.centerXAnchor.constraint(equalTo: iconContainerView.centerXAnchor), + iconImageView.centerYAnchor.constraint(equalTo: iconContainerView.centerYAnchor), + ]) + + // animate the checkmark icon to bounce/appear + iconImageView.alpha = 0 + iconImageView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) + UIView.animate( + withDuration: 0.35, + delay: 0.35, + usingSpringWithDamping: 0.45, + initialSpringVelocity: 0.1, + animations: { + iconImageView.alpha = 1.0 + iconImageView.transform = .identity + } + ) + return iconContainerView +} + +private func CreateSubtitleText( + isLinkingOneAccount: Bool, + showSaveToLinkFailedNotice: Bool +) -> String { + if showSaveToLinkFailedNotice { + if isLinkingOneAccount { + return STPLocalizedString( + "Your account was connected, but couldn't be saved to Link.", + "The subtitle/description of the success screen that appears when a user is done with the process of connecting their bank account to an application. Now that the bank account is connected, the user will be able to use the bank account for payments." + ) + } else { // multiple bank accounts + return STPLocalizedString( + "Your accounts were connected, but couldn't be saved to Link.", + "The subtitle/description of the success screen that appears when a user is done with the process of connecting their bank account to an application. Now that the bank account is connected, the user will be able to use the bank account for payments." + ) + } + } else if isLinkingOneAccount { + return STPLocalizedString( + "Your account was connected.", + "The subtitle/description of the success screen that appears when a user is done with the process of connecting their bank account to an application. Now that the bank account is connected (or linked), the user will be able to use the bank account for payments." + ) + } else { // multiple bank accounts + return STPLocalizedString( + "Your accounts were connected.", + "The subtitle/description of the success screen that appears when a user is done with the process of connecting their bank accounts to an application. Now that the bank accounts are connected (or linked), the user will be able to use those bank accounts for payments." + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/TerminalError/TerminalErrorView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/TerminalError/TerminalErrorView.swift new file mode 100644 index 00000000..cd4afefd --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/TerminalError/TerminalErrorView.swift @@ -0,0 +1,65 @@ +// +// TerminalErrorView.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 2/7/24. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +func TerminalErrorView( + allowManualEntry: Bool, + theme: FinancialConnectionsTheme, + didSelectManualEntry: @escaping () -> Void, + didSelectClose: @escaping () -> Void +) -> UIView { + return PaneLayoutView( + contentView: PaneLayoutView.createContentView( + iconView: RoundedIconView( + image: .image(.warning_triangle), + style: .circle, + theme: theme + ), + title: STPLocalizedString( + "Something went wrong", + "Title of a screen that shows an error. The error screen appears after user has selected a bank. The error is a generic one: something wrong happened and we are not sure what." + ), + subtitle: { + if allowManualEntry { + return STPLocalizedString( + "Your account can’t be connected at this time. Please enter your bank details manually or try again later.", + "The subtitle/description of a screen that shows an error. The error is generic: something wrong happened and we are not sure what." + ) + } else { + return STPLocalizedString( + "Your account can’t be connected at this time. Please try again later.", + "The subtitle/description of a screen that shows an error. The error is generic: something wrong happened and we are not sure what." + ) + } + }(), + contentView: nil + ), + footerView: PaneLayoutView.createFooterView( + primaryButtonConfiguration: { + if allowManualEntry { + return PaneLayoutView.ButtonConfiguration( + title: String.Localized.enter_bank_details_manually, + action: { + didSelectManualEntry() + } + ) + } else { + return PaneLayoutView.ButtonConfiguration( + title: "Close", // TODO: once we localize use String.Localized.close + action: { + didSelectClose() + } + ) + } + }(), + theme: theme + ).footerView + ).createView() +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/TerminalError/TerminalErrorViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/TerminalError/TerminalErrorViewController.swift new file mode 100644 index 00000000..d6a328a0 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/TerminalError/TerminalErrorViewController.swift @@ -0,0 +1,55 @@ +// +// TerminalErrorViewController.swift +// StripeFinancialConnections +// +// Created by Krisjanis Gaidis on 9/15/22. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol TerminalErrorViewControllerDelegate: AnyObject { + func terminalErrorViewController(_ viewController: TerminalErrorViewController, didCloseWithError error: Error) + func terminalErrorViewControllerDidSelectManualEntry(_ viewController: TerminalErrorViewController) +} + +final class TerminalErrorViewController: UIViewController { + + private let error: Error + private let allowManualEntry: Bool + private let theme: FinancialConnectionsTheme + weak var delegate: TerminalErrorViewControllerDelegate? + + init(error: Error, allowManualEntry: Bool, theme: FinancialConnectionsTheme) { + self.error = error + self.allowManualEntry = allowManualEntry + self.theme = theme + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .customBackgroundColor + navigationItem.hidesBackButton = true + + let terminalErrorView = TerminalErrorView( + allowManualEntry: allowManualEntry, + theme: theme, + didSelectManualEntry: { [weak self] in + guard let self = self else { return } + self.delegate?.terminalErrorViewControllerDidSelectManualEntry(self) + }, + didSelectClose: { [weak self] in + guard let self = self else { return } + self.delegate?.terminalErrorViewController(self, didCloseWithError: self.error) + } + ) + view.addAndPinSubview(terminalErrorView) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/StripeCore+Import.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/StripeCore+Import.swift new file mode 100644 index 00000000..969a1ebc --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/StripeCore+Import.swift @@ -0,0 +1,9 @@ +// +// StripeCore+Import.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/3/21. +// + +import Foundation +@_exported import StripeCore diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Web/AuthenticationSessionManager.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/AuthenticationSessionManager.swift new file mode 100644 index 00000000..7554aa9e --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/AuthenticationSessionManager.swift @@ -0,0 +1,157 @@ +// +// AuthenticationSessionManager.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/3/21. +// + +import AuthenticationServices +@_spi(STP) import StripeCore +import UIKit + +final class AuthenticationSessionManager: NSObject { + + // MARK: - Types + + enum Result { + case success(returnUrl: URL) + case webCancelled + case nativeCancelled + case redirect(url: URL) + } + + // MARK: - Properties + + private var authSession: ASWebAuthenticationSession? + private let manifest: FinancialConnectionsSessionManifest + private var window: UIWindow? + + // MARK: - Init + + init(manifest: FinancialConnectionsSessionManifest, window: UIWindow?) { + self.manifest = manifest + self.window = window + } + + // MARK: - Public + + func start(additionalQueryParameters: String?) -> Promise { + let promise = Promise() + + guard let hostedAuthUrl = manifest.hostedAuthUrl else { + promise.reject(with: FinancialConnectionsSheetError.unknown(debugDescription: "NULL `hostedAuthUrl`")) + return promise + } + + let queryParameters = additionalQueryParameters ?? "" + let urlString = hostedAuthUrl + queryParameters + + guard let url = URL(string: urlString) else { + promise.reject(with: FinancialConnectionsSheetError.unknown(debugDescription: "Malformed hosted auth URL")) + return promise + } + + guard let successUrl = manifest.successUrl else { + promise.reject(with: FinancialConnectionsSheetError.unknown(debugDescription: "NULL `successUrl`")) + return promise + } + + let authSession = ASWebAuthenticationSession( + url: url, + callbackURLScheme: URL(string: successUrl)?.scheme, + completionHandler: { [weak self] returnUrl, error in + guard let self = self else { return } + if let error = error { + if let authenticationSessionError = error as? ASWebAuthenticationSessionError { + switch authenticationSessionError.code { + case .canceledLogin: + promise.resolve(with: .nativeCancelled) + default: + promise.reject(with: authenticationSessionError) + } + } else { + promise.reject(with: error) + } + return + } + guard let returnUrl = returnUrl else { + promise.reject(with: FinancialConnectionsSheetError.unknown(debugDescription: "Missing return URL")) + return + } + let returnUrlString = returnUrl.absoluteString + + // `matchesSchemeHostAndPath` is necessary for instant debits which + // contains additional query parameters at the end of the `successUrl` + if returnUrl.matchesSchemeHostAndPath(of: URL(string: self.manifest.successUrl ?? "")) { + promise.resolve(with: .success(returnUrl: returnUrl)) + } else if returnUrl.matchesSchemeHostAndPath(of: URL(string: self.manifest.cancelUrl ?? "")) { + promise.resolve(with: .webCancelled) + } else if returnUrlString.hasNativeRedirectPrefix, + let targetURL = URL(string: returnUrlString.droppingNativeRedirectPrefix()) + { + promise.resolve(with: .redirect(url: targetURL)) + } else { + promise.reject(with: FinancialConnectionsSheetError.unknown(debugDescription: "Nil return URL")) + } + } + ) + authSession.presentationContextProvider = self + authSession.prefersEphemeralWebBrowserSession = true + + self.authSession = authSession + if #available(iOS 13.4, *) { + if !authSession.canStart { + promise.reject( + with: FinancialConnectionsSheetError.unknown(debugDescription: "Failed to start session") + ) + return promise + } + } + /** + This terribly hacky animation disabling is needed to control the presentation of ASWebAuthenticationSession underlying view controller. + Since we present a modal already that itself presents ASWebAuthenticationSession, the double modal animation is jarring and a bad UX. + We disable animations for a second. Sometimes there is a delay in creating the ASWebAuthenticationSession underlying view controller + to be safe, I made the delay a full second. I didn't find a good way to make this approach less clowny. + PresentedViewController is not KVO compliant and the notifications sent by presentation view controller that could help with knowing when + ASWebAuthenticationSession underlying view controller finished presenting are considered private API. + */ + let animationsEnabledOriginalValue = UIView.areAnimationsEnabled + UIView.setAnimationsEnabled(false) + + if !authSession.start() { + UIView.setAnimationsEnabled(animationsEnabledOriginalValue) + promise.reject(with: FinancialConnectionsSheetError.unknown(debugDescription: "Failed to start session")) + return promise + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + UIView.setAnimationsEnabled(animationsEnabledOriginalValue) + } + + return promise + } +} + +// MARK: - ASWebAuthenticationPresentationContextProviding + +/// :nodoc: + +extension AuthenticationSessionManager: ASWebAuthenticationPresentationContextProviding { + + func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return self.window ?? ASPresentationAnchor() + } +} + +extension URL { + fileprivate func matchesSchemeHostAndPath(of otherURL: URL?) -> Bool { + guard let otherURL = otherURL else { + return false + } + return ( + self.scheme == otherURL.scheme && + self.host == otherURL.host && + self.path == otherURL.path + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Web/ContinueStateView.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/ContinueStateView.swift new file mode 100644 index 00000000..1f521f64 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/ContinueStateView.swift @@ -0,0 +1,71 @@ +// +// ContinueStateView.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 10/5/22. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +final class ContinueStateViews { + + let contentView: UIView + private var primaryButton: StripeUICore.Button? + private var secondaryButton: StripeUICore.Button? + let footerView: UIView? + + init( + institutionImageUrl: String?, + theme: FinancialConnectionsTheme, + didSelectContinue: @escaping () -> Void, + didSelectCancel: (() -> Void)? = nil + ) { + self.contentView = PaneLayoutView.createContentView( + iconView: { + if let institutionImageUrl { + let institutionIconView = InstitutionIconView() + institutionIconView.setImageUrl(institutionImageUrl) + return institutionIconView + } else { + return nil + } + }(), + title: STPLocalizedString( + "Continue linking your account", + "Title for a label of a screen telling users to tap below to continue linking process." + ), + subtitle: STPLocalizedString( + "You haven't finished linking your account. Press continue to finish the process.", + "Title for a label explaining that the linking process hasn't finished yet." + ), + contentView: nil + ) + let footerViewTuple = PaneLayoutView.createFooterView( + primaryButtonConfiguration: PaneLayoutView.ButtonConfiguration( + title: "Continue", // TODO: when Financial Connections starts supporting localization, change this to `String.Localized.continue`, + action: didSelectContinue + ), + secondaryButtonConfiguration: { + if let didSelectCancel { + return PaneLayoutView.ButtonConfiguration( + title: "Cancel", // TODO: when Financial Connections starts supporting localization, change this to `String.Localized.cancel` + action: didSelectCancel + ) + } else { + return nil + } + }(), + theme: theme + ) + self.footerView = footerViewTuple.footerView + self.primaryButton = footerViewTuple.primaryButton + self.secondaryButton = footerViewTuple.secondaryButton + } + + func showLoadingView(_ show: Bool) { + primaryButton?.isLoading = show + secondaryButton?.isEnabled = !show + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsAccountFetcher.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsAccountFetcher.swift new file mode 100644 index 00000000..e1fd4ace --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsAccountFetcher.swift @@ -0,0 +1,69 @@ +// +// FinancialConnectionsAccountFetcher.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/30/21. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol FinancialConnectionsAccountFetcher { + func fetchAccounts( + initial: [StripeAPI.FinancialConnectionsAccount] + ) -> Future<[StripeAPI.FinancialConnectionsAccount]> +} + +class FinancialConnectionsAccountAPIFetcher: FinancialConnectionsAccountFetcher { + + // MARK: - Properties + + private let api: FinancialConnectionsAPI + private let clientSecret: String + + // MARK: - Init + + init(api: FinancialConnectionsAPI, clientSecret: String) { + self.api = api + self.clientSecret = clientSecret + } + + // MARK: - FinancialConnectionsAccountFetcher + + func fetchAccounts(initial: [StripeAPI.FinancialConnectionsAccount]) -> Future< + [StripeAPI.FinancialConnectionsAccount] + > { + return fetchAccounts(resultsSoFar: initial) + } +} + +// MARK: - Helpers + +extension FinancialConnectionsAccountAPIFetcher { + + private func fetchAccounts( + resultsSoFar: [StripeAPI.FinancialConnectionsAccount] + ) -> Future<[StripeAPI.FinancialConnectionsAccount]> { + let lastId = resultsSoFar.last?.id + let promise = api.fetchFinancialConnectionsAccounts( + clientSecret: clientSecret, + startingAfterAccountId: lastId + ) + return promise.chained { list in + let combinedResults = resultsSoFar + list.data + guard list.hasMore, combinedResults.count < Constants.maxAccountLimit else { + return Promise(value: combinedResults) + } + return self.fetchAccounts(resultsSoFar: combinedResults) + } + + } +} + +// MARK: - Constants + +extension FinancialConnectionsAccountAPIFetcher { + private enum Constants { + static let maxAccountLimit = 100 + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsSessionFetcher.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsSessionFetcher.swift new file mode 100644 index 00000000..c3deccb6 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsSessionFetcher.swift @@ -0,0 +1,68 @@ +// +// FinancialConnectionsSessionFetcher.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 1/20/22. +// + +import Foundation +@_spi(STP) import StripeCore + +protocol FinancialConnectionsSessionFetcher { + func fetchSession() -> Future +} + +class FinancialConnectionsSessionAPIFetcher: FinancialConnectionsSessionFetcher { + + // MARK: - Properties + + private let api: FinancialConnectionsAPI + private let clientSecret: String + private let accountFetcher: FinancialConnectionsAccountFetcher + + // MARK: - Init + + init( + api: FinancialConnectionsAPI, + clientSecret: String, + accountFetcher: FinancialConnectionsAccountFetcher + ) { + self.api = api + self.clientSecret = clientSecret + self.accountFetcher = accountFetcher + } + + // MARK: - AccountFetcher + + func fetchSession() -> Future { + api.fetchFinancialConnectionsSession(clientSecret: clientSecret).chained { [weak self] session in + guard session.accounts.hasMore, let self = self else { + return Promise(value: session) + } + + return self.accountFetcher + .fetchAccounts(initial: session.accounts.data) + .chained { fullAccountList in + /** + Here we create a synthetic FinancialConnectionsSession object with full account list. + */ + let fullList = StripeAPI.FinancialConnectionsSession.AccountList( + data: fullAccountList, + hasMore: false + ) + let sessionWithFullAccountList = StripeAPI.FinancialConnectionsSession( + clientSecret: session.clientSecret, + id: session.id, + accounts: fullList, + livemode: session.livemode, + paymentAccount: session.paymentAccount, + bankAccountToken: session.bankAccountToken, + status: session.status, + statusDetails: session.statusDetails + ) + return Promise(value: sessionWithFullAccountList) + } + } + } + +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsWebFlowViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsWebFlowViewController.swift new file mode 100644 index 00000000..f0d2ae99 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Web/FinancialConnectionsWebFlowViewController.swift @@ -0,0 +1,521 @@ +// +// FinancialConnectionsWebFlowViewController.swift +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 12/1/21. +// + +import CoreMedia +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +protocol FinancialConnectionsWebFlowViewControllerDelegate: AnyObject { + + func webFlowViewController( + _ viewController: FinancialConnectionsWebFlowViewController, + didFinish result: HostControllerResult + ) + + func webFlowViewController( + _ webFlowViewController: UIViewController, + didReceiveEvent event: FinancialConnectionsEvent + ) +} + +final class FinancialConnectionsWebFlowViewController: UIViewController { + + // MARK: - Properties + + weak var delegate: FinancialConnectionsWebFlowViewControllerDelegate? + + private var authSessionManager: AuthenticationSessionManager? + private var fetchSessionError: Error? + + // MARK: - Waiting state view + + private lazy var continueStateView: UIView = { + let continueStateViews = ContinueStateViews( + institutionImageUrl: nil, + theme: manifest.theme, + didSelectContinue: { [weak self] in + guard let self else { return } + if let url = self.lastOpenedNativeURL { + self.redirect(to: url) + } else { + self.startAuthenticationSession(manifest: self.manifest) + } + }, + didSelectCancel: nil + ) + return PaneLayoutView( + contentView: continueStateViews.contentView, + footerView: continueStateViews.footerView + ).createView() + }() + + /** + Unfortunately there is a need for this state-full parameter. When we get url callback the app might not be in foreground state. + If we then restart authentication session ASWebAuthenticationSession will fail as you can't start it in a non-foreground state. + We keep the parameters as a state and pass on to resuming the authentication session and clearing this state. + */ + private var unprocessedReturnURLParameters: String? + private var subscribedToURLNotifications = false + private var subscribedToAppActiveNotifications = false + private var lastOpenedNativeURL: URL? + + private let clientSecret: String + private let apiClient: FinancialConnectionsAPIClient + private let sessionFetcher: FinancialConnectionsSessionFetcher + private let manifest: FinancialConnectionsSessionManifest + private let returnURL: String? + private let elementsSessionContext: ElementsSessionContext? + + // MARK: - UI + + private lazy var closeItem: UIBarButtonItem = { + let item = UIBarButtonItem( + image: Image.close.makeImage(template: false), + style: .plain, + target: self, + action: #selector(didTapClose) + ) + item.tintColor = .iconDefault + item.imageInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5) + return item + }() + + // Use nil theme so the spinner view doesn't flash to the theme's color before launching the webview. + private let loadingView = LoadingView(frame: .zero, theme: nil) + + // MARK: - Init + + init( + clientSecret: String, + apiClient: FinancialConnectionsAPIClient, + manifest: FinancialConnectionsSessionManifest, + sessionFetcher: FinancialConnectionsSessionFetcher, + returnURL: String?, + elementsSessionContext: ElementsSessionContext? + ) { + self.clientSecret = clientSecret + self.apiClient = apiClient + self.manifest = manifest + self.sessionFetcher = sessionFetcher + self.returnURL = returnURL + self.elementsSessionContext = elementsSessionContext + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - UIViewController + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .customBackgroundColor + navigationItem.rightBarButtonItem = closeItem + loadingView.tryAgainButton.addTarget(self, action: #selector(didTapTryAgainButton), for: .touchUpInside) + view.addSubview(loadingView) + + continueStateView.isHidden = true + view.addSubview(continueStateView) + view.addAndPinSubviewToSafeArea(continueStateView) + + // start authentication session + loadingView.errorView.isHidden = true + startAuthenticationSession(manifest: manifest) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + loadingView.frame = view.bounds.inset(by: view.safeAreaInsets) + } +} + +// MARK: - Helpers + +extension FinancialConnectionsWebFlowViewController { + + private func notifyDelegate(result: HostControllerResult) { + let updatedResult = result.updateWith(manifest) + delegate?.webFlowViewController(self, didFinish: updatedResult) + delegate = nil // prevent the delegate from being called again + } + + private func startAuthenticationSession( + manifest: FinancialConnectionsSessionManifest, + additionalQueryParameters: String? = nil + ) { + guard authSessionManager == nil else { return } + loadingView.showLoading(true) + authSessionManager = AuthenticationSessionManager(manifest: manifest, window: view.window) + let additionalQueryParameters = Self.buildEncodedUrlParameters( + startingAdditionalParameters: additionalQueryParameters, + isInstantDebits: manifest.isProductInstantDebits, + linkMode: elementsSessionContext?.linkMode, + prefillDetails: elementsSessionContext?.prefillDetails, + billingDetails: elementsSessionContext?.billingDetails + ) + authSessionManager? + .start(additionalQueryParameters: additionalQueryParameters) + .observe(using: { [weak self] (result) in + guard let self = self else { return } + self.loadingView.showLoading(false) + switch result { + case .success(.success(let returnUrl)): + if manifest.isProductInstantDebits { + if let paymentMethod = returnUrl.extractLinkBankPaymentMethod() { + let instantDebitsLinkedBank = InstantDebitsLinkedBank( + paymentMethod: paymentMethod, + bankName: returnUrl.extractValue(forKey: "bank_name")? + // backend can return "+" instead of a more-common encoding of "%20" for spaces + .replacingOccurrences(of: "+", with: " "), + last4: returnUrl.extractValue(forKey: "last4"), + linkMode: elementsSessionContext?.linkMode + ) + self.notifyDelegateOfSuccess(result: .instantDebits(instantDebitsLinkedBank)) + } else { + self.notifyDelegateOfFailure( + error: FinancialConnectionsSheetError.unknown( + debugDescription: "Invalid payment_method returned" + ) + ) + } + } else { + self.fetchSession() + } + case .success(.webCancelled): + if manifest.isProductInstantDebits { + self.notifyDelegateOfCancel() + } else { + self.fetchSession(webCancelled: true) + } + case .success(.nativeCancelled): + if manifest.isProductInstantDebits { + self.notifyDelegateOfCancel() + } else { + self.fetchSession(userDidCancelInNative: true) + } + case .failure(let error): + self.notifyDelegateOfFailure(error: error) + case .success(.redirect(url: let url)): + self.redirect(to: url) + } + self.authSessionManager = nil + }) + } + + private func redirect(to url: URL) { + DispatchQueue.main.async { + self.continueStateView.isHidden = false + self.subscribeToURLAndAppActiveNotifications() + self.lastOpenedNativeURL = url + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + + private func fetchSession(userDidCancelInNative: Bool = false, webCancelled: Bool = false) { + loadingView.showLoading(true) + loadingView.errorView.isHidden = true + sessionFetcher + .fetchSession() + .observe { [weak self] (result) in + guard let self = self else { return } + self.loadingView.showLoading(false) + switch result { + case .success(let session): + if userDidCancelInNative { + // Users can cancel the web flow even if they successfully linked + // accounts. As a result, we check whether they linked any + // before returning "cancelled." + if !session.accounts.data.isEmpty || session.paymentAccount != nil || session.bankAccountToken != nil { + self.notifyDelegateOfSuccess(result: .financialConnections(session)) + } else { + self.notifyDelegateOfCancel() + } + } else if webCancelled { + if session.status == .cancelled && session.statusDetails?.cancelled?.reason == .customManualEntry { + self.notifyDelegate(result: .failed(error: FinancialConnectionsCustomManualEntryRequiredError())) + } else { + self.notifyDelegateOfCancel() + } + } else { + self.notifyDelegateOfSuccess(result: .financialConnections(session)) + } + case .failure(let error): + self.loadingView.errorView.isHidden = false + self.fetchSessionError = error + } + } + } + + private func notifyDelegateOfSuccess(result: HostControllerResult.Completed) { + let session: StripeAPI.FinancialConnectionsSession? + if case .financialConnections(let wrappedSession) = result { + session = wrappedSession + } else { + session = nil + } + delegate?.webFlowViewController( + self, + didReceiveEvent: FinancialConnectionsEvent( + name: .success, + metadata: FinancialConnectionsEvent.Metadata( + manualEntry: session?.paymentAccount?.isManualEntry ?? false + ) + ) + ) + notifyDelegate(result: .completed(result)) + } + + private func notifyDelegateOfCancel() { + delegate?.webFlowViewController( + self, + didReceiveEvent: FinancialConnectionsEvent(name: .cancel) + ) + notifyDelegate(result: .canceled) + } + + // all failures except custom manual entry failure + private func notifyDelegateOfFailure(error: Error) { + FinancialConnectionsEvent + .events(fromError: error) + .forEach { event in + delegate?.webFlowViewController(self, didReceiveEvent: event) + } + + notifyDelegate(result: .failed(error: error)) + } +} + +private extension URL { + + /// The URL contains a base64-encoded payment method. We store its values in `LinkBankPaymentMethod` so that + /// we can parse it back in StripeCore. + func extractLinkBankPaymentMethod() -> LinkBankPaymentMethod? { + guard let encodedPaymentMethod = extractValue(forKey: "payment_method") else { + return nil + } + + guard let data = Data(base64Encoded: encodedPaymentMethod) else { + return nil + } + + let result: Result = STPAPIClient.decodeResponse( + data: data, + error: nil, + response: nil + ) + + return try? result.get() + } + + func extractValue(forKey key: String) -> String? { + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { + assertionFailure("Invalid URL") + return nil + } + return components + .queryItems? + .first(where: { $0.name == key })? + .value? + .removingPercentEncoding + } +} + +// MARK: - STPURLCallbackListener + +extension FinancialConnectionsWebFlowViewController: STPURLCallbackListener { + func handleURLCallback(_ url: URL) -> Bool { + DispatchQueue.main.async { + self.unprocessedReturnURLParameters = FinancialConnectionsWebFlowViewController.returnURLParameters( + from: url + ) + self.restartAuthenticationIfNeeded() + } + return true + } +} + +// MARK: - UI Helpers + +private extension FinancialConnectionsWebFlowViewController { + + @objc + private func didTapTryAgainButton() { + fetchSession() + } + + @objc + private func didTapClose() { + manuallyCloseWebFlowViewController() + } + + private func manuallyCloseWebFlowViewController() { + if let fetchSessionError = fetchSessionError { + notifyDelegateOfFailure(error: fetchSessionError) + } else { + notifyDelegate(result: .canceled) + } + } +} + +// MARK: - Authentication restart helpers + +private extension FinancialConnectionsWebFlowViewController { + + private func restartAuthenticationIfNeeded() { + dispatchPrecondition(condition: .onQueue(.main)) + + guard UIApplication.shared.applicationState == .active, let parameters = unprocessedReturnURLParameters else { + /** + When we get url callback the app might not be in foreground state. + If we then restart authentication session ASWebAuthenticationSession will fail as you can't start it in a non-foreground state. + */ + return + } + startAuthenticationSession(manifest: manifest, additionalQueryParameters: parameters) + unprocessedReturnURLParameters = nil + lastOpenedNativeURL = nil + continueStateView.isHidden = true + unsubscribeFromNotifications() + } + + private func subscribeToURLAndAppActiveNotifications() { + dispatchPrecondition(condition: .onQueue(.main)) + + subscribeToURLNotifications() + if !subscribedToAppActiveNotifications { + subscribedToAppActiveNotifications = true + NotificationCenter.default.addObserver( + self, + selector: #selector(handleDidBecomeActiveNotification), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + } + } + + private func subscribeToURLNotifications() { + dispatchPrecondition(condition: .onQueue(.main)) + + guard let returnURL = returnURL, let url = URL(string: returnURL) else { + return + } + if !subscribedToURLNotifications { + subscribedToURLNotifications = true + STPURLCallbackHandler.shared().register( + self, + for: url + ) + } + } + + @objc func handleDidBecomeActiveNotification() { + DispatchQueue.main.async { + self.restartAuthenticationIfNeeded() + } + } + + private func unsubscribeFromNotifications() { + dispatchPrecondition(condition: .onQueue(.main)) + + NotificationCenter.default.removeObserver( + self, + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + STPURLCallbackHandler.shared().unregisterListener(self) + subscribedToURLNotifications = false + subscribedToAppActiveNotifications = false + } + + private static func returnURLParameters(from incoming: URL) -> String { + let startPollingParam = "&startPolling=true" + guard let fragment = incoming.fragment else { + return startPollingParam + } + return startPollingParam + "&\(fragment)" + } +} + +extension FinancialConnectionsWebFlowViewController { + static func buildEncodedUrlParameters( + startingAdditionalParameters: String?, + isInstantDebits: Bool, + linkMode: LinkMode?, + prefillDetails: ElementsSessionContext.PrefillDetails?, + billingDetails: ElementsSessionContext.BillingDetails? + ) -> String? { + var parameters: [String] = [] + + if let startingAdditionalParameters { + parameters.append(startingAdditionalParameters) + } + + if isInstantDebits { + parameters.append("return_payment_method=true") + parameters.append("expand_payment_method=true") + if let linkMode { + parameters.append("link_mode=\(linkMode.rawValue)") + } + + if let billingDetails = billingDetails { + if let name = billingDetails.name, !name.isEmpty { + parameters.append("billingDetails[name]=\(name)") + } + if let email = billingDetails.email, !email.isEmpty { + parameters.append("billingDetails[email]=\(email)") + } + if let phone = billingDetails.phone, !phone.isEmpty { + parameters.append("billingDetails[phone]=\(phone)") + } + if let address = billingDetails.address { + if let city = address.city, !city.isEmpty { + parameters.append("billingDetails[address][city]=\(city)") + } + if let country = address.country, !country.isEmpty { + parameters.append("billingDetails[address][country]=\(country)") + } + if let line1 = address.line1, !line1.isEmpty { + parameters.append("billingDetails[address][line1]=\(line1)") + } + if let line2 = address.line2, !line2.isEmpty { + parameters.append("billingDetails[address][line2]=\(line2)") + } + if let postalCode = address.postalCode, !postalCode.isEmpty { + parameters.append("billingDetails[address][postal_code]=\(postalCode)") + } + if let state = address.state, !state.isEmpty { + parameters.append("billingDetails[address][state]=\(state)") + } + } + } + } + + if let prefillDetails = prefillDetails { + if let email = prefillDetails.email, !email.isEmpty { + parameters.append("email=\(email)") + } + if let phoneNumber = prefillDetails.unformattedPhoneNumber, !phoneNumber.isEmpty { + parameters.append("linkMobilePhone=\(phoneNumber)") + } + if let countryCode = prefillDetails.countryCode, !countryCode.isEmpty { + parameters.append("linkMobilePhoneCountry=\(countryCode)") + } + } + + // Join all values with an &, and URL encode. + // We encode these parameters since they will be appended to the auth flow URL. + guard let result = parameters.joined(separator: "&").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { + return nil + } + // Start the result with a & if it is not empty and doesn't already start with one. + return result.isEmpty ? nil : result.hasPrefix("&") ? result : "&" + result + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnections/StripeFinancialConnections.h b/StripeFinancialConnections/StripeFinancialConnections/StripeFinancialConnections.h new file mode 100644 index 00000000..ef9874a6 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnections/StripeFinancialConnections.h @@ -0,0 +1,18 @@ +// +// StripeFinancialConnections.h +// StripeFinancialConnections +// +// Created by Vardges Avetisyan on 11/9/21. +// + +#import + +//! Project version number for StripeFinancialConnections. +FOUNDATION_EXPORT double StripeFinancialConnectionsVersionNumber; + +//! Project version string for StripeFinancialConnections. +FOUNDATION_EXPORT const unsigned char StripeFinancialConnectionsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/APIPollingHelperTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/APIPollingHelperTests.swift new file mode 100644 index 00000000..493a4311 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/APIPollingHelperTests.swift @@ -0,0 +1,342 @@ +// +// APIPollingHelperTests.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 10/2/22. +// + +@testable@_spi(STP) import StripeCore +@testable import StripeFinancialConnections +import XCTest + +final class APIPollingHelperTests: XCTestCase { + + func testPollingSuccessOnFirstTry() throws { + let dataSource = DataSource( + numberOfRetriesUntilServerReturnsSuccess: 0, + maxNumberOfRetriesClientWillTry: 0 + ) + + let expectation = expectation(description: "expect that DataSource 'server' returns a value") + var result: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + result = apiCallResult + expectation.fulfill() + } + wait(for: [expectation], timeout: 5) + + switch result { + case .success: + break // we expect success + case .failure: + XCTFail() + case .none: + XCTFail() + } + } + + func testPollingSuccessAfterFiveTries() throws { + let dataSource = DataSource( + numberOfRetriesUntilServerReturnsSuccess: 5, + maxNumberOfRetriesClientWillTry: 5 + ) + + let expectation = expectation(description: "expect that DataSource 'server' returns a value") + var result: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + result = apiCallResult + expectation.fulfill() + } + wait(for: [expectation], timeout: 5) + + switch result { + case .success: + break // we expect success + case .failure: + XCTFail() + case .none: + XCTFail() + } + } + + func testClientPollingMoreThanWhatServerNeeds() throws { + let dataSource = DataSource( + numberOfRetriesUntilServerReturnsSuccess: 5, + // client is able to try more than the "5" required times + maxNumberOfRetriesClientWillTry: 10 + ) + + let expectation = expectation(description: "expect that DataSource 'server' returns a value") + var result: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + result = apiCallResult + expectation.fulfill() + } + wait(for: [expectation], timeout: 5) + + switch result { + case .success: + break // we expect success + case .failure: + XCTFail() + case .none: + XCTFail() + } + } + + func testClientPollingLessThanWhatServerNeeds() throws { + let dataSource = DataSource( + numberOfRetriesUntilServerReturnsSuccess: 6, + maxNumberOfRetriesClientWillTry: 5 + ) + + let expectation = expectation(description: "expect that DataSource 'server' returns a value") + var result: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + result = apiCallResult + expectation.fulfill() + } + wait(for: [expectation], timeout: 5) + + switch result { + case .success: + XCTFail() + case .failure: + break // we expect failure + case .none: + XCTFail() + } + } + + func testPollingDefaults() throws { + let dataSource = DataSource( + numberOfRetriesUntilServerReturnsSuccess: 2, + maxNumberOfRetriesClientWillTry: nil // use default polling + ) + + let expectation = expectation(description: "expect that DataSource 'server' returns a value") + var result: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + result = apiCallResult + expectation.fulfill() + } + wait(for: [expectation], timeout: 10) // add extra time-out to make up for defaults + + switch result { + case .success: + break // we expect success + case .failure: + XCTFail() + case .none: + XCTFail() + } + } + + func testPollingTwice() throws { + let dataSource = DataSource( + numberOfRetriesUntilServerReturnsSuccess: 4, + maxNumberOfRetriesClientWillTry: 2 + ) + + // lets try polling, but it will fail because server needs 4 tries (we only try 2 times) + let firstPollingExpectation = expectation(description: "expect that DataSource 'server' returns a value") + var firstPollResult: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + firstPollResult = apiCallResult + firstPollingExpectation.fulfill() + } + wait(for: [firstPollingExpectation], timeout: 5) + switch firstPollResult { + case .success: + XCTFail() + case .failure: + break // we expect failure + case .none: + XCTFail() + } + + // lets try polling again, and it should now succeed + // + // we expect client to "reset" the poll try count + let secondPollingExpectation = expectation(description: "expect that DataSource 'server' returns a value") + var secondPollResult: Result? + dataSource.pollAPICall() + .observe { apiCallResult in + secondPollResult = apiCallResult + secondPollingExpectation.fulfill() + } + wait(for: [secondPollingExpectation], timeout: 5) + switch secondPollResult { + case .success: + break // we expect to succeed the second time + case .failure: + XCTFail() + case .none: + XCTFail() + } + } + + func testPollingHelperDeallocAfterPollingFinishes() { + let apiCallFinishedExpectation = expectation(description: "") + let apiCall: () -> Future = { + DispatchQueue.main.async { + apiCallFinishedExpectation.fulfill() + } + return Promise(value: TestModel()) + } + + var apiPollingHelper: APIPollingHelper? = APIPollingHelper( + apiCall: apiCall, + pollTimingOptions: APIPollingHelper.PollTimingOptions( + initialPollDelay: 0.3 // delay to prevent api from calling immediately + ) + ) + // after this point `apiPollingHelper` should have a strong reference to itself + apiPollingHelper?.startPollingApiCall() + .observe { _ in } + weak var weakAPIPollingHelper = apiPollingHelper + apiPollingHelper = nil + XCTAssert(weakAPIPollingHelper != nil) + + // wait for the API call to finish + wait(for: [apiCallFinishedExpectation], timeout: 1) + + // the `nil` happens after DispatchQueue.main.async, so wait a little bit + let nilExpectation = expectation(description: "") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + nilExpectation.fulfill() + } + wait(for: [nilExpectation], timeout: 1) + + // at this point the API should have executed and polling helper should have deallocated itself + XCTAssert(weakAPIPollingHelper == nil) + } + + func testInitialDelay() { + var didCallAPI = false + let apiCall: () -> Future = { + didCallAPI = true + return Promise(value: TestModel()) + } + + let apiPollingHelper = APIPollingHelper( + apiCall: apiCall, + pollTimingOptions: APIPollingHelper.PollTimingOptions( + initialPollDelay: 0.5 // delay to prevent api from calling immediately + ) + ) + apiPollingHelper.startPollingApiCall() + .observe { _ in } + + let beforeDelayExpiresExpectation = expectation(description: "") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + beforeDelayExpiresExpectation.fulfill() + } + wait(for: [beforeDelayExpiresExpectation], timeout: 5) + + XCTAssert(!didCallAPI, "API call should be delayed") + + let afterDelayExpiresExpectation = expectation(description: "") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.26) { + afterDelayExpiresExpectation.fulfill() + } + wait(for: [afterDelayExpiresExpectation], timeout: 5) + + XCTAssert(didCallAPI, "API call should have been called already") + } + + func test202ErrorCreation() throws { + let error = Create202Error() + if case .apiError(let stripeAPIError) = error { + XCTAssert(stripeAPIError.statusCode == 202) + } else { + XCTFail() + } + } +} + +private final class DataSource { + + private var numberOfRetriesUntilServerReturnsSuccess: Int + private let maxNumberOfRetriesClientWillTry: Int? + + init( + numberOfRetriesUntilServerReturnsSuccess: Int, + maxNumberOfRetriesClientWillTry: Int? // null means to use default values + ) { + self.numberOfRetriesUntilServerReturnsSuccess = numberOfRetriesUntilServerReturnsSuccess + self.maxNumberOfRetriesClientWillTry = maxNumberOfRetriesClientWillTry + } + + func pollAPICall() -> Future { + let apiCall: () -> Future = { [weak self] in + guard let self = self else { + return Promise( + error: + FinancialConnectionsSheetError + .unknown( + debugDescription: "DataSource deallocated." + ) + ) + } + return self.serverAPICall() + } + + let apiPollingHelper = APIPollingHelper( + apiCall: apiCall, + pollTimingOptions: { + if let maxNumberOfRetriesClientWillTry = maxNumberOfRetriesClientWillTry { + return APIPollingHelper.PollTimingOptions( + initialPollDelay: 0, + maxNumberOfRetries: maxNumberOfRetriesClientWillTry, + retryInterval: 0 + ) + } else { + // use default Values + return APIPollingHelper.PollTimingOptions() + } + }() + ) + return apiPollingHelper.startPollingApiCall() + } + + // this method pretends to be a "server" that will + // send a "retry" `numberOfRetriesUntilServerReturnsSuccess` + // amount of times + private func serverAPICall() -> Future { + if numberOfRetriesUntilServerReturnsSuccess > 0 { + numberOfRetriesUntilServerReturnsSuccess -= 1 + return Promise(error: Create202Error()) + } else { + return Promise(value: TestModel()) + } + } +} + +private struct TestModel: Codable {} + +// "202 response status code indicates that the request has been +// accepted for processing, but the processing has not been completed" +private func Create202Error() -> StripeError { + let errorJson: [String: Any] = [ + "error": [ + "type": "api_error" + ], + ] + let errorJsonData = try! JSONSerialization.data( + withJSONObject: errorJson, + options: [.prettyPrinted] + ) + let decodedErrorResponse: StripeAPIErrorResponse = try! StripeJSONDecoder.decode( + jsonData: errorJsonData + ) + var apiError = decodedErrorResponse.error! + apiError.statusCode = 202 + return StripeError.apiError(apiError) +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/AccountFetcherTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/AccountFetcherTests.swift new file mode 100644 index 00000000..d29a34c3 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/AccountFetcherTests.swift @@ -0,0 +1,142 @@ +// +// AccountFetcherTests.swift +// StripeFinancialConnectionsTests +// +// Created by Vardges Avetisyan on 12/30/21. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeCoreTestUtils +@testable import StripeFinancialConnections +import XCTest + +class PaginatedAPIClient: EmptyFinancialConnectionsAPIClient { + + // MARK: - Init + + init(count: Int, limit: Int) { + self.count = count + self.limit = limit + } + + // MARK: - Properties + + private let count: Int + private let limit: Int + private lazy var accounts: [StripeAPI.FinancialConnectionsAccount] = (0...count - 1).map { + StripeAPI.FinancialConnectionsAccount( + balance: nil, + balanceRefresh: nil, + ownership: nil, + ownershipRefresh: nil, + displayName: "\($0)", + institutionName: "TestBank", + last4: "\($0)", + category: .cash, + created: 1, + id: "\($0)", + livemode: false, + permissions: nil, + status: .active, + subcategory: .checking, + supportedPaymentMethodTypes: [.usBankAccount] + ) + } + + // MARK: - FinancialConnectionsAPIClient + + override func fetchFinancialConnectionsAccounts( + clientSecret: String, + startingAfterAccountId: String? + ) -> Promise { + guard let startingAfterAccountId = startingAfterAccountId, let index = Int(startingAfterAccountId) else { + let list = StripeAPI.FinancialConnectionsSession.AccountList( + data: subarray(start: 0), + hasMore: true + ) + return Promise(value: list) + + } + let subArray = subarray(start: index + 1) + let hasMore = index + limit < accounts.count - 1 + let list = StripeAPI.FinancialConnectionsSession.AccountList( + data: subArray, + hasMore: hasMore + ) + return Promise(value: list) + } + + // MARK: - Helpers + + private func subarray(start: Int) -> [StripeAPI.FinancialConnectionsAccount] { + guard start + limit < accounts.count else { + return [StripeAPI.FinancialConnectionsAccount](accounts[start...]) + } + return [StripeAPI.FinancialConnectionsAccount](accounts[start...start + limit]) + } +} + +class AccountFetcherTests: XCTestCase { + + func testPaginationMax100() { + let fetcher = FinancialConnectionsAccountAPIFetcher( + api: PaginatedAPIClient(count: 120, limit: 1), + clientSecret: "" + ) + fetcher.fetchAccounts(initial: []).observe { result in + switch result { + case .success(let linkedAccounts): + XCTAssertEqual(linkedAccounts.count, 100) + case .failure: + XCTFail() + } + } + } + + func testPaginationUnderLimit() { + let fetcher = FinancialConnectionsAccountAPIFetcher( + api: PaginatedAPIClient(count: 3, limit: 1), + clientSecret: "" + ) + fetcher.fetchAccounts(initial: []).observe { result in + switch result { + case .success(let linkedAccounts): + XCTAssertEqual(linkedAccounts.count, 3) + case .failure: + XCTFail() + } + } + } + + func testPaginationUnderLimitLargePageSize() { + let fetcher = FinancialConnectionsAccountAPIFetcher( + api: PaginatedAPIClient(count: 3, limit: 10), + clientSecret: "" + ) + fetcher.fetchAccounts(initial: []).observe { result in + switch result { + case .success(let linkedAccounts): + let info = linkedAccounts.map { $0.id } + print(info) + XCTAssertEqual(linkedAccounts.count, 3) + case .failure: + XCTFail() + } + } + } + + func testPaginationUnderLimitSmallPageSize() { + let fetcher = FinancialConnectionsAccountAPIFetcher( + api: PaginatedAPIClient(count: 80, limit: 10), + clientSecret: "" + ) + fetcher.fetchAccounts(initial: []).observe { result in + switch result { + case .success(let linkedAccounts): + XCTAssertEqual(linkedAccounts.count, 80) + case .failure: + XCTFail() + } + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/AccountPickerHelpersTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/AccountPickerHelpersTests.swift new file mode 100644 index 00000000..a0fa25b5 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/AccountPickerHelpersTests.swift @@ -0,0 +1,41 @@ +// +// AccountPickerHelpersTests.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 9/8/22. +// + +@testable import StripeFinancialConnections +import XCTest + +class AccountPickerHelpersTests: XCTestCase { + func testCurrencyStringsFromUsd() { + XCTAssertEqual(currencyString(currency: "usd", balanceAmount: 1000000), "$10,000.00") + XCTAssertEqual(currencyString(currency: "usd", balanceAmount: 1000), "$10.00") + XCTAssertEqual(currencyString(currency: "eur", balanceAmount: 10), "€0.10") + XCTAssertEqual(currencyString(currency: "gbp", balanceAmount: 999), "£9.99") + XCTAssertEqual(currencyString(currency: "jpy", balanceAmount: 543), "¥543") + XCTAssertEqual(currencyString(currency: "krw", balanceAmount: 123456), "₩123,456") + XCTAssertEqual(currencyString(currency: "usd", balanceAmount: 0), "$0.00") + XCTAssertEqual(currencyString(currency: "usd", balanceAmount: -1000), "-$10.00") + XCTAssertEqual(currencyString(currency: "usd", balanceAmount: -1000000), "-$10,000.00") + } + + func testCurrencyStringsFromCadToUsd() { + let currencyString = AccountPickerHelpers.currencyString( + currency: "usd", + balanceAmount: 1000, + locale: Locale(identifier: "en_CA") + ) + XCTAssertEqual(currencyString, "US$10.00") + } + + // Helper function to hard-code a USD locale. + private func currencyString(currency: String, balanceAmount: Int) -> String? { + AccountPickerHelpers.currencyString( + currency: currency, + balanceAmount: balanceAmount, + locale: Locale(identifier: "en_US") + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/AuthFlowHelpersTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/AuthFlowHelpersTests.swift new file mode 100644 index 00000000..12e6cce0 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/AuthFlowHelpersTests.swift @@ -0,0 +1,30 @@ +// +// AuthFlowHelpersTests.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 10/5/22. +// + +@testable import StripeFinancialConnections +import XCTest + +class AuthFlowHelpersTests: XCTestCase { + + func testFormatUrlString() throws { + XCTAssert(AuthFlowHelpers.formatUrlString(nil) == nil) + XCTAssert(AuthFlowHelpers.formatUrlString("") == "") + XCTAssert(AuthFlowHelpers.formatUrlString("www.") == "") + XCTAssert(AuthFlowHelpers.formatUrlString("http://") == "") + XCTAssert(AuthFlowHelpers.formatUrlString("https://") == "") + XCTAssert(AuthFlowHelpers.formatUrlString("/") == "") + XCTAssert(AuthFlowHelpers.formatUrlString("stripe.com") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("stripe.com/") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("www.stripe.com") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("https://stripe.com") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("http://stripe.com") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("http://www.stripe.com") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("https://www.stripe.com") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("https://www.stripe.com/") == "stripe.com") + XCTAssert(AuthFlowHelpers.formatUrlString("https://www.wow.stripe.com/") == "wow.stripe.com") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/EmptyFinancialConnectionsAPIClient.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/EmptyFinancialConnectionsAPIClient.swift new file mode 100644 index 00000000..b50a34c4 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/EmptyFinancialConnectionsAPIClient.swift @@ -0,0 +1,244 @@ +// +// EmptyFinancialConnectionsAPIClient.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 1/20/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeCoreTestUtils +@testable import StripeFinancialConnections + +class EmptyFinancialConnectionsAPIClient: FinancialConnectionsAPI { + + func fetchFinancialConnectionsAccounts(clientSecret: String, startingAfterAccountId: String?) -> Promise< + StripeAPI.FinancialConnectionsSession.AccountList + > { + return Promise() + } + + func fetchFinancialConnectionsSession(clientSecret: String) -> Promise { + return Promise() + } + + func synchronize( + clientSecret: String, + returnURL: String? + ) -> Future { + return Promise() + } + + func markConsentAcquired(clientSecret: String) -> Promise { + return Promise() + } + + func fetchFeaturedInstitutions(clientSecret: String) -> Promise { + return Promise() + } + + func fetchInstitutions(clientSecret: String, query: String) -> Future { + return Promise() + } + + func createAuthSession(clientSecret: String, institutionId: String) -> Promise { + return Promise() + } + + func cancelAuthSession(clientSecret: String, authSessionId: String) -> Promise { + return Promise() + } + + func retrieveAuthSession( + clientSecret: String, + authSessionId: String + ) -> Future { + return Promise() + } + + func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) -> Future< + FinancialConnectionsMixedOAuthParams + > { + return Promise() + } + + func authorizeAuthSession(clientSecret: String, authSessionId: String, publicToken: String?) -> Promise< + FinancialConnectionsAuthSession + > { + return Promise() + } + + func fetchAuthSessionAccounts( + clientSecret: String, + authSessionId: String, + initialPollDelay: TimeInterval + ) -> Future { + return Promise() + } + + func selectAuthSessionAccounts(clientSecret: String, authSessionId: String, selectedAccountIds: [String]) + -> Promise + { + return Promise() + } + + func markLinkingMoreAccounts(clientSecret: String) -> Promise { + return Promise() + } + + func completeFinancialConnectionsSession( + clientSecret: String, + terminalError: String? + ) -> Future { + return Promise() + } + + func attachBankAccountToLinkAccountSession( + clientSecret: String, + accountNumber: String, + routingNumber: String, + consumerSessionClientSecret: String? + ) -> Future { + return Promise() + } + + func attachLinkedAccountIdToLinkAccountSession( + clientSecret: String, + linkedAccountId: String, + consumerSessionClientSecret: String? + ) -> Future { + return Promise() + } + + func recordAuthSessionEvent( + clientSecret: String, + authSessionId: String, + eventNamespace: String, + eventName: String + ) -> Future { + return Promise() + } + + func saveAccountsToNetworkAndLink( + shouldPollAccounts: Bool, + selectedAccounts: [FinancialConnectionsPartnerAccount]?, + emailAddress: String?, + phoneNumber: String?, + country: String?, + consumerSessionClientSecret: String?, + clientSecret: String + ) -> Future<( + manifest: FinancialConnectionsSessionManifest, + customSuccessPaneMessage: String? + )> { + return Promise<( + manifest: FinancialConnectionsSessionManifest, + customSuccessPaneMessage: String? + )>() + } + + func disableNetworking( + disabledReason: String?, + clientSuggestedNextPaneOnDisableNetworking: String?, + clientSecret: String + ) -> Future { + Promise() + } + + func fetchNetworkedAccounts( + clientSecret: String, + consumerSessionClientSecret: String + ) -> StripeCore.Future { + return Promise() + } + + func markLinkVerified( + clientSecret: String + ) -> StripeCore.Future { + return Promise() + } + + func selectNetworkedAccounts( + selectedAccountIds: [String], + clientSecret: String, + consumerSessionClientSecret: String, + consentAcquired: Bool? + ) -> StripeCore.Future { + return Promise() + } + + func consumerSessionLookup( + emailAddress: String, + clientSecret: String + ) -> Future { + return Promise() + } + + func consumerSessionStartVerification( + otpType: String, + customEmailType: String?, + connectionsMerchantName: String?, + consumerSessionClientSecret: String + ) -> StripeCore.Future { + return Promise() + } + + func consumerSessionConfirmVerification( + otpCode: String, + otpType: String, + consumerSessionClientSecret: String + ) -> StripeCore.Future { + return Promise() + } + + func markLinkStepUpAuthenticationVerified( + clientSecret: String + ) -> Future { + return Promise() + } + + func linkAccountSignUp( + emailAddress: String, + phoneNumber: String, + country: String, + amount: Int?, + currency: String?, + intentId: ElementsSessionContext.IntentID? + ) -> Future { + return Promise() + } + + func attachLinkConsumerToLinkAccountSession( + linkAccountSession: String, + consumerSessionClientSecret: String + ) -> Future { + return Promise() + } + + func paymentDetails( + consumerSessionClientSecret: String, + bankAccountId: String, + billingAddress: BillingAddress?, + billingEmail: String? + ) -> StripeCore.Future { + Promise() + } + + func sharePaymentDetails( + consumerSessionClientSecret: String, + paymentDetailsId: String, + expectedPaymentMethodType: String, + billingEmail: String?, + billingPhone: String? + ) -> Future { + Promise() + } + + func paymentMethods( + consumerSessionClientSecret: String, + paymentDetailsId: String, + billingDetails: ElementsSessionContext.BillingDetails? + ) -> StripeCore.Future { + Promise() + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsAPIClientTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsAPIClientTests.swift new file mode 100644 index 00000000..9f99f96d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsAPIClientTests.swift @@ -0,0 +1,104 @@ +// +// FinancialConnectionsAPIClientTests.swift +// StripeFinancialConnectionsTests +// +// Created by Mat Schmid on 2024-08-02. +// + +import OHHTTPStubs +import OHHTTPStubsSwift +import XCTest + +@_spi(STP) import StripeCore +@_spi(STP) import StripeCoreTestUtils +@testable @_spi(STP) import StripeFinancialConnections + +class FinancialConnectionsAPIClientTests: XCTestCase { + private let mockApiClient = APIStubbedTestCase.stubbedAPIClient() + + func testConusmerPublishableKeyProvider() { + let apiClient = FinancialConnectionsAPIClient(apiClient: mockApiClient) + XCTAssertNil(apiClient.consumerPublishableKeyProvider(canUseConsumerKey: true)) + + let consumerPublishableKey = "consumerPublishableKey" + apiClient.consumerPublishableKey = consumerPublishableKey + XCTAssertNil(apiClient.consumerPublishableKeyProvider(canUseConsumerKey: true)) + + apiClient.isLinkWithStripe = true + XCTAssertNil(apiClient.consumerPublishableKeyProvider(canUseConsumerKey: true)) + + let unverifiedConsumerSession = ConsumerSessionData( + clientSecret: "clientSecret", + emailAddress: "emailAddress", + redactedFormattedPhoneNumber: "redactedFormattedPhoneNumber", + verificationSessions: [] + ) + apiClient.consumerSession = unverifiedConsumerSession + XCTAssertNil(apiClient.consumerPublishableKeyProvider(canUseConsumerKey: true)) + + let verifiedConsumerSession = ConsumerSessionData( + clientSecret: "clientSecret", + emailAddress: "emailAddress", + redactedFormattedPhoneNumber: "redactedFormattedPhoneNumber", + verificationSessions: [ + VerificationSession( + type: .sms, + state: .verified + ), + ] + ) + apiClient.consumerSession = verifiedConsumerSession + XCTAssertEqual(apiClient.consumerPublishableKeyProvider(canUseConsumerKey: true), consumerPublishableKey) + + XCTAssertNil(apiClient.consumerPublishableKeyProvider(canUseConsumerKey: false)) + } + + func testEmptyBillingAddressEncodedAsParameters() throws { + let billingAddress = BillingAddress() + let encodedBillingAddress = try FinancialConnectionsAPIClient.encodeAsParameters(billingAddress) + + XCTAssertNil(encodedBillingAddress) + } + + func testBillingAddressEncodedAsParameters() throws { + let billingAddress = BillingAddress( + name: "Bobby Tables", + line1: "123 Fake St", + line2: nil, + city: "Utopia", + state: "CA", + postalCode: "90210", + countryCode: "US" + ) + let encodedBillingAddress = try FinancialConnectionsAPIClient.encodeAsParameters(billingAddress) + + XCTAssertEqual(encodedBillingAddress?["name"] as? String, "Bobby Tables") + XCTAssertEqual(encodedBillingAddress?["line_1"] as? String, "123 Fake St") + XCTAssertNil(encodedBillingAddress?["line_2"]) + XCTAssertEqual(encodedBillingAddress?["locality"] as? String, "Utopia") + XCTAssertEqual(encodedBillingAddress?["administrative_area"] as? String, "CA") + XCTAssertEqual(encodedBillingAddress?["postal_code"] as? String, "90210") + XCTAssertEqual(encodedBillingAddress?["country_code"] as? String, "US") + } + + func testBillingAddressEncodedAsParametersNonNilLine2() throws { + let billingAddress = BillingAddress( + name: "Bobby Tables", + line1: "123 Fake St", + line2: "", + city: "Utopia", + state: "CA", + postalCode: "90210", + countryCode: "US" + ) + let encodedBillingAddress = try FinancialConnectionsAPIClient.encodeAsParameters(billingAddress) + + XCTAssertEqual(encodedBillingAddress?["name"] as? String, "Bobby Tables") + XCTAssertEqual(encodedBillingAddress?["line_1"] as? String, "123 Fake St") + XCTAssertNil(encodedBillingAddress?["line_2"]) + XCTAssertEqual(encodedBillingAddress?["locality"] as? String, "Utopia") + XCTAssertEqual(encodedBillingAddress?["administrative_area"] as? String, "CA") + XCTAssertEqual(encodedBillingAddress?["postal_code"] as? String, "90210") + XCTAssertEqual(encodedBillingAddress?["country_code"] as? String, "US") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsAnalyticsTest.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsAnalyticsTest.swift new file mode 100644 index 00000000..a37d17fb --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsAnalyticsTest.swift @@ -0,0 +1,74 @@ +// +// FinancialConnectionsAnalyticsTest.swift +// StripeFinancialConnectionsTests +// +// Created by Vardges Avetisyan on 12/9/21. +// + +import XCTest + +@_spi(STP) import StripeCore +@testable @_spi(STP) import StripeFinancialConnections + +final class FinancialConnectionsSheetAnalyticsTest: XCTestCase { + + func testFinancialConnectionsSheetFailedAnalyticEncoding() { + let analytic = FinancialConnectionsSheetFailedAnalytic( + clientSecret: "test", + error: FinancialConnectionsSheetError.unknown(debugDescription: "some description") + ) + XCTAssertNotNil(analytic.error) + + let errorDict = analytic.error.serializeForV2Logging() + XCTAssertNil(errorDict["user_info"]) + XCTAssertEqual(errorDict["code"] as? Int, 0) + XCTAssertEqual(errorDict["domain"] as? String, "Stripe.FinancialConnectionsSheetError") + } + + func testFinancialConnectionsSheetCompletionAnalyticCompleted() { + let accountList = StripeAPI.FinancialConnectionsSession.AccountList(data: [], hasMore: false) + let session = StripeAPI.FinancialConnectionsSession( + clientSecret: "", + id: "", + accounts: accountList, + livemode: false, + paymentAccount: nil, + bankAccountToken: nil, + status: nil, + statusDetails: nil + ) + let analytic = FinancialConnectionsSheetCompletionAnalytic.make( + clientSecret: "secret", + result: .completed(.financialConnections(session)) + ) + guard let closedAnalytic = analytic as? FinancialConnectionsSheetClosedAnalytic else { + return XCTFail("Expected `FinancialConnectionsSheetClosedAnalytic`") + } + + XCTAssertEqual(closedAnalytic.clientSecret, "secret") + XCTAssertEqual(closedAnalytic.result, "completed") + } + + func testFinancialConnectionsSheetCompletionAnalyticCanceled() { + let analytic = FinancialConnectionsSheetCompletionAnalytic.make(clientSecret: "secret", result: .canceled) + guard let closedAnalytic = analytic as? FinancialConnectionsSheetClosedAnalytic else { + return XCTFail("Expected `FinancialConnectionsSheetClosedAnalytic`") + } + + XCTAssertEqual(closedAnalytic.clientSecret, "secret") + XCTAssertEqual(closedAnalytic.result, "cancelled") + } + + func testFinancialConnectionsSheetCompletionAnalyticFailed() { + let analytic = FinancialConnectionsSheetCompletionAnalytic.make( + clientSecret: "secret", + result: .failed(error: FinancialConnectionsSheetError.unknown(debugDescription: "some description")) + ) + guard let failedAnalytic = analytic as? FinancialConnectionsSheetFailedAnalytic else { + return XCTFail("Expected `FinancialConnectionsSheetFailedAnalytic`") + } + + XCTAssertEqual(failedAnalytic.clientSecret, "secret") + XCTAssert(failedAnalytic.error is FinancialConnectionsSheetError) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsSessionTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsSessionTests.swift new file mode 100644 index 00000000..89f8ee0b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsSessionTests.swift @@ -0,0 +1,53 @@ +// +// FinancialConnectionsSessionTests.swift +// StripeFinancialConnectionsTests +// +// Created by Vardges Avetisyan on 4/29/22. +// + +import Foundation +import StripeCoreTestUtils +@testable import StripeFinancialConnections +import XCTest + +enum FinancialConnectionsSessionMock: String, MockData { + typealias ResponseType = StripeAPI.FinancialConnectionsSession + var bundle: Bundle { return Bundle(for: ClassForBundle.self) } + + case bothAccountsAndLinkedAccountsPresent = "FinancialConnectionsSession_both_accounts_la" + case onlyAccountsPresent = "FinancialConnectionsSession_only_accounts" + case bothAccountsAndLinkedAccountsMissing = "FinancialConnectionsSession_only_both_missing" + case onlyLinkedAccountsPresent = "FinancialConnectionsSession_only_la" +} + +// Dummy class to determine this bundle +private class ClassForBundle {} + +final class FinancialConnectionsSessionTests: XCTestCase { + + func testBothAccountsAndLinkedAccountsPresentFavorsAccounts() { + guard let session = try? FinancialConnectionsSessionMock.bothAccountsAndLinkedAccountsPresent.make() else { + return XCTFail("Could not load FinancialConnectionsSession") + } + XCTAssertEqual(session.accounts.data.count, 5) + } + + func testOnlyAccountsPresentParsesCorrectly() { + guard let session = try? FinancialConnectionsSessionMock.onlyAccountsPresent.make() else { + return XCTFail("Could not load FinancialConnectionsSession") + } + XCTAssertEqual(session.accounts.data.count, 5) + } + + func testOnlyLinkedAccountsPresentParsesCorrectly() { + guard let session = try? FinancialConnectionsSessionMock.onlyLinkedAccountsPresent.make() else { + return XCTFail("Could not load FinancialConnectionsSession") + } + XCTAssertEqual(session.accounts.data.count, 5) + } + + func testBothAccountsAndLinkedAccountsMissingFailsToParse() { + XCTAssertThrowsError(try FinancialConnectionsSessionMock.bothAccountsAndLinkedAccountsMissing.make()) + } + +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsSheetTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsSheetTests.swift new file mode 100644 index 00000000..490e437d --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsSheetTests.swift @@ -0,0 +1,78 @@ +// +// FinancialConnectionsSheetTests.swift +// StripeFinancialConnectionsTests +// +// Created by Vardges Avetisyan on 11/9/21. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeCoreTestUtils +@testable @_spi(STP) import StripeFinancialConnections +import XCTest + +class EmptySessionFetcher: FinancialConnectionsSessionFetcher { + func fetchSession() -> Future { + return Promise() + } +} + +class FinancialConnectionsSheetTests: XCTestCase { + private let mockViewController = UIViewController() + private let mockClientSecret = "las_123345" + private let mockAnalyticsClient = MockAnalyticsClient() + private let mockApiClient = FinancialConnectionsAPIClient(apiClient: APIStubbedTestCase.stubbedAPIClient()) + + override func setUpWithError() throws { + try super.setUpWithError() + mockAnalyticsClient.reset() + } + + func testAnalytics() { + let sheet = FinancialConnectionsSheet( + financialConnectionsSessionClientSecret: mockClientSecret, + returnURL: nil, + analyticsClient: mockAnalyticsClient + ) + sheet.present(from: mockViewController) { (_: FinancialConnectionsSheet.Result) in } + + // Verify presented analytic is logged + XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 1) + guard + let presentedAnalytic = mockAnalyticsClient.loggedAnalytics.first + as? FinancialConnectionsSheetPresentedAnalytic + else { + return XCTFail("Expected `FinancialConnectionsSheetPresentedAnalytic`") + } + XCTAssertEqual(presentedAnalytic.clientSecret, mockClientSecret) + + // Mock that financialConnections is completed + let host = HostController( + apiClient: mockApiClient, + analyticsClientV1: mockAnalyticsClient, + clientSecret: "test", + elementsSessionContext: nil, + returnURL: nil, + publishableKey: "test", + stripeAccount: nil + ) + sheet.hostController(host, viewController: UIViewController(), didFinish: .canceled) + + // Verify closed analytic is logged + XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 2) + guard let closedAnalytic = mockAnalyticsClient.loggedAnalytics.last as? FinancialConnectionsSheetClosedAnalytic + else { + return XCTFail("Expected `FinancialConnectionsSheetClosedAnalytic`") + } + XCTAssertEqual(closedAnalytic.clientSecret, mockClientSecret) + XCTAssertEqual(closedAnalytic.result, "cancelled") + } + + func testAnalyticsProductUsage() { + _ = FinancialConnectionsSheet( + financialConnectionsSessionClientSecret: mockClientSecret, + returnURL: nil, + analyticsClient: mockAnalyticsClient + ) + XCTAssertEqual(mockAnalyticsClient.productUsage, ["FinancialConnectionsSheet"]) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsWebFlowTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsWebFlowTests.swift new file mode 100644 index 00000000..768caa99 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/FinancialConnectionsWebFlowTests.swift @@ -0,0 +1,228 @@ +// +// FinancialConnectionsWebFlowTests.swift +// StripeFinancialConnectionsTests +// +// Created by Mat Schmid on 2024-09-25. +// + +@_spi(STP) import StripeCore +@testable import StripeFinancialConnections +import XCTest + +final class FinancialConnectionsWebFlowTests: XCTestCase { + func test_noAdditionalParameters_empty() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: nil, + isInstantDebits: false, + linkMode: nil, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertNil(additionalParameters) + } + + func test_someAdditionalParameters_notInstantDebits_noLinkMode() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "", + isInstantDebits: false, + linkMode: nil, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertNil(additionalParameters) + } + + func test_someAdditionalParameters_instantDebits_noLinkMode() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "&testmode=true", + isInstantDebits: true, + linkMode: nil, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&testmode=true&return_payment_method=true&expand_payment_method=true") + } + + func test_additionalParameters_instantDebits_noLinkMode() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "", + isInstantDebits: true, + linkMode: nil, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&return_payment_method=true&expand_payment_method=true") + } + + func test_additionalParameters_notInstantDebits_someLinkMode() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "", + isInstantDebits: false, + linkMode: .passthrough, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertNil(additionalParameters) + } + + func test_someAdditionalParameters_instantDebits_passthroughLinkMode() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "&testmode=true", + isInstantDebits: true, + linkMode: .passthrough, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&testmode=true&return_payment_method=true&expand_payment_method=true&link_mode=PASSTHROUGH") + } + + func test_additionalParameters_instantDebits_linkCardBrandLinkMode() { + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "", + isInstantDebits: true, + linkMode: .linkCardBrand, + prefillDetails: nil, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&return_payment_method=true&expand_payment_method=true&link_mode=LINK_CARD_BRAND") + } + + func test_additionalParameters_emptyPrefillDetails() { + let prefillDetails = ElementsSessionContext.PrefillDetails( + email: nil, + formattedPhoneNumber: nil, + unformattedPhoneNumber: nil, + countryCode: nil + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: nil, + isInstantDebits: false, + linkMode: nil, + prefillDetails: prefillDetails, + billingDetails: nil + ) + XCTAssertNil(additionalParameters) + } + + func test_additionalParameters_prefilledEmail() { + let prefillDetails = ElementsSessionContext.PrefillDetails( + email: "test@example.com", + formattedPhoneNumber: nil, + unformattedPhoneNumber: nil, + countryCode: nil + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: nil, + isInstantDebits: false, + linkMode: nil, + prefillDetails: prefillDetails, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&email=test%40example.com") + } + + func test_additionalParameters_fullPrefillDetails() { + let prefillDetails = ElementsSessionContext.PrefillDetails( + email: "test@example.com", + formattedPhoneNumber: "+1 (123) 456-7890", + unformattedPhoneNumber: "1234567890", + countryCode: "US" + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: nil, + isInstantDebits: false, + linkMode: nil, + prefillDetails: prefillDetails, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&email=test%40example.com&linkMobilePhone=1234567890&linkMobilePhoneCountry=US") + } + + func test_additionalParameters_fullPrefillDetails_instantDebits_passthroughLinkMode() { + let prefillDetails = ElementsSessionContext.PrefillDetails( + email: "test@example.com", + formattedPhoneNumber: "+1 (123) 456-7890", + unformattedPhoneNumber: "1234567890", + countryCode: "US" + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "&testmode=true", + isInstantDebits: true, + linkMode: .passthrough, + prefillDetails: prefillDetails, + billingDetails: nil + ) + XCTAssertEqual(additionalParameters, "&testmode=true&return_payment_method=true&expand_payment_method=true&link_mode=PASSTHROUGH&email=test%40example.com&linkMobilePhone=1234567890&linkMobilePhoneCountry=US") + } + + func test_additionalParameters_emptyBillingDetails() { + let billingDetails = ElementsSessionContext.BillingDetails( + name: nil, + email: nil, + phone: nil, + address: nil + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: nil, + isInstantDebits: false, + linkMode: nil, + prefillDetails: nil, + billingDetails: billingDetails + ) + XCTAssertNil(additionalParameters) + } + + func test_additionalParameters_billingDetails() { + let billingDetails = ElementsSessionContext.BillingDetails( + name: "Foo Bar", + email: "foo@bar.com", + phone: "+1 (123) 456-7890", + address: ElementsSessionContext.BillingDetails.Address( + city: "Toronto", + country: "CA", + line1: "123 Main St", + line2: "", + postalCode: "A0B 1C2", + state: "ON" + ) + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: nil, + isInstantDebits: true, + linkMode: nil, + prefillDetails: nil, + billingDetails: billingDetails + ) + XCTAssertEqual(additionalParameters, "&return_payment_method=true&expand_payment_method=true&billingDetails%5Bname%5D=Foo%20Bar&billingDetails%5Bemail%5D=foo%40bar.com&billingDetails%5Bphone%5D=+1%20(123)%20456-7890&billingDetails%5Baddress%5D%5Bcity%5D=Toronto&billingDetails%5Baddress%5D%5Bcountry%5D=CA&billingDetails%5Baddress%5D%5Bline1%5D=123%20Main%20St&billingDetails%5Baddress%5D%5Bpostal_code%5D=A0B%201C2&billingDetails%5Baddress%5D%5Bstate%5D=ON") + } + + func test_additionalParameters_fullBillingDetails_fullPrefillDetails_instantDebits_passthroughLinkMode() { + let prefillDetails = ElementsSessionContext.PrefillDetails( + email: "test@example.com", + formattedPhoneNumber: "+1 (123) 456-7890", + unformattedPhoneNumber: "1234567890", + countryCode: "US" + ) + let billingDetails = ElementsSessionContext.BillingDetails( + name: "Foo Bar", + email: "foo@bar.com", + phone: "+1 (123) 456-7890", + address: ElementsSessionContext.BillingDetails.Address( + city: "Toronto", + country: "CA", + line1: "123 Main St", + line2: nil, + postalCode: "A0B 1C2", + state: "ON" + ) + ) + let additionalParameters = FinancialConnectionsWebFlowViewController.buildEncodedUrlParameters( + startingAdditionalParameters: "&testmode=true", + isInstantDebits: true, + linkMode: .passthrough, + prefillDetails: prefillDetails, + billingDetails: billingDetails + ) + XCTAssertEqual(additionalParameters, "&testmode=true&return_payment_method=true&expand_payment_method=true&link_mode=PASSTHROUGH&billingDetails%5Bname%5D=Foo%20Bar&billingDetails%5Bemail%5D=foo%40bar.com&billingDetails%5Bphone%5D=+1%20(123)%20456-7890&billingDetails%5Baddress%5D%5Bcity%5D=Toronto&billingDetails%5Baddress%5D%5Bcountry%5D=CA&billingDetails%5Baddress%5D%5Bline1%5D=123%20Main%20St&billingDetails%5Baddress%5D%5Bpostal_code%5D=A0B%201C2&billingDetails%5Baddress%5D%5Bstate%5D=ON&email=test%40example.com&linkMobilePhone=1234567890&linkMobilePhoneCountry=US") + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/FlowRouterTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/FlowRouterTests.swift new file mode 100644 index 00000000..88583891 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/FlowRouterTests.swift @@ -0,0 +1,187 @@ +// +// FlowRouterTests.swift +// StripeFinancialConnectionsTests +// +// Created by Mat Schmid on 2024-07-24. +// + +@_spi(STP) import StripeCoreTestUtils +@testable import StripeFinancialConnections +import XCTest + +class FlowRouterTests: XCTestCase { + private static let exmapleAppNativeOverrideKey = "FINANCIAL_CONNECTIONS_EXAMPLE_APP_ENABLE_NATIVE" + + private enum Experience { + case financialConnections + case instantDebits + } + + var flowRouter: FlowRouter! + let mockAnalyticsClient = FinancialConnectionsAnalyticsClient(analyticsClient: MockAnalyticsClientV2()) + + override func tearDown() { + super.tearDown() + flowRouter = nil + UserDefaults.standard.removeObject(forKey: Self.exmapleAppNativeOverrideKey) + } + + // MARK: Financial Connections + + func testFinancialConnectionsKillswitchActive() { + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .financialConnections, + killswitchActive: true, + nativeExperimentEnabled: true + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .webFinancialConnections) + } + + func testFinancialConnectionsKillswitchNotActive() { + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .financialConnections, + killswitchActive: false, + nativeExperimentEnabled: true + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .nativeFinancialConnections) + } + + func testFinancialConnectionsNativeExperimentDisabled() { + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .financialConnections, + killswitchActive: false, + nativeExperimentEnabled: false + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .webFinancialConnections) + } + + func testFinancialConnectionsNativeSdkOverrideTrue() { + UserDefaults.standard.set(true, forKey: Self.exmapleAppNativeOverrideKey) + + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .financialConnections, + killswitchActive: false, + nativeExperimentEnabled: false + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .nativeFinancialConnections) + } + + func testFinancialConnectionsNativeSdkOverrideFalse() { + UserDefaults.standard.set(false, forKey: Self.exmapleAppNativeOverrideKey) + + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .financialConnections, + killswitchActive: false, + nativeExperimentEnabled: false + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .webFinancialConnections) + } + + // MARK: Instant Debits + + func testInstantDebitsKillswitchActive() { + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .instantDebits, + killswitchActive: true, + nativeExperimentEnabled: true + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .webInstantDebits) + } + + func testInstantDebitsKillswitchNotActive() { + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .instantDebits, + killswitchActive: false, + nativeExperimentEnabled: true + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .nativeInstantDebits) + } + + func testInstantDebitsNativeSdkOverrideTrue() { + UserDefaults.standard.set(true, forKey: Self.exmapleAppNativeOverrideKey) + + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .instantDebits, + killswitchActive: false, + nativeExperimentEnabled: false + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .nativeInstantDebits) + } + + func testInstantDebitsNativeSdkOverrideFalse() { + UserDefaults.standard.set(false, forKey: Self.exmapleAppNativeOverrideKey) + + flowRouter = FlowRouter( + synchronizePayload: synchronizePayload( + experience: .instantDebits, + killswitchActive: false, + nativeExperimentEnabled: false + ), + analyticsClient: mockAnalyticsClient + ) + + XCTAssertEqual(flowRouter.flow, .webInstantDebits) + } + + // MARK: Helpers + + private func synchronizePayload(experience: Experience, killswitchActive: Bool, nativeExperimentEnabled: Bool) -> FinancialConnectionsSynchronize { + FinancialConnectionsSynchronize( + manifest: FinancialConnectionsSessionManifest( + allowManualEntry: false, + consentRequired: false, + customManualEntryHandling: false, + disableLinkMoreAccounts: false, + experimentAssignments: ["connections_mobile_native": nativeExperimentEnabled ? "treatment" : ""], + features: ["bank_connections_mobile_native_version_killswitch": killswitchActive], + instantVerificationDisabled: false, + institutionSearchDisabled: false, + livemode: false, + manualEntryMode: .automatic, + manualEntryUsesMicrodeposits: false, + nextPane: .consent, + permissions: [], + product: experience == .instantDebits ? "instant_debits" : "connections", + singleAccount: true + ), + text: nil, + visual: .init( + reducedBranding: false, + merchantLogo: [], + reduceManualEntryProminenceInErrors: false + ) + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/Info.plist b/StripeFinancialConnections/StripeFinancialConnectionsTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/ManualEntryValidatorTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/ManualEntryValidatorTests.swift new file mode 100644 index 00000000..3d246d27 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/ManualEntryValidatorTests.swift @@ -0,0 +1,76 @@ +// +// ManualEntryValidatorTests.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 8/26/22. +// + +@testable import StripeFinancialConnections +import XCTest + +class ManualEntryValidatorTests: XCTestCase { + + func testValidateRoutingNumber() throws { + XCTAssert(ManualEntryValidator.validateRoutingNumber("") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("1") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("12") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("123") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("1234") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("12345") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("123456") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("1234567") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("12345678") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("123456789") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("123456789") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("1234567890") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("021000021") == nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("011401533") == nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("091000019") == nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("x91000019") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("09100001x") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("0910x0019") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber("-21000021") != nil) + XCTAssert(ManualEntryValidator.validateRoutingNumber(":21000021") != nil) + } + + func testValidateAccountingNumber() throws { + XCTAssert(ManualEntryValidator.validateAccountNumber("") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("1") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("12") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("123") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("1234") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("12345") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("123456") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("1234567") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("12345678") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("0123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("00123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("000123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("0000123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("00000123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("000000123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("0000000123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("00000000123456789") == nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("000000000123456789") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("x0000000123456789") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("-0000000123456789") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumber(":0000000123456789") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("0000000012345678x") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumber("0000000x123456789") != nil) + } + + func testValidateAccountNumberConfirmation() throws { + XCTAssert(ManualEntryValidator.validateAccountNumberConfirmation("", accountNumber: "") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumberConfirmation("1", accountNumber: "1") == nil) + XCTAssert( + ManualEntryValidator.validateAccountNumberConfirmation( + "00000000123456789", + accountNumber: "00000000123456789" + ) == nil + ) + XCTAssert(ManualEntryValidator.validateAccountNumberConfirmation("1", accountNumber: "2") != nil) + XCTAssert(ManualEntryValidator.validateAccountNumberConfirmation("2", accountNumber: "1") != nil) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/MarkdownBoldAttributedStringTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/MarkdownBoldAttributedStringTests.swift new file mode 100644 index 00000000..f0ff2738 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/MarkdownBoldAttributedStringTests.swift @@ -0,0 +1,168 @@ +// +// MarkdownBoldAttributedStringTests.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 9/22/22. +// + +@testable import StripeFinancialConnections +import XCTest + +class MarkdownBoldAttributedStringTests: XCTestCase { + + func testEmptyString() { + let attributedString = NSMutableAttributedString(string: "") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "")) + } + + func testOneAsterisk() { + let attributedString = NSMutableAttributedString(string: "*") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "*")) + } + + func testTwoAsterisk() { + let attributedString = NSMutableAttributedString(string: "**") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "**")) + } + + func testThreeAsterisk() { + let attributedString = NSMutableAttributedString(string: "***") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "***")) + } + + func testFourAsterisk() { + let attributedString = NSMutableAttributedString(string: "****") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "****")) + } + + func testFiveAsterisk() { + let attributedString = NSMutableAttributedString(string: "*****") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "*****")) + } + + func testNoAsterisks() { + let attributedString = NSMutableAttributedString(string: "bold string") + attributedString.addBoldFontAttributesByMarkdownRules( + boldFont: FinancialConnectionsFont.label(.smallEmphasized).uiFont + ) + XCTAssert(attributedString == NSMutableAttributedString(string: "bold string")) + } + + func testOneBold() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let attributedString = NSMutableAttributedString(string: "**One Bold**") + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + XCTAssert( + attributedString + == { + let expectedAttributedString = NSMutableAttributedString(string: "One Bold") + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 0, length: 8)) + return expectedAttributedString + }() + ) + } + + // this is a double-check that tests aren't just returning "true" all the time + func testOneBoldNotEquals() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let attributedString = NSMutableAttributedString(string: "**One Bold**") + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + XCTAssert(attributedString != NSMutableAttributedString(string: "One Bold")) + } + + func testOneBoldComplex() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let attributedString = NSMutableAttributedString(string: "**One - $1.00 Bold**") + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + XCTAssert( + attributedString + == { + let expectedAttributedString = NSMutableAttributedString(string: "One - $1.00 Bold") + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 0, length: 16)) + return expectedAttributedString + }() + ) + } + + func testOneBoldComplexVersionTwo() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let attributedString = NSMutableAttributedString(string: "**One Bold** - $1.00") + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + XCTAssert( + attributedString + == { + let expectedAttributedString = NSMutableAttributedString(string: "One Bold - $1.00") + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 0, length: 8)) + return expectedAttributedString + }() + ) + } + + func testOneBoldWithURL() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let attributedString = NSMutableAttributedString(string: "[**One Bold**](https://www.stripe.com)") + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + XCTAssert( + attributedString + == { + let expectedAttributedString = NSMutableAttributedString( + string: "[One Bold](https://www.stripe.com)" + ) + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 1, length: 8)) + return expectedAttributedString + }() + ) + } + + func testOneBoldWithExistingAttributes() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let url = URL(string: "https://www.stripe.com")! + + let attributedString = NSMutableAttributedString(string: "word **One Bold** word") + attributedString.addAttributes([.link: url], range: NSRange(location: 5, length: 12)) + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + + XCTAssert( + attributedString + == { + let expectedAttributedString = NSMutableAttributedString(string: "word One Bold word") + expectedAttributedString.addAttributes([.link: url], range: NSRange(location: 5, length: 8)) + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 5, length: 8)) + return expectedAttributedString + }() + ) + } + + func testTwoBold() { + let boldFont = FinancialConnectionsFont.label(.smallEmphasized).uiFont + let attributedString = NSMutableAttributedString(string: "word **One Bold** word **Two Bold** word") + attributedString.addBoldFontAttributesByMarkdownRules(boldFont: boldFont) + XCTAssert( + attributedString + == { + let expectedAttributedString = NSMutableAttributedString(string: "word One Bold word Two Bold word") + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 5, length: 8)) + expectedAttributedString.addAttributes([.font: boldFont], range: NSRange(location: 19, length: 8)) + return expectedAttributedString + }() + ) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_both_accounts_la.json b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_both_accounts_la.json new file mode 100644 index 00000000..bdbe3b81 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_both_accounts_la.json @@ -0,0 +1,204 @@ +{ + "id": "fcsess_testststststm", + "object": "link_account_session", + "client_secret": "las_client_secrettest_tests", + "linked_accounts": { + "object": "list", + "data": [ + { + "id": "fca_1asdfe23dsdfs", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "credit", + "created": 1651248322, + "display_name": "Test Credit Card", + "institution_name": "Bank of Testing", + "last4": "6666", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "credit_card", + "supported_payment_method_types": [ + + ] + }, + { + "id": "fca_asdfasdfe", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Tests", + "institution_name": "Bank of Testing", + "last4": "5766", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1sdfwedfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Checking Test", + "institution_name": "Bank of Testing", + "last4": "4242", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + } + ], + "has_more": false, + "total_count": 5, + "url": "/v1/linked_accounts" + }, + "accounts": { + "object": "list", + "data": [ + { + "id": "fca_1KtwJsdsdfdsf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Bank Savings", + "institution_name": "Bank of Testing", + "last4": "7777", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1KtwJsdfadfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Bank", + "institution_name": "Bank of Testing", + "last4": "3333", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + }, + { + "id": "fca_1asdfe23dsdfs", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "credit", + "created": 1651248322, + "display_name": "Test Credit Card", + "institution_name": "Bank of Testing", + "last4": "6666", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "credit_card", + "supported_payment_method_types": [ + + ] + }, + { + "id": "fca_asdfasdfe", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Tests", + "institution_name": "Bank of Testing", + "last4": "5766", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1sdfwedfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Checking Test", + "institution_name": "Bank of Testing", + "last4": "4242", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + } + ], + "has_more": false, + "total_count": 5, + "url": "/v1/linked_accounts" + }, + "livemode": true, + "permissions": [ + "payment_method" + ] +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_accounts.json b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_accounts.json new file mode 100644 index 00000000..037b9438 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_accounts.json @@ -0,0 +1,129 @@ +{ + "id": "fcsess_testststststm", + "object": "link_account_session", + "client_secret": "las_client_secrettest_tests", + "accounts": { + "object": "list", + "data": [ + { + "id": "fca_1KtwJsdsdfdsf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Bank Savings", + "institution_name": "Bank of Testing", + "last4": "7777", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1KtwJsdfadfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Bank", + "institution_name": "Bank of Testing", + "last4": "3333", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + }, + { + "id": "fca_1asdfe23dsdfs", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "credit", + "created": 1651248322, + "display_name": "Test Credit Card", + "institution_name": "Bank of Testing", + "last4": "6666", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "credit_card", + "supported_payment_method_types": [ + + ] + }, + { + "id": "fca_asdfasdfe", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Tests", + "institution_name": "Bank of Testing", + "last4": "5766", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1sdfwedfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Checking Test", + "institution_name": "Bank of Testing", + "last4": "4242", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + } + ], + "has_more": false, + "total_count": 5, + "url": "/v1/linked_accounts" + }, + "livemode": true, + "permissions": [ + "payment_method" + ] +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_both_missing.json b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_both_missing.json new file mode 100644 index 00000000..29d880ac --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_both_missing.json @@ -0,0 +1,9 @@ +{ + "id": "fcsess_testststststm", + "object": "link_account_session", + "client_secret": "las_client_secrettest_tests", + "livemode": true, + "permissions": [ + "payment_method" + ] +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_la.json b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_la.json new file mode 100644 index 00000000..757e27fa --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/MockData/FinancialConnectionsSession_only_la.json @@ -0,0 +1,129 @@ +{ + "id": "fcsess_testststststm", + "object": "link_account_session", + "client_secret": "las_client_secrettest_tests", + "linked_accounts": { + "object": "list", + "data": [ + { + "id": "fca_1KtwJsdsdfdsf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Bank Savings", + "institution_name": "Bank of Testing", + "last4": "7777", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1KtwJsdfadfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Bank", + "institution_name": "Bank of Testing", + "last4": "3333", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + }, + { + "id": "fca_1asdfe23dsdfs", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "credit", + "created": 1651248322, + "display_name": "Test Credit Card", + "institution_name": "Bank of Testing", + "last4": "6666", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "credit_card", + "supported_payment_method_types": [ + + ] + }, + { + "id": "fca_asdfasdfe", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Test Tests", + "institution_name": "Bank of Testing", + "last4": "5766", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "savings", + "supported_payment_method_types": [ + "us_bank_account" + ] + }, + { + "id": "fca_1sdfwedfsdf", + "object": "linked_account", + "balance": null, + "balance_refresh": null, + "category": "cash", + "created": 1651248322, + "display_name": "Checking Test", + "institution_name": "Bank of Testing", + "last4": "4242", + "livemode": true, + "ownership": null, + "ownership_refresh": null, + "permissions": [ + "payment_method" + ], + "status": "active", + "subcategory": "checking", + "supported_payment_method_types": [ + "us_bank_account", + "link" + ] + } + ], + "has_more": false, + "total_count": 5, + "url": "/v1/linked_accounts" + }, + "livemode": true, + "permissions": [ + "payment_method" + ] +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/SessionFetcherTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/SessionFetcherTests.swift new file mode 100644 index 00000000..8347458b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/SessionFetcherTests.swift @@ -0,0 +1,108 @@ +// +// SessionFetcherTests.swift +// StripeFinancialConnectionsTests +// +// Created by Vardges Avetisyan on 1/20/22. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeCoreTestUtils +@testable import StripeFinancialConnections +import XCTest + +class NoMoreAccountSessionAPIClient: EmptyFinancialConnectionsAPIClient { + + // MARK: - Properties + + private let hasMore: Bool + + // MARK: - Init + + init(hasMore: Bool) { + self.hasMore = hasMore + } + + // MARK: - FinancialConnectionsAPIClient + + override func fetchFinancialConnectionsAccounts(clientSecret: String, startingAfterAccountId: String?) -> Promise< + StripeAPI.FinancialConnectionsSession.AccountList + > { + let account = StripeAPI.FinancialConnectionsAccount( + balance: nil, + balanceRefresh: nil, + ownership: nil, + ownershipRefresh: nil, + displayName: nil, + institutionName: "bank", + last4: nil, + category: .credit, + created: 3, + id: "12", + livemode: false, + permissions: nil, + status: .active, + subcategory: .checking, + supportedPaymentMethodTypes: [.usBankAccount] + ) + let fullList = StripeAPI.FinancialConnectionsSession.AccountList(data: [account], hasMore: false) + return Promise(value: fullList) + } + + override func fetchFinancialConnectionsSession(clientSecret: String) -> Promise< + StripeAPI.FinancialConnectionsSession + > { + let fullList = StripeAPI.FinancialConnectionsSession.AccountList(data: [], hasMore: hasMore) + let sessionWithFullAccountList = StripeAPI.FinancialConnectionsSession( + clientSecret: "las", + id: "1234", + accounts: fullList, + livemode: false, + paymentAccount: nil, + bankAccountToken: nil, + status: nil, + statusDetails: nil + ) + return Promise(value: sessionWithFullAccountList) + } +} + +class SessionFetcherTests: XCTestCase { + + func testShouldNotFetchAccountsIfSessionIsExhaustive() { + let api = NoMoreAccountSessionAPIClient(hasMore: false) + let accountFetcher = FinancialConnectionsAccountAPIFetcher(api: api, clientSecret: "las") + let fetcher = FinancialConnectionsSessionAPIFetcher( + api: api, + clientSecret: "las", + accountFetcher: accountFetcher + ) + + fetcher.fetchSession().observe { (result) in + switch result { + case .success(let session): + XCTAssertEqual(session.accounts.data.count, 0) + case .failure: + XCTFail() + } + } + } + + func testShouldFetchMoreAccountsIfSessionHasMore() { + let api = NoMoreAccountSessionAPIClient(hasMore: true) + let accountFetcher = FinancialConnectionsAccountAPIFetcher(api: api, clientSecret: "las") + let fetcher = FinancialConnectionsSessionAPIFetcher( + api: api, + clientSecret: "las", + accountFetcher: accountFetcher + ) + + fetcher.fetchSession().observe { (result) in + switch result { + case .success(let session): + XCTAssertEqual(session.accounts.data.count, 1) + case .failure: + XCTFail() + } + } + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/SoftLinkTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/SoftLinkTests.swift new file mode 100644 index 00000000..eada4da8 --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/SoftLinkTests.swift @@ -0,0 +1,19 @@ +// +// SoftLinkTests.swift +// StripeFinancialConnectionsTests +// +// Created by Vardges Avetisyan on 3/4/22. +// + +import Foundation +@_spi(STP) import StripeCore +import XCTest + +class SoftLinkTest: XCTestCase { + func testLoadingImplementationClass() { + let klass = + NSClassFromString("StripeFinancialConnections.FinancialConnectionsSDKImplementation") + as? FinancialConnectionsSDKInterface.Type + XCTAssertNotNil(klass) + } +} diff --git a/StripeFinancialConnections/StripeFinancialConnectionsTests/StringExtensionsTests.swift b/StripeFinancialConnections/StripeFinancialConnectionsTests/StringExtensionsTests.swift new file mode 100644 index 00000000..c5cf880b --- /dev/null +++ b/StripeFinancialConnections/StripeFinancialConnectionsTests/StringExtensionsTests.swift @@ -0,0 +1,51 @@ +// +// StringExtensionsTests.swift +// StripeFinancialConnectionsTests +// +// Created by Krisjanis Gaidis on 7/14/22. +// + +@testable import StripeFinancialConnections +import XCTest + +class StringExtensionsTests: XCTestCase { + + private let nonBreakingSpace = "\u{00a0}" + + func testExtractingLinksFromString() throws { + XCTAssert("Not Equal Test".extractLinks() != ("Wrong Word", [])) + XCTAssert("No Link".extractLinks() == ("No Link", [])) + XCTAssert( + "[One Link](https://www.stripe.com/terms)".extractLinks() + == ( + "One\(nonBreakingSpace)Link", + [String.Link(range: NSRange(location: 0, length: 8), urlString: "https://www.stripe.com/terms")] + ) + ) + XCTAssert( + "[Complex Link](https://stripe.com/docs/api/financial_connections/ownership/object#financial_connections_ownership_object-id)" + .extractLinks() + == ( + "Complex\(nonBreakingSpace)Link", + [ + String.Link( + range: NSRange(location: 0, length: 12), + urlString: + "https://stripe.com/docs/api/financial_connections/ownership/object#financial_connections_ownership_object-id" + ), + ] + ) + ) + XCTAssert( + "Word [Link 1](https://www.stripe.com/link1) word [Link 2](https://www.stripe.com/link2) word" + .extractLinks() + == ( + "Word Link\(nonBreakingSpace)1 word Link\(nonBreakingSpace)2 word", + [ + String.Link(range: NSRange(location: 5, length: 6), urlString: "https://www.stripe.com/link1"), + String.Link(range: NSRange(location: 17, length: 6), urlString: "https://www.stripe.com/link2"), + ] + ) + ) + } +} diff --git a/StripeIdentity/README.md b/StripeIdentity/README.md new file mode 100644 index 00000000..e78b2300 --- /dev/null +++ b/StripeIdentity/README.md @@ -0,0 +1,58 @@ +# Stripe Identity iOS SDK + +The Stripe Identity iOS SDK makes it quick and easy to verify your user's identity in your iOS app. We provide a prebuilt UI to collect your user's ID documents, match photo ID with selfies, and validate ID numbers. + +> To get access to the Identity iOS SDK, visit the [Identity Settings](https://dashboard.stripe.com/settings/identity) page and click **Enable**. + +## Table of contents + + +* [Features](#features) +* [Requirements](#requirements) +* [Getting started](#getting-started) + * [Integration](#integration) + * [Example](#example) +* [Manual linking](#manual-linking) + + + +## Features + +**Simplified security**: We've made it simple for you to securely collect your user's personally identifiable information (PII) such as identity document images. Sensitive PII data is sent directly to Stripe Identity instead of passing through your server. For more information, see our [integration security guide](https://stripe.com/docs/security). + +**Automatic document capture**: We automatically capture images of the front and back of government-issued photo ID to ensure a clear and readable image. + +**Selfie matching**: We capture photos of your user's face and review it to confirm that the photo ID belongs to them. For more information, see our guide on [adding selfie checks](https://stripe.com/docs/identity/selfie). + +**Identity information collection**: We collect name, date of birth, and government ID number to validate that it is real. + +**Prebuilt UI**: We provide [`IdentityVerificationSheet`](https://stripe.dev/stripe-ios/stripe-identity/Classes/IdentityVerificationSheet.html), a prebuilt UI that combines all the steps required to collect ID documents, selfies, and ID numbers into a single sheet that displays on top of your app. + +**Automated verification**: Stripe Identity's automated verification technology looks for patterns to help determine if an ID document is real or fake and uses distinctive physiological characteristics of faces to match your users' selfies to photos on their ID document. Collected identity information is checked against a global set of databases to confirm that it exists. Learn more about the [verification checks supported by Stripe Identity](https://stripe.com/docs/identity/verification-checks), [accessing verification results](https://stripe.com/docs/identity/access-verification-results), or our integration guide on [handling verification outcomes](https://stripe.com/docs/identity/handle-verification-outcomes). + +## Requirements + +The Stripe Identity iOS SDK is compatible with apps targeting iOS 13.0 or above. + +If you intend to use this SDK with Stripe's Identity service, you must not modify this SDK. Using a modified version of this SDK with Stripe's Identity service, without Stripe's written authorization, is a breach of your agreement with Stripe and may result in your Stripe account being shut down. + + +## Getting started + +### Integration + +Get started with Stripe Identity's [📚 iOS integration guide](https://stripe.com/docs/identity/verify-identity-documents?platform=ios) and [example project](../Example/IdentityVerification%20Example), or [📘 browse the SDK reference](https://stripe.dev/stripe-ios/stripe-identity/index.html) for fine-grained documentation of all the classes and methods in the SDK. + +> Identity SDK uses camera to scan documents and selfies, you'll need to set `NSCameraUsageDescription` in your application's plist, and provide a reason for accessing the camera (e.g. "This app uses the camera to take a picture of your identity documents."). + +### Example + +[Identity Verification Example](../Example/IdentityVerification%20Example) – This example demonstrates how to capture your users' ID documents on iOS and securely send them to Stripe Identity for identity verification. + +## Manual linking + +If you link the Stripe Identity library manually, use a version from our [releases](https://github.com/stripe/stripe-ios/releases) page and make sure to embed all of the following frameworks: +- `StripeIdentity.xcframework` +- `StripeCore.xcframework` +- `StripeCameraCore.xcframework` +- `StripeUICore.xcframework` diff --git a/StripeIdentity/StripeIdentity.xcodeproj/project.pbxproj b/StripeIdentity/StripeIdentity.xcodeproj/project.pbxproj new file mode 100644 index 00000000..8d353a25 --- /dev/null +++ b/StripeIdentity/StripeIdentity.xcodeproj/project.pbxproj @@ -0,0 +1,1990 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 047B7B3A70037FA1172A164C /* HeaderViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94838BCA1111F735A8FBC072 /* HeaderViewSnapshotTest.swift */; }; + 063C2C263111DA52ECC92DC0 /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94605BCADE912561ED6F710 /* STPLocalizedString.swift */; }; + 0758EE8C7A032BF378F6AA59 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 2714B21F1CD18CEED8772AB6 /* OHHTTPStubsSwift */; }; + 0C3C571F4C5E8B00ED18A004 /* IndividualViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9145699AC820A2F2F916F142 /* IndividualViewControllerTest.swift */; }; + 0CC741018E3A07A925A4CCC3 /* FaceScanner+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 725E481300FDDDD41EBAA835 /* FaceScanner+API.swift */; }; + 0FBE6D53EBE7C2B93DEA34B4 /* IdentityFlowNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050900C738C01F6D96F7ED5B /* IdentityFlowNavigationController.swift */; }; + 10B606812179D64E67BFFFCF /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D0F4646D7565055396DD8F5 /* StripeCore.framework */; }; + 12542E187994CC0683E2E3D8 /* DocumentUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C334F59C93D3D11534FACBBA /* DocumentUploader.swift */; }; + 135B58FCD4663D3D8E4C82F7 /* VerificationPage_200.json in Resources */ = {isa = PBXBuildFile; fileRef = 3218882B7E1610E17BDB4522 /* VerificationPage_200.json */; }; + 1D4D9CDF6B1D335963849940 /* VerificationSheetAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70585F354B2B8B1420311CB /* VerificationSheetAnalytics.swift */; }; + 1DB3040542D8274848932380 /* TimeInterval+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E5237659945E4A05ADE6DB0 /* TimeInterval+StripeIdentity.swift */; }; + 1E48ABBE603C4DB065DD2093 /* CGImage+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 744CE13E8488F0E4AC951D6C /* CGImage+StripeIdentity.swift */; }; + 1ED629559BED28AC3FAB8573 /* ErrorViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3943F18216635D56A3E6019C /* ErrorViewControllerSnapshotTest.swift */; }; + 230C42264D444023F416A7A1 /* IdentityAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59680E002FC6E7BC9906BD2 /* IdentityAPIClientTest.swift */; }; + 24E6928BEA4044DB779D4189 /* VNBarcodeSymbology+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C78174C0059A8957832825 /* VNBarcodeSymbology+StripeIdentity.swift */; }; + 2522A0773650573501106F4D /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D40A569C2BB62F86D57C01 /* Image.swift */; }; + 25A7B4E2E5CB3FD549F6819B /* InstructionListViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8389CF3DBB12A384BCF0959F /* InstructionListViewSnapshotTest.swift */; }; + 286BF86B773F41210E015B2B /* IdentityDataCollecting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9CB453179082B1AE6B242FD /* IdentityDataCollecting.swift */; }; + 28CAAFE7E31A8D17233615F3 /* VerificationSheetFlowControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69C1BC7C63512FE24B32433 /* VerificationSheetFlowControllerTest.swift */; }; + 28D0DEBE960D92E18CEB4D71 /* Enums+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26F8082A859AC1F5C5A3D13 /* Enums+CustomStringConvertible.swift */; }; + 29E974CDD5827CF390349EA4 /* FaceScannerOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3E90C7076A10469E4893B73 /* FaceScannerOutput.swift */; }; + 2B7C095E1029C163D107CF68 /* ListViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D82EAF85863CAAA250E393 /* ListViewSnapshotTest.swift */; }; + 2E943A5B252D7927CE3C32E3 /* IdentityVerificationSheetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD5FEA0E4E7EF1A4BA9646B /* IdentityVerificationSheetError.swift */; }; + 313F5F812B0BE5DE00BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F802B0BE5DE00BD98A9 /* Docs.docc */; }; + 33F0FF8DB7D784C95647B693 /* FaceCaptureData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4943A54EF548F28DB66DD1F /* FaceCaptureData.swift */; }; + 349A4B5CC24DF5539B773A84 /* MLDetectorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18593C820FE0E35DDEE36956 /* MLDetectorConfiguration.swift */; }; + 34ABF501E050B043CE1E859C /* IdentityImageUploaderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F08E49ED4B7010002D5FC734 /* IdentityImageUploaderTest.swift */; }; + 34C09421E30F9BA262AB1532 /* CGImage+StripeIdentityUnitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEBC1A25D2C446219E7E733 /* CGImage+StripeIdentityUnitTest.swift */; }; + 36F0967E814234FE61EF3A0B /* VerificationPageDataDob.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E9FA49B13AC0A8826F8257 /* VerificationPageDataDob.swift */; }; + 375C63BC59BFC5F8615E266F /* VerificationPageData_no_errors.json in Resources */ = {isa = PBXBuildFile; fileRef = 3AA3F3B346FFC8E58BF8739A /* VerificationPageData_no_errors.json */; }; + 3B9DB565B6C06BC058BF62D6 /* IndividualViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E703FD28F5352DFC4115F1C /* IndividualViewControllerSnapshotTest.swift */; }; + 3C228BF28D1D56F1E74AE7AB /* VerificationPageData_submitted.json in Resources */ = {isa = PBXBuildFile; fileRef = 99607F36AF24C978953964E9 /* VerificationPageData_submitted.json */; }; + 3CB793BE23FC9716A0F35CCB /* IdentityElementsFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF56E95E47DA51C3517E1EC /* IdentityElementsFactoryTest.swift */; }; + 3D9D1BA3B4C7E2464D457FF7 /* VerificationPageDataRequirementError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E15397783A643B74921A00 /* VerificationPageDataRequirementError.swift */; }; + 3E64C28D0E13D6F9F455C3F2 /* SuccessViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FF19274A29802CDB696D1DF /* SuccessViewControllerSnapshotTest.swift */; }; + 3F9FB3BC02DDC7BCF4207F73 /* MotionBlurDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F22CC7DFA4B98A1A533CE0 /* MotionBlurDetector.swift */; }; + 40609779D5BD7D6AE61FE01D /* PhoneOtpViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F4C291ACBB73ED95504131 /* PhoneOtpViewControllerTest.swift */; }; + 41B1C1B08CA7DB8C18A7C9C4 /* VerificationPage_type_address.json in Resources */ = {isa = PBXBuildFile; fileRef = EA7EF0C427E327A1E945BC28 /* VerificationPage_type_address.json */; }; + 443F1D1D9FE899158D58461E /* cgimage_stripeidentity_test.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F772F905FB81552FA1728B2 /* cgimage_stripeidentity_test.png */; }; + 45B515AC217F10059EE155E2 /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; productRef = 960247DC321CBDB422FE713D /* iOSSnapshotTestCase */; }; + 46A6C4DCF553DF24F3674002 /* VerificationSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5082A4100CEE0C8378AD185 /* VerificationSheetController.swift */; }; + 471F567D8201BBA6D82D9932 /* front_drivers_license.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 280A8D95B0045A940A917B1A /* front_drivers_license.jpg */; }; + 47E285A86960CDE275E5EA53 /* VerificationPageDataFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14244B002D9F37B56BE8D4F4 /* VerificationPageDataFace.swift */; }; + 49FE192DE8D8DA9342A5AE60 /* IdentityTextButtonElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E1EF4A7AA64856F9C887C2A /* IdentityTextButtonElement.swift */; }; + 4A5FEB151B079C41FD40FED5 /* StripeCameraCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14C231755842F322A25160ED /* StripeCameraCoreTestUtils.framework */; }; + 4A6D49DED2555FBDACF13B3F /* VerificationFlowWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658F7A165D731F5046ACDF29 /* VerificationFlowWebView.swift */; }; + 4A824CD6A0A518F616D428CF /* FaceScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D2F49DD0BAC5FEF308BA165 /* FaceScanner.swift */; }; + 4B46A1DC7BAE3ED249C4EF05 /* VerificationPageDataName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110A0200099F462B576F2ED4 /* VerificationPageDataName.swift */; }; + 4C9D354F6C1E9E89B13D0367 /* AnimatedBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA099682E4BE0A34CFCBE6C /* AnimatedBorderView.swift */; }; + 4D4430064CB7F2CE3AD3266A /* VerificationFlowWebViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EB7348CE21DB1A86706532 /* VerificationFlowWebViewControllerTest.swift */; }; + 4E39FB668EC68320579F23BC /* IDDetectorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDF024DA4F519CB21A717AA /* IDDetectorOutput.swift */; }; + 4E44D8C9967E301DB34EB6F0 /* VerificationPageDataDocumentFileData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083865663173CDD9F77417A4 /* VerificationPageDataDocumentFileData.swift */; }; + 4E6AF5DD963FAB8B6E06F482 /* ImageScanningSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99DB710C60AC2A98449D51 /* ImageScanningSession.swift */; }; + 4F20C4AF111778413B77DA22 /* DocumentScannerOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E1204F31DC9F8FEA0D312A /* DocumentScannerOutput.swift */; }; + 4FAB896404856F5AED99B89B /* DocumentScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D7B9CB1FF34AE667C65B3 /* DocumentScanner.swift */; }; + 50B7C62A3845596BB256BC3E /* VerificationPageDataPhone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9429D10FEFFFEFEE08DBA4F8 /* VerificationPageDataPhone.swift */; }; + 51DCAEAEEA0B32038A989A51 /* MLModelLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748D38C4370E78DBC9E20E95 /* MLModelLoader.swift */; }; + 541CA755717969560BF2C694 /* FaceScannerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E0AD187733319776268351 /* FaceScannerConfiguration.swift */; }; + 548C24D5A0F63E60BC83676F /* DocumentUploaderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE691D40B29AB0F31443479A /* DocumentUploaderMock.swift */; }; + 54909D14587287B674027374 /* CGImage_StripeIdentitySnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1096AFFD3877D9A22703E6CA /* CGImage_StripeIdentitySnapshotTest.swift */; }; + 54C882D8194AC39EB58BCDE7 /* DocumentCaptureViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA7F9B0204BF18C483A474A /* DocumentCaptureViewControllerTest.swift */; }; + 56299123BD0C28891DD7EFEC /* IdentityTopLevelDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 467BA77070B91055F1F2DCD4 /* IdentityTopLevelDestination.swift */; }; + 5642050BB7FEB20E05B41DCD /* IndividualWelcomeViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4952611570326FFC52F0F30 /* IndividualWelcomeViewControllerTest.swift */; }; + 56583B45EA53659E843E35F6 /* VerificationPageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3B539D323CF2AACB580596B /* VerificationPageData.swift */; }; + 590772C305CDA9FE9157F92C /* TruncatedDecimalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6918DC6A9681E769D44BC78 /* TruncatedDecimalTest.swift */; }; + 5B4F08774CC9CA1D93216D0D /* VerificationFlowWebViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91362066B0F066C00CB45F2 /* VerificationFlowWebViewTest.swift */; }; + 5BC21FBB6B4A1B1C8EC04270 /* VerificationPageCollectedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F4F55F9FBE67FCFE3767816 /* VerificationPageCollectedData.swift */; }; + 5DD28B1BE8DF2CC04E9190E5 /* MLDetectorMetricsTrackerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 786574907AC9065E6E658D9C /* MLDetectorMetricsTrackerMock.swift */; }; + 5E00E8AC4088548403B3E630 /* VerificationFlowWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A449D0BE66D0A555183AF4 /* VerificationFlowWebViewController.swift */; }; + 60B3D5B1B19D31D69D5049A0 /* SelfieUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB8E3DF263505065E8545AB /* SelfieUploader.swift */; }; + 6195A200621E125B15A80285 /* VerificationPage_200_testMode.json in Resources */ = {isa = PBXBuildFile; fileRef = 16ADAC122F0818BB64C5EF41 /* VerificationPage_200_testMode.json */; }; + 66BD1F1CB366B61D1ECD0E84 /* IdentityAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCC019FA1D6D8AEB598D9EC5 /* IdentityAnalyticsClient.swift */; }; + 683FA0F250620BE62EFA2807 /* ListItemViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65B0A0F124B3E0C5490D2B6 /* ListItemViewSnapshotTest.swift */; }; + 692A9E2ADC11AB3D26A92780 /* IndividualFormElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0CF1B086B99DEEF4F4F2A1D /* IndividualFormElement.swift */; }; + 695E12945F47ED3060B8AE19 /* StripeCameraCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB639F497EF62CAE81822820 /* StripeCameraCore.framework */; }; + 6A06F81B7D6B68EA72A293F5 /* StripeIdentity.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C69B4A6F36A8BD814CB05D9 /* StripeIdentity.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6A1B2E52B09C2D436CF7794D /* IdentityVerificationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F1B93DB4F1708B41125301 /* IdentityVerificationSheet.swift */; }; + 6B8DDA62E6ABFBC770A8AC86 /* ImageScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 149453E1854C9566CC8D8484 /* ImageScanner.swift */; }; + 6C1E79C83069CDD64BF03563 /* back_drivers_license.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9048C4B9F47CE82127D6C1B3 /* back_drivers_license.jpg */; }; + 6DC8C4AFA61DFDA20278114F /* VerificationPageData_submitted_not_closed.json in Resources */ = {isa = PBXBuildFile; fileRef = B38AE7F15E61D960EFA15659 /* VerificationPageData_submitted_not_closed.json */; }; + 6F896A9A689A7524F2AE823C /* SelfieWarmupViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DB72C7F61A4A042D898236 /* SelfieWarmupViewSnapshotTest.swift */; }; + 709DB807A26A17B4571EA9D8 /* VerificationPage_type_doc_require_address.json in Resources */ = {isa = PBXBuildFile; fileRef = 2FC99DD88994E0DB581E19C6 /* VerificationPage_type_doc_require_address.json */; }; + 74701BC18362926FDED42E5A /* VerificationFlowResult+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FB883B057ED79F2C58BC58 /* VerificationFlowResult+Equatable.swift */; }; + 752E548B7E39B3CD8FC2F76A /* VerificationSheetFlowControllerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CDAA5C16C934746013C6CE6 /* VerificationSheetFlowControllerError.swift */; }; + 760BDB66D60C8CCC7B72577C /* VerificationPage_type_idNumber.json in Resources */ = {isa = PBXBuildFile; fileRef = FCD3770DF9B6356D1A82870A /* VerificationPage_type_idNumber.json */; }; + 76890B79CA3C83BBD7362065 /* VerifyWebURLHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75A138199A6CC2DB31DD2D5 /* VerifyWebURLHelper.swift */; }; + 7797883309701F3BB36B1F20 /* VerificationPage_require_live_capture.json in Resources */ = {isa = PBXBuildFile; fileRef = 7FD2B7B0EDA3144115F080BC /* VerificationPage_require_live_capture.json */; }; + 78BC6F625665CE6EAB33EA62 /* ImageScannerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B804DA867C37809656BB3 /* ImageScannerMock.swift */; }; + 79BA026188FE0DB22ECE1BB0 /* VisionBasedDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E170B07B4958901EE3864A4 /* VisionBasedDetector.swift */; }; + 7C261F9C94175AD5345D384A /* IdentityHTMLViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2AF434F2405C5F38F259112 /* IdentityHTMLViewSnapshotTest.swift */; }; + 7CA57E67AC14DA90AE87743B /* ListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3BED2C3A9B548341D73D3CC /* ListItemView.swift */; }; + 7D4F147D0CEC17856D070EC2 /* SelfieWarmupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4023102F8B76C16522BC02B9 /* SelfieWarmupView.swift */; }; + 7F18785CB587E7F36288BF02 /* VerificationPage_200_no_consent_header.json in Resources */ = {isa = PBXBuildFile; fileRef = 7ECA816022703F05AD759897 /* VerificationPage_200_no_consent_header.json */; }; + 812B45A86DD6B28A06A8FAC7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 989A2C5379C60B9F0E0E2711 /* Localizable.strings */; }; + 81D31513A44A5B3A8830FC7C /* DebugViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEB322F07A4F2BE5B1FED0AD /* DebugViewControllerTest.swift */; }; + 867C0D3782143FC44501485C /* UINavigationController+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF0871DEB2B2BA56EFADEEA6 /* UINavigationController+StripeIdentity.swift */; }; + 869F5D2A387FED3C1D1674BA /* PhoneOtpViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DAFC23BB4CDD7DEB3286704 /* PhoneOtpViewControllerSnapshotTest.swift */; }; + 874EF196DF573CA524785AEF /* SelfieWarmupViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AFCD229BD9CB58303E19111 /* SelfieWarmupViewControllerTest.swift */; }; + 87A4B9B848DF1CFA11F32FC4 /* VerificationPageClearData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FC19E901C6E57D948833FD /* VerificationPageClearData.swift */; }; + 8802941AB1CAFB81BDE75249 /* VerificationPage_type_phone.json in Resources */ = {isa = PBXBuildFile; fileRef = E21D37D0CA9F5426F6318ADC /* VerificationPage_type_phone.json */; }; + 882C201AF4C8F48816DB740F /* IdentityElementsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC3450DDBE415B82707F4B /* IdentityElementsFactory.swift */; }; + 89562A38C9B3C7870B5F22F2 /* VerificationPage_type_doc_require_idNumber_and_address.json in Resources */ = {isa = PBXBuildFile; fileRef = 531D9220055F0F54CF6D72CE /* VerificationPage_type_doc_require_idNumber_and_address.json */; }; + 8DACF642D2E0C5FE35637EAE /* VerificationClientSecretTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD771C14C0711019D8AC9C2 /* VerificationClientSecretTest.swift */; }; + 8E420E7B59168A4B81C19123 /* BiometricConsentViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B264A8F97FA8CB0706F140F /* BiometricConsentViewControllerTest.swift */; }; + 8E5D382BF426061CDD83197B /* ImageScanningConcurrencyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC8F619B76D191FF2DEC8660 /* ImageScanningConcurrencyManager.swift */; }; + 8EB5D825D1ADC9DAB6E7D5A3 /* DocumentCaptureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF040F11A7D0693A7783654 /* DocumentCaptureView.swift */; }; + 924DE7C0E856DD2C5B80D53A /* Array+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC057ECE030E8374AD9CF61 /* Array+StripeIdentity.swift */; }; + 92B9210AA596D5A135F24710 /* SelfieUploader+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EFF781FE9ED055F99F767D3 /* SelfieUploader+API.swift */; }; + 9665F0EF90739A5D033C03A6 /* DocumentSide.swift in Sources */ = {isa = PBXBuildFile; fileRef = E310DFA6CAC355B848E82998 /* DocumentSide.swift */; }; + 96C17BE1FF29C4A24FAFFDBB /* IdentityFlowViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E7C993DB897B55B2A172CD9 /* IdentityFlowViewSnapshotTest.swift */; }; + 981D82CFC8B9EBF104B166F7 /* VerificationSheetFlowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AF8558B79CD52154D6EF3B /* VerificationSheetFlowController.swift */; }; + 99243751D031F96B17286315 /* DocumentFileUploadViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0B317274417308FBA55E55 /* DocumentFileUploadViewControllerTest.swift */; }; + 9D73810AC9A6AF921038DC54 /* DocumentScannerConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DE93D9C60DFB779F71D5DC /* DocumentScannerConfiguration.swift */; }; + 9F07B2D6AEC03BCCBCCF5908 /* AnimatedBorderViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31C88110FBCDDA9FB912491 /* AnimatedBorderViewSnapshotTest.swift */; }; + 9F3790F6AB77036ADCC4B2C8 /* IdentityMLModelLoaderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111A55FA39B400E6E87A5175 /* IdentityMLModelLoaderMock.swift */; }; + A10A4F91CF1ECB610909ECD5 /* BiometricConsentViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957545FFD547CDA1CFB719E6 /* BiometricConsentViewControllerSnapshotTest.swift */; }; + A41FF7CAF8EBEBCB16115DCB /* DocumentUploaderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FE476C4E5F3E281B7E6E136 /* DocumentUploaderTest.swift */; }; + A44BC25EF5F443C7D0CBB783 /* VerificationSheetControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F459D2DF594C15C03585F3AF /* VerificationSheetControllerMock.swift */; }; + A6E190F4F1E9D599C2422624 /* VerificationSheetFlowControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CAC6276996043D1B3FC92F7 /* VerificationSheetFlowControllerMock.swift */; }; + A7AC2D0D9FDCF8AC5298708D /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD376CBC869531C40036081B /* StripeUICore.framework */; }; + A7B8782A3E807D11A6E6A239 /* VerificationSheetAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F06CA1B36DA48B8E8BD7A5 /* VerificationSheetAnalyticsTest.swift */; }; + A8EDE16782B8ABEB359CADAC /* IdentityVerificationSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D5C67D8BB38BC3EA6282FF2 /* IdentityVerificationSheetTest.swift */; }; + A8EEE8C2CD83374877393E8C /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D4A09C8B4EBF06D00B80994 /* ListView.swift */; }; + A9C41FB2EB8034BCCD6CAC29 /* StripeIdentityBundleLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE8340912F2A489BB80D39B /* StripeIdentityBundleLocator.swift */; }; + AB4F7B2D348FAA2DD0BB40BD /* ErrorViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1FCF877D109689CA724EB4 /* ErrorViewSnapshotTest.swift */; }; + AB6B3BDF7D8F3F84EEA7208A /* StripeCore+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CA21A528C7D39BAA2F2B51 /* StripeCore+Import.swift */; }; + ABC1E2DDC2A4D02702A19E2B /* IndividualElementTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E8B042346D6DF41AA6CCFC /* IndividualElementTest.swift */; }; + AD1ADA8B98C1FDB5C8C0A39F /* BarcodeDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63826305A96644687BF980CC /* BarcodeDetector.swift */; }; + ADA2674008520B832585A8EE /* IDDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB41D1D6B1D35C92EA6F2B7 /* IDDetector.swift */; }; + AF16352FECF8EE27ECD6AAC9 /* MLDetectorMetricsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0989CE5CF80CF69BD28675DE /* MLDetectorMetricsTracker.swift */; }; + AF48EAD4D52EB59134652A08 /* SnapshotTestMockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037D27A0AAECB846C8579452 /* SnapshotTestMockData.swift */; }; + AF90AF76409959324FB270EA /* HeaderIconViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C30DB2230DF9B47CD20AAF /* HeaderIconViewSnapshotTest.swift */; }; + B2377EEDCE440269E0766410 /* IdentityImageUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4C3F2BAFB6BEFFFC1B566F /* IdentityImageUploader.swift */; }; + B292E22D9DD3198D44F249A7 /* SelfieCaptureViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95321B67AA78B88A32E37BE /* SelfieCaptureViewSnapshotTest.swift */; }; + B5B5C232C6541F15AEF3C4BC /* StripeIdentity.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D604F6AABE4D6D5CCF802685 /* StripeIdentity.framework */; }; + B6A52680DC3B117DD0EE13E2 /* VerificationPage_type_doc_require_idNumber.json in Resources */ = {isa = PBXBuildFile; fileRef = CCB84457F36C8EA327239852 /* VerificationPage_type_doc_require_idNumber.json */; }; + B6D4EF6BB1A9178AFA18675B /* SelfieScanningViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD7373453206D23A9ABF5E17 /* SelfieScanningViewSnapshotTest.swift */; }; + B6E0CD4C1CDE03985005F08D /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5726517468179B69D50692C0 /* StripeCoreTestUtils.framework */; }; + B729E396A57DC7E1DC3D1FBA /* DocumentScanningViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67F4A7C86FF6784C6844649 /* DocumentScanningViewSnapshotTest.swift */; }; + B8E47E623C9E4DAF53C3520C /* NonMaxSuppression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93F2EE0B99059C7BFB72AFAF /* NonMaxSuppression.swift */; }; + BA655B97F1F31F3B8BA14199 /* ImageScanningConcurrencyManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005D7E5D0E239D61ED1E9796 /* ImageScanningConcurrencyManagerMock.swift */; }; + BD4686589B16ED96A748CCA8 /* DocumentScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A26F37BDBB5330AE87E7CB /* DocumentScanningView.swift */; }; + BDF0199A4A88D5E66751A033 /* InstructionalDocumentScanningViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C54327529E9A3BD81F37F4 /* InstructionalDocumentScanningViewSnapshotTest.swift */; }; + BDF8D30919A290A0CF52059C /* header_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7CD11009097B994FEFF5393E /* header_icon.png */; }; + BFA24F4BD97F5BE9F5C0E934 /* IdentityUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEB6AB9A9BAF6E50B962C339 /* IdentityUI.swift */; }; + C1AA4B758A20557CDBE72FD9 /* SelfieScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18BE1F9B0EB046BF0E1C2D8B /* SelfieScanningView.swift */; }; + C1DF05059A82FE217A7831BA /* mock.html in Resources */ = {isa = PBXBuildFile; fileRef = ADA1D4AB1AE9B0E45C68FFD2 /* mock.html */; }; + C258E3B8806DFD2C288089DE /* VerificationPage_200_submitted.json in Resources */ = {isa = PBXBuildFile; fileRef = E0B9A86D071960D13AE81563 /* VerificationPage_200_submitted.json */; }; + C378303DE7D3674CE4C771B7 /* DocumentType+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE1984CE45A111B5694A3F49 /* DocumentType+StripeIdentity.swift */; }; + C4BBB6E780EA8288EB46E795 /* ImageScanningSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE163E90F54FE4A075C7B74 /* ImageScanningSessionDelegate.swift */; }; + C5DB8461793523F9488DBBFA /* MLModelUnexpectedOutputError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FD2C7D94586AB8984DB7389 /* MLModelUnexpectedOutputError.swift */; }; + C5E8CA0B4DFEF0F017AEE8D7 /* NSAttributedString+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42768A09155B5439E724B360 /* NSAttributedString+HTML.swift */; }; + C63EAF360DB9887856543E68 /* ErrorViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20129AFBC6B4D304B494CBA6 /* ErrorViewControllerTest.swift */; }; + C6DEF5EEB22CC714DAC66887 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 6E2CB8EBFA629D3FF26481AB /* OHHTTPStubs */; }; + C81F6192AA4A1B30AB1655A4 /* IdentityFlowNavigationControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22681AA4BBA6A907A0DD29DB /* IdentityFlowNavigationControllerTest.swift */; }; + C83DD219624645AACB2F29A9 /* RequiredInternationalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F28FD09DBAFAEB609EAD25 /* RequiredInternationalAddress.swift */; }; + C9E49126B9BEC74E605C6DD0 /* SelfieCaptureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FEB1C2A314572B0234005A7 /* SelfieCaptureView.swift */; }; + CB09C0EFFCBA132257D6914D /* DebugViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30748D6B84FC605EE612C2D3 /* DebugViewControllerSnapshotTest.swift */; }; + CB3E51C6E407E6A4A1001AAC /* IdentityAnalyticsClientTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43D3DC62FE3C5CC9FD22A50 /* IdentityAnalyticsClientTestHelpers.swift */; }; + CBA4B3CAFDFF232B72393842 /* VerificationClientSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E01DDEABC6ABAB0F03B7CEC /* VerificationClientSecret.swift */; }; + CE7F2D308363EEDD627AEBF7 /* InstructionalDocumentScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CDF8D78C430D9B26DD92B11 /* InstructionalDocumentScanningView.swift */; }; + CF5E139AD71239C64376815D /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1559E966A487EE3867DD7B8 /* String+Localized.swift */; }; + CFA141490408B4644A4F0375 /* IdentityMockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738A5EB6B7F1C5621F190A72 /* IdentityMockData.swift */; }; + D19E35C5D5263F212071AE75 /* IDDetectorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14ED7B7A9CC6DE447C14B5CB /* IDDetectorConstants.swift */; }; + D2309DFF2EDABB6C1A139513 /* VerificationPageDataIdNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A49996D90714452E6A533A /* VerificationPageDataIdNumber.swift */; }; + D467B7222B9BF270001EFE99 /* VerificationPageStaticContentExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D467B7212B9BF270001EFE99 /* VerificationPageStaticContentExperiment.swift */; }; + D467B7252B9C3008001EFE99 /* IdentityAnalyticsClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D467B7242B9C3008001EFE99 /* IdentityAnalyticsClientTest.swift */; }; + D467B7272B9CD929001EFE99 /* VerificationPage_200_no_exp.json in Resources */ = {isa = PBXBuildFile; fileRef = D467B7262B9CD929001EFE99 /* VerificationPage_200_no_exp.json */; }; + D4715B972B0E8E20008D07CC /* VerificationPageStaticContentBottomSheetLineContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B852B0E8E1F008D07CC /* VerificationPageStaticContentBottomSheetLineContent.swift */; }; + D4715B982B0E8E20008D07CC /* VerificationPageStaticContentIndividualPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B862B0E8E1F008D07CC /* VerificationPageStaticContentIndividualPage.swift */; }; + D4715B992B0E8E20008D07CC /* VerificationPageStaticContentPhoneOtpPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B872B0E8E1F008D07CC /* VerificationPageStaticContentPhoneOtpPage.swift */; }; + D4715B9A2B0E8E20008D07CC /* VerificationPageStaticContentIndividualWelcomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B882B0E8E1F008D07CC /* VerificationPageStaticContentIndividualWelcomePage.swift */; }; + D4715B9B2B0E8E20008D07CC /* VerificationPageIconType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B892B0E8E1F008D07CC /* VerificationPageIconType.swift */; }; + D4715B9C2B0E8E20008D07CC /* VerificationPageStaticContentDocumentCapturePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B8A2B0E8E1F008D07CC /* VerificationPageStaticContentDocumentCapturePage.swift */; }; + D4715B9D2B0E8E20008D07CC /* VerificationPageRequirements.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B8B2B0E8E1F008D07CC /* VerificationPageRequirements.swift */; }; + D4715B9E2B0E8E20008D07CC /* VerificationPageStaticContentDocumentCaptureModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B8C2B0E8E1F008D07CC /* VerificationPageStaticContentDocumentCaptureModels.swift */; }; + D4715B9F2B0E8E20008D07CC /* VerificationPageStaticContentSelfieModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B8D2B0E8E1F008D07CC /* VerificationPageStaticContentSelfieModels.swift */; }; + D4715BA02B0E8E20008D07CC /* VerificationPageStaticContentCountryNotListedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B8E2B0E8E1F008D07CC /* VerificationPageStaticContentCountryNotListedPage.swift */; }; + D4715BA12B0E8E20008D07CC /* VerificationPageFieldType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B8F2B0E8E1F008D07CC /* VerificationPageFieldType.swift */; }; + D4715BA22B0E8E20008D07CC /* VerificationPageStaticContentBottomSheetContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B902B0E8E1F008D07CC /* VerificationPageStaticContentBottomSheetContent.swift */; }; + D4715BA32B0E8E20008D07CC /* VerificationPageStaticContentSelfiePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B912B0E8E1F008D07CC /* VerificationPageStaticContentSelfiePage.swift */; }; + D4715BA42B0E8E20008D07CC /* VerificationPageStaticContentConsentPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B922B0E8E1F008D07CC /* VerificationPageStaticContentConsentPage.swift */; }; + D4715BA52B0E8E20008D07CC /* VerificationPageStaticContentDocumentSelectPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B932B0E8E1F008D07CC /* VerificationPageStaticContentDocumentSelectPage.swift */; }; + D4715BA62B0E8E20008D07CC /* VerificationPageStaticContentTextPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B942B0E8E1F008D07CC /* VerificationPageStaticContentTextPage.swift */; }; + D4715BA72B0E8E20008D07CC /* VerificationPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B952B0E8E1F008D07CC /* VerificationPage.swift */; }; + D4715BA82B0E8E20008D07CC /* VerificationPageStaticConsentLineContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715B962B0E8E1F008D07CC /* VerificationPageStaticConsentLineContent.swift */; }; + D4715BB82B0E8E80008D07CC /* CameraPreviewContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BA92B0E8E80008D07CC /* CameraPreviewContainerView.swift */; }; + D4715BB92B0E8E80008D07CC /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BAA2B0E8E80008D07CC /* ErrorView.swift */; }; + D4715BBA2B0E8E80008D07CC /* IdentityFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BAB2B0E8E80008D07CC /* IdentityFlowView.swift */; }; + D4715BBB2B0E8E80008D07CC /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BAC2B0E8E80008D07CC /* HeaderView.swift */; }; + D4715BBC2B0E8E80008D07CC /* BottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BAD2B0E8E80008D07CC /* BottomSheetView.swift */; }; + D4715BBD2B0E8E80008D07CC /* InstructionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BAE2B0E8E80008D07CC /* InstructionListView.swift */; }; + D4715BBE2B0E8E80008D07CC /* ShadowConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BAF2B0E8E80008D07CC /* ShadowConfiguration.swift */; }; + D4715BBF2B0E8E80008D07CC /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB02B0E8E80008D07CC /* DebugView.swift */; }; + D4715BC02B0E8E80008D07CC /* ShadowedCorneredImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB12B0E8E80008D07CC /* ShadowedCorneredImageView.swift */; }; + D4715BC12B0E8E80008D07CC /* HeaderIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB22B0E8E80008D07CC /* HeaderIconView.swift */; }; + D4715BC22B0E8E80008D07CC /* CompleteOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB32B0E8E80008D07CC /* CompleteOptionView.swift */; }; + D4715BC32B0E8E80008D07CC /* PhoneOtpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB42B0E8E80008D07CC /* PhoneOtpView.swift */; }; + D4715BC42B0E8E80008D07CC /* DocumentWarmupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB52B0E8E80008D07CC /* DocumentWarmupView.swift */; }; + D4715BC52B0E8E80008D07CC /* ContentCenteringScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB62B0E8E80008D07CC /* ContentCenteringScrollView.swift */; }; + D4715BC62B0E8E80008D07CC /* BottomAlignedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BB72B0E8E80008D07CC /* BottomAlignedLabel.swift */; }; + D4715BDB2B0E8E96008D07CC /* DocumentWarmupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BC72B0E8E95008D07CC /* DocumentWarmupViewController.swift */; }; + D4715BDC2B0E8E96008D07CC /* UIViewController+SafariExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BC82B0E8E95008D07CC /* UIViewController+SafariExtension.swift */; }; + D4715BDD2B0E8E96008D07CC /* ErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BC92B0E8E95008D07CC /* ErrorViewController.swift */; }; + D4715BDE2B0E8E96008D07CC /* DocumentFileUploadViewController+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BCA2B0E8E95008D07CC /* DocumentFileUploadViewController+Strings.swift */; }; + D4715BDF2B0E8E96008D07CC /* CountryNotListedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BCB2B0E8E96008D07CC /* CountryNotListedViewController.swift */; }; + D4715BE02B0E8E96008D07CC /* IdentityFlowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BCC2B0E8E96008D07CC /* IdentityFlowViewController.swift */; }; + D4715BE12B0E8E96008D07CC /* IndividualWelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BCD2B0E8E96008D07CC /* IndividualWelcomeViewController.swift */; }; + D4715BE22B0E8E96008D07CC /* SelfieCaptureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BCE2B0E8E96008D07CC /* SelfieCaptureViewController.swift */; }; + D4715BE32B0E8E96008D07CC /* SelfieWarmupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BCF2B0E8E96008D07CC /* SelfieWarmupViewController.swift */; }; + D4715BE42B0E8E96008D07CC /* DebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD02B0E8E96008D07CC /* DebugViewController.swift */; }; + D4715BE52B0E8E96008D07CC /* PhoneOtpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD12B0E8E96008D07CC /* PhoneOtpViewController.swift */; }; + D4715BE62B0E8E96008D07CC /* SuccessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD22B0E8E96008D07CC /* SuccessViewController.swift */; }; + D4715BE72B0E8E96008D07CC /* BiometricConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD32B0E8E96008D07CC /* BiometricConsentViewController.swift */; }; + D4715BE82B0E8E96008D07CC /* DocumentCaptureViewController+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD42B0E8E96008D07CC /* DocumentCaptureViewController+Strings.swift */; }; + D4715BE92B0E8E96008D07CC /* IndividualViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD52B0E8E96008D07CC /* IndividualViewController.swift */; }; + D4715BEA2B0E8E96008D07CC /* DocumentFileUploadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD62B0E8E96008D07CC /* DocumentFileUploadViewController.swift */; }; + D4715BEB2B0E8E96008D07CC /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD72B0E8E96008D07CC /* LoadingViewController.swift */; }; + D4715BEC2B0E8E96008D07CC /* DocumentCaptureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD82B0E8E96008D07CC /* DocumentCaptureViewController.swift */; }; + D4715BED2B0E8E96008D07CC /* BottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BD92B0E8E96008D07CC /* BottomSheetViewController.swift */; }; + D4715BEE2B0E8E96008D07CC /* SelfieCaptureViewController+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BDA2B0E8E96008D07CC /* SelfieCaptureViewController+Strings.swift */; }; + D4715BF32B0E8EBF008D07CC /* HTMLViewWithIconLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BEF2B0E8EBF008D07CC /* HTMLViewWithIconLabels.swift */; }; + D4715BF42B0E8EBF008D07CC /* MultilineIconLabelHTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BF02B0E8EBF008D07CC /* MultilineIconLabelHTMLView.swift */; }; + D4715BF52B0E8EBF008D07CC /* HTMLTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BF12B0E8EBF008D07CC /* HTMLTextView.swift */; }; + D4715BF62B0E8EBF008D07CC /* IconLabelHTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4715BF22B0E8EBF008D07CC /* IconLabelHTMLView.swift */; }; + D4715C0C2B0E8F43008D07CC /* icon_warning2@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BF72B0E8F42008D07CC /* icon_warning2@3x.png */; }; + D4715C0D2B0E8F43008D07CC /* icon_phone@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BF82B0E8F42008D07CC /* icon_phone@3x.png */; }; + D4715C0E2B0E8F43008D07CC /* icon_create_identity_verification@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BF92B0E8F42008D07CC /* icon_create_identity_verification@3x.png */; }; + D4715C0F2B0E8F43008D07CC /* icon_add@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BFA2B0E8F42008D07CC /* icon_add@3x.png */; }; + D4715C102B0E8F43008D07CC /* icon_document@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BFB2B0E8F42008D07CC /* icon_document@3x.png */; }; + D4715C112B0E8F43008D07CC /* icon_ellipsis@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BFC2B0E8F42008D07CC /* icon_ellipsis@3x.png */; }; + D4715C122B0E8F43008D07CC /* icon_checkmark_92@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BFD2B0E8F42008D07CC /* icon_checkmark_92@3x.png */; }; + D4715C132B0E8F43008D07CC /* icon_id_front@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BFE2B0E8F42008D07CC /* icon_id_front@3x.png */; }; + D4715C142B0E8F43008D07CC /* icon_clock@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715BFF2B0E8F42008D07CC /* icon_clock@3x.png */; }; + D4715C152B0E8F43008D07CC /* icon_checkmark@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C002B0E8F42008D07CC /* icon_checkmark@3x.png */; }; + D4715C162B0E8F43008D07CC /* icon_lock@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C012B0E8F42008D07CC /* icon_lock@3x.png */; }; + D4715C172B0E8F43008D07CC /* icon_moved@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C022B0E8F42008D07CC /* icon_moved@3x.png */; }; + D4715C182B0E8F43008D07CC /* icon_camera_classic@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C032B0E8F42008D07CC /* icon_camera_classic@3x.png */; }; + D4715C192B0E8F43008D07CC /* icon_info@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C042B0E8F42008D07CC /* icon_info@3x.png */; }; + D4715C1A2B0E8F43008D07CC /* icon_selfie_warmup@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C052B0E8F42008D07CC /* icon_selfie_warmup@3x.png */; }; + D4715C1B2B0E8F43008D07CC /* icon_warning@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C062B0E8F42008D07CC /* icon_warning@3x.png */; }; + D4715C1C2B0E8F43008D07CC /* icon_cloud@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C072B0E8F42008D07CC /* icon_cloud@3x.png */; }; + D4715C1D2B0E8F43008D07CC /* icon_dispute_protection@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C082B0E8F42008D07CC /* icon_dispute_protection@3x.png */; }; + D4715C1E2B0E8F43008D07CC /* icon_warning_92@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C092B0E8F43008D07CC /* icon_warning_92@3x.png */; }; + D4715C1F2B0E8F43008D07CC /* icon_wallet@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C0A2B0E8F43008D07CC /* icon_wallet@3x.png */; }; + D4715C202B0E8F43008D07CC /* icon_camera@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D4715C0B2B0E8F43008D07CC /* icon_camera@3x.png */; }; + D4888BF82B8FEF3C00B69C89 /* UIImage+utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4888BF72B8FEF3C00B69C89 /* UIImage+utils.swift */; }; + D55B24D4D03E43A9438BCFE5 /* IdNumberElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB73F630CCAFE6AAB9727C62 /* IdNumberElement.swift */; }; + DB6005016F47D09559DA0C98 /* DocumentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFAC1FACF9A36AAFC19BF2F /* DocumentType.swift */; }; + DCFBA69CC4437E2E6061F50B /* IdentityMLModelLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4546F9D432ACFF45E9CE1368 /* IdentityMLModelLoader.swift */; }; + DCFFA11A461B012F7515941F /* VerificationFlowWebViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22A64D0C0989BD43624BB506 /* VerificationFlowWebViewSnapshotTests.swift */; }; + E15D79E05C4FA68CA56B0CC8 /* TruncatedDecimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9DA29DC85773AC710A5201 /* TruncatedDecimal.swift */; }; + E1D4A5DD8C188725C5D23BBB /* VerificationPage_no_selfie.json in Resources */ = {isa = PBXBuildFile; fileRef = D5C7698331B899B80AF4F730 /* VerificationPage_no_selfie.json */; }; + E646201B58937F04897840EB /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB1A0B05C00E547ED1A0D09 /* XCTest.framework */; }; + E64EE8A82FFB624F981D7E17 /* VerificationPageDataUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15E15D64B5F7EF76235D0BA5 /* VerificationPageDataUpdate.swift */; }; + E72C7C6D3632324FED428A68 /* IndividualWelcomeViewControllerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A99CDE8D3C390BCAEF13F938 /* IndividualWelcomeViewControllerSnapshotTest.swift */; }; + EA2A1C7D4A9FB826A2146808 /* DocumentScanner+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC4AE39C12E613AFB80FF4E8 /* DocumentScanner+API.swift */; }; + ED5777E49F4AE61121539EBC /* LaplacianBlurDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2061B962162CC2D8C4C6494C /* LaplacianBlurDetector.swift */; }; + EF277282173FFF6A52FE1EA3 /* FaceDetectorOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD9BA1457F98160EBCBEE670 /* FaceDetectorOutput.swift */; }; + EF343909D610063574D0F911 /* VerificationPageData_no_errors_needback.json in Resources */ = {isa = PBXBuildFile; fileRef = 9D112C14362D55A94E9B9E6E /* VerificationPageData_no_errors_needback.json */; }; + F146D14E6D35174CB94A8A48 /* FaceDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9BD50679E8B87237BF3955E /* FaceDetector.swift */; }; + F1CA28CC435B94C289B08257 /* DocumentUploader+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643CB84029E6A12167838C3A /* DocumentUploader+API.swift */; }; + F1DAE15C305F9733A7C40673 /* VerificationPageDataRequirements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A566C584F41FE23108295E3 /* VerificationPageDataRequirements.swift */; }; + F2BBAF668109BC42FF4FCD09 /* IdentityAPIClientTestMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D0D5F9E17825856B55E4D1 /* IdentityAPIClientTestMock.swift */; }; + F2F041A9228AFAEFCB8A03B7 /* VerificationSheetControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB8B409BCEBD89987293A94 /* VerificationSheetControllerTest.swift */; }; + F831E6E67E7F9057FED3F4B8 /* IdentityAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF2BB05DF09AABFEEC1C66D /* IdentityAPIClient.swift */; }; + FD6FB63B061FEABF17053225 /* NSAttributedString_HTMLSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D95CE7A231D14C9FD32A7848 /* NSAttributedString_HTMLSnapshotTest.swift */; }; + FDB82DE4F471545F5D7B4CCA /* MLMultiArray+StripeIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82D731669A9237F0B147B366 /* MLMultiArray+StripeIdentity.swift */; }; + FF3D439778B2EF580053AC15 /* VerificationPageData_200.json in Resources */ = {isa = PBXBuildFile; fileRef = B75F0E4F340355B8661085F7 /* VerificationPageData_200.json */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + C5559AE430066C0A94C2686A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0705CAA185B63201FD561508 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F251015845B21C036CFBC636; + remoteInfo = StripeIdentity; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 048FEFC038A0C2BC99CCB683 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 559B276C9F23AEDE2D54B9A8 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 005D7E5D0E239D61ED1E9796 /* ImageScanningConcurrencyManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScanningConcurrencyManagerMock.swift; sourceTree = ""; }; + 005EAC562365676CE56F306D /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 01C54327529E9A3BD81F37F4 /* InstructionalDocumentScanningViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionalDocumentScanningViewSnapshotTest.swift; sourceTree = ""; }; + 037D27A0AAECB846C8579452 /* SnapshotTestMockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotTestMockData.swift; sourceTree = ""; }; + 03C30DB2230DF9B47CD20AAF /* HeaderIconViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderIconViewSnapshotTest.swift; sourceTree = ""; }; + 050900C738C01F6D96F7ED5B /* IdentityFlowNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityFlowNavigationController.swift; sourceTree = ""; }; + 0631A038B5360C337B8E596C /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 083865663173CDD9F77417A4 /* VerificationPageDataDocumentFileData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataDocumentFileData.swift; sourceTree = ""; }; + 0989CE5CF80CF69BD28675DE /* MLDetectorMetricsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLDetectorMetricsTracker.swift; sourceTree = ""; }; + 09F06CA1B36DA48B8E8BD7A5 /* VerificationSheetAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalyticsTest.swift; sourceTree = ""; }; + 0ACA3969442F9B14DF70ABE7 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 0AFAC8C1757DE685E0E9BFB3 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + 0CFB27562E05409A1C0263EA /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + 0EA099682E4BE0A34CFCBE6C /* AnimatedBorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedBorderView.swift; sourceTree = ""; }; + 0F99DB710C60AC2A98449D51 /* ImageScanningSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScanningSession.swift; sourceTree = ""; }; + 1096AFFD3877D9A22703E6CA /* CGImage_StripeIdentitySnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGImage_StripeIdentitySnapshotTest.swift; sourceTree = ""; }; + 110A0200099F462B576F2ED4 /* VerificationPageDataName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataName.swift; sourceTree = ""; }; + 111A55FA39B400E6E87A5175 /* IdentityMLModelLoaderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityMLModelLoaderMock.swift; sourceTree = ""; }; + 12F4C291ACBB73ED95504131 /* PhoneOtpViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneOtpViewControllerTest.swift; sourceTree = ""; }; + 14244B002D9F37B56BE8D4F4 /* VerificationPageDataFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataFace.swift; sourceTree = ""; }; + 149453E1854C9566CC8D8484 /* ImageScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScanner.swift; sourceTree = ""; }; + 14C231755842F322A25160ED /* StripeCameraCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCameraCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 14ED7B7A9CC6DE447C14B5CB /* IDDetectorConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDDetectorConstants.swift; sourceTree = ""; }; + 15E15D64B5F7EF76235D0BA5 /* VerificationPageDataUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataUpdate.swift; sourceTree = ""; }; + 16ADAC122F0818BB64C5EF41 /* VerificationPage_200_testMode.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_200_testMode.json; sourceTree = ""; }; + 18593C820FE0E35DDEE36956 /* MLDetectorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLDetectorConfiguration.swift; sourceTree = ""; }; + 18BE1F9B0EB046BF0E1C2D8B /* SelfieScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieScanningView.swift; sourceTree = ""; }; + 1A566C584F41FE23108295E3 /* VerificationPageDataRequirements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataRequirements.swift; sourceTree = ""; }; + 1B264A8F97FA8CB0706F140F /* BiometricConsentViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricConsentViewControllerTest.swift; sourceTree = ""; }; + 1CAC6276996043D1B3FC92F7 /* VerificationSheetFlowControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetFlowControllerMock.swift; sourceTree = ""; }; + 1CDF8D78C430D9B26DD92B11 /* InstructionalDocumentScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionalDocumentScanningView.swift; sourceTree = ""; }; + 1E01DDEABC6ABAB0F03B7CEC /* VerificationClientSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationClientSecret.swift; sourceTree = ""; }; + 1E7C993DB897B55B2A172CD9 /* IdentityFlowViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityFlowViewSnapshotTest.swift; sourceTree = ""; }; + 1F4F55F9FBE67FCFE3767816 /* VerificationPageCollectedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageCollectedData.swift; sourceTree = ""; }; + 20129AFBC6B4D304B494CBA6 /* ErrorViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewControllerTest.swift; sourceTree = ""; }; + 2061B962162CC2D8C4C6494C /* LaplacianBlurDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaplacianBlurDetector.swift; sourceTree = ""; }; + 20D40A569C2BB62F86D57C01 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; + 21DB72C7F61A4A042D898236 /* SelfieWarmupViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieWarmupViewSnapshotTest.swift; sourceTree = ""; }; + 22681AA4BBA6A907A0DD29DB /* IdentityFlowNavigationControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityFlowNavigationControllerTest.swift; sourceTree = ""; }; + 22A64D0C0989BD43624BB506 /* VerificationFlowWebViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebViewSnapshotTests.swift; sourceTree = ""; }; + 22B3E26EBB8F43F7559C635B /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + 2344DA31ADA3573F9C2EE7A0 /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + 280A8D95B0045A940A917B1A /* front_drivers_license.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = front_drivers_license.jpg; sourceTree = ""; }; + 29E0AD187733319776268351 /* FaceScannerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceScannerConfiguration.swift; sourceTree = ""; }; + 2CDF024DA4F519CB21A717AA /* IDDetectorOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDDetectorOutput.swift; sourceTree = ""; }; + 2E5237659945E4A05ADE6DB0 /* TimeInterval+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+StripeIdentity.swift"; sourceTree = ""; }; + 2FAE3BFE9870AF3F0553AB9C /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 2FC476BFBFA09FDC174857A6 /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + 2FC99DD88994E0DB581E19C6 /* VerificationPage_type_doc_require_address.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_type_doc_require_address.json; sourceTree = ""; }; + 2FD2C7D94586AB8984DB7389 /* MLModelUnexpectedOutputError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLModelUnexpectedOutputError.swift; sourceTree = ""; }; + 2FEB1C2A314572B0234005A7 /* SelfieCaptureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieCaptureView.swift; sourceTree = ""; }; + 30748D6B84FC605EE612C2D3 /* DebugViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewControllerSnapshotTest.swift; sourceTree = ""; }; + 3118D24F94912E8A9BB86BFE /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + 313F5F802B0BE5DE00BD98A9 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; + 31CC3450DDBE415B82707F4B /* IdentityElementsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityElementsFactory.swift; sourceTree = ""; }; + 3218882B7E1610E17BDB4522 /* VerificationPage_200.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_200.json; sourceTree = ""; }; + 3943F18216635D56A3E6019C /* ErrorViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewControllerSnapshotTest.swift; sourceTree = ""; }; + 3954F595B70115874BD88CCC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 3AA3F3B346FFC8E58BF8739A /* VerificationPageData_no_errors.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPageData_no_errors.json; sourceTree = ""; }; + 3AFAC1FACF9A36AAFC19BF2F /* DocumentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentType.swift; sourceTree = ""; }; + 3DD5FEA0E4E7EF1A4BA9646B /* IdentityVerificationSheetError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityVerificationSheetError.swift; sourceTree = ""; }; + 4023102F8B76C16522BC02B9 /* SelfieWarmupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieWarmupView.swift; sourceTree = ""; }; + 40DAFA9171B6D631DE9B6830 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 40F28FD09DBAFAEB609EAD25 /* RequiredInternationalAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredInternationalAddress.swift; sourceTree = ""; }; + 42768A09155B5439E724B360 /* NSAttributedString+HTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+HTML.swift"; sourceTree = ""; }; + 434BC5FFBC1163872413A40A /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + 4546F9D432ACFF45E9CE1368 /* IdentityMLModelLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityMLModelLoader.swift; sourceTree = ""; }; + 45CA21A528C7D39BAA2F2B51 /* StripeCore+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripeCore+Import.swift"; sourceTree = ""; }; + 467BA77070B91055F1F2DCD4 /* IdentityTopLevelDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityTopLevelDestination.swift; sourceTree = ""; }; + 48A449D0BE66D0A555183AF4 /* VerificationFlowWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebViewController.swift; sourceTree = ""; }; + 48E1204F31DC9F8FEA0D312A /* DocumentScannerOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScannerOutput.swift; sourceTree = ""; }; + 4A1FCF877D109689CA724EB4 /* ErrorViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorViewSnapshotTest.swift; sourceTree = ""; }; + 4A59C0CC87CDB7AABFE96686 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 4AFCD229BD9CB58303E19111 /* SelfieWarmupViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieWarmupViewControllerTest.swift; sourceTree = ""; }; + 4C0B804DA867C37809656BB3 /* ImageScannerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScannerMock.swift; sourceTree = ""; }; + 4D0F4646D7565055396DD8F5 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4D2F49DD0BAC5FEF308BA165 /* FaceScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceScanner.swift; sourceTree = ""; }; + 4D4C3F2BAFB6BEFFFC1B566F /* IdentityImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityImageUploader.swift; sourceTree = ""; }; + 4D5C67D8BB38BC3EA6282FF2 /* IdentityVerificationSheetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityVerificationSheetTest.swift; sourceTree = ""; }; + 4DEBC1A25D2C446219E7E733 /* CGImage+StripeIdentityUnitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGImage+StripeIdentityUnitTest.swift"; sourceTree = ""; }; + 4F6B26A21285054BEE9CA244 /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 50A49996D90714452E6A533A /* VerificationPageDataIdNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataIdNumber.swift; sourceTree = ""; }; + 52DD224D2BE08E0553EF8AA6 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + 52FB883B057ED79F2C58BC58 /* VerificationFlowResult+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VerificationFlowResult+Equatable.swift"; sourceTree = ""; }; + 531D9220055F0F54CF6D72CE /* VerificationPage_type_doc_require_idNumber_and_address.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_type_doc_require_idNumber_and_address.json; sourceTree = ""; }; + 53F1B93DB4F1708B41125301 /* IdentityVerificationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityVerificationSheet.swift; sourceTree = ""; }; + 5726517468179B69D50692C0 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 57F22CC7DFA4B98A1A533CE0 /* MotionBlurDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionBlurDetector.swift; sourceTree = ""; }; + 598A711C4A2B8988EFBEEF77 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 5B975279521B43A8F82EBAB3 /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + 5D4A09C8B4EBF06D00B80994 /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; }; + 5F0B317274417308FBA55E55 /* DocumentFileUploadViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentFileUploadViewControllerTest.swift; sourceTree = ""; }; + 6182719E4413B5136BB763D1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 63826305A96644687BF980CC /* BarcodeDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarcodeDetector.swift; sourceTree = ""; }; + 643CB84029E6A12167838C3A /* DocumentUploader+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DocumentUploader+API.swift"; sourceTree = ""; }; + 658F7A165D731F5046ACDF29 /* VerificationFlowWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebView.swift; sourceTree = ""; }; + 6BC7102DF0964D1204E2BFEB /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + 6F772F905FB81552FA1728B2 /* cgimage_stripeidentity_test.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cgimage_stripeidentity_test.png; sourceTree = ""; }; + 6FF56E95E47DA51C3517E1EC /* IdentityElementsFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityElementsFactoryTest.swift; sourceTree = ""; }; + 704D26339D2158FD8FADE1C2 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + 725E481300FDDDD41EBAA835 /* FaceScanner+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FaceScanner+API.swift"; sourceTree = ""; }; + 738A5EB6B7F1C5621F190A72 /* IdentityMockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityMockData.swift; sourceTree = ""; }; + 744CE13E8488F0E4AC951D6C /* CGImage+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGImage+StripeIdentity.swift"; sourceTree = ""; }; + 748D38C4370E78DBC9E20E95 /* MLModelLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLModelLoader.swift; sourceTree = ""; }; + 74D9040BDF0DBA526C156C3D /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + 786574907AC9065E6E658D9C /* MLDetectorMetricsTrackerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLDetectorMetricsTrackerMock.swift; sourceTree = ""; }; + 7CD11009097B994FEFF5393E /* header_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = header_icon.png; sourceTree = ""; }; + 7CDAA5C16C934746013C6CE6 /* VerificationSheetFlowControllerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetFlowControllerError.swift; sourceTree = ""; }; + 7EAFBBEEA27B11E702B78497 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + 7ECA816022703F05AD759897 /* VerificationPage_200_no_consent_header.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_200_no_consent_header.json; sourceTree = ""; }; + 7F9DA29DC85773AC710A5201 /* TruncatedDecimal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedDecimal.swift; sourceTree = ""; }; + 7FB8B409BCEBD89987293A94 /* VerificationSheetControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetControllerTest.swift; sourceTree = ""; }; + 7FD2B7B0EDA3144115F080BC /* VerificationPage_require_live_capture.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_require_live_capture.json; sourceTree = ""; }; + 80C34F71161E8793947E18E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 80F9C4B0CFCD5FF45D203B62 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 82D731669A9237F0B147B366 /* MLMultiArray+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MLMultiArray+StripeIdentity.swift"; sourceTree = ""; }; + 8389CF3DBB12A384BCF0959F /* InstructionListViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionListViewSnapshotTest.swift; sourceTree = ""; }; + 85E8B042346D6DF41AA6CCFC /* IndividualElementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualElementTest.swift; sourceTree = ""; }; + 8605B809AD7EBA140CC9DBE9 /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + 8AB1A0B05C00E547ED1A0D09 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 8AF040F11A7D0693A7783654 /* DocumentCaptureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentCaptureView.swift; sourceTree = ""; }; + 8DAFC23BB4CDD7DEB3286704 /* PhoneOtpViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneOtpViewControllerSnapshotTest.swift; sourceTree = ""; }; + 8E1EF4A7AA64856F9C887C2A /* IdentityTextButtonElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityTextButtonElement.swift; sourceTree = ""; }; + 8EFF781FE9ED055F99F767D3 /* SelfieUploader+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SelfieUploader+API.swift"; sourceTree = ""; }; + 8FE476C4E5F3E281B7E6E136 /* DocumentUploaderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentUploaderTest.swift; sourceTree = ""; }; + 8FF19274A29802CDB696D1DF /* SuccessViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessViewControllerSnapshotTest.swift; sourceTree = ""; }; + 9048C4B9F47CE82127D6C1B3 /* back_drivers_license.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = back_drivers_license.jpg; sourceTree = ""; }; + 9145699AC820A2F2F916F142 /* IndividualViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualViewControllerTest.swift; sourceTree = ""; }; + 93F2EE0B99059C7BFB72AFAF /* NonMaxSuppression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonMaxSuppression.swift; sourceTree = ""; }; + 9429D10FEFFFEFEE08DBA4F8 /* VerificationPageDataPhone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataPhone.swift; sourceTree = ""; }; + 94838BCA1111F735A8FBC072 /* HeaderViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderViewSnapshotTest.swift; sourceTree = ""; }; + 957545FFD547CDA1CFB719E6 /* BiometricConsentViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricConsentViewControllerSnapshotTest.swift; sourceTree = ""; }; + 98AF8558B79CD52154D6EF3B /* VerificationSheetFlowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetFlowController.swift; sourceTree = ""; }; + 99607F36AF24C978953964E9 /* VerificationPageData_submitted.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPageData_submitted.json; sourceTree = ""; }; + 9A5A1304CA30DC06B715BD92 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; + 9BB8E3DF263505065E8545AB /* SelfieUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieUploader.swift; sourceTree = ""; }; + 9C69B4A6F36A8BD814CB05D9 /* StripeIdentity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeIdentity.h; sourceTree = ""; }; + 9D112C14362D55A94E9B9E6E /* VerificationPageData_no_errors_needback.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPageData_no_errors_needback.json; sourceTree = ""; }; + 9E170B07B4958901EE3864A4 /* VisionBasedDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisionBasedDetector.swift; sourceTree = ""; }; + 9E703FD28F5352DFC4115F1C /* IndividualViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualViewControllerSnapshotTest.swift; sourceTree = ""; }; + A163E1EC771058E5DF5FFE66 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + A2AF434F2405C5F38F259112 /* IdentityHTMLViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityHTMLViewSnapshotTest.swift; sourceTree = ""; }; + A31C88110FBCDDA9FB912491 /* AnimatedBorderViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedBorderViewSnapshotTest.swift; sourceTree = ""; }; + A5082A4100CEE0C8378AD185 /* VerificationSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetController.swift; sourceTree = ""; }; + A59680E002FC6E7BC9906BD2 /* IdentityAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAPIClientTest.swift; sourceTree = ""; }; + A5D517A4C8669379A029BBAD /* StripeIdentityTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeIdentityTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A5DE93D9C60DFB779F71D5DC /* DocumentScannerConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScannerConfiguration.swift; sourceTree = ""; }; + A69C1BC7C63512FE24B32433 /* VerificationSheetFlowControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetFlowControllerTest.swift; sourceTree = ""; }; + A6E9FA49B13AC0A8826F8257 /* VerificationPageDataDob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataDob.swift; sourceTree = ""; }; + A99CDE8D3C390BCAEF13F938 /* IndividualWelcomeViewControllerSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualWelcomeViewControllerSnapshotTest.swift; sourceTree = ""; }; + AD7373453206D23A9ABF5E17 /* SelfieScanningViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieScanningViewSnapshotTest.swift; sourceTree = ""; }; + AD8443DFD20A6F7E547E4DAE /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + ADA1D4AB1AE9B0E45C68FFD2 /* mock.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = mock.html; sourceTree = ""; }; + AE92BAAC65F86A87F25B9C08 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + B17C20418C972E7B06B84370 /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + B2E15397783A643B74921A00 /* VerificationPageDataRequirementError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageDataRequirementError.swift; sourceTree = ""; }; + B2EB7348CE21DB1A86706532 /* VerificationFlowWebViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebViewControllerTest.swift; sourceTree = ""; }; + B38AE7F15E61D960EFA15659 /* VerificationPageData_submitted_not_closed.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPageData_submitted_not_closed.json; sourceTree = ""; }; + B3B539D323CF2AACB580596B /* VerificationPageData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageData.swift; sourceTree = ""; }; + B3BED2C3A9B548341D73D3CC /* ListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemView.swift; sourceTree = ""; }; + B634C1568CBC56B3E33CE3E2 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + B65B0A0F124B3E0C5490D2B6 /* ListItemViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemViewSnapshotTest.swift; sourceTree = ""; }; + B67F4A7C86FF6784C6844649 /* DocumentScanningViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanningViewSnapshotTest.swift; sourceTree = ""; }; + B75F0E4F340355B8661085F7 /* VerificationPageData_200.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPageData_200.json; sourceTree = ""; }; + B9BD50679E8B87237BF3955E /* FaceDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceDetector.swift; sourceTree = ""; }; + B9CB453179082B1AE6B242FD /* IdentityDataCollecting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityDataCollecting.swift; sourceTree = ""; }; + BCA7F9B0204BF18C483A474A /* DocumentCaptureViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentCaptureViewControllerTest.swift; sourceTree = ""; }; + BCB41D1D6B1D35C92EA6F2B7 /* IDDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDDetector.swift; sourceTree = ""; }; + BCC019FA1D6D8AEB598D9EC5 /* IdentityAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAnalyticsClient.swift; sourceTree = ""; }; + BD9BA1457F98160EBCBEE670 /* FaceDetectorOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceDetectorOutput.swift; sourceTree = ""; }; + C334F59C93D3D11534FACBBA /* DocumentUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentUploader.swift; sourceTree = ""; }; + C3C78174C0059A8957832825 /* VNBarcodeSymbology+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VNBarcodeSymbology+StripeIdentity.swift"; sourceTree = ""; }; + C43D3DC62FE3C5CC9FD22A50 /* IdentityAnalyticsClientTestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAnalyticsClientTestHelpers.swift; sourceTree = ""; }; + C4891127AAACF7829BEE80E2 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + C4943A54EF548F28DB66DD1F /* FaceCaptureData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceCaptureData.swift; sourceTree = ""; }; + C74D7B9CB1FF34AE667C65B3 /* DocumentScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanner.swift; sourceTree = ""; }; + C8D0D5F9E17825856B55E4D1 /* IdentityAPIClientTestMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAPIClientTestMock.swift; sourceTree = ""; }; + C8D82EAF85863CAAA250E393 /* ListViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewSnapshotTest.swift; sourceTree = ""; }; + CB639F497EF62CAE81822820 /* StripeCameraCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCameraCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CBC057ECE030E8374AD9CF61 /* Array+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+StripeIdentity.swift"; sourceTree = ""; }; + CC4AE39C12E613AFB80FF4E8 /* DocumentScanner+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DocumentScanner+API.swift"; sourceTree = ""; }; + CCB84457F36C8EA327239852 /* VerificationPage_type_doc_require_idNumber.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_type_doc_require_idNumber.json; sourceTree = ""; }; + CE691D40B29AB0F31443479A /* DocumentUploaderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentUploaderMock.swift; sourceTree = ""; }; + CEE8340912F2A489BB80D39B /* StripeIdentityBundleLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeIdentityBundleLocator.swift; sourceTree = ""; }; + CF2EEA927FA9ACE159A67063 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + D105F7A265B60FE9AD82B0DF /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + D467B7212B9BF270001EFE99 /* VerificationPageStaticContentExperiment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentExperiment.swift; sourceTree = ""; }; + D467B7242B9C3008001EFE99 /* IdentityAnalyticsClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAnalyticsClientTest.swift; sourceTree = ""; }; + D467B7262B9CD929001EFE99 /* VerificationPage_200_no_exp.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = VerificationPage_200_no_exp.json; sourceTree = ""; }; + D4715B852B0E8E1F008D07CC /* VerificationPageStaticContentBottomSheetLineContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentBottomSheetLineContent.swift; sourceTree = ""; }; + D4715B862B0E8E1F008D07CC /* VerificationPageStaticContentIndividualPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentIndividualPage.swift; sourceTree = ""; }; + D4715B872B0E8E1F008D07CC /* VerificationPageStaticContentPhoneOtpPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentPhoneOtpPage.swift; sourceTree = ""; }; + D4715B882B0E8E1F008D07CC /* VerificationPageStaticContentIndividualWelcomePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentIndividualWelcomePage.swift; sourceTree = ""; }; + D4715B892B0E8E1F008D07CC /* VerificationPageIconType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageIconType.swift; sourceTree = ""; }; + D4715B8A2B0E8E1F008D07CC /* VerificationPageStaticContentDocumentCapturePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentDocumentCapturePage.swift; sourceTree = ""; }; + D4715B8B2B0E8E1F008D07CC /* VerificationPageRequirements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageRequirements.swift; sourceTree = ""; }; + D4715B8C2B0E8E1F008D07CC /* VerificationPageStaticContentDocumentCaptureModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentDocumentCaptureModels.swift; sourceTree = ""; }; + D4715B8D2B0E8E1F008D07CC /* VerificationPageStaticContentSelfieModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentSelfieModels.swift; sourceTree = ""; }; + D4715B8E2B0E8E1F008D07CC /* VerificationPageStaticContentCountryNotListedPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentCountryNotListedPage.swift; sourceTree = ""; }; + D4715B8F2B0E8E1F008D07CC /* VerificationPageFieldType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageFieldType.swift; sourceTree = ""; }; + D4715B902B0E8E1F008D07CC /* VerificationPageStaticContentBottomSheetContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentBottomSheetContent.swift; sourceTree = ""; }; + D4715B912B0E8E1F008D07CC /* VerificationPageStaticContentSelfiePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentSelfiePage.swift; sourceTree = ""; }; + D4715B922B0E8E1F008D07CC /* VerificationPageStaticContentConsentPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentConsentPage.swift; sourceTree = ""; }; + D4715B932B0E8E1F008D07CC /* VerificationPageStaticContentDocumentSelectPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentDocumentSelectPage.swift; sourceTree = ""; }; + D4715B942B0E8E1F008D07CC /* VerificationPageStaticContentTextPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticContentTextPage.swift; sourceTree = ""; }; + D4715B952B0E8E1F008D07CC /* VerificationPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPage.swift; sourceTree = ""; }; + D4715B962B0E8E1F008D07CC /* VerificationPageStaticConsentLineContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerificationPageStaticConsentLineContent.swift; sourceTree = ""; }; + D4715BA92B0E8E80008D07CC /* CameraPreviewContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraPreviewContainerView.swift; sourceTree = ""; }; + D4715BAA2B0E8E80008D07CC /* ErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + D4715BAB2B0E8E80008D07CC /* IdentityFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityFlowView.swift; sourceTree = ""; }; + D4715BAC2B0E8E80008D07CC /* HeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; + D4715BAD2B0E8E80008D07CC /* BottomSheetView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheetView.swift; sourceTree = ""; }; + D4715BAE2B0E8E80008D07CC /* InstructionListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstructionListView.swift; sourceTree = ""; }; + D4715BAF2B0E8E80008D07CC /* ShadowConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowConfiguration.swift; sourceTree = ""; }; + D4715BB02B0E8E80008D07CC /* DebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = ""; }; + D4715BB12B0E8E80008D07CC /* ShadowedCorneredImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowedCorneredImageView.swift; sourceTree = ""; }; + D4715BB22B0E8E80008D07CC /* HeaderIconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderIconView.swift; sourceTree = ""; }; + D4715BB32B0E8E80008D07CC /* CompleteOptionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompleteOptionView.swift; sourceTree = ""; }; + D4715BB42B0E8E80008D07CC /* PhoneOtpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneOtpView.swift; sourceTree = ""; }; + D4715BB52B0E8E80008D07CC /* DocumentWarmupView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentWarmupView.swift; sourceTree = ""; }; + D4715BB62B0E8E80008D07CC /* ContentCenteringScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentCenteringScrollView.swift; sourceTree = ""; }; + D4715BB72B0E8E80008D07CC /* BottomAlignedLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomAlignedLabel.swift; sourceTree = ""; }; + D4715BC72B0E8E95008D07CC /* DocumentWarmupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentWarmupViewController.swift; sourceTree = ""; }; + D4715BC82B0E8E95008D07CC /* UIViewController+SafariExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+SafariExtension.swift"; sourceTree = ""; }; + D4715BC92B0E8E95008D07CC /* ErrorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorViewController.swift; sourceTree = ""; }; + D4715BCA2B0E8E95008D07CC /* DocumentFileUploadViewController+Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DocumentFileUploadViewController+Strings.swift"; sourceTree = ""; }; + D4715BCB2B0E8E96008D07CC /* CountryNotListedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountryNotListedViewController.swift; sourceTree = ""; }; + D4715BCC2B0E8E96008D07CC /* IdentityFlowViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityFlowViewController.swift; sourceTree = ""; }; + D4715BCD2B0E8E96008D07CC /* IndividualWelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndividualWelcomeViewController.swift; sourceTree = ""; }; + D4715BCE2B0E8E96008D07CC /* SelfieCaptureViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfieCaptureViewController.swift; sourceTree = ""; }; + D4715BCF2B0E8E96008D07CC /* SelfieWarmupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfieWarmupViewController.swift; sourceTree = ""; }; + D4715BD02B0E8E96008D07CC /* DebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugViewController.swift; sourceTree = ""; }; + D4715BD12B0E8E96008D07CC /* PhoneOtpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneOtpViewController.swift; sourceTree = ""; }; + D4715BD22B0E8E96008D07CC /* SuccessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuccessViewController.swift; sourceTree = ""; }; + D4715BD32B0E8E96008D07CC /* BiometricConsentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BiometricConsentViewController.swift; sourceTree = ""; }; + D4715BD42B0E8E96008D07CC /* DocumentCaptureViewController+Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DocumentCaptureViewController+Strings.swift"; sourceTree = ""; }; + D4715BD52B0E8E96008D07CC /* IndividualViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndividualViewController.swift; sourceTree = ""; }; + D4715BD62B0E8E96008D07CC /* DocumentFileUploadViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentFileUploadViewController.swift; sourceTree = ""; }; + D4715BD72B0E8E96008D07CC /* LoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingViewController.swift; sourceTree = ""; }; + D4715BD82B0E8E96008D07CC /* DocumentCaptureViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentCaptureViewController.swift; sourceTree = ""; }; + D4715BD92B0E8E96008D07CC /* BottomSheetViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheetViewController.swift; sourceTree = ""; }; + D4715BDA2B0E8E96008D07CC /* SelfieCaptureViewController+Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SelfieCaptureViewController+Strings.swift"; sourceTree = ""; }; + D4715BEF2B0E8EBF008D07CC /* HTMLViewWithIconLabels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLViewWithIconLabels.swift; sourceTree = ""; }; + D4715BF02B0E8EBF008D07CC /* MultilineIconLabelHTMLView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineIconLabelHTMLView.swift; sourceTree = ""; }; + D4715BF12B0E8EBF008D07CC /* HTMLTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLTextView.swift; sourceTree = ""; }; + D4715BF22B0E8EBF008D07CC /* IconLabelHTMLView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconLabelHTMLView.swift; sourceTree = ""; }; + D4715BF72B0E8F42008D07CC /* icon_warning2@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_warning2@3x.png"; sourceTree = ""; }; + D4715BF82B0E8F42008D07CC /* icon_phone@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_phone@3x.png"; sourceTree = ""; }; + D4715BF92B0E8F42008D07CC /* icon_create_identity_verification@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_create_identity_verification@3x.png"; sourceTree = ""; }; + D4715BFA2B0E8F42008D07CC /* icon_add@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_add@3x.png"; sourceTree = ""; }; + D4715BFB2B0E8F42008D07CC /* icon_document@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_document@3x.png"; sourceTree = ""; }; + D4715BFC2B0E8F42008D07CC /* icon_ellipsis@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_ellipsis@3x.png"; sourceTree = ""; }; + D4715BFD2B0E8F42008D07CC /* icon_checkmark_92@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_checkmark_92@3x.png"; sourceTree = ""; }; + D4715BFE2B0E8F42008D07CC /* icon_id_front@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_id_front@3x.png"; sourceTree = ""; }; + D4715BFF2B0E8F42008D07CC /* icon_clock@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_clock@3x.png"; sourceTree = ""; }; + D4715C002B0E8F42008D07CC /* icon_checkmark@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_checkmark@3x.png"; sourceTree = ""; }; + D4715C012B0E8F42008D07CC /* icon_lock@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_lock@3x.png"; sourceTree = ""; }; + D4715C022B0E8F42008D07CC /* icon_moved@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_moved@3x.png"; sourceTree = ""; }; + D4715C032B0E8F42008D07CC /* icon_camera_classic@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_camera_classic@3x.png"; sourceTree = ""; }; + D4715C042B0E8F42008D07CC /* icon_info@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_info@3x.png"; sourceTree = ""; }; + D4715C052B0E8F42008D07CC /* icon_selfie_warmup@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_selfie_warmup@3x.png"; sourceTree = ""; }; + D4715C062B0E8F42008D07CC /* icon_warning@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_warning@3x.png"; sourceTree = ""; }; + D4715C072B0E8F42008D07CC /* icon_cloud@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_cloud@3x.png"; sourceTree = ""; }; + D4715C082B0E8F42008D07CC /* icon_dispute_protection@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_dispute_protection@3x.png"; sourceTree = ""; }; + D4715C092B0E8F43008D07CC /* icon_warning_92@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_warning_92@3x.png"; sourceTree = ""; }; + D4715C0A2B0E8F43008D07CC /* icon_wallet@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_wallet@3x.png"; sourceTree = ""; }; + D4715C0B2B0E8F43008D07CC /* icon_camera@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_camera@3x.png"; sourceTree = ""; }; + D4888BF72B8FEF3C00B69C89 /* UIImage+utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+utils.swift"; sourceTree = ""; }; + D5C7698331B899B80AF4F730 /* VerificationPage_no_selfie.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_no_selfie.json; sourceTree = ""; }; + D604F6AABE4D6D5CCF802685 /* StripeIdentity.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeIdentity.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D6918DC6A9681E769D44BC78 /* TruncatedDecimalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TruncatedDecimalTest.swift; sourceTree = ""; }; + D6A26F37BDBB5330AE87E7CB /* DocumentScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanningView.swift; sourceTree = ""; }; + D75A138199A6CC2DB31DD2D5 /* VerifyWebURLHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyWebURLHelper.swift; sourceTree = ""; }; + D95CE7A231D14C9FD32A7848 /* NSAttributedString_HTMLSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedString_HTMLSnapshotTest.swift; sourceTree = ""; }; + DC8F619B76D191FF2DEC8660 /* ImageScanningConcurrencyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScanningConcurrencyManager.swift; sourceTree = ""; }; + DEB322F07A4F2BE5B1FED0AD /* DebugViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewControllerTest.swift; sourceTree = ""; }; + E0B9A86D071960D13AE81563 /* VerificationPage_200_submitted.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_200_submitted.json; sourceTree = ""; }; + E0CF1B086B99DEEF4F4F2A1D /* IndividualFormElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualFormElement.swift; sourceTree = ""; }; + E1FC19E901C6E57D948833FD /* VerificationPageClearData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationPageClearData.swift; sourceTree = ""; }; + E21D37D0CA9F5426F6318ADC /* VerificationPage_type_phone.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_type_phone.json; sourceTree = ""; }; + E26F8082A859AC1F5C5A3D13 /* Enums+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Enums+CustomStringConvertible.swift"; sourceTree = ""; }; + E310DFA6CAC355B848E82998 /* DocumentSide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentSide.swift; sourceTree = ""; }; + E3A5A9765EF649A580A0519F /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + E3E90C7076A10469E4893B73 /* FaceScannerOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceScannerOutput.swift; sourceTree = ""; }; + E4952611570326FFC52F0F30 /* IndividualWelcomeViewControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualWelcomeViewControllerTest.swift; sourceTree = ""; }; + E6C2F1CDF5594786FB4BAA49 /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + E70585F354B2B8B1420311CB /* VerificationSheetAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalytics.swift; sourceTree = ""; }; + E850C3F1FEA42490F1B3761E /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + E91362066B0F066C00CB45F2 /* VerificationFlowWebViewTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebViewTest.swift; sourceTree = ""; }; + E94605BCADE912561ED6F710 /* STPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = ""; }; + EA7EF0C427E327A1E945BC28 /* VerificationPage_type_address.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_type_address.json; sourceTree = ""; }; + EB73F630CCAFE6AAB9727C62 /* IdNumberElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdNumberElement.swift; sourceTree = ""; }; + EB9B2148051885B8591A13DE /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + EE1984CE45A111B5694A3F49 /* DocumentType+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DocumentType+StripeIdentity.swift"; sourceTree = ""; }; + EEF2BB05DF09AABFEEC1C66D /* IdentityAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityAPIClient.swift; sourceTree = ""; }; + F08E49ED4B7010002D5FC734 /* IdentityImageUploaderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityImageUploaderTest.swift; sourceTree = ""; }; + F0CB25FB0857C39B915505DF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + F1559E966A487EE3867DD7B8 /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + F43124EF97ADAB2BD1B7DEE2 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + F44D3A78A44A0EB11BA51E5F /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + F459D2DF594C15C03585F3AF /* VerificationSheetControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetControllerMock.swift; sourceTree = ""; }; + F46E474400B21A35FA6066F8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + F48A3390B6B74481B1F64EC6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + F524F862B05C4744F3A5FCD4 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + F8103B1FD36D79D22EACF7EE /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + F8521F2AA5740FAB9952B58E /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + F95321B67AA78B88A32E37BE /* SelfieCaptureViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfieCaptureViewSnapshotTest.swift; sourceTree = ""; }; + F9D552632FFF310FC6A4DB26 /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + FB119F30000F66B9660A44E8 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + FCD3770DF9B6356D1A82870A /* VerificationPage_type_idNumber.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = VerificationPage_type_idNumber.json; sourceTree = ""; }; + FD376CBC869531C40036081B /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FEB6AB9A9BAF6E50B962C339 /* IdentityUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityUI.swift; sourceTree = ""; }; + FF0871DEB2B2BA56EFADEEA6 /* UINavigationController+StripeIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+StripeIdentity.swift"; sourceTree = ""; }; + FFD771C14C0711019D8AC9C2 /* VerificationClientSecretTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationClientSecretTest.swift; sourceTree = ""; }; + FFE163E90F54FE4A075C7B74 /* ImageScanningSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScanningSessionDelegate.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 65EE834154FF7B955E7239D7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 695E12945F47ED3060B8AE19 /* StripeCameraCore.framework in Frameworks */, + 10B606812179D64E67BFFFCF /* StripeCore.framework in Frameworks */, + A7AC2D0D9FDCF8AC5298708D /* StripeUICore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 942AEFA5BCC5A034EF066C3E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E646201B58937F04897840EB /* XCTest.framework in Frameworks */, + 4A5FEB151B079C41FD40FED5 /* StripeCameraCoreTestUtils.framework in Frameworks */, + B6E0CD4C1CDE03985005F08D /* StripeCoreTestUtils.framework in Frameworks */, + B5B5C232C6541F15AEF3C4BC /* StripeIdentity.framework in Frameworks */, + 45B515AC217F10059EE155E2 /* iOSSnapshotTestCase in Frameworks */, + C6DEF5EEB22CC714DAC66887 /* OHHTTPStubs in Frameworks */, + 0758EE8C7A032BF378F6AA59 /* OHHTTPStubsSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00D6B9FD0E5C501B4BF57EF9 /* StripeIdentityTests */ = { + isa = PBXGroup; + children = ( + F70954CA70A6D39896F5E2B5 /* Helpers */, + 21094F500C248952675A92E7 /* Mock Files */, + 6C8A36498C59C6AA87239ADC /* Snapshot */, + C240F6CABCF2EAA5A9F29733 /* Unit */, + F0CB25FB0857C39B915505DF /* Info.plist */, + ); + path = StripeIdentityTests; + sourceTree = ""; + }; + 0DB7E4B2463D591EEF46036C /* DocumentScanner */ = { + isa = PBXGroup; + children = ( + C74D7B9CB1FF34AE667C65B3 /* DocumentScanner.swift */, + A5DE93D9C60DFB779F71D5DC /* DocumentScannerConfiguration.swift */, + 48E1204F31DC9F8FEA0D312A /* DocumentScannerOutput.swift */, + ); + path = DocumentScanner; + sourceTree = ""; + }; + 1839EB588B1D039EC1FC1671 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 4F6B26A21285054BEE9CA244 /* Project-Debug.xcconfig */, + A163E1EC771058E5DF5FFE66 /* Project-Release.xcconfig */, + 8605B809AD7EBA140CC9DBE9 /* StripeiOS Tests-Debug.xcconfig */, + 434BC5FFBC1163872413A40A /* StripeiOS Tests-Release.xcconfig */, + B17C20418C972E7B06B84370 /* StripeiOS-Debug.xcconfig */, + E850C3F1FEA42490F1B3761E /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 1F0F886BEAFD97F97250E0A6 /* API Bindings */ = { + isa = PBXGroup; + children = ( + C79CD3902BA22B78A8E4A03E /* Models */, + CC4AE39C12E613AFB80FF4E8 /* DocumentScanner+API.swift */, + EE1984CE45A111B5694A3F49 /* DocumentType+StripeIdentity.swift */, + 643CB84029E6A12167838C3A /* DocumentUploader+API.swift */, + 725E481300FDDDD41EBAA835 /* FaceScanner+API.swift */, + EEF2BB05DF09AABFEEC1C66D /* IdentityAPIClient.swift */, + 8EFF781FE9ED055F99F767D3 /* SelfieUploader+API.swift */, + ); + path = "API Bindings"; + sourceTree = ""; + }; + 21094F500C248952675A92E7 /* Mock Files */ = { + isa = PBXGroup; + children = ( + 4A0742257602474CE098C00E /* Mock Photos */, + 281E4F7E9C4222212FE549EA /* VerificationPage */, + 7072DCFE3A5741D38837C873 /* VerificationPageData */, + ADA1D4AB1AE9B0E45C68FFD2 /* mock.html */, + ); + path = "Mock Files"; + sourceTree = ""; + }; + 281E4F7E9C4222212FE549EA /* VerificationPage */ = { + isa = PBXGroup; + children = ( + 7ECA816022703F05AD759897 /* VerificationPage_200_no_consent_header.json */, + E0B9A86D071960D13AE81563 /* VerificationPage_200_submitted.json */, + 16ADAC122F0818BB64C5EF41 /* VerificationPage_200_testMode.json */, + 3218882B7E1610E17BDB4522 /* VerificationPage_200.json */, + D467B7262B9CD929001EFE99 /* VerificationPage_200_no_exp.json */, + D5C7698331B899B80AF4F730 /* VerificationPage_no_selfie.json */, + 7FD2B7B0EDA3144115F080BC /* VerificationPage_require_live_capture.json */, + EA7EF0C427E327A1E945BC28 /* VerificationPage_type_address.json */, + 2FC99DD88994E0DB581E19C6 /* VerificationPage_type_doc_require_address.json */, + 531D9220055F0F54CF6D72CE /* VerificationPage_type_doc_require_idNumber_and_address.json */, + CCB84457F36C8EA327239852 /* VerificationPage_type_doc_require_idNumber.json */, + FCD3770DF9B6356D1A82870A /* VerificationPage_type_idNumber.json */, + E21D37D0CA9F5426F6318ADC /* VerificationPage_type_phone.json */, + ); + path = VerificationPage; + sourceTree = ""; + }; + 2EBA502F418D648EEF72EDB9 /* Detectors */ = { + isa = PBXGroup; + children = ( + 71CAD90E0B821CC823D976E9 /* FaceDetector */, + E32A0AA1EF4E64B83646D59C /* IDDetector */, + 63826305A96644687BF980CC /* BarcodeDetector.swift */, + 2061B962162CC2D8C4C6494C /* LaplacianBlurDetector.swift */, + 18593C820FE0E35DDEE36956 /* MLDetectorConfiguration.swift */, + 0989CE5CF80CF69BD28675DE /* MLDetectorMetricsTracker.swift */, + 57F22CC7DFA4B98A1A533CE0 /* MotionBlurDetector.swift */, + 9E170B07B4958901EE3864A4 /* VisionBasedDetector.swift */, + ); + path = Detectors; + sourceTree = ""; + }; + 33C89482CBE5232226CC592E /* ListView */ = { + isa = PBXGroup; + children = ( + B3BED2C3A9B548341D73D3CC /* ListItemView.swift */, + 5D4A09C8B4EBF06D00B80994 /* ListView.swift */, + ); + path = ListView; + sourceTree = ""; + }; + 33CCD5EF14F04A59D399E045 /* Coordinators */ = { + isa = PBXGroup; + children = ( + 8FE476C4E5F3E281B7E6E136 /* DocumentUploaderTest.swift */, + F08E49ED4B7010002D5FC734 /* IdentityImageUploaderTest.swift */, + 4D5C67D8BB38BC3EA6282FF2 /* IdentityVerificationSheetTest.swift */, + 7FB8B409BCEBD89987293A94 /* VerificationSheetControllerTest.swift */, + A69C1BC7C63512FE24B32433 /* VerificationSheetFlowControllerTest.swift */, + ); + path = Coordinators; + sourceTree = ""; + }; + 377C902DA22E0F9853BF81F4 /* FaceScanner */ = { + isa = PBXGroup; + children = ( + C4943A54EF548F28DB66DD1F /* FaceCaptureData.swift */, + 4D2F49DD0BAC5FEF308BA165 /* FaceScanner.swift */, + 29E0AD187733319776268351 /* FaceScannerConfiguration.swift */, + E3E90C7076A10469E4893B73 /* FaceScannerOutput.swift */, + ); + path = FaceScanner; + sourceTree = ""; + }; + 3B9E0DCE151FFC05A3D3E05B /* VerificationPage */ = { + isa = PBXGroup; + children = ( + D4715B952B0E8E1F008D07CC /* VerificationPage.swift */, + D4715B8F2B0E8E1F008D07CC /* VerificationPageFieldType.swift */, + D4715B892B0E8E1F008D07CC /* VerificationPageIconType.swift */, + D4715B8B2B0E8E1F008D07CC /* VerificationPageRequirements.swift */, + D4715B962B0E8E1F008D07CC /* VerificationPageStaticConsentLineContent.swift */, + D4715B902B0E8E1F008D07CC /* VerificationPageStaticContentBottomSheetContent.swift */, + D4715B852B0E8E1F008D07CC /* VerificationPageStaticContentBottomSheetLineContent.swift */, + D4715B922B0E8E1F008D07CC /* VerificationPageStaticContentConsentPage.swift */, + D4715B8E2B0E8E1F008D07CC /* VerificationPageStaticContentCountryNotListedPage.swift */, + D4715B8C2B0E8E1F008D07CC /* VerificationPageStaticContentDocumentCaptureModels.swift */, + D4715B8A2B0E8E1F008D07CC /* VerificationPageStaticContentDocumentCapturePage.swift */, + D4715B932B0E8E1F008D07CC /* VerificationPageStaticContentDocumentSelectPage.swift */, + D4715B862B0E8E1F008D07CC /* VerificationPageStaticContentIndividualPage.swift */, + D4715B882B0E8E1F008D07CC /* VerificationPageStaticContentIndividualWelcomePage.swift */, + D4715B872B0E8E1F008D07CC /* VerificationPageStaticContentPhoneOtpPage.swift */, + D4715B8D2B0E8E1F008D07CC /* VerificationPageStaticContentSelfieModels.swift */, + D4715B912B0E8E1F008D07CC /* VerificationPageStaticContentSelfiePage.swift */, + D4715B942B0E8E1F008D07CC /* VerificationPageStaticContentTextPage.swift */, + D467B7212B9BF270001EFE99 /* VerificationPageStaticContentExperiment.swift */, + ); + path = VerificationPage; + sourceTree = ""; + }; + 4A0742257602474CE098C00E /* Mock Photos */ = { + isa = PBXGroup; + children = ( + 9048C4B9F47CE82127D6C1B3 /* back_drivers_license.jpg */, + 6F772F905FB81552FA1728B2 /* cgimage_stripeidentity_test.png */, + 280A8D95B0045A940A917B1A /* front_drivers_license.jpg */, + 7CD11009097B994FEFF5393E /* header_icon.png */, + ); + path = "Mock Photos"; + sourceTree = ""; + }; + 4A93B9E4F9CD7EF6BC8D8202 /* Selfie */ = { + isa = PBXGroup; + children = ( + 2FEB1C2A314572B0234005A7 /* SelfieCaptureView.swift */, + 18BE1F9B0EB046BF0E1C2D8B /* SelfieScanningView.swift */, + 4023102F8B76C16522BC02B9 /* SelfieWarmupView.swift */, + ); + path = Selfie; + sourceTree = ""; + }; + 4CC5469F73EDF726A534C43A /* Source */ = { + isa = PBXGroup; + children = ( + 6FF221966B2E1A26EB5771B8 /* Analytics */, + 1F0F886BEAFD97F97250E0A6 /* API Bindings */, + A15C667249E105B9A37EC6AA /* Categories */, + E4939A3B0BCBB7B44F717847 /* Elements */, + 882FA766B74C9E5C2E2F5382 /* Helpers */, + 921749968993B6299670C8F0 /* NativeComponents */, + 8B9E16932812CEDA69E79947 /* WebWrapper */, + E26F8082A859AC1F5C5A3D13 /* Enums+CustomStringConvertible.swift */, + 53F1B93DB4F1708B41125301 /* IdentityVerificationSheet.swift */, + 3DD5FEA0E4E7EF1A4BA9646B /* IdentityVerificationSheetError.swift */, + 45CA21A528C7D39BAA2F2B51 /* StripeCore+Import.swift */, + 1E01DDEABC6ABAB0F03B7CEC /* VerificationClientSecret.swift */, + E70585F354B2B8B1420311CB /* VerificationSheetAnalytics.swift */, + ); + path = Source; + sourceTree = ""; + }; + 4F960B140ECD6032C9036EC6 /* VerificationPageData */ = { + isa = PBXGroup; + children = ( + B3B539D323CF2AACB580596B /* VerificationPageData.swift */, + B2E15397783A643B74921A00 /* VerificationPageDataRequirementError.swift */, + 1A566C584F41FE23108295E3 /* VerificationPageDataRequirements.swift */, + ); + path = VerificationPageData; + sourceTree = ""; + }; + 500DB8B56AFF3E5D762BA726 /* StripeIdentity */ = { + isa = PBXGroup; + children = ( + 313F5F802B0BE5DE00BD98A9 /* Docs.docc */, + DD11F7A6C34DDED8661D6182 /* Resources */, + 4CC5469F73EDF726A534C43A /* Source */, + 80C34F71161E8793947E18E7 /* Info.plist */, + 9C69B4A6F36A8BD814CB05D9 /* StripeIdentity.h */, + ); + path = StripeIdentity; + sourceTree = ""; + }; + 6C8A36498C59C6AA87239ADC /* Snapshot */ = { + isa = PBXGroup; + children = ( + A31C88110FBCDDA9FB912491 /* AnimatedBorderViewSnapshotTest.swift */, + 957545FFD547CDA1CFB719E6 /* BiometricConsentViewControllerSnapshotTest.swift */, + 1096AFFD3877D9A22703E6CA /* CGImage_StripeIdentitySnapshotTest.swift */, + 30748D6B84FC605EE612C2D3 /* DebugViewControllerSnapshotTest.swift */, + B67F4A7C86FF6784C6844649 /* DocumentScanningViewSnapshotTest.swift */, + 3943F18216635D56A3E6019C /* ErrorViewControllerSnapshotTest.swift */, + 4A1FCF877D109689CA724EB4 /* ErrorViewSnapshotTest.swift */, + 03C30DB2230DF9B47CD20AAF /* HeaderIconViewSnapshotTest.swift */, + 94838BCA1111F735A8FBC072 /* HeaderViewSnapshotTest.swift */, + 1E7C993DB897B55B2A172CD9 /* IdentityFlowViewSnapshotTest.swift */, + A2AF434F2405C5F38F259112 /* IdentityHTMLViewSnapshotTest.swift */, + 9E703FD28F5352DFC4115F1C /* IndividualViewControllerSnapshotTest.swift */, + A99CDE8D3C390BCAEF13F938 /* IndividualWelcomeViewControllerSnapshotTest.swift */, + 01C54327529E9A3BD81F37F4 /* InstructionalDocumentScanningViewSnapshotTest.swift */, + 8389CF3DBB12A384BCF0959F /* InstructionListViewSnapshotTest.swift */, + B65B0A0F124B3E0C5490D2B6 /* ListItemViewSnapshotTest.swift */, + C8D82EAF85863CAAA250E393 /* ListViewSnapshotTest.swift */, + D95CE7A231D14C9FD32A7848 /* NSAttributedString_HTMLSnapshotTest.swift */, + 8DAFC23BB4CDD7DEB3286704 /* PhoneOtpViewControllerSnapshotTest.swift */, + F95321B67AA78B88A32E37BE /* SelfieCaptureViewSnapshotTest.swift */, + AD7373453206D23A9ABF5E17 /* SelfieScanningViewSnapshotTest.swift */, + 21DB72C7F61A4A042D898236 /* SelfieWarmupViewSnapshotTest.swift */, + 8FF19274A29802CDB696D1DF /* SuccessViewControllerSnapshotTest.swift */, + 22A64D0C0989BD43624BB506 /* VerificationFlowWebViewSnapshotTests.swift */, + ); + path = Snapshot; + sourceTree = ""; + }; + 6FF221966B2E1A26EB5771B8 /* Analytics */ = { + isa = PBXGroup; + children = ( + BCC019FA1D6D8AEB598D9EC5 /* IdentityAnalyticsClient.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 7018612D148DB5EC9137EEDF /* WebWrapper */ = { + isa = PBXGroup; + children = ( + B2EB7348CE21DB1A86706532 /* VerificationFlowWebViewControllerTest.swift */, + E91362066B0F066C00CB45F2 /* VerificationFlowWebViewTest.swift */, + ); + path = WebWrapper; + sourceTree = ""; + }; + 7072DCFE3A5741D38837C873 /* VerificationPageData */ = { + isa = PBXGroup; + children = ( + B75F0E4F340355B8661085F7 /* VerificationPageData_200.json */, + 9D112C14362D55A94E9B9E6E /* VerificationPageData_no_errors_needback.json */, + 3AA3F3B346FFC8E58BF8739A /* VerificationPageData_no_errors.json */, + B38AE7F15E61D960EFA15659 /* VerificationPageData_submitted_not_closed.json */, + 99607F36AF24C978953964E9 /* VerificationPageData_submitted.json */, + ); + path = VerificationPageData; + sourceTree = ""; + }; + 71CAD90E0B821CC823D976E9 /* FaceDetector */ = { + isa = PBXGroup; + children = ( + B9BD50679E8B87237BF3955E /* FaceDetector.swift */, + BD9BA1457F98160EBCBEE670 /* FaceDetectorOutput.swift */, + ); + path = FaceDetector; + sourceTree = ""; + }; + 735252221308FED2A1BB2982 /* Project */ = { + isa = PBXGroup; + children = ( + 1839EB588B1D039EC1FC1671 /* BuildConfigurations */, + 500DB8B56AFF3E5D762BA726 /* StripeIdentity */, + 00D6B9FD0E5C501B4BF57EF9 /* StripeIdentityTests */, + ); + name = Project; + sourceTree = ""; + }; + 7B6CB500A83165695FFF5CE4 /* IdentityHTMLView */ = { + isa = PBXGroup; + children = ( + D4715BF12B0E8EBF008D07CC /* HTMLTextView.swift */, + D4715BEF2B0E8EBF008D07CC /* HTMLViewWithIconLabels.swift */, + D4715BF22B0E8EBF008D07CC /* IconLabelHTMLView.swift */, + D4715BF02B0E8EBF008D07CC /* MultilineIconLabelHTMLView.swift */, + ); + path = IdentityHTMLView; + sourceTree = ""; + }; + 87394A902F17C225C9801233 /* ML */ = { + isa = PBXGroup; + children = ( + FB26528E34B8AFBD225061AF /* Helpers */, + 4546F9D432ACFF45E9CE1368 /* IdentityMLModelLoader.swift */, + ); + path = ML; + sourceTree = ""; + }; + 8809E2951514DA7B6B8E9C7F /* Categories */ = { + isa = PBXGroup; + children = ( + 4DEBC1A25D2C446219E7E733 /* CGImage+StripeIdentityUnitTest.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 881271B2FC813730D937B96E /* ViewControllers */ = { + isa = PBXGroup; + children = ( + D4715BD32B0E8E96008D07CC /* BiometricConsentViewController.swift */, + D4715BD92B0E8E96008D07CC /* BottomSheetViewController.swift */, + D4715BCB2B0E8E96008D07CC /* CountryNotListedViewController.swift */, + D4715BD02B0E8E96008D07CC /* DebugViewController.swift */, + D4715BD82B0E8E96008D07CC /* DocumentCaptureViewController.swift */, + D4715BD42B0E8E96008D07CC /* DocumentCaptureViewController+Strings.swift */, + D4715BD62B0E8E96008D07CC /* DocumentFileUploadViewController.swift */, + D4715BCA2B0E8E95008D07CC /* DocumentFileUploadViewController+Strings.swift */, + D4715BC72B0E8E95008D07CC /* DocumentWarmupViewController.swift */, + D4715BC92B0E8E95008D07CC /* ErrorViewController.swift */, + D4715BCC2B0E8E96008D07CC /* IdentityFlowViewController.swift */, + D4715BD52B0E8E96008D07CC /* IndividualViewController.swift */, + D4715BCD2B0E8E96008D07CC /* IndividualWelcomeViewController.swift */, + D4715BD72B0E8E96008D07CC /* LoadingViewController.swift */, + D4715BD12B0E8E96008D07CC /* PhoneOtpViewController.swift */, + D4715BCE2B0E8E96008D07CC /* SelfieCaptureViewController.swift */, + D4715BDA2B0E8E96008D07CC /* SelfieCaptureViewController+Strings.swift */, + D4715BCF2B0E8E96008D07CC /* SelfieWarmupViewController.swift */, + D4715BD22B0E8E96008D07CC /* SuccessViewController.swift */, + D4715BC82B0E8E95008D07CC /* UIViewController+SafariExtension.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + 882FA766B74C9E5C2E2F5382 /* Helpers */ = { + isa = PBXGroup; + children = ( + 20D40A569C2BB62F86D57C01 /* Image.swift */, + E94605BCADE912561ED6F710 /* STPLocalizedString.swift */, + F1559E966A487EE3867DD7B8 /* String+Localized.swift */, + CEE8340912F2A489BB80D39B /* StripeIdentityBundleLocator.swift */, + D4888BF72B8FEF3C00B69C89 /* UIImage+utils.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 8A94C8D885B3CEA5BBB73D84 /* Localizations */ = { + isa = PBXGroup; + children = ( + 989A2C5379C60B9F0E0E2711 /* Localizable.strings */, + ); + path = Localizations; + sourceTree = ""; + }; + 8B9E16932812CEDA69E79947 /* WebWrapper */ = { + isa = PBXGroup; + children = ( + 658F7A165D731F5046ACDF29 /* VerificationFlowWebView.swift */, + 48A449D0BE66D0A555183AF4 /* VerificationFlowWebViewController.swift */, + D75A138199A6CC2DB31DD2D5 /* VerifyWebURLHelper.swift */, + ); + path = WebWrapper; + sourceTree = ""; + }; + 921749968993B6299670C8F0 /* NativeComponents */ = { + isa = PBXGroup; + children = ( + D9B3846C593CC86E22146C09 /* Coordinators */, + 2EBA502F418D648EEF72EDB9 /* Detectors */, + 87394A902F17C225C9801233 /* ML */, + 881271B2FC813730D937B96E /* ViewControllers */, + A1E77974EEA3CA7771B4F624 /* Views */, + E310DFA6CAC355B848E82998 /* DocumentSide.swift */, + B9CB453179082B1AE6B242FD /* IdentityDataCollecting.swift */, + 050900C738C01F6D96F7ED5B /* IdentityFlowNavigationController.swift */, + FEB6AB9A9BAF6E50B962C339 /* IdentityUI.swift */, + ); + path = NativeComponents; + sourceTree = ""; + }; + 9C674AEFDAF7791E3C23E052 /* Images */ = { + isa = PBXGroup; + children = ( + D4715BFA2B0E8F42008D07CC /* icon_add@3x.png */, + D4715C032B0E8F42008D07CC /* icon_camera_classic@3x.png */, + D4715C0B2B0E8F43008D07CC /* icon_camera@3x.png */, + D4715BFD2B0E8F42008D07CC /* icon_checkmark_92@3x.png */, + D4715C002B0E8F42008D07CC /* icon_checkmark@3x.png */, + D4715BFF2B0E8F42008D07CC /* icon_clock@3x.png */, + D4715C072B0E8F42008D07CC /* icon_cloud@3x.png */, + D4715BF92B0E8F42008D07CC /* icon_create_identity_verification@3x.png */, + D4715C082B0E8F42008D07CC /* icon_dispute_protection@3x.png */, + D4715BFB2B0E8F42008D07CC /* icon_document@3x.png */, + D4715BFC2B0E8F42008D07CC /* icon_ellipsis@3x.png */, + D4715BFE2B0E8F42008D07CC /* icon_id_front@3x.png */, + D4715C042B0E8F42008D07CC /* icon_info@3x.png */, + D4715C012B0E8F42008D07CC /* icon_lock@3x.png */, + D4715C022B0E8F42008D07CC /* icon_moved@3x.png */, + D4715BF82B0E8F42008D07CC /* icon_phone@3x.png */, + D4715C052B0E8F42008D07CC /* icon_selfie_warmup@3x.png */, + D4715C0A2B0E8F43008D07CC /* icon_wallet@3x.png */, + D4715C092B0E8F43008D07CC /* icon_warning_92@3x.png */, + D4715C062B0E8F42008D07CC /* icon_warning@3x.png */, + D4715BF72B0E8F42008D07CC /* icon_warning2@3x.png */, + ); + path = Images; + sourceTree = ""; + }; + A15C667249E105B9A37EC6AA /* Categories */ = { + isa = PBXGroup; + children = ( + CBC057ECE030E8374AD9CF61 /* Array+StripeIdentity.swift */, + 744CE13E8488F0E4AC951D6C /* CGImage+StripeIdentity.swift */, + 82D731669A9237F0B147B366 /* MLMultiArray+StripeIdentity.swift */, + 42768A09155B5439E724B360 /* NSAttributedString+HTML.swift */, + 2E5237659945E4A05ADE6DB0 /* TimeInterval+StripeIdentity.swift */, + FF0871DEB2B2BA56EFADEEA6 /* UINavigationController+StripeIdentity.swift */, + C3C78174C0059A8957832825 /* VNBarcodeSymbology+StripeIdentity.swift */, + ); + path = Categories; + sourceTree = ""; + }; + A1E77974EEA3CA7771B4F624 /* Views */ = { + isa = PBXGroup; + children = ( + D4715BB72B0E8E80008D07CC /* BottomAlignedLabel.swift */, + D4715BAD2B0E8E80008D07CC /* BottomSheetView.swift */, + D4715BA92B0E8E80008D07CC /* CameraPreviewContainerView.swift */, + D4715BB32B0E8E80008D07CC /* CompleteOptionView.swift */, + D4715BB62B0E8E80008D07CC /* ContentCenteringScrollView.swift */, + D4715BB02B0E8E80008D07CC /* DebugView.swift */, + D4715BB52B0E8E80008D07CC /* DocumentWarmupView.swift */, + D4715BAA2B0E8E80008D07CC /* ErrorView.swift */, + D4715BB22B0E8E80008D07CC /* HeaderIconView.swift */, + D4715BAC2B0E8E80008D07CC /* HeaderView.swift */, + D4715BAB2B0E8E80008D07CC /* IdentityFlowView.swift */, + D4715BAE2B0E8E80008D07CC /* InstructionListView.swift */, + D4715BB42B0E8E80008D07CC /* PhoneOtpView.swift */, + D4715BAF2B0E8E80008D07CC /* ShadowConfiguration.swift */, + D4715BB12B0E8E80008D07CC /* ShadowedCorneredImageView.swift */, + E8F859A98FD354C4D369663B /* DocumentCapture */, + 7B6CB500A83165695FFF5CE4 /* IdentityHTMLView */, + 33C89482CBE5232226CC592E /* ListView */, + 4A93B9E4F9CD7EF6BC8D8202 /* Selfie */, + ); + path = Views; + sourceTree = ""; + }; + B676F5058454EA479CE2B699 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8AB1A0B05C00E547ED1A0D09 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BC502BBBD4439DAD6B20C36B /* Elements */ = { + isa = PBXGroup; + children = ( + 6FF56E95E47DA51C3517E1EC /* IdentityElementsFactoryTest.swift */, + 85E8B042346D6DF41AA6CCFC /* IndividualElementTest.swift */, + ); + path = Elements; + sourceTree = ""; + }; + BD87721D7BE91AA7EA25E952 /* ImageScanningSession */ = { + isa = PBXGroup; + children = ( + 0F99DB710C60AC2A98449D51 /* ImageScanningSession.swift */, + FFE163E90F54FE4A075C7B74 /* ImageScanningSessionDelegate.swift */, + ); + path = ImageScanningSession; + sourceTree = ""; + }; + C240F6CABCF2EAA5A9F29733 /* Unit */ = { + isa = PBXGroup; + children = ( + D467B7232B9C2FE0001EFE99 /* Analytics */, + FA68623DC1EA02B69E91D537 /* API Bindings */, + 8809E2951514DA7B6B8E9C7F /* Categories */, + BC502BBBD4439DAD6B20C36B /* Elements */, + E05EEC720175CFA2B7B02DF9 /* NativeComponents */, + 7018612D148DB5EC9137EEDF /* WebWrapper */, + FFD771C14C0711019D8AC9C2 /* VerificationClientSecretTest.swift */, + 09F06CA1B36DA48B8E8BD7A5 /* VerificationSheetAnalyticsTest.swift */, + ); + path = Unit; + sourceTree = ""; + }; + C30D6A52DEFD4953D1003B59 = { + isa = PBXGroup; + children = ( + 735252221308FED2A1BB2982 /* Project */, + B676F5058454EA479CE2B699 /* Frameworks */, + DA1A05D0BE4F60655BC0E2CD /* Products */, + ); + sourceTree = ""; + }; + C79CD3902BA22B78A8E4A03E /* Models */ = { + isa = PBXGroup; + children = ( + 3B9E0DCE151FFC05A3D3E05B /* VerificationPage */, + 4F960B140ECD6032C9036EC6 /* VerificationPageData */, + DD82B7FB4D8A2A274B406258 /* VerificationPageDataUpdate */, + 3AFAC1FACF9A36AAFC19BF2F /* DocumentType.swift */, + 7F9DA29DC85773AC710A5201 /* TruncatedDecimal.swift */, + ); + path = Models; + sourceTree = ""; + }; + C9888FDFA7892093618E47C9 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 1B264A8F97FA8CB0706F140F /* BiometricConsentViewControllerTest.swift */, + DEB322F07A4F2BE5B1FED0AD /* DebugViewControllerTest.swift */, + BCA7F9B0204BF18C483A474A /* DocumentCaptureViewControllerTest.swift */, + 5F0B317274417308FBA55E55 /* DocumentFileUploadViewControllerTest.swift */, + 20129AFBC6B4D304B494CBA6 /* ErrorViewControllerTest.swift */, + 22681AA4BBA6A907A0DD29DB /* IdentityFlowNavigationControllerTest.swift */, + 9145699AC820A2F2F916F142 /* IndividualViewControllerTest.swift */, + E4952611570326FFC52F0F30 /* IndividualWelcomeViewControllerTest.swift */, + 12F4C291ACBB73ED95504131 /* PhoneOtpViewControllerTest.swift */, + 4AFCD229BD9CB58303E19111 /* SelfieWarmupViewControllerTest.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + D20B93DC17B56E6A4B98A3B0 /* ImageUploaders */ = { + isa = PBXGroup; + children = ( + C334F59C93D3D11534FACBBA /* DocumentUploader.swift */, + 4D4C3F2BAFB6BEFFFC1B566F /* IdentityImageUploader.swift */, + 9BB8E3DF263505065E8545AB /* SelfieUploader.swift */, + ); + path = ImageUploaders; + sourceTree = ""; + }; + D467B7232B9C2FE0001EFE99 /* Analytics */ = { + isa = PBXGroup; + children = ( + D467B7242B9C3008001EFE99 /* IdentityAnalyticsClientTest.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + D9B3846C593CC86E22146C09 /* Coordinators */ = { + isa = PBXGroup; + children = ( + F2A0B1EE1A4A9208812559DF /* ImageScanner */, + BD87721D7BE91AA7EA25E952 /* ImageScanningSession */, + D20B93DC17B56E6A4B98A3B0 /* ImageUploaders */, + 467BA77070B91055F1F2DCD4 /* IdentityTopLevelDestination.swift */, + A5082A4100CEE0C8378AD185 /* VerificationSheetController.swift */, + 98AF8558B79CD52154D6EF3B /* VerificationSheetFlowController.swift */, + 7CDAA5C16C934746013C6CE6 /* VerificationSheetFlowControllerError.swift */, + ); + path = Coordinators; + sourceTree = ""; + }; + DA1A05D0BE4F60655BC0E2CD /* Products */ = { + isa = PBXGroup; + children = ( + CB639F497EF62CAE81822820 /* StripeCameraCore.framework */, + 14C231755842F322A25160ED /* StripeCameraCoreTestUtils.framework */, + 4D0F4646D7565055396DD8F5 /* StripeCore.framework */, + 5726517468179B69D50692C0 /* StripeCoreTestUtils.framework */, + D604F6AABE4D6D5CCF802685 /* StripeIdentity.framework */, + A5D517A4C8669379A029BBAD /* StripeIdentityTests.xctest */, + FD376CBC869531C40036081B /* StripeUICore.framework */, + ); + name = Products; + sourceTree = ""; + }; + DD11F7A6C34DDED8661D6182 /* Resources */ = { + isa = PBXGroup; + children = ( + 9C674AEFDAF7791E3C23E052 /* Images */, + 8A94C8D885B3CEA5BBB73D84 /* Localizations */, + ); + path = Resources; + sourceTree = ""; + }; + DD82B7FB4D8A2A274B406258 /* VerificationPageDataUpdate */ = { + isa = PBXGroup; + children = ( + 40F28FD09DBAFAEB609EAD25 /* RequiredInternationalAddress.swift */, + E1FC19E901C6E57D948833FD /* VerificationPageClearData.swift */, + 1F4F55F9FBE67FCFE3767816 /* VerificationPageCollectedData.swift */, + A6E9FA49B13AC0A8826F8257 /* VerificationPageDataDob.swift */, + 083865663173CDD9F77417A4 /* VerificationPageDataDocumentFileData.swift */, + 14244B002D9F37B56BE8D4F4 /* VerificationPageDataFace.swift */, + 50A49996D90714452E6A533A /* VerificationPageDataIdNumber.swift */, + 110A0200099F462B576F2ED4 /* VerificationPageDataName.swift */, + 9429D10FEFFFEFEE08DBA4F8 /* VerificationPageDataPhone.swift */, + 15E15D64B5F7EF76235D0BA5 /* VerificationPageDataUpdate.swift */, + ); + path = VerificationPageDataUpdate; + sourceTree = ""; + }; + E05EEC720175CFA2B7B02DF9 /* NativeComponents */ = { + isa = PBXGroup; + children = ( + 33CCD5EF14F04A59D399E045 /* Coordinators */, + C9888FDFA7892093618E47C9 /* ViewControllers */, + ); + path = NativeComponents; + sourceTree = ""; + }; + E32A0AA1EF4E64B83646D59C /* IDDetector */ = { + isa = PBXGroup; + children = ( + BCB41D1D6B1D35C92EA6F2B7 /* IDDetector.swift */, + 14ED7B7A9CC6DE447C14B5CB /* IDDetectorConstants.swift */, + 2CDF024DA4F519CB21A717AA /* IDDetectorOutput.swift */, + ); + path = IDDetector; + sourceTree = ""; + }; + E4939A3B0BCBB7B44F717847 /* Elements */ = { + isa = PBXGroup; + children = ( + 31CC3450DDBE415B82707F4B /* IdentityElementsFactory.swift */, + 8E1EF4A7AA64856F9C887C2A /* IdentityTextButtonElement.swift */, + EB73F630CCAFE6AAB9727C62 /* IdNumberElement.swift */, + E0CF1B086B99DEEF4F4F2A1D /* IndividualFormElement.swift */, + ); + path = Elements; + sourceTree = ""; + }; + E8F859A98FD354C4D369663B /* DocumentCapture */ = { + isa = PBXGroup; + children = ( + 0EA099682E4BE0A34CFCBE6C /* AnimatedBorderView.swift */, + 8AF040F11A7D0693A7783654 /* DocumentCaptureView.swift */, + D6A26F37BDBB5330AE87E7CB /* DocumentScanningView.swift */, + 1CDF8D78C430D9B26DD92B11 /* InstructionalDocumentScanningView.swift */, + ); + path = DocumentCapture; + sourceTree = ""; + }; + F2A0B1EE1A4A9208812559DF /* ImageScanner */ = { + isa = PBXGroup; + children = ( + 0DB7E4B2463D591EEF46036C /* DocumentScanner */, + 377C902DA22E0F9853BF81F4 /* FaceScanner */, + 149453E1854C9566CC8D8484 /* ImageScanner.swift */, + DC8F619B76D191FF2DEC8660 /* ImageScanningConcurrencyManager.swift */, + ); + path = ImageScanner; + sourceTree = ""; + }; + F70954CA70A6D39896F5E2B5 /* Helpers */ = { + isa = PBXGroup; + children = ( + CE691D40B29AB0F31443479A /* DocumentUploaderMock.swift */, + C43D3DC62FE3C5CC9FD22A50 /* IdentityAnalyticsClientTestHelpers.swift */, + C8D0D5F9E17825856B55E4D1 /* IdentityAPIClientTestMock.swift */, + 111A55FA39B400E6E87A5175 /* IdentityMLModelLoaderMock.swift */, + 738A5EB6B7F1C5621F190A72 /* IdentityMockData.swift */, + 4C0B804DA867C37809656BB3 /* ImageScannerMock.swift */, + 005D7E5D0E239D61ED1E9796 /* ImageScanningConcurrencyManagerMock.swift */, + 786574907AC9065E6E658D9C /* MLDetectorMetricsTrackerMock.swift */, + 037D27A0AAECB846C8579452 /* SnapshotTestMockData.swift */, + 52FB883B057ED79F2C58BC58 /* VerificationFlowResult+Equatable.swift */, + F459D2DF594C15C03585F3AF /* VerificationSheetControllerMock.swift */, + 1CAC6276996043D1B3FC92F7 /* VerificationSheetFlowControllerMock.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + FA68623DC1EA02B69E91D537 /* API Bindings */ = { + isa = PBXGroup; + children = ( + A59680E002FC6E7BC9906BD2 /* IdentityAPIClientTest.swift */, + D6918DC6A9681E769D44BC78 /* TruncatedDecimalTest.swift */, + ); + path = "API Bindings"; + sourceTree = ""; + }; + FB26528E34B8AFBD225061AF /* Helpers */ = { + isa = PBXGroup; + children = ( + 748D38C4370E78DBC9E20E95 /* MLModelLoader.swift */, + 2FD2C7D94586AB8984DB7389 /* MLModelUnexpectedOutputError.swift */, + 93F2EE0B99059C7BFB72AFAF /* NonMaxSuppression.swift */, + ); + path = Helpers; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 9B1562BF42FD34FD2D6229B6 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A06F81B7D6B68EA72A293F5 /* StripeIdentity.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 5883CCEAA4C73E1DB1F0D3A5 /* StripeIdentityTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7C14F7F327D79A07F63042E0 /* Build configuration list for PBXNativeTarget "StripeIdentityTests" */; + buildPhases = ( + 2B7015585124CD15B8ECF68A /* Sources */, + 2FB257609A15689D3483EA9B /* Resources */, + 048FEFC038A0C2BC99CCB683 /* Embed Frameworks */, + 942AEFA5BCC5A034EF066C3E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 90F68D87EFF3F3EE0E36B03A /* PBXTargetDependency */, + ); + name = StripeIdentityTests; + packageProductDependencies = ( + 960247DC321CBDB422FE713D /* iOSSnapshotTestCase */, + 6E2CB8EBFA629D3FF26481AB /* OHHTTPStubs */, + 2714B21F1CD18CEED8772AB6 /* OHHTTPStubsSwift */, + ); + productName = StripeIdentityTests; + productReference = A5D517A4C8669379A029BBAD /* StripeIdentityTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + F251015845B21C036CFBC636 /* StripeIdentity */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9E28A14ACC690D35FD59690A /* Build configuration list for PBXNativeTarget "StripeIdentity" */; + buildPhases = ( + 9B1562BF42FD34FD2D6229B6 /* Headers */, + 3DCE29146A781338C4D3AC2A /* Sources */, + A037CEBE08A7F64ADB4986B9 /* Resources */, + 559B276C9F23AEDE2D54B9A8 /* Embed Frameworks */, + 65EE834154FF7B955E7239D7 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeIdentity; + packageProductDependencies = ( + ); + productName = StripeIdentity; + productReference = D604F6AABE4D6D5CCF802685 /* StripeIdentity.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0705CAA185B63201FD561508 /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = 8BD83D322879659AB98AEA29 /* Build configuration list for PBXProject "StripeIdentity" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = C30D6A52DEFD4953D1003B59; + packageReferences = ( + 98906E4295D5EDD251F81145 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + 1034C700B96E0B79C2107621 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + D44AE4E72B9A66ED00446682 /* XCRemoteSwiftPackageReference "capture-core-sp" */, + ); + productRefGroup = DA1A05D0BE4F60655BC0E2CD /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F251015845B21C036CFBC636 /* StripeIdentity */, + 5883CCEAA4C73E1DB1F0D3A5 /* StripeIdentityTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2FB257609A15689D3483EA9B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6C1E79C83069CDD64BF03563 /* back_drivers_license.jpg in Resources */, + 443F1D1D9FE899158D58461E /* cgimage_stripeidentity_test.png in Resources */, + 471F567D8201BBA6D82D9932 /* front_drivers_license.jpg in Resources */, + BDF8D30919A290A0CF52059C /* header_icon.png in Resources */, + 135B58FCD4663D3D8E4C82F7 /* VerificationPage_200.json in Resources */, + 7F18785CB587E7F36288BF02 /* VerificationPage_200_no_consent_header.json in Resources */, + C258E3B8806DFD2C288089DE /* VerificationPage_200_submitted.json in Resources */, + 6195A200621E125B15A80285 /* VerificationPage_200_testMode.json in Resources */, + E1D4A5DD8C188725C5D23BBB /* VerificationPage_no_selfie.json in Resources */, + 7797883309701F3BB36B1F20 /* VerificationPage_require_live_capture.json in Resources */, + 41B1C1B08CA7DB8C18A7C9C4 /* VerificationPage_type_address.json in Resources */, + 709DB807A26A17B4571EA9D8 /* VerificationPage_type_doc_require_address.json in Resources */, + B6A52680DC3B117DD0EE13E2 /* VerificationPage_type_doc_require_idNumber.json in Resources */, + 89562A38C9B3C7870B5F22F2 /* VerificationPage_type_doc_require_idNumber_and_address.json in Resources */, + 760BDB66D60C8CCC7B72577C /* VerificationPage_type_idNumber.json in Resources */, + 8802941AB1CAFB81BDE75249 /* VerificationPage_type_phone.json in Resources */, + FF3D439778B2EF580053AC15 /* VerificationPageData_200.json in Resources */, + 375C63BC59BFC5F8615E266F /* VerificationPageData_no_errors.json in Resources */, + EF343909D610063574D0F911 /* VerificationPageData_no_errors_needback.json in Resources */, + 3C228BF28D1D56F1E74AE7AB /* VerificationPageData_submitted.json in Resources */, + D467B7272B9CD929001EFE99 /* VerificationPage_200_no_exp.json in Resources */, + 6DC8C4AFA61DFDA20278114F /* VerificationPageData_submitted_not_closed.json in Resources */, + C1DF05059A82FE217A7831BA /* mock.html in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A037CEBE08A7F64ADB4986B9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D4715C1C2B0E8F43008D07CC /* icon_cloud@3x.png in Resources */, + D4715C192B0E8F43008D07CC /* icon_info@3x.png in Resources */, + D4715C132B0E8F43008D07CC /* icon_id_front@3x.png in Resources */, + D4715C1A2B0E8F43008D07CC /* icon_selfie_warmup@3x.png in Resources */, + D4715C172B0E8F43008D07CC /* icon_moved@3x.png in Resources */, + D4715C182B0E8F43008D07CC /* icon_camera_classic@3x.png in Resources */, + D4715C152B0E8F43008D07CC /* icon_checkmark@3x.png in Resources */, + D4715C1D2B0E8F43008D07CC /* icon_dispute_protection@3x.png in Resources */, + D4715C0D2B0E8F43008D07CC /* icon_phone@3x.png in Resources */, + D4715C122B0E8F43008D07CC /* icon_checkmark_92@3x.png in Resources */, + D4715C1B2B0E8F43008D07CC /* icon_warning@3x.png in Resources */, + D4715C202B0E8F43008D07CC /* icon_camera@3x.png in Resources */, + D4715C1F2B0E8F43008D07CC /* icon_wallet@3x.png in Resources */, + D4715C142B0E8F43008D07CC /* icon_clock@3x.png in Resources */, + D4715C0E2B0E8F43008D07CC /* icon_create_identity_verification@3x.png in Resources */, + D4715C112B0E8F43008D07CC /* icon_ellipsis@3x.png in Resources */, + D4715C0F2B0E8F43008D07CC /* icon_add@3x.png in Resources */, + D4715C0C2B0E8F43008D07CC /* icon_warning2@3x.png in Resources */, + D4715C1E2B0E8F43008D07CC /* icon_warning_92@3x.png in Resources */, + 812B45A86DD6B28A06A8FAC7 /* Localizable.strings in Resources */, + D4715C102B0E8F43008D07CC /* icon_document@3x.png in Resources */, + D4715C162B0E8F43008D07CC /* icon_lock@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2B7015585124CD15B8ECF68A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 548C24D5A0F63E60BC83676F /* DocumentUploaderMock.swift in Sources */, + F2BBAF668109BC42FF4FCD09 /* IdentityAPIClientTestMock.swift in Sources */, + CB3E51C6E407E6A4A1001AAC /* IdentityAnalyticsClientTestHelpers.swift in Sources */, + 9F3790F6AB77036ADCC4B2C8 /* IdentityMLModelLoaderMock.swift in Sources */, + CFA141490408B4644A4F0375 /* IdentityMockData.swift in Sources */, + 78BC6F625665CE6EAB33EA62 /* ImageScannerMock.swift in Sources */, + BA655B97F1F31F3B8BA14199 /* ImageScanningConcurrencyManagerMock.swift in Sources */, + D467B7252B9C3008001EFE99 /* IdentityAnalyticsClientTest.swift in Sources */, + 5DD28B1BE8DF2CC04E9190E5 /* MLDetectorMetricsTrackerMock.swift in Sources */, + AF48EAD4D52EB59134652A08 /* SnapshotTestMockData.swift in Sources */, + 74701BC18362926FDED42E5A /* VerificationFlowResult+Equatable.swift in Sources */, + A44BC25EF5F443C7D0CBB783 /* VerificationSheetControllerMock.swift in Sources */, + A6E190F4F1E9D599C2422624 /* VerificationSheetFlowControllerMock.swift in Sources */, + 9F07B2D6AEC03BCCBCCF5908 /* AnimatedBorderViewSnapshotTest.swift in Sources */, + A10A4F91CF1ECB610909ECD5 /* BiometricConsentViewControllerSnapshotTest.swift in Sources */, + 54909D14587287B674027374 /* CGImage_StripeIdentitySnapshotTest.swift in Sources */, + CB09C0EFFCBA132257D6914D /* DebugViewControllerSnapshotTest.swift in Sources */, + B729E396A57DC7E1DC3D1FBA /* DocumentScanningViewSnapshotTest.swift in Sources */, + 1ED629559BED28AC3FAB8573 /* ErrorViewControllerSnapshotTest.swift in Sources */, + AB4F7B2D348FAA2DD0BB40BD /* ErrorViewSnapshotTest.swift in Sources */, + AF90AF76409959324FB270EA /* HeaderIconViewSnapshotTest.swift in Sources */, + 047B7B3A70037FA1172A164C /* HeaderViewSnapshotTest.swift in Sources */, + 96C17BE1FF29C4A24FAFFDBB /* IdentityFlowViewSnapshotTest.swift in Sources */, + 7C261F9C94175AD5345D384A /* IdentityHTMLViewSnapshotTest.swift in Sources */, + 3B9DB565B6C06BC058BF62D6 /* IndividualViewControllerSnapshotTest.swift in Sources */, + E72C7C6D3632324FED428A68 /* IndividualWelcomeViewControllerSnapshotTest.swift in Sources */, + 25A7B4E2E5CB3FD549F6819B /* InstructionListViewSnapshotTest.swift in Sources */, + BDF0199A4A88D5E66751A033 /* InstructionalDocumentScanningViewSnapshotTest.swift in Sources */, + 683FA0F250620BE62EFA2807 /* ListItemViewSnapshotTest.swift in Sources */, + 2B7C095E1029C163D107CF68 /* ListViewSnapshotTest.swift in Sources */, + FD6FB63B061FEABF17053225 /* NSAttributedString_HTMLSnapshotTest.swift in Sources */, + 869F5D2A387FED3C1D1674BA /* PhoneOtpViewControllerSnapshotTest.swift in Sources */, + B292E22D9DD3198D44F249A7 /* SelfieCaptureViewSnapshotTest.swift in Sources */, + B6D4EF6BB1A9178AFA18675B /* SelfieScanningViewSnapshotTest.swift in Sources */, + 6F896A9A689A7524F2AE823C /* SelfieWarmupViewSnapshotTest.swift in Sources */, + 3E64C28D0E13D6F9F455C3F2 /* SuccessViewControllerSnapshotTest.swift in Sources */, + DCFFA11A461B012F7515941F /* VerificationFlowWebViewSnapshotTests.swift in Sources */, + 230C42264D444023F416A7A1 /* IdentityAPIClientTest.swift in Sources */, + 590772C305CDA9FE9157F92C /* TruncatedDecimalTest.swift in Sources */, + 34C09421E30F9BA262AB1532 /* CGImage+StripeIdentityUnitTest.swift in Sources */, + 3CB793BE23FC9716A0F35CCB /* IdentityElementsFactoryTest.swift in Sources */, + ABC1E2DDC2A4D02702A19E2B /* IndividualElementTest.swift in Sources */, + A41FF7CAF8EBEBCB16115DCB /* DocumentUploaderTest.swift in Sources */, + 34ABF501E050B043CE1E859C /* IdentityImageUploaderTest.swift in Sources */, + A8EDE16782B8ABEB359CADAC /* IdentityVerificationSheetTest.swift in Sources */, + F2F041A9228AFAEFCB8A03B7 /* VerificationSheetControllerTest.swift in Sources */, + 28CAAFE7E31A8D17233615F3 /* VerificationSheetFlowControllerTest.swift in Sources */, + 8E420E7B59168A4B81C19123 /* BiometricConsentViewControllerTest.swift in Sources */, + 81D31513A44A5B3A8830FC7C /* DebugViewControllerTest.swift in Sources */, + 54C882D8194AC39EB58BCDE7 /* DocumentCaptureViewControllerTest.swift in Sources */, + 99243751D031F96B17286315 /* DocumentFileUploadViewControllerTest.swift in Sources */, + C63EAF360DB9887856543E68 /* ErrorViewControllerTest.swift in Sources */, + C81F6192AA4A1B30AB1655A4 /* IdentityFlowNavigationControllerTest.swift in Sources */, + 0C3C571F4C5E8B00ED18A004 /* IndividualViewControllerTest.swift in Sources */, + 5642050BB7FEB20E05B41DCD /* IndividualWelcomeViewControllerTest.swift in Sources */, + 40609779D5BD7D6AE61FE01D /* PhoneOtpViewControllerTest.swift in Sources */, + 874EF196DF573CA524785AEF /* SelfieWarmupViewControllerTest.swift in Sources */, + 8DACF642D2E0C5FE35637EAE /* VerificationClientSecretTest.swift in Sources */, + A7B8782A3E807D11A6E6A239 /* VerificationSheetAnalyticsTest.swift in Sources */, + 4D4430064CB7F2CE3AD3266A /* VerificationFlowWebViewControllerTest.swift in Sources */, + 5B4F08774CC9CA1D93216D0D /* VerificationFlowWebViewTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3DCE29146A781338C4D3AC2A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EA2A1C7D4A9FB826A2146808 /* DocumentScanner+API.swift in Sources */, + D4715BE92B0E8E96008D07CC /* IndividualViewController.swift in Sources */, + C378303DE7D3674CE4C771B7 /* DocumentType+StripeIdentity.swift in Sources */, + F1CA28CC435B94C289B08257 /* DocumentUploader+API.swift in Sources */, + 0CC741018E3A07A925A4CCC3 /* FaceScanner+API.swift in Sources */, + D4715BE82B0E8E96008D07CC /* DocumentCaptureViewController+Strings.swift in Sources */, + F831E6E67E7F9057FED3F4B8 /* IdentityAPIClient.swift in Sources */, + DB6005016F47D09559DA0C98 /* DocumentType.swift in Sources */, + D4715BDF2B0E8E96008D07CC /* CountryNotListedViewController.swift in Sources */, + E15D79E05C4FA68CA56B0CC8 /* TruncatedDecimal.swift in Sources */, + 56583B45EA53659E843E35F6 /* VerificationPageData.swift in Sources */, + D4715B972B0E8E20008D07CC /* VerificationPageStaticContentBottomSheetLineContent.swift in Sources */, + D4715BC42B0E8E80008D07CC /* DocumentWarmupView.swift in Sources */, + 3D9D1BA3B4C7E2464D457FF7 /* VerificationPageDataRequirementError.swift in Sources */, + D4715BE02B0E8E96008D07CC /* IdentityFlowViewController.swift in Sources */, + D4715BBE2B0E8E80008D07CC /* ShadowConfiguration.swift in Sources */, + D4715BA12B0E8E20008D07CC /* VerificationPageFieldType.swift in Sources */, + F1DAE15C305F9733A7C40673 /* VerificationPageDataRequirements.swift in Sources */, + C83DD219624645AACB2F29A9 /* RequiredInternationalAddress.swift in Sources */, + D4715BEB2B0E8E96008D07CC /* LoadingViewController.swift in Sources */, + 87A4B9B848DF1CFA11F32FC4 /* VerificationPageClearData.swift in Sources */, + 5BC21FBB6B4A1B1C8EC04270 /* VerificationPageCollectedData.swift in Sources */, + 36F0967E814234FE61EF3A0B /* VerificationPageDataDob.swift in Sources */, + D467B7222B9BF270001EFE99 /* VerificationPageStaticContentExperiment.swift in Sources */, + 4E44D8C9967E301DB34EB6F0 /* VerificationPageDataDocumentFileData.swift in Sources */, + D4715BA72B0E8E20008D07CC /* VerificationPage.swift in Sources */, + D4715BE62B0E8E96008D07CC /* SuccessViewController.swift in Sources */, + 47E285A86960CDE275E5EA53 /* VerificationPageDataFace.swift in Sources */, + D2309DFF2EDABB6C1A139513 /* VerificationPageDataIdNumber.swift in Sources */, + D4715BBF2B0E8E80008D07CC /* DebugView.swift in Sources */, + D4715BBD2B0E8E80008D07CC /* InstructionListView.swift in Sources */, + D4888BF82B8FEF3C00B69C89 /* UIImage+utils.swift in Sources */, + 4B46A1DC7BAE3ED249C4EF05 /* VerificationPageDataName.swift in Sources */, + 50B7C62A3845596BB256BC3E /* VerificationPageDataPhone.swift in Sources */, + D4715BBA2B0E8E80008D07CC /* IdentityFlowView.swift in Sources */, + E64EE8A82FFB624F981D7E17 /* VerificationPageDataUpdate.swift in Sources */, + 92B9210AA596D5A135F24710 /* SelfieUploader+API.swift in Sources */, + 66BD1F1CB366B61D1ECD0E84 /* IdentityAnalyticsClient.swift in Sources */, + D4715B9C2B0E8E20008D07CC /* VerificationPageStaticContentDocumentCapturePage.swift in Sources */, + 924DE7C0E856DD2C5B80D53A /* Array+StripeIdentity.swift in Sources */, + 1E48ABBE603C4DB065DD2093 /* CGImage+StripeIdentity.swift in Sources */, + D4715BC32B0E8E80008D07CC /* PhoneOtpView.swift in Sources */, + D4715BB82B0E8E80008D07CC /* CameraPreviewContainerView.swift in Sources */, + FDB82DE4F471545F5D7B4CCA /* MLMultiArray+StripeIdentity.swift in Sources */, + C5E8CA0B4DFEF0F017AEE8D7 /* NSAttributedString+HTML.swift in Sources */, + 1DB3040542D8274848932380 /* TimeInterval+StripeIdentity.swift in Sources */, + 867C0D3782143FC44501485C /* UINavigationController+StripeIdentity.swift in Sources */, + D4715BE22B0E8E96008D07CC /* SelfieCaptureViewController.swift in Sources */, + 24E6928BEA4044DB779D4189 /* VNBarcodeSymbology+StripeIdentity.swift in Sources */, + D55B24D4D03E43A9438BCFE5 /* IdNumberElement.swift in Sources */, + 882C201AF4C8F48816DB740F /* IdentityElementsFactory.swift in Sources */, + D4715BDE2B0E8E96008D07CC /* DocumentFileUploadViewController+Strings.swift in Sources */, + 49FE192DE8D8DA9342A5AE60 /* IdentityTextButtonElement.swift in Sources */, + 692A9E2ADC11AB3D26A92780 /* IndividualFormElement.swift in Sources */, + 28D0DEBE960D92E18CEB4D71 /* Enums+CustomStringConvertible.swift in Sources */, + 2522A0773650573501106F4D /* Image.swift in Sources */, + 063C2C263111DA52ECC92DC0 /* STPLocalizedString.swift in Sources */, + CF5E139AD71239C64376815D /* String+Localized.swift in Sources */, + A9C41FB2EB8034BCCD6CAC29 /* StripeIdentityBundleLocator.swift in Sources */, + D4715BDD2B0E8E96008D07CC /* ErrorViewController.swift in Sources */, + 6A1B2E52B09C2D436CF7794D /* IdentityVerificationSheet.swift in Sources */, + D4715BEE2B0E8E96008D07CC /* SelfieCaptureViewController+Strings.swift in Sources */, + D4715BA82B0E8E20008D07CC /* VerificationPageStaticConsentLineContent.swift in Sources */, + 2E943A5B252D7927CE3C32E3 /* IdentityVerificationSheetError.swift in Sources */, + 56299123BD0C28891DD7EFEC /* IdentityTopLevelDestination.swift in Sources */, + 4FAB896404856F5AED99B89B /* DocumentScanner.swift in Sources */, + 9D73810AC9A6AF921038DC54 /* DocumentScannerConfiguration.swift in Sources */, + 4F20C4AF111778413B77DA22 /* DocumentScannerOutput.swift in Sources */, + 33F0FF8DB7D784C95647B693 /* FaceCaptureData.swift in Sources */, + D4715BEA2B0E8E96008D07CC /* DocumentFileUploadViewController.swift in Sources */, + D4715B9D2B0E8E20008D07CC /* VerificationPageRequirements.swift in Sources */, + 4A824CD6A0A518F616D428CF /* FaceScanner.swift in Sources */, + 541CA755717969560BF2C694 /* FaceScannerConfiguration.swift in Sources */, + D4715BDC2B0E8E96008D07CC /* UIViewController+SafariExtension.swift in Sources */, + 29E974CDD5827CF390349EA4 /* FaceScannerOutput.swift in Sources */, + D4715BE72B0E8E96008D07CC /* BiometricConsentViewController.swift in Sources */, + 313F5F812B0BE5DE00BD98A9 /* Docs.docc in Sources */, + 6B8DDA62E6ABFBC770A8AC86 /* ImageScanner.swift in Sources */, + 8E5D382BF426061CDD83197B /* ImageScanningConcurrencyManager.swift in Sources */, + 4E6AF5DD963FAB8B6E06F482 /* ImageScanningSession.swift in Sources */, + D4715BE52B0E8E96008D07CC /* PhoneOtpViewController.swift in Sources */, + D4715BA32B0E8E20008D07CC /* VerificationPageStaticContentSelfiePage.swift in Sources */, + C4BBB6E780EA8288EB46E795 /* ImageScanningSessionDelegate.swift in Sources */, + 12542E187994CC0683E2E3D8 /* DocumentUploader.swift in Sources */, + D4715BA02B0E8E20008D07CC /* VerificationPageStaticContentCountryNotListedPage.swift in Sources */, + B2377EEDCE440269E0766410 /* IdentityImageUploader.swift in Sources */, + 60B3D5B1B19D31D69D5049A0 /* SelfieUploader.swift in Sources */, + D4715BC02B0E8E80008D07CC /* ShadowedCorneredImageView.swift in Sources */, + D4715BC22B0E8E80008D07CC /* CompleteOptionView.swift in Sources */, + D4715B982B0E8E20008D07CC /* VerificationPageStaticContentIndividualPage.swift in Sources */, + 46A6C4DCF553DF24F3674002 /* VerificationSheetController.swift in Sources */, + 981D82CFC8B9EBF104B166F7 /* VerificationSheetFlowController.swift in Sources */, + 752E548B7E39B3CD8FC2F76A /* VerificationSheetFlowControllerError.swift in Sources */, + AD1ADA8B98C1FDB5C8C0A39F /* BarcodeDetector.swift in Sources */, + F146D14E6D35174CB94A8A48 /* FaceDetector.swift in Sources */, + EF277282173FFF6A52FE1EA3 /* FaceDetectorOutput.swift in Sources */, + D4715BC62B0E8E80008D07CC /* BottomAlignedLabel.swift in Sources */, + ADA2674008520B832585A8EE /* IDDetector.swift in Sources */, + D19E35C5D5263F212071AE75 /* IDDetectorConstants.swift in Sources */, + 4E39FB668EC68320579F23BC /* IDDetectorOutput.swift in Sources */, + ED5777E49F4AE61121539EBC /* LaplacianBlurDetector.swift in Sources */, + D4715BDB2B0E8E96008D07CC /* DocumentWarmupViewController.swift in Sources */, + D4715B9B2B0E8E20008D07CC /* VerificationPageIconType.swift in Sources */, + 349A4B5CC24DF5539B773A84 /* MLDetectorConfiguration.swift in Sources */, + AF16352FECF8EE27ECD6AAC9 /* MLDetectorMetricsTracker.swift in Sources */, + 3F9FB3BC02DDC7BCF4207F73 /* MotionBlurDetector.swift in Sources */, + 79BA026188FE0DB22ECE1BB0 /* VisionBasedDetector.swift in Sources */, + 9665F0EF90739A5D033C03A6 /* DocumentSide.swift in Sources */, + 286BF86B773F41210E015B2B /* IdentityDataCollecting.swift in Sources */, + 0FBE6D53EBE7C2B93DEA34B4 /* IdentityFlowNavigationController.swift in Sources */, + BFA24F4BD97F5BE9F5C0E934 /* IdentityUI.swift in Sources */, + D4715BA42B0E8E20008D07CC /* VerificationPageStaticContentConsentPage.swift in Sources */, + 51DCAEAEEA0B32038A989A51 /* MLModelLoader.swift in Sources */, + C5DB8461793523F9488DBBFA /* MLModelUnexpectedOutputError.swift in Sources */, + B8E47E623C9E4DAF53C3520C /* NonMaxSuppression.swift in Sources */, + DCFBA69CC4437E2E6061F50B /* IdentityMLModelLoader.swift in Sources */, + D4715BA52B0E8E20008D07CC /* VerificationPageStaticContentDocumentSelectPage.swift in Sources */, + D4715BBB2B0E8E80008D07CC /* HeaderView.swift in Sources */, + D4715B9A2B0E8E20008D07CC /* VerificationPageStaticContentIndividualWelcomePage.swift in Sources */, + D4715BE12B0E8E96008D07CC /* IndividualWelcomeViewController.swift in Sources */, + D4715BF42B0E8EBF008D07CC /* MultilineIconLabelHTMLView.swift in Sources */, + D4715B9F2B0E8E20008D07CC /* VerificationPageStaticContentSelfieModels.swift in Sources */, + D4715BA22B0E8E20008D07CC /* VerificationPageStaticContentBottomSheetContent.swift in Sources */, + D4715BC12B0E8E80008D07CC /* HeaderIconView.swift in Sources */, + D4715B992B0E8E20008D07CC /* VerificationPageStaticContentPhoneOtpPage.swift in Sources */, + D4715BBC2B0E8E80008D07CC /* BottomSheetView.swift in Sources */, + D4715B9E2B0E8E20008D07CC /* VerificationPageStaticContentDocumentCaptureModels.swift in Sources */, + D4715BED2B0E8E96008D07CC /* BottomSheetViewController.swift in Sources */, + 4C9D354F6C1E9E89B13D0367 /* AnimatedBorderView.swift in Sources */, + 8EB5D825D1ADC9DAB6E7D5A3 /* DocumentCaptureView.swift in Sources */, + D4715BF32B0E8EBF008D07CC /* HTMLViewWithIconLabels.swift in Sources */, + D4715BE32B0E8E96008D07CC /* SelfieWarmupViewController.swift in Sources */, + BD4686589B16ED96A748CCA8 /* DocumentScanningView.swift in Sources */, + D4715BB92B0E8E80008D07CC /* ErrorView.swift in Sources */, + CE7F2D308363EEDD627AEBF7 /* InstructionalDocumentScanningView.swift in Sources */, + D4715BF52B0E8EBF008D07CC /* HTMLTextView.swift in Sources */, + D4715BE42B0E8E96008D07CC /* DebugViewController.swift in Sources */, + D4715BA62B0E8E20008D07CC /* VerificationPageStaticContentTextPage.swift in Sources */, + 7CA57E67AC14DA90AE87743B /* ListItemView.swift in Sources */, + A8EEE8C2CD83374877393E8C /* ListView.swift in Sources */, + C9E49126B9BEC74E605C6DD0 /* SelfieCaptureView.swift in Sources */, + D4715BF62B0E8EBF008D07CC /* IconLabelHTMLView.swift in Sources */, + C1AA4B758A20557CDBE72FD9 /* SelfieScanningView.swift in Sources */, + D4715BEC2B0E8E96008D07CC /* DocumentCaptureViewController.swift in Sources */, + 7D4F147D0CEC17856D070EC2 /* SelfieWarmupView.swift in Sources */, + AB6B3BDF7D8F3F84EEA7208A /* StripeCore+Import.swift in Sources */, + CBA4B3CAFDFF232B72393842 /* VerificationClientSecret.swift in Sources */, + 1D4D9CDF6B1D335963849940 /* VerificationSheetAnalytics.swift in Sources */, + D4715BC52B0E8E80008D07CC /* ContentCenteringScrollView.swift in Sources */, + 4A6D49DED2555FBDACF13B3F /* VerificationFlowWebView.swift in Sources */, + 5E00E8AC4088548403B3E630 /* VerificationFlowWebViewController.swift in Sources */, + 76890B79CA3C83BBD7362065 /* VerifyWebURLHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 90F68D87EFF3F3EE0E36B03A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeIdentity; + target = F251015845B21C036CFBC636 /* StripeIdentity */; + targetProxy = C5559AE430066C0A94C2686A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 989A2C5379C60B9F0E0E2711 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + AD8443DFD20A6F7E547E4DAE /* bg-BG */, + 005EAC562365676CE56F306D /* ca-ES */, + 0CFB27562E05409A1C0263EA /* cs-CZ */, + F524F862B05C4744F3A5FCD4 /* da */, + 3954F595B70115874BD88CCC /* de */, + D105F7A265B60FE9AD82B0DF /* el-GR */, + 6182719E4413B5136BB763D1 /* en */, + F8521F2AA5740FAB9952B58E /* en-GB */, + F48A3390B6B74481B1F64EC6 /* es */, + 52DD224D2BE08E0553EF8AA6 /* es-419 */, + 0AFAC8C1757DE685E0E9BFB3 /* et-EE */, + 80F9C4B0CFCD5FF45D203B62 /* fi */, + 3118D24F94912E8A9BB86BFE /* fil */, + AE92BAAC65F86A87F25B9C08 /* fr */, + 7EAFBBEEA27B11E702B78497 /* fr-CA */, + F44D3A78A44A0EB11BA51E5F /* hr */, + CF2EEA927FA9ACE159A67063 /* hu */, + 4A59C0CC87CDB7AABFE96686 /* id */, + F46E474400B21A35FA6066F8 /* it */, + EB9B2148051885B8591A13DE /* ja */, + 704D26339D2158FD8FADE1C2 /* ko */, + 22B3E26EBB8F43F7559C635B /* lt-LT */, + FB119F30000F66B9660A44E8 /* lv-LV */, + E6C2F1CDF5594786FB4BAA49 /* ms-MY */, + 6BC7102DF0964D1204E2BFEB /* mt */, + 2FAE3BFE9870AF3F0553AB9C /* nb */, + F8103B1FD36D79D22EACF7EE /* nl */, + 5B975279521B43A8F82EBAB3 /* nn-NO */, + 9A5A1304CA30DC06B715BD92 /* pl-PL */, + F43124EF97ADAB2BD1B7DEE2 /* pt-BR */, + 74D9040BDF0DBA526C156C3D /* pt-PT */, + 2FC476BFBFA09FDC174857A6 /* ro-RO */, + 40DAFA9171B6D631DE9B6830 /* ru */, + F9D552632FFF310FC6A4DB26 /* sk-SK */, + 2344DA31ADA3573F9C2EE7A0 /* sl-SI */, + 0ACA3969442F9B14DF70ABE7 /* sv */, + C4891127AAACF7829BEE80E2 /* tr */, + 0631A038B5360C337B8E596C /* vi */, + 598A711C4A2B8988EFBEEF77 /* zh-Hans */, + E3A5A9765EF649A580A0519F /* zh-Hant */, + B634C1568CBC56B3E33CE3E2 /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 02EBEC1B77551185440D525C /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 434BC5FFBC1163872413A40A /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeIdentityTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeIdentityTests; + PRODUCT_NAME = StripeIdentityTests; + SDKROOT = iphoneos; + }; + name = Release; + }; + 10F159BEF3E08EF01CDA43CB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E850C3F1FEA42490F1B3761E /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeIdentity/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-identity"; + PRODUCT_NAME = StripeIdentity; + SDKROOT = iphoneos; + }; + name = Release; + }; + 41CCDE6EB399A8EBA8367328 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4F6B26A21285054BEE9CA244 /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 80564639A42659CBBA6AD565 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A163E1EC771058E5DF5FFE66 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 95BCFF11E6F19877B02921C2 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B17C20418C972E7B06B84370 /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeIdentity/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-identity"; + PRODUCT_NAME = StripeIdentity; + SDKROOT = iphoneos; + }; + name = Debug; + }; + ACBD80600170C45C9CC051A4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8605B809AD7EBA140CC9DBE9 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeIdentityTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeIdentityTests; + PRODUCT_NAME = StripeIdentityTests; + SDKROOT = iphoneos; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7C14F7F327D79A07F63042E0 /* Build configuration list for PBXNativeTarget "StripeIdentityTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ACBD80600170C45C9CC051A4 /* Debug */, + 02EBEC1B77551185440D525C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8BD83D322879659AB98AEA29 /* Build configuration list for PBXProject "StripeIdentity" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 41CCDE6EB399A8EBA8367328 /* Debug */, + 80564639A42659CBBA6AD565 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9E28A14ACC690D35FD59690A /* Build configuration list for PBXNativeTarget "StripeIdentity" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 95BCFF11E6F19877B02921C2 /* Debug */, + 10F159BEF3E08EF01CDA43CB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 1034C700B96E0B79C2107621 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; + 98906E4295D5EDD251F81145 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davidme-stripe/OHHTTPStubs"; + requirement = { + branch = "stripe-mock"; + kind = branch; + }; + }; + D44AE4E72B9A66ED00446682 /* XCRemoteSwiftPackageReference "capture-core-sp" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/BlinkID/capture-core-sp"; + requirement = { + kind = exactVersion; + version = 1.2.3; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 2714B21F1CD18CEED8772AB6 /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; + 6E2CB8EBFA629D3FF26481AB /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; + 960247DC321CBDB422FE713D /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 0705CAA185B63201FD561508 /* Project object */; +} diff --git a/StripeIdentity/StripeIdentity.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeIdentity/StripeIdentity.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeIdentity/StripeIdentity.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeIdentity/StripeIdentity.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/StripeIdentity/StripeIdentity.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..90e876c3 --- /dev/null +++ b/StripeIdentity/StripeIdentity.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "capture-core-sp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/BlinkID/capture-core-sp", + "state" : { + "branch" : "main", + "revision" : "9045eab5c75384292666f4aca2e3318d17466f67" + } + }, + { + "identity" : "ios-snapshot-test-case", + "kind" : "remoteSourceControl", + "location" : "https://github.com/uber/ios-snapshot-test-case", + "state" : { + "revision" : "7b10770333a961be6e5a41c9ce04b8c6d3990126", + "version" : "8.0.0" + } + }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/davidme-stripe/OHHTTPStubs", + "state" : { + "branch" : "master", + "revision" : "94cf8e1d9e38b0edd580ddc699dda23a7bf66515" + } + } + ], + "version" : 2 +} diff --git a/StripeIdentity/StripeIdentity.xcodeproj/xcshareddata/xcschemes/StripeIdentity.xcscheme b/StripeIdentity/StripeIdentity.xcodeproj/xcshareddata/xcschemes/StripeIdentity.xcscheme new file mode 100644 index 00000000..0e42f2c7 --- /dev/null +++ b/StripeIdentity/StripeIdentity.xcodeproj/xcshareddata/xcschemes/StripeIdentity.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeIdentity/StripeIdentity/Docs.docc/StripeIdentity.md b/StripeIdentity/StripeIdentity/Docs.docc/StripeIdentity.md new file mode 100644 index 00000000..a1701435 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Docs.docc/StripeIdentity.md @@ -0,0 +1,3 @@ +# ``StripeIdentity`` + +Placeholder diff --git a/StripeIdentity/StripeIdentity/Info.plist b/StripeIdentity/StripeIdentity/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeIdentity/StripeIdentity/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_add@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_add@3x.png new file mode 100644 index 00000000..e1ba9932 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_add@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_camera@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_camera@3x.png new file mode 100644 index 00000000..d1bf3b6a Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_camera@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_camera_classic@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_camera_classic@3x.png new file mode 100644 index 00000000..28a388e1 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_camera_classic@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_checkmark@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_checkmark@3x.png new file mode 100644 index 00000000..61858478 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_checkmark@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_checkmark_92@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_checkmark_92@3x.png new file mode 100644 index 00000000..441ace02 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_checkmark_92@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_clock@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_clock@3x.png new file mode 100644 index 00000000..acd99db5 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_clock@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_cloud@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_cloud@3x.png new file mode 100644 index 00000000..e2c60550 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_cloud@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_create_identity_verification@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_create_identity_verification@3x.png new file mode 100644 index 00000000..64d8c464 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_create_identity_verification@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_dispute_protection@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_dispute_protection@3x.png new file mode 100644 index 00000000..194f3cf0 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_dispute_protection@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_document@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_document@3x.png new file mode 100644 index 00000000..c69af24d Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_document@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_ellipsis@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_ellipsis@3x.png new file mode 100644 index 00000000..47792a7f Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_ellipsis@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_id_front@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_id_front@3x.png new file mode 100644 index 00000000..741ffa1f Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_id_front@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_info@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_info@3x.png new file mode 100644 index 00000000..b4528fb6 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_info@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_lock@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_lock@3x.png new file mode 100644 index 00000000..e1facfb9 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_lock@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_moved@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_moved@3x.png new file mode 100644 index 00000000..3c7c427f Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_moved@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_phone@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_phone@3x.png new file mode 100644 index 00000000..9c9e3c4e Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_phone@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_selfie_warmup@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_selfie_warmup@3x.png new file mode 100644 index 00000000..8a08cd63 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_selfie_warmup@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_wallet@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_wallet@3x.png new file mode 100644 index 00000000..7379e623 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_wallet@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_warning2@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_warning2@3x.png new file mode 100644 index 00000000..b0abed0b Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_warning2@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_warning@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_warning@3x.png new file mode 100644 index 00000000..4c7160a4 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_warning@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Images/icon_warning_92@3x.png b/StripeIdentity/StripeIdentity/Resources/Images/icon_warning_92@3x.png new file mode 100644 index 00000000..64912b60 Binary files /dev/null and b/StripeIdentity/StripeIdentity/Resources/Images/icon_warning_92@3x.png differ diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/bg-BG.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..93e900f0 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/bg-BG.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "На следващата стъпка автоматично ще бъдат направени няколко снимки, за да се потвърди, че това сте вие."; + +"Accepted forms of ID include" = "Приемат се следните форми на документи за самоличност"; + +"Alternatively, you may manually upload a photo of your identity document." = "Като алтернатива можете да качите ръчно снимка на документа си за самоличност."; + +"App Settings" = "Настройки на приложението"; + +"Back identity card photo successfully uploaded" = "Успешно качена снимка на задната страна на лична карта"; + +"Back of identity card" = "Задна страна на лична карта"; + +"Back of identity document" = "Задна страна на документ за самоличност"; + +"Camera permission" = "Разрешение за камерата"; + +"Camera unavailable" = "Камерата не е достъпна"; + +"Capturing…" = "Заснемане..."; + +"Choose File" = "Избор на файл"; + +"Consent" = "Съгласие"; + +"Could not capture image" = "Заснемането беше неуспешно"; + +"Date of Birth" = "Дата на раждане"; + +"Date of birth does not look valid" = "Датата на раждане не изглежда валидна"; + +"Details not visible" = "Подробностите не се виждат"; + +"Flip your identity card over to the other side" = "Обърнете личната си карта от другата страна."; + +"Front identity card photo successfully uploaded" = "Успешно качена снимка на предната страна на лична карта"; + +"Front of identity card" = "Предна страна на лична карта"; + +"Front of identity document" = "Предна страна на документ за самоличност"; + +"Get ready to scan your photo ID" = "Подгответе се за сканиране на Вашия документ за самоличност със снимка"; + +"Get ready to take a selfie" = "Пригответе се да си направите селфи"; + +"Go Back" = "Назад"; + +"Hold still, scanning" = "Застанете неподвижно, сканира се"; + +"I'm ready" = "Готов съм"; + +"ID Number" = "Идентификационен номер"; + +"Individual CPF" = "CPF на физическо лице"; + +"Keep ID level" = "Подравнете документа за самоличност"; + +"Last 4 of Social Security number" = "Последните 4 цифри от социалноосигурителния номер"; + +"Loading" = "Зареждане"; + +"Make sure all details are visible and focus" = "Уверете се, че всички данни са видими и на фокус."; + +"Make sure you're in a well lit space." = "Уверете се, че сте на добре осветено място."; + +"Move closer" = "Приближете"; + +"Move farther" = "Отдалечете"; + +"Move to a darker area" = "Преместете се на по-тъмно място"; + +"Move to a well-lit area" = "Преместете се на добре осветено място"; + +"NRIC or FIN" = "NRIC или FIN"; + +"No document detected" = "Не е открит документ"; + +"Personal ID number" = "Личен идентификационен номер"; + +"Personal Information" = "Лична информация"; + +"Phone Number" = "Телефонен номер"; + +"Phone Verification" = "Удостоверяване чрез телефон"; + +"Photo Library" = "Библиотека със снимки"; + +"Please upload images of the front and back of your identity card" = "Моля, качете изображение на предната и задната част на Вашата лична карта."; + +"Position your face in the center of the frame." = "Позиционирайте лицето си в центъра на рамката."; + +"Position your identity card in the center of the frame" = "Позиционирайте личната си карта в центъра на рамката"; + +"Retake Photos" = "Повторно заснемане на снимки"; + +"Scan" = "Сканирай"; + +"Scanned" = "Сканиран"; + +"Select" = "Избиране"; + +"Select a location to upload the back of your identity document from" = "Изберете местоположение за качване на изображение на задната страна на Вашия документ за самоличност от"; + +"Select a location to upload the front of your identity document from" = "Изберете местоположение за качване на изображение на предната страна на Вашия документ за самоличност от"; + +"Select back identity card photo" = "Изберете снимка на задната страна на лична карта"; + +"Select front identity card photo" = "Изберете снимка на предната страна на лична карта"; + +"Selfie" = "Селфи"; + +"Selfie captures" = "Заснети селфита"; + +"Selfie captures are complete" = "Заснемането на селфита е завършено"; + +"Take Photo" = "Заснемане"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Изображенията на Вашия документ за самоличност не са запазени. Искате ли да излезете?"; + +"There was an error accessing the camera." = "Възникна грешка, свързана с достъпа до камерата."; + +"Try Again" = "Опитайте отново"; + +"Try reduce glare and make ID visible" = "Опитайте да намалите отблясъците и се уверете, че документът за самоличност е видим."; + +"Unable to establish a connection." = "Не може да бъде осъществена връзка."; + +"Unsaved changes" = "Незапазени промени"; + +"Upload" = "Качване"; + +"Upload a Photo" = "Качете снимка"; + +"Upload your photo ID" = "Качете документ за самоличност със снимка"; + +"Uploading back identity card photo" = "Качване на снимка на задната страна на лична карта"; + +"Uploading front identity card photo" = "Качване на снимка на предната страна на лична карта"; + +"Verify your identity" = "Удостоверете Вашата самоличност"; + +"We could not capture a high-quality image." = "Не успяхме да заснемем изображение с високо качество."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Нуждаем се от разрешение за използване на Вашата камера. Моля, разрешете достъп до камерата в настройките на приложението."; + +"Welcome" = "Добре дошли"; + +"You can either try again or upload an image from your device." = "Можете да опитате отново или да качите изображение от устройството си."; + +"Your selfie images have not been saved. Do you want to leave?" = "Вашите селфи изображения не са запазени. Искате ли да излезете?"; + +"driver's license" = "свидетелство за управление на МПС"; + +"government-issued photo ID" = "документ за самоличност със снимка, издаден от държавен орган"; + +"passport" = "паспорт"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/ca-ES.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..231bba32 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/ca-ES.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Es faran unes quantes fotos automàticament al següent pas per verificar que sou vosaltres"; + +"Accepted forms of ID include" = "Les formes d'identificació acceptades inclouen"; + +"Alternatively, you may manually upload a photo of your identity document." = "També podeu pujar manualment una foto del document d'identitat."; + +"App Settings" = "Configuració de l'aplicació"; + +"Back identity card photo successfully uploaded" = "La foto del revers document d'identitat s'ha carregat correctament"; + +"Back of identity card" = "Revers del document d'identitat"; + +"Back of identity document" = "Dors del document d'identitat"; + +"Camera permission" = "Permís de càmera"; + +"Camera unavailable" = "Càmera no disponible"; + +"Capturing…" = "S'està capturant la imatge..."; + +"Choose File" = "Selecciona fitxer"; + +"Consent" = "Consentiment"; + +"Could not capture image" = "No s'ha pogut capturar la imatge"; + +"Date of Birth" = "Data de naixement"; + +"Date of birth does not look valid" = "La data de naixement no és vàlida"; + +"Details not visible" = "Els detalls no es veuen bé"; + +"Flip your identity card over to the other side" = "Gireu el document d'identitat perquè se'n vegi l'altra cara"; + +"Front identity card photo successfully uploaded" = "La foto de l'anvers del document d'identitat s'ha carregat correctament"; + +"Front of identity card" = "Anvers del document d'identitat"; + +"Front of identity document" = "Anvers del document d'identitat"; + +"Get ready to scan your photo ID" = "Prepareu-vos per escanejar la vostra identificació amb foto"; + +"Get ready to take a selfie" = "Prepareu-vos per fer-vos l'autofoto"; + +"Go Back" = "Enrere"; + +"Hold still, scanning" = "S'està escanejant; espereu"; + +"I'm ready" = "Estic a punt"; + +"ID Number" = "Número d'identificació"; + +"Individual CPF" = "CPF particular"; + +"Keep ID level" = "Col·loca bé el document d'identitat"; + +"Last 4 of Social Security number" = "Últims 4 números de la Seguretat Social"; + +"Loading" = "S'està carregant"; + +"Make sure all details are visible and focus" = "Assegureu-vos que tots els detalls siguin visibles"; + +"Make sure you're in a well lit space." = "Assegureu-vos que esteu en un espai ben il·luminat."; + +"Move closer" = "Apropa'l"; + +"Move farther" = "Allunya'l"; + +"Move to a darker area" = "Mou-lo a un espai més fosc"; + +"Move to a well-lit area" = "Mou-te a un espai ben il·luminat"; + +"NRIC or FIN" = "NRIC o FIN"; + +"No document detected" = "No s'ha detectat cap document"; + +"Personal ID number" = "Número d'identificació personal"; + +"Personal Information" = "Dades personals"; + +"Phone Number" = "Número de telèfon"; + +"Phone Verification" = "Verificació del telèfon"; + +"Photo Library" = "Biblioteca de fotos"; + +"Please upload images of the front and back of your identity card" = "Carregueu les imatges de l'anvers i el revers del document d'identitat"; + +"Position your face in the center of the frame." = "Col·loqueu la cara al centre del marc."; + +"Position your identity card in the center of the frame" = "Col·loqueu el document d'identitat al centre del marc"; + +"Retake Photos" = "Torna a fer les fotos"; + +"Scan" = "Escaneig"; + +"Scanned" = "S'ha escanejat correctament"; + +"Select" = "Selecciona"; + +"Select a location to upload the back of your identity document from" = "Seleccioneu una ubicació des d'on carregar el revers del document d'identitat"; + +"Select a location to upload the front of your identity document from" = "Seleccioneu una ubicació des d'on carregar l'anvers del document d'identitat"; + +"Select back identity card photo" = "Seleccioneu la foto del revers del document d'identitat"; + +"Select front identity card photo" = "Seleccioneu la foto del document d'identitat"; + +"Selfie" = "Autofoto"; + +"Selfie captures" = "Autofotos"; + +"Selfie captures are complete" = "S'han completat les autofotos"; + +"Take Photo" = "Fes foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "No s'han desat les imatges del document d'identitat. Voleu sortir?"; + +"There was an error accessing the camera." = "Hi ha hagut un error en accedir a la càmera."; + +"Try Again" = "Torna-ho a intentar"; + +"Try reduce glare and make ID visible" = "Intenteu reduir l'enlluernament i feu que l'ID sigui visible."; + +"Unable to establish a connection." = "No s'ha pogut establir la connexió."; + +"Unsaved changes" = "Canvis no desats"; + +"Upload" = "Carrega"; + +"Upload a Photo" = "Carrega una foto"; + +"Upload your photo ID" = "Carregueu la vostra ID amb foto"; + +"Uploading back identity card photo" = "S'està carregant la foto del revers del document d'identitat"; + +"Uploading front identity card photo" = "S'està carregant la foto de l'anvers del document d'identitat"; + +"Verify your identity" = "Verifica la teva identitat"; + +"We could not capture a high-quality image." = "No hem pogut capturar una imatge d'alta qualitat."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Necessitem permís per utilitzar la càmera. Permeteu l'accés a la càmera a la configuració de l'aplicació."; + +"Welcome" = "Hola"; + +"You can either try again or upload an image from your device." = "Podeu tornar-ho a provar o carregar una imatge des del vostre dispositiu."; + +"Your selfie images have not been saved. Do you want to leave?" = "No s'han desat les autofotos. Voleu sortir?"; + +"driver's license" = "permís de conduir"; + +"government-issued photo ID" = "identificació governamental amb foto"; + +"passport" = "passaport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/cs-CZ.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..d108b9ec --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "V dalším kroku se automaticky pořídí několik fotografií, aby se ověřilo, že jste to vy."; + +"Accepted forms of ID include" = "Přijímané formy průkazu totožnosti zahrnují"; + +"Alternatively, you may manually upload a photo of your identity document." = "Můžete také ručně nahrát fotografii dokladu totožnosti."; + +"App Settings" = "Nastavení aplikace"; + +"Back identity card photo successfully uploaded" = "Fotografie ze zadní strany průkazu totožnosti byla úspěšně nahrána"; + +"Back of identity card" = "Zadní strana průkazu totožnosti"; + +"Back of identity document" = "Zadní strana průkazu totožnosti"; + +"Camera permission" = "Povolení k použití fotoaparátu"; + +"Camera unavailable" = "Fotoaparát není dostupný"; + +"Capturing…" = "Probíhá fotografování..."; + +"Choose File" = "Vybrat soubor"; + +"Consent" = "Souhlas"; + +"Could not capture image" = "Nebylo možné pořídit obrázek"; + +"Date of Birth" = "Datum narození"; + +"Date of birth does not look valid" = "Datum narození nevypadá jako platný údaj"; + +"Details not visible" = "Detaily nejsou viditelné"; + +"Flip your identity card over to the other side" = "Převraťte průkaz totožnosti na druhou stranu"; + +"Front identity card photo successfully uploaded" = "Fotografie z přední strany průkazu totožnosti byla úspěšně nahrána"; + +"Front of identity card" = "Přední strana průkazu totožnosti"; + +"Front of identity document" = "Přední strana průkazu totožnosti"; + +"Get ready to scan your photo ID" = "Připravte se na skenování průkazu totožnosti s fotografií"; + +"Get ready to take a selfie" = "Připravte se na pořízení selfie"; + +"Go Back" = "Zpět"; + +"Hold still, scanning" = "Držte rovně, probíhá skenování"; + +"I'm ready" = "Jsem připraven/a"; + +"ID Number" = "Identifikační číslo"; + +"Individual CPF" = "Individuální CPF"; + +"Keep ID level" = "Zachovat úroveň dokladu"; + +"Last 4 of Social Security number" = "Poslední 4 pozice čísla sociálního pojištění"; + +"Loading" = "Načítání"; + +"Make sure all details are visible and focus" = "Ujistěte se, že jsou všechny detaily viditelné a zaostřené"; + +"Make sure you're in a well lit space." = "Ujistěte se, že jste v řádně osvětleném prostoru."; + +"Move closer" = "Posuňte blíže"; + +"Move farther" = "Posuňte dále"; + +"Move to a darker area" = "Přesuňte se do tmavší oblasti."; + +"Move to a well-lit area" = "Přesuňte se na dobře osvětlené místo"; + +"NRIC or FIN" = "NRIC nebo FIN"; + +"No document detected" = "Nebyl zjištěn žádný dokument"; + +"Personal ID number" = "Osobní identifikační číslo"; + +"Personal Information" = "Osobní informace"; + +"Phone Number" = "Telefonní číslo"; + +"Phone Verification" = "Telefonické ověření"; + +"Photo Library" = "Knihovna fotek"; + +"Please upload images of the front and back of your identity card" = "Nahrajte prosím obrázky přední a zadní strany vašeho průkazu totožnosti"; + +"Position your face in the center of the frame." = "Svoji tvář umístěte do středu rámečku."; + +"Position your identity card in the center of the frame" = "Umístěte průkaz totožnosti do středu rámečku"; + +"Retake Photos" = "Znovu pořídit fotografie"; + +"Scan" = "Skenovat"; + +"Scanned" = "Naskenováno"; + +"Select" = "Vybrat"; + +"Select a location to upload the back of your identity document from" = "Vyberte místo k nahrání zadní strany vašeho průkazu totožnosti z"; + +"Select a location to upload the front of your identity document from" = "Vyberte místo k nahrání přední strany vašeho průkazu totožnosti z"; + +"Select back identity card photo" = "Vyberte fotografii ze zadní strany řidičského průkazu"; + +"Select front identity card photo" = "Vyberte fotografii z přední strany průkazu totožnosti"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Pořízené snímky selfie"; + +"Selfie captures are complete" = "Pořízení snímků selfie je dokončené"; + +"Take Photo" = "Pořídit fotku"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Obrázky z vašeho průkazu totožnosti nebyly uloženy. Chcete odejít?"; + +"There was an error accessing the camera." = "Při přístupu k fotoaparátu došlo k chybě."; + +"Try Again" = "Zkusit znovu"; + +"Try reduce glare and make ID visible" = "Zkuste snížit jas a zviditelnit doklad."; + +"Unable to establish a connection." = "Nelze navázat spojení."; + +"Unsaved changes" = "Neuložené změny"; + +"Upload" = "Nahrát"; + +"Upload a Photo" = "Nahrát fotografii"; + +"Upload your photo ID" = "Nahrajte svou fotografii"; + +"Uploading back identity card photo" = "Nahrávání fotografie ze zadní strany průkazu totožnosti"; + +"Uploading front identity card photo" = "Nahrávání fotografie z přední strany průkazu totožnosti"; + +"Verify your identity" = "Potvrďte svou totožnost"; + +"We could not capture a high-quality image." = "Nepodařilo se nám pořídit vysoce kvalitní snímek."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Potřebujeme povolení k použití vašeho fotoaparátu. V nastavení aplikace povolte přístup k fotoaparátu."; + +"Welcome" = "Vítejte"; + +"You can either try again or upload an image from your device." = "Můžete to zkusit znovu nebo nahrát obrázek ze svého zařízení."; + +"Your selfie images have not been saved. Do you want to leave?" = "Vaše selfie snímky nebyly uloženy. Chcete skutečně skončit?"; + +"driver's license" = "řidičský průkaz"; + +"government-issued photo ID" = "průkaz totožnosti s fotografií vydaný státem"; + +"passport" = "pas"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/da.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/da.lproj/Localizable.strings new file mode 100644 index 00000000..018c38f4 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/da.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Der tages automatisk nogle få billeder i det næste trin for at bekræfte, at det er dig"; + +"Accepted forms of ID include" = "Godkendte former for ID inkluderer"; + +"Alternatively, you may manually upload a photo of your identity document." = "Alternativt kan du manuelt uploade et foto af dit identifikationsdokument."; + +"App Settings" = "Appindstillinger"; + +"Back identity card photo successfully uploaded" = "Foto af bagside af identitetskort blev uploadet"; + +"Back of identity card" = "Bagside af identitetskort"; + +"Back of identity document" = "Bagsiden af identitetsdokumentet"; + +"Camera permission" = "Kameratilladelse"; + +"Camera unavailable" = "Kamera ikke tilgængeligt"; + +"Capturing…" = "Optager..."; + +"Choose File" = "Vælg fil"; + +"Consent" = "Samtykke"; + +"Could not capture image" = "Kunne ikke tage billede"; + +"Date of Birth" = "Fødselsdato"; + +"Date of birth does not look valid" = "Fødselsdatoen ser ikke ud til at være gyldig."; + +"Details not visible" = "Detaljer ikke synlige"; + +"Flip your identity card over to the other side" = "Vend dit identitetskort om på bagsiden"; + +"Front identity card photo successfully uploaded" = "Foto af forside af identitetskort blev uploadet"; + +"Front of identity card" = "Forside af identitetskort"; + +"Front of identity document" = "Forsiden af identitetsdokumentet"; + +"Get ready to scan your photo ID" = "Gør dig klar til at scanne dit foto-ID"; + +"Get ready to take a selfie" = "Gør klar til at tage en selfie"; + +"Go Back" = "Gå tilbage"; + +"Hold still, scanning" = "Hold stille, scanner"; + +"I'm ready" = "Jeg er klar"; + +"ID Number" = "ID-nummer"; + +"Individual CPF" = "Individuel CPF"; + +"Keep ID level" = "Hold id'et vandret"; + +"Last 4 of Social Security number" = "Sidste 4 cifre af personnummer"; + +"Loading" = "Indlæser"; + +"Make sure all details are visible and focus" = "Kontrollér, at alle detaljer er synlige og i fokus"; + +"Make sure you're in a well lit space." = "Sørg for, at du er et sted, der er ordentligt oplyst."; + +"Move closer" = "Flyt tættere på"; + +"Move farther" = "Flyt længere væk"; + +"Move to a darker area" = "Gå til et mørkere område"; + +"Move to a well-lit area" = "Gå til et område med god belysning"; + +"NRIC or FIN" = "NRIC eller FIN"; + +"No document detected" = "Intet dokument registreret"; + +"Personal ID number" = "Personligt id-nummer"; + +"Personal Information" = "Personlige oplysninger"; + +"Phone Number" = "Telefonnummer"; + +"Phone Verification" = "Telefonbekræftelse"; + +"Photo Library" = "Fotobibliotek"; + +"Please upload images of the front and back of your identity card" = "Upload billeder af forsiden og bagsiden af dit identitetskort"; + +"Position your face in the center of the frame." = "Placer dit ansigt i midten af rammen."; + +"Position your identity card in the center of the frame" = "Placer dit identitetskort midt i rammen"; + +"Retake Photos" = "Tag billeder igen"; + +"Scan" = "Scan"; + +"Scanned" = "Scannet"; + +"Select" = "Vælg"; + +"Select a location to upload the back of your identity document from" = "Vælg en placering, som bagsiden af dit identitetsdokument skal uploades fra"; + +"Select a location to upload the front of your identity document from" = "Vælg en placering, som forsiden af dit identitetsdokument skal uploades fra"; + +"Select back identity card photo" = "Vælg foto af bagside af identitetskort"; + +"Select front identity card photo" = "Vælg foto af forside af identitetskort"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfie-optagelser"; + +"Selfie captures are complete" = "Optagelse af selfier er fuldført"; + +"Take Photo" = "Tag foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Billederne af dit identitetsdokument er ikke blevet gemt. Ønsker du at forlade denne session?"; + +"There was an error accessing the camera." = "Det var ikke muligt at få adgang til kameraet."; + +"Try Again" = "Prøv igen"; + +"Try reduce glare and make ID visible" = "Prøv at reducere genskin, og sørg for, at id'et er synligt"; + +"Unable to establish a connection." = "Kunne ikke oprette forbindelse."; + +"Unsaved changes" = "Ikke-gemte ændringer"; + +"Upload" = "Upload"; + +"Upload a Photo" = "Upload et foto"; + +"Upload your photo ID" = "Upload dit foto-id"; + +"Uploading back identity card photo" = "Uploader foto af bagside af identitetskort"; + +"Uploading front identity card photo" = "Uploader foto af forside af identitetskort"; + +"Verify your identity" = "Bekræft din identitet"; + +"We could not capture a high-quality image." = "Vi kunne ikke tage et billede i høj kvalitet."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Vi skal bruge en tilladelse til at bruge dit kamera. Tillad kameraadgang i appindstillingerne."; + +"Welcome" = "Velkommen"; + +"You can either try again or upload an image from your device." = "Du kan enten prøve igen eller uploade et billede fra din enhed."; + +"Your selfie images have not been saved. Do you want to leave?" = "Dine selfie-billeder er ikke blevet gemt. Ønsker du at forlade denne session?"; + +"driver's license" = "kørekort"; + +"government-issued photo ID" = "myndighedsudstedt foto-ID"; + +"passport" = "pas"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/de.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/de.lproj/Localizable.strings new file mode 100644 index 00000000..d2001a50 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/de.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Im nächsten Schritt werden automatisch einige Fotos gemacht, um Ihre Identität zu verifizieren"; + +"Accepted forms of ID include" = "Zulässige Ausweisdokumente sind"; + +"Alternatively, you may manually upload a photo of your identity document." = "Alternativ können Sie manuell ein Foto Ihres Ausweisdokuments hochladen."; + +"App Settings" = "App-Einstellungen"; + +"Back identity card photo successfully uploaded" = "Das Foto der Rückseite des Ausweisdokuments wurde erfolgreich hochgeladen."; + +"Back of identity card" = "Rückseite des Personalausweises"; + +"Back of identity document" = "Rückseite des Ausweisdokuments"; + +"Camera permission" = "Kameraberechtigung"; + +"Camera unavailable" = "Kamera nicht verfügbar"; + +"Capturing…" = "Aufnahme läuft ..."; + +"Choose File" = "Datei auswählen"; + +"Consent" = "Zustimmen"; + +"Could not capture image" = "Aufnahme nicht möglich"; + +"Date of Birth" = "Geburtsdatum"; + +"Date of birth does not look valid" = "Das Geburtsdatum scheint ungültig zu sein"; + +"Details not visible" = "Details sind nicht erkennbar"; + +"Flip your identity card over to the other side" = "Drehen Sie Ihr Ausweisdokument um"; + +"Front identity card photo successfully uploaded" = "Das Foto der Vorderseite des Ausweisdokuments wurde erfolgreich hochgeladen."; + +"Front of identity card" = "Vorderseite des Personalausweises"; + +"Front of identity document" = "Vorderseite des Ausweisdokuments"; + +"Get ready to scan your photo ID" = "Machen Sie sich zum Scannen Ihres Lichtbildausweises bereit"; + +"Get ready to take a selfie" = "Machen Sie sich für ein Selfie bereit"; + +"Go Back" = "Zurück"; + +"Hold still, scanning" = "Bitte nicht bewegen, Scanvorgang läuft"; + +"I'm ready" = "Ich bin bereit"; + +"ID Number" = "Ausweisnummer"; + +"Individual CPF" = "Individuelle CPF"; + +"Keep ID level" = "Ausweisdokument gerade halten"; + +"Last 4 of Social Security number" = "Letzte vier Ziffern der Sozialversicherungsnummer"; + +"Loading" = "Wird geladen"; + +"Make sure all details are visible and focus" = "Vergewissern Sie sich, dass alle Details sichtbar und scharf sind"; + +"Make sure you're in a well lit space." = "Sorgen Sie für ausreichende Beleuchtung."; + +"Move closer" = "Abstand verringern"; + +"Move farther" = "Mehr Abstand halten"; + +"Move to a darker area" = "Begeben Sie sich in eine dunklere Umgebung"; + +"Move to a well-lit area" = "Begeben Sie sich in eine gut beleuchtete Umgebung"; + +"NRIC or FIN" = "NRIC oder FIN"; + +"No document detected" = "Kein Dokument erkannt"; + +"Personal ID number" = "Personalausweisnummer"; + +"Personal Information" = "Persönliche Angaben"; + +"Phone Number" = "Telefonnummer"; + +"Phone Verification" = "Telefonische Überprüfung"; + +"Photo Library" = "Fotobibliothek"; + +"Please upload images of the front and back of your identity card" = "Bitte laden Sie Bilder der Vorder- und Rückseite Ihres Personalausweises hoch"; + +"Position your face in the center of the frame." = "Platzieren Sie Ihr Gesicht genau in der Mitte des Rahmens."; + +"Position your identity card in the center of the frame" = "Platzieren Sie Ihr Ausweisdokument genau in der Mitte des Rahmens."; + +"Retake Photos" = "Foto erneut aufnehmen"; + +"Scan" = "Scannen"; + +"Scanned" = "Eingescannt"; + +"Select" = "Auswählen"; + +"Select a location to upload the back of your identity document from" = "Wählen Sie einen Ort zum Hochladen der Rückseite Ihres Ausweisdokuments aus"; + +"Select a location to upload the front of your identity document from" = "Wählen Sie einen Ort zum Hochladen der Vorderseite Ihres Ausweisdokuments aus"; + +"Select back identity card photo" = "Foto der Rückseite des Ausweisdokuments auswählen"; + +"Select front identity card photo" = "Foto der Vorderseite des Ausweisdokuments auswählen"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfie-Aufnahmen"; + +"Selfie captures are complete" = "Die Selfie-Aufnahmen sind abgeschlossen."; + +"Take Photo" = "Aufnahme machen"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Die Bilder Ihres Ausweisdokuments wurden nicht gespeichert. Möchten Sie die Seite verlassen?"; + +"There was an error accessing the camera." = "Beim Zugriff auf die Kamera ist ein Fehler aufgetreten."; + +"Try Again" = "Erneut versuchen"; + +"Try reduce glare and make ID visible" = "Verringern Sie Blendeffekte und achten Sie auf die Sichtbarkeit des Ausweisdokuments"; + +"Unable to establish a connection." = "Es kann keine Verbindung hergestellt werden."; + +"Unsaved changes" = "Nicht gespeicherte Änderungen"; + +"Upload" = "Hochladen"; + +"Upload a Photo" = "Foto hochladen"; + +"Upload your photo ID" = "Lichtbildausweis hochladen"; + +"Uploading back identity card photo" = "Foto der Rückseite des Ausweisdokuments wird hochgeladen"; + +"Uploading front identity card photo" = "Foto der Vorderseite des Ausweisdokuments wird hochgeladen"; + +"Verify your identity" = "Identität verifizieren"; + +"We could not capture a high-quality image." = "Wir konnten kein qualitativ hochwertiges Bild aufnehmen."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Wir benötigen die Erlaubnis, Ihre Kamera zu verwenden. Bitte gewähren Sie den Kamerazugriff in den App-Einstellungen."; + +"Welcome" = "Willkommen"; + +"You can either try again or upload an image from your device." = "Sie können es noch einmal versuchen oder ein Bild von Ihrem Endgerät hochladen."; + +"Your selfie images have not been saved. Do you want to leave?" = "Ihre Selfie-Aufnahmen wurden nicht gespeichert. Möchten Sie die Seite verlassen?"; + +"driver's license" = "Führerschein"; + +"government-issued photo ID" = "Behördlich ausgestellter Lichtbildausweis"; + +"passport" = "Reisepass"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/el-GR.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..6d47ae1d --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/el-GR.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Στο επόμενο βήμα θα ληφθούν αυτόματα μερικές φωτογραφίες για να επαληθεύσουμε ότι είστε εσείς"; + +"Accepted forms of ID include" = "Οι αποδεκτές μορφές εγγράφου ταυτότητας περιλαμβάνουν"; + +"Alternatively, you may manually upload a photo of your identity document." = "Εναλλακτικά, μπορείτε να αποστείλετε με μη αυτόματο τρόπο μια φωτογραφία του εγγράφου της ταυτότητάς σας."; + +"App Settings" = "Ρυθμίσεις εφαρμογής"; + +"Back identity card photo successfully uploaded" = "Η φωτογραφία της πίσω πλευράς της ταυτότητας στάλθηκε με επιτυχία"; + +"Back of identity card" = "Πίσω πλευρά ταυτότητας"; + +"Back of identity document" = "Πίσω πλευρά εγγράφου ταυτότητας"; + +"Camera permission" = "Άδεια κάμερας"; + +"Camera unavailable" = "Μη διαθέσιμη κάμερα"; + +"Capturing…" = "Καταγραφή…"; + +"Choose File" = "Επιλέξτε αρχείο"; + +"Consent" = "Συγκατάθεση"; + +"Could not capture image" = "Δεν ήταν δυνατή η καταγραφή εικόνας"; + +"Date of Birth" = "Ημερομηνία γέννησης"; + +"Date of birth does not look valid" = "Η ημερομηνία γέννησης δεν φαίνεται έγκυρη"; + +"Details not visible" = "Οι λεπτομέρειες δεν είναι ορατές"; + +"Flip your identity card over to the other side" = "Γυρίστε την ταυτότητά σας από την άλλη πλευρά"; + +"Front identity card photo successfully uploaded" = "Η φωτογραφία της μπροστινής πλευράς της ταυτότητας στάλθηκε με επιτυχία"; + +"Front of identity card" = "Μπροστινή πλευρά ταυτότητας"; + +"Front of identity document" = "Μπροστινή πλευρά εγγράφου ταυτότητας"; + +"Get ready to scan your photo ID" = "Ετοιμαστείτε να σαρώσετε την ταυτότητα με φωτογραφία"; + +"Get ready to take a selfie" = "Ετοιμαστείτε να βγάλετε μια selfie"; + +"Go Back" = "Επιστροφή"; + +"Hold still, scanning" = "Κρατήστε σταθερά, γίνεται σάρωση"; + +"I'm ready" = "Είμαι έτοιμος"; + +"ID Number" = "Αριθμός ταυτότητας"; + +"Individual CPF" = "Ατομικό CPF"; + +"Keep ID level" = "Κρατήστε οριζόντια την ταυτότητα"; + +"Last 4 of Social Security number" = "Τελευταία 4 ψηφία του Αριθμού κοινωνικής ασφάλισης"; + +"Loading" = "Φόρτωση"; + +"Make sure all details are visible and focus" = "Βεβαιωθείτε ότι όλες οι λεπτομέρειες είναι ορατές και εστιασμένες"; + +"Make sure you're in a well lit space." = "Βεβαιωθείτε ότι βρίσκεστε σε έναν καλά φωτισμένο χώρο."; + +"Move closer" = "Μετακινήστε πιο κοντά"; + +"Move farther" = "Μετακινήστε πιο μακριά"; + +"Move to a darker area" = "Μεταβείτε σε έναν σκοτεινότερο χώρο"; + +"Move to a well-lit area" = "Μεταβείτε σε έναν χώρο με καλό φωτισμό"; + +"NRIC or FIN" = "NRIC ή FIN"; + +"No document detected" = "Δεν ανιχνεύτηκε κανένα έγγραφο"; + +"Personal ID number" = "Αριθμός ταυτότητας"; + +"Personal Information" = "Προσωπικές πληροφορίες"; + +"Phone Number" = "Αριθμός τηλεφώνου"; + +"Phone Verification" = "Επαλήθευση αριθμού τηλεφώνου"; + +"Photo Library" = "Βιβλιοθήκη φωτογραφιών"; + +"Please upload images of the front and back of your identity card" = "Αποστείλετε εικόνες της μπροστινής και της πίσω πλευράς της ταυτότητάς σας"; + +"Position your face in the center of the frame." = "Τοποθετήστε το πρόσωπό σας στο κέντρο του πλαισίου."; + +"Position your identity card in the center of the frame" = "Τοποθετήστε την ταυτότητά σας στο κέντρο του πλαισίου"; + +"Retake Photos" = "Λήψη φωτογραφιών εκ νέου"; + +"Scan" = "Σάρωση"; + +"Scanned" = "Σαρώθηκε"; + +"Select" = "Επιλογή"; + +"Select a location to upload the back of your identity document from" = "Επιλέξτε την τοποθεσία από την οποία θα αποστείλετε την πίσω πλευρά του εγγράφου ταυτότητάς σας"; + +"Select a location to upload the front of your identity document from" = "Επιλέξτε την τοποθεσία από την οποία θα αποστείλετε την μπροστινή πλευρά του εγγράφου ταυτότητάς σας"; + +"Select back identity card photo" = "Επιλογή φωτογραφίας πίσω πλευράς ταυτότητας"; + +"Select front identity card photo" = "Επιλογή φωτογραφίας μπροστινής πλευράς ταυτότητας"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Λήψεις selfie"; + +"Selfie captures are complete" = "Οι λήψεις selfie ολοκληρώθηκαν"; + +"Take Photo" = "Λήψη φωτογραφίας"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Οι εικόνες του εγγράφου ταυτότητάς σας δεν έχουν αποθηκευτεί. Θέλετε να αποχωρήσετε;"; + +"There was an error accessing the camera." = "Προέκυψε σφάλμα κατά την πρόσβαση στην κάμερα."; + +"Try Again" = "Δοκιμάστε ξανά"; + +"Try reduce glare and make ID visible" = "Δοκιμάστε να μειώσετε την αντανάκλαση για να είναι ορατή η ταυτότητα"; + +"Unable to establish a connection." = "Δεν είναι δυνατή η σύνδεση."; + +"Unsaved changes" = "Μη αποθηκευμένες αλλαγές"; + +"Upload" = "Αποστολή"; + +"Upload a Photo" = "Αποστολή φωτογραφίας"; + +"Upload your photo ID" = "Αποστείλετε την ταυτότητα με φωτογραφία"; + +"Uploading back identity card photo" = "Αποστολή φωτογραφίας πίσω πλευράς ταυτότητας"; + +"Uploading front identity card photo" = "Αποστολή φωτογραφίας μπροστινής πλευράς ταυτότητας"; + +"Verify your identity" = "Επαληθεύστε την ταυτότητά σας"; + +"We could not capture a high-quality image." = "Δεν ήταν δυνατή η καταγραφή εικόνας υψηλής ποιότητας."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Χρειαζόμαστε άδεια για να χρησιμοποιήσουμε την κάμερά σας. Επιτρέψτε την πρόσβαση της κάμερας από τις ρυθμίσεις εφαρμογής."; + +"Welcome" = "Καλωσήρθατε"; + +"You can either try again or upload an image from your device." = "Μπορείτε να δοκιμάσετε ξανά ή να αποστείλετε μια εικόνα από τη συσκευή σας."; + +"Your selfie images have not been saved. Do you want to leave?" = "Οι selfie δεν αποθηκεύτηκαν. Θέλετε να πραγματοποιήσετε έξοδο;"; + +"driver's license" = "δίπλωμα οδήγησης"; + +"government-issued photo ID" = "ταυτότητα με φωτογραφία που έχει εκδοθεί από κρατική υπηρεσία"; + +"passport" = "διαβατήριο"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/en-GB.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..1dda479e --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/en-GB.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "A few photos will be taken automatically on the next step to verify it's you"; + +"Accepted forms of ID include" = "Accepted forms of ID include"; + +"Alternatively, you may manually upload a photo of your identity document." = "Alternatively, you may manually upload a photo of your identity document."; + +"App Settings" = "App Settings"; + +"Back identity card photo successfully uploaded" = "Back identity card photo successfully uploaded"; + +"Back of identity card" = "Back of identity card"; + +"Back of identity document" = "Back of identity document"; + +"Camera permission" = "Camera permission"; + +"Camera unavailable" = "Camera unavailable"; + +"Capturing…" = "Capturing…"; + +"Choose File" = "Choose File"; + +"Consent" = "Consent"; + +"Could not capture image" = "Could not capture image"; + +"Date of Birth" = "Date of Birth"; + +"Date of birth does not look valid" = "Date of birth does not look valid"; + +"Details not visible" = "Details not visible"; + +"Flip your identity card over to the other side" = "Flip your identity card over to the other side"; + +"Front identity card photo successfully uploaded" = "Front identity card photo successfully uploaded"; + +"Front of identity card" = "Front of identity card"; + +"Front of identity document" = "Front of identity document"; + +"Get ready to scan your photo ID" = "Get ready to scan your photo ID"; + +"Get ready to take a selfie" = "Get ready to take a selfie"; + +"Go Back" = "Go Back"; + +"Hold still, scanning" = "Hold still, scanning"; + +"I'm ready" = "I'm ready"; + +"ID Number" = "ID Number"; + +"Individual CPF" = "Individual CPF"; + +"Keep ID level" = "Keep ID level"; + +"Last 4 of Social Security number" = "Last 4 of Social Security number"; + +"Loading" = "Loading"; + +"Make sure all details are visible and focus" = "Make sure all details are visible and focus"; + +"Make sure you're in a well lit space." = "Make sure you're in a well lit space."; + +"Move closer" = "Move closer"; + +"Move farther" = "Move further away"; + +"Move to a darker area" = "Move to a darker area"; + +"Move to a well-lit area" = "Move to a well-lit area"; + +"NRIC or FIN" = "NRIC or FIN"; + +"No document detected" = "No document detected"; + +"Personal ID number" = "Personal ID number"; + +"Personal Information" = "Personal Information"; + +"Phone Number" = "Phone Number"; + +"Phone Verification" = "Phone Verification"; + +"Photo Library" = "Photo Library"; + +"Please upload images of the front and back of your identity card" = "Please upload images of the front and back of your identity card"; + +"Position your face in the center of the frame." = "Position your face in the centre of the frame."; + +"Position your identity card in the center of the frame" = "Position your identity card in the centre of the frame"; + +"Retake Photos" = "Retake Photos"; + +"Scan" = "Scan"; + +"Scanned" = "Scanned"; + +"Select" = "Select"; + +"Select a location to upload the back of your identity document from" = "Select a location to upload the back of your identity document from"; + +"Select a location to upload the front of your identity document from" = "Select a location to upload the front of your identity document from"; + +"Select back identity card photo" = "Select back identity card photo"; + +"Select front identity card photo" = "Select front identity card photo"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfie captures"; + +"Selfie captures are complete" = "Selfie captures are complete"; + +"Take Photo" = "Take Photo"; + +"The images of your identity document have not been saved. Do you want to leave?" = "The images of your identity document have not been saved. Do you want to leave?"; + +"There was an error accessing the camera." = "There was an error accessing the camera."; + +"Try Again" = "Try Again"; + +"Try reduce glare and make ID visible" = "Try to reduce glare and make ID visible"; + +"Unable to establish a connection." = "Unable to establish a connection."; + +"Unsaved changes" = "Unsaved changes"; + +"Upload" = "Upload"; + +"Upload a Photo" = "Upload a Photo"; + +"Upload your photo ID" = "Upload your photo ID"; + +"Uploading back identity card photo" = "Uploading back identity card photo"; + +"Uploading front identity card photo" = "Uploading front identity card photo"; + +"Verify your identity" = "Verify your identity"; + +"We could not capture a high-quality image." = "We could not capture a high-quality image."; + +"We need permission to use your camera. Please allow camera access in app settings." = "We need permission to use your camera. Please allow camera access in app settings."; + +"Welcome" = "Welcome"; + +"You can either try again or upload an image from your device." = "You can either try again or upload an image from your device."; + +"Your selfie images have not been saved. Do you want to leave?" = "Your selfie images have not been saved. Do you want to leave?"; + +"driver's license" = "driver's license"; + +"government-issued photo ID" = "government-issued photo ID"; + +"passport" = "passport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/en.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..f082fab4 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/en.lproj/Localizable.strings @@ -0,0 +1,241 @@ +/* Body for selfie warmup page */ +"A few photos will be taken automatically on the next step to verify it's you" = "A few photos will be taken automatically on the next step to verify it's you"; + +/* Title for accepted types of ids */ +"Accepted forms of ID include" = "Accepted forms of ID include"; + +/* Line 2 of error text displayed to the user when camera permissions have been denied and manually uploading a file is allowed */ +"Alternatively, you may manually upload a photo of your identity document." = "Alternatively, you may manually upload a photo of your identity document."; + +/* Opens the app's settings in the Settings app */ +"App Settings" = "App Settings"; + +/* Accessibility label when back identity card photo has successfully uploaded */ +"Back identity card photo successfully uploaded" = "Back identity card photo successfully uploaded"; + +/* Description of back of identity card image */ +"Back of identity card" = "Back of identity card"; + +/* Title of ID document scanning screen when scanning the back of an identity card */ +"Back of identity document" = "Back of identity document"; + +/* Error title displayed to the user when camera permissions have been denied + Title displayed when requesting camera permissions */ +"Camera permission" = "Camera permission"; + +/* Error title displayed to the user when the device's camera is not available */ +"Camera unavailable" = "Camera unavailable"; + +/* Instructional text for scanning selfies */ +"Capturing…" = "Capturing…"; + +/* When selected in an action sheet, opens the device's file system browser */ +"Choose File" = "Choose File"; + +/* Back button title for returning to consent screen of Identity verification */ +"Consent" = "Consent"; + +/* Error title displayed to the user if we could not scan a high quality image of the user's identity document in a reasonable amount of time */ +"Could not capture image" = "Could not capture image"; + +/* Label for Date of birth field */ +"Date of Birth" = "Date of Birth"; + +/* Message for invalid Date of birth field */ +"Date of birth does not look valid" = "Date of birth does not look valid"; + +/* Instructional text when environement is too bright + Instructional text when the document is too glarry + Instructional text when user needs remove anything blocking the document */ +"Details not visible" = "Details not visible"; + +/* driver's license */ +"driver's license" = "driver's license"; + +/* Instructional text for scanning back of a identity card */ +"Flip your identity card over to the other side" = "Flip your identity card over to the other side"; + +/* Accessibility label when front identity card photo has successfully uploaded */ +"Front identity card photo successfully uploaded" = "Front identity card photo successfully uploaded"; + +/* Description of front of identity card image */ +"Front of identity card" = "Front of identity card"; + +/* Title of ID document scanning screen when scanning the front of an identity card */ +"Front of identity document" = "Front of identity document"; + +/* Title for document front warmup page */ +"Get ready to scan your photo ID" = "Get ready to scan your photo ID"; + +/* Title for selfie warmup page */ +"Get ready to take a selfie" = "Get ready to take a selfie"; + +/* Button to go back to the previous screen */ +"Go Back" = "Go Back"; + +/* id issued by government */ +"government-issued photo ID" = "government-issued photo ID"; + +/* Instructional text when camera is focusing on a document while scanning it */ +"Hold still, scanning" = "Hold still, scanning"; + +/* Ready button text on warmup screen */ +"I'm ready" = "I'm ready"; + +/* Label for ID number section */ +"ID Number" = "ID Number"; + +/* Label for the ID field to collect individual CPF for Brazilian ID */ +"Individual CPF" = "Individual CPF"; + +/* Instructional text when user align the edge with camera + Instructional text when user needs to rorate to document to align with camera */ +"Keep ID level" = "Keep ID level"; + +/* Label for the ID field to collect last 4 of social security number for US ID */ +"Last 4 of Social Security number" = "Last 4 of Social Security number"; + +/* Status while screen is loading */ +"Loading" = "Loading"; + +/* Instructional text when the document is too blurry */ +"Make sure all details are visible and focus" = "Make sure all details are visible and focus"; + +/* Body for selfie warmup page */ +"Make sure you're in a well lit space." = "Make sure you're in a well lit space."; + +/* Instructional text when camera is too far from a document */ +"Move closer" = "Move closer"; + +/* Instructional text when camera is too close to a document */ +"Move farther" = "Move farther"; + +/* Instructional text when environement is too bright */ +"Move to a darker area" = "Move to a darker area"; + +/* Instructional text when environement is too dark */ +"Move to a well-lit area" = "Move to a well-lit area"; + +/* Instructional text when there is no document in camera frame */ +"No document detected" = "No document detected"; + +/* Label for the ID field to collect NRIC or FIN for Singaporean ID */ +"NRIC or FIN" = "NRIC or FIN"; + +/* passport */ +"passport" = "passport"; + +/* Label for the personal id number field in the hosted verification details collection form for countries without an exception */ +"Personal ID number" = "Personal ID number"; + +/* Back button title for returning to the individual's perssonal infomation screen */ +"Personal Information" = "Personal Information"; + +/* Section title for collection phone number */ +"Phone Number" = "Phone Number"; + +/* Back button title for returning to the phone verification page */ +"Phone Verification" = "Phone Verification"; + +/* When selected in an action sheet, opens the device's photo library */ +"Photo Library" = "Photo Library"; + +/* Instructions for uploading images of identity card */ +"Please upload images of the front and back of your identity card" = "Please upload images of the front and back of your identity card"; + +/* Instructional text for scanning selfies */ +"Position your face in the center of the frame." = "Position your face in the center of the frame."; + +/* Instructional text for scanning front of a identity card */ +"Position your identity card in the center of the frame" = "Position your identity card in the center of the frame"; + +/* Button text displayed to the user to retake photo */ +"Retake Photos" = "Retake Photos"; + +/* Back button title for returning to the document scan screen */ +"Scan" = "Scan"; + +/* State when identity document has been successfully scanned */ +"Scanned" = "Scanned"; + +/* Button to select a file to upload */ +"Select" = "Select"; + +/* Help text for action sheet that presents ways to upload the back of an identity document image */ +"Select a location to upload the back of your identity document from" = "Select a location to upload the back of your identity document from"; + +/* Help text for action sheet that presents ways to upload the front of an identity document image */ +"Select a location to upload the front of your identity document from" = "Select a location to upload the front of your identity document from"; + +/* Accessibility label to select a photo of back of identity card */ +"Select back identity card photo" = "Select back identity card photo"; + +/* Accessibility label to select a photo of front of identity card */ +"Select front identity card photo" = "Select front identity card photo"; + +/* Accessibility label of captured selfie images + Back button title for returning to the selfie screen */ +"Selfie" = "Selfie"; + +/* Title of selfie capture screen */ +"Selfie captures" = "Selfie captures"; + +/* Status text when selfie images have been captured */ +"Selfie captures are complete" = "Selfie captures are complete"; + +/* When selected in an action sheet, opens the device's camera interface */ +"Take Photo" = "Take Photo"; + +/* Text for message of warning alert */ +"The images of your identity document have not been saved. Do you want to leave?" = "The images of your identity document have not been saved. Do you want to leave?"; + +/* Error text displayed to the user when the device's camera is not available */ +"There was an error accessing the camera." = "There was an error accessing the camera."; + +/* Button to attempt to re-scan identity document image */ +"Try Again" = "Try Again"; + +/* Instructional text when the document is too glarry */ +"Try reduce glare and make ID visible" = "Try reduce glare and make ID visible"; + +/* Error message that displays when we're unable to connect to the server. */ +"Unable to establish a connection." = "Unable to establish a connection."; + +/* Title for warning alert */ +"Unsaved changes" = "Unsaved changes"; + +/* Back button label for the identity document file upload screen */ +"Upload" = "Upload"; + +/* Button that opens file upload screen + Button to upload a photo */ +"Upload a Photo" = "Upload a Photo"; + +/* Title of document upload screen */ +"Upload your photo ID" = "Upload your photo ID"; + +/* Accessibility label while photo of back of identity card is uploading */ +"Uploading back identity card photo" = "Uploading back identity card photo"; + +/* Accessibility label while photo of front of identity card is uploading */ +"Uploading front identity card photo" = "Uploading front identity card photo"; + +/* Displays in the navigation bar title of the Identity Verification Sheet */ +"Verify your identity" = "Verify your identity"; + +/* Error text displayed to the user if we could not scan a high quality image of the user's identity document in a reasonable amount of time */ +"We could not capture a high-quality image." = "We could not capture a high-quality image."; + +/* Line 1 of error text displayed to the user when camera permissions have been denied + Text displayed when requesting camera permissions */ +"We need permission to use your camera. Please allow camera access in app settings." = "We need permission to use your camera. Please allow camera access in app settings."; + +/* Back button title for returning to welcome screen of Identity verification */ +"Welcome" = "Welcome"; + +/* Line 2 of error text displayed to the user if we could not scan a high quality image of the user's identity document in a reasonable amount of time and manually uploading a file is allowed */ +"You can either try again or upload an image from your device." = "You can either try again or upload an image from your device."; + +/* Text for message of warning alert */ +"Your selfie images have not been saved. Do you want to leave?" = "Your selfie images have not been saved. Do you want to leave?"; + diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/es-419.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..e1e7d8c5 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/es-419.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "En el siguiente paso, se tomarán automáticamente algunas fotos para verificar tu identidad."; + +"Accepted forms of ID include" = "Se aceptan las siguientes formas de documentos de identidad"; + +"Alternatively, you may manually upload a photo of your identity document." = "También puedes subir una foto del documento de identidad manualmente."; + +"App Settings" = "Configuración de la aplicación"; + +"Back identity card photo successfully uploaded" = "La foto del dorso del documento de identidad se cargó correctamente."; + +"Back of identity card" = "Dorso del documento de identidad"; + +"Back of identity document" = "Reverso del documento de identidad"; + +"Camera permission" = "Permiso de acceso a la cámara"; + +"Camera unavailable" = "Cámara no disponible"; + +"Capturing…" = "Capturando..."; + +"Choose File" = "Elegir archivo"; + +"Consent" = "Consentimiento"; + +"Could not capture image" = "No se pudo captar la imagen."; + +"Date of Birth" = "Fecha de nacimiento"; + +"Date of birth does not look valid" = "La fecha de nacimiento no parece válida."; + +"Details not visible" = "No se ven los datos"; + +"Flip your identity card over to the other side" = "Da vuelta tu documento de identidad."; + +"Front identity card photo successfully uploaded" = "La foto del frente del documento de identidad se cargó correctamente."; + +"Front of identity card" = "Frente del documento de identidad"; + +"Front of identity document" = "Anverso del documento de identidad"; + +"Get ready to scan your photo ID" = "Prepárate para escanear tu documento de identidad con foto"; + +"Get ready to take a selfie" = "Prepárate para hacerte la selfie"; + +"Go Back" = "Volver"; + +"Hold still, scanning" = "No te muevas. Escaneando..."; + +"I'm ready" = "Listo"; + +"ID Number" = "Número de documento"; + +"Individual CPF" = "CPF individual"; + +"Keep ID level" = "Mantén el documento de identidad nivelado"; + +"Last 4 of Social Security number" = "Últimos 4 dígitos del número de seguridad social"; + +"Loading" = "Cargando"; + +"Make sure all details are visible and focus" = "Asegúrate de que todos tus datos se vean y estén enfocados"; + +"Make sure you're in a well lit space." = "Asegúrate de que estás en un lugar bien iluminado."; + +"Move closer" = "Acércalo"; + +"Move farther" = "Aleja la cámara"; + +"Move to a darker area" = "Muévete a una zona más oscura"; + +"Move to a well-lit area" = "Muévete a una zona bien iluminada"; + +"NRIC or FIN" = "NRIC o FIN"; + +"No document detected" = "No se detectó ningún documento"; + +"Personal ID number" = "Número de documento personal"; + +"Personal Information" = "Información personal"; + +"Phone Number" = "Número de teléfono"; + +"Phone Verification" = "Verificación por teléfono"; + +"Photo Library" = "Librería de fotos"; + +"Please upload images of the front and back of your identity card" = "Carga las imágenes del frente y el dorso de tu documento de identidad."; + +"Position your face in the center of the frame." = "Coloca la cara en el centro del recuadro."; + +"Position your identity card in the center of the frame" = "Coloca el documento de identidad en el centro del recuadro."; + +"Retake Photos" = "Volver a sacar las fotos"; + +"Scan" = "Escanear"; + +"Scanned" = "Escaneado"; + +"Select" = "Seleccionar"; + +"Select a location to upload the back of your identity document from" = "Selecciona una ubicación desde donde cargar el dorso de tu documento de identidad."; + +"Select a location to upload the front of your identity document from" = "Selecciona una ubicación desde donde cargar el frente de tu documento de identidad."; + +"Select back identity card photo" = "Seleccionar la foto del dorso del documento de identidad"; + +"Select front identity card photo" = "Seleccionar la foto del frente del documento de identidad"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Capturas de selfie"; + +"Selfie captures are complete" = "Se completaron las capturas de selfie."; + +"Take Photo" = "Tomar una foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "No se guardaron las imágenes de tu documento de identidad. ¿Quieres salir?"; + +"There was an error accessing the camera." = "Se produjo un error al acceder a la cámara."; + +"Try Again" = "Vuelve a intentarlo"; + +"Try reduce glare and make ID visible" = "Intenta reducir el brillo y haz que el documento de identidad sea visible."; + +"Unable to establish a connection." = "No fue posible establecer conexión."; + +"Unsaved changes" = "Cambios no guardados"; + +"Upload" = "Cargar"; + +"Upload a Photo" = "Cargar una foto"; + +"Upload your photo ID" = "Carga tu documento de identidad con foto"; + +"Uploading back identity card photo" = "Cargando la foto del dorso del documento de identidad"; + +"Uploading front identity card photo" = "Cargando la foto del frente del documento de identidad"; + +"Verify your identity" = "Verifica tu identidad"; + +"We could not capture a high-quality image." = "No pudimos captar una imagen de alta calidad."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Necesitamos tu autorización para usar la cámara. Permite el acceso a la cámara en la configuración de la aplicación."; + +"Welcome" = "Te damos la bienvenida"; + +"You can either try again or upload an image from your device." = "Puedes volver a intentarlo o cargar una imagen desde tu dispositivo."; + +"Your selfie images have not been saved. Do you want to leave?" = "No se guardaron las capturas de selfie. ¿Quieres salir?"; + +"driver's license" = "licencia de conducir"; + +"government-issued photo ID" = "documento de identidad con foto, emitido por un organismo público"; + +"passport" = "pasaporte"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/es.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/es.lproj/Localizable.strings new file mode 100644 index 00000000..86308286 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/es.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "En el siguiente paso se tomarán automáticamente algunas fotos para verificar tu identidad."; + +"Accepted forms of ID include" = "Se aceptan las siguientes formas de documentos de identidad"; + +"Alternatively, you may manually upload a photo of your identity document." = "También puedes subir una foto de tu documento de identidad de forma manual."; + +"App Settings" = "Configuración de la aplicación"; + +"Back identity card photo successfully uploaded" = "La foto del reverso del documento de identidad se ha cargado correctamente"; + +"Back of identity card" = "Reverso del documento de identidad"; + +"Back of identity document" = "Reverso del documento de identidad"; + +"Camera permission" = "Permiso de cámara"; + +"Camera unavailable" = "Cámara no disponible"; + +"Capturing…" = "Realizando la selfie..."; + +"Choose File" = "Elegir archivo"; + +"Consent" = "Consentimiento"; + +"Could not capture image" = "No se ha podido capturar la imagen"; + +"Date of Birth" = "Fecha de nacimiento"; + +"Date of birth does not look valid" = "Parece que la fecha de nacimiento no es válida"; + +"Details not visible" = "No se ven los datos"; + +"Flip your identity card over to the other side" = "Gira tu documento de identidad hacia el lado contrario"; + +"Front identity card photo successfully uploaded" = "La foto del anverso del documento de identidad se ha cargado correctamente"; + +"Front of identity card" = "Anverso del documento de identidad"; + +"Front of identity document" = "Anverso del documento de identidad"; + +"Get ready to scan your photo ID" = "Prepárate para escanear tu documento de identidad con foto"; + +"Get ready to take a selfie" = "Prepárate para hacerte la selfie"; + +"Go Back" = "Volver atrás"; + +"Hold still, scanning" = "Espera, se está escaneando"; + +"I'm ready" = "A punto"; + +"ID Number" = "Número de ID"; + +"Individual CPF" = "CPF individual"; + +"Keep ID level" = "Mantén el documento de identidad nivelado"; + +"Last 4 of Social Security number" = "Últimos 4 dígitos del número de seguridad social"; + +"Loading" = "Cargando"; + +"Make sure all details are visible and focus" = "Asegúrate de que todos tus datos se vean y estén enfocados."; + +"Make sure you're in a well lit space." = "Asegúrate de que estás en un lugar bien iluminado."; + +"Move closer" = "Acércalo"; + +"Move farther" = "Aleja la cámara"; + +"Move to a darker area" = "Cámbiate a una zona más oscura"; + +"Move to a well-lit area" = "Cámbiate a una zona bien iluminada"; + +"NRIC or FIN" = "NRIC o FIN"; + +"No document detected" = "No se ha detectado ningún documento"; + +"Personal ID number" = "Número de identificación personal"; + +"Personal Information" = "Datos personales"; + +"Phone Number" = "Número de teléfono"; + +"Phone Verification" = "Verificación por teléfono"; + +"Photo Library" = "Biblioteca de fotos"; + +"Please upload images of the front and back of your identity card" = "Carga las imágenes del anverso y el reverso de tu documento de identidad"; + +"Position your face in the center of the frame." = "Coloca la cara en el centro del marco."; + +"Position your identity card in the center of the frame" = "Coloca el documento de identidad en el centro del marco"; + +"Retake Photos" = "Volver a sacar las fotos"; + +"Scan" = "Escanear"; + +"Scanned" = "Escaneado"; + +"Select" = "Seleccionar"; + +"Select a location to upload the back of your identity document from" = "Selecciona una ubicación desde la que cargar el reverso de tu documento de identidad"; + +"Select a location to upload the front of your identity document from" = "Selecciona una ubicación desde la que cargar el anverso de tu documento de identidad"; + +"Select back identity card photo" = "Selecciona la foto del reverso del documento de identidad"; + +"Select front identity card photo" = "Selecciona la foto del anverso del documento de identidad"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Capturas de selfies"; + +"Selfie captures are complete" = "Se han realizado las selfies."; + +"Take Photo" = "Hacer una foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "No se han guardado las imágenes de tu documento de identidad. ¿Quieres salir?"; + +"There was an error accessing the camera." = "Ha habido un error al acceder a la cámara."; + +"Try Again" = "Reintentar"; + +"Try reduce glare and make ID visible" = "Intenta reducir el brillo y haz que el documento de identidad sea visible."; + +"Unable to establish a connection." = "No se ha podido establecer conexión"; + +"Unsaved changes" = "Cambios no guardados"; + +"Upload" = "Cargar"; + +"Upload a Photo" = "Cargar una foto"; + +"Upload your photo ID" = "Sube tu documento de identidad con foto"; + +"Uploading back identity card photo" = "Cargando la foto del reverso del documento de identidad"; + +"Uploading front identity card photo" = "Cargando la foto del anverso del documento de identidad"; + +"Verify your identity" = "Verifica tu identidad"; + +"We could not capture a high-quality image." = "No hemos podido captar una imagen de alta calidad."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Necesitamos permiso para usar tu cámara. Autoriza el acceso a la cámara en los ajustes de la aplicación."; + +"Welcome" = "Bienvenido"; + +"You can either try again or upload an image from your device." = "Puedes volver a intentarlo o subir una imagen desde tu dispositivo."; + +"Your selfie images have not been saved. Do you want to leave?" = "No se han guardado las selfies. ¿Quieres salir?"; + +"driver's license" = "permiso de conducir"; + +"government-issued photo ID" = "Documento de identidad con foto expedido por el gobierno"; + +"passport" = "pasaporte"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/et-EE.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..30e8badc --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/et-EE.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Järgmises etapis tehakse automaatselt mõned fotod, et kinnitada, et see olete teie"; + +"Accepted forms of ID include" = "Aktsepteeritud isikut tõendavad dokumendid hõlmavad järgmist"; + +"Alternatively, you may manually upload a photo of your identity document." = "Samuti võite oma isikut tõendava dokumendi foto käsitsi üles laadida."; + +"App Settings" = "Rakenduse sätted"; + +"Back identity card photo successfully uploaded" = "ID-kaardi tagakülje foto üleslaadimine õnnestus"; + +"Back of identity card" = "ID-kaardi tagakülg"; + +"Back of identity document" = "Isikut tõendava dokumendi tagakülg"; + +"Camera permission" = "Kaamera luba"; + +"Camera unavailable" = "Kaamera pole saadaval"; + +"Capturing…" = "Pildistamine …"; + +"Choose File" = "Vali fail"; + +"Consent" = "Nõusolek"; + +"Could not capture image" = "Pilti ei saanud teha"; + +"Date of Birth" = "Sünnikuupäev"; + +"Date of birth does not look valid" = "Sünnikuupäev ei näi olevat õige"; + +"Details not visible" = "Üksikasjad pole nähtaval"; + +"Flip your identity card over to the other side" = "Pöörake ID-kaart ümber"; + +"Front identity card photo successfully uploaded" = "ID-kaardi esikülje foto üleslaadimine õnnestus"; + +"Front of identity card" = "ID-kaardi esikülg"; + +"Front of identity document" = "Isikut tõendava dokumendi esikülg"; + +"Get ready to scan your photo ID" = "Valmistuge oma fotoga isikut tõendava dokumendi skannimiseks"; + +"Get ready to take a selfie" = "Valmistuge tegema selfit"; + +"Go Back" = "Mine tagasi"; + +"Hold still, scanning" = "Hoidke paigal, skannimine"; + +"I'm ready" = "Olen valmis jätkama"; + +"ID Number" = "Isikukood"; + +"Individual CPF" = "Eraisiku CPF"; + +"Keep ID level" = "Hoidke isikut tõendavat dokumenti rõhtsalt"; + +"Last 4 of Social Security number" = "Sotsiaalkindlustuse numbri viimased 4 numbrit"; + +"Loading" = "Laadimine"; + +"Make sure all details are visible and focus" = "Veenduge, et kõik üksikasjad oleksid nähtavad ja teravad"; + +"Make sure you're in a well lit space." = "Veenduge, et olete hästi valgustatud kohas."; + +"Move closer" = "Asetage lähemale"; + +"Move farther" = "Asetage kaugemale"; + +"Move to a darker area" = "Liikuge pimedamasse kohta"; + +"Move to a well-lit area" = "Liikuge hästi valgustatud kohta"; + +"NRIC or FIN" = "NRIC või FIN"; + +"No document detected" = "Dokumenti ei tuvastatud"; + +"Personal ID number" = "Isikukood"; + +"Personal Information" = "Isikuteave"; + +"Phone Number" = "Telefoninumber"; + +"Phone Verification" = "Telefoni kinnitus"; + +"Photo Library" = "Fototeek"; + +"Please upload images of the front and back of your identity card" = "Laadige üles oma ID-kaardi esi- ja tagakülje pildid"; + +"Position your face in the center of the frame." = "Jälgige, et nägu oleks raami keskel."; + +"Position your identity card in the center of the frame" = "Jälgige, et ID-kaart oleks raami keskel"; + +"Retake Photos" = "Tee fotod uuesti"; + +"Scan" = "Skannimine"; + +"Scanned" = "Skannitud"; + +"Select" = "Vali"; + +"Select a location to upload the back of your identity document from" = "Valige asukoht, kust laadida üles oma isikut tõendava dokumendi tagakülg"; + +"Select a location to upload the front of your identity document from" = "Valige asukoht, kust laadida üles oma isikut tõendava dokumendi esikülg"; + +"Select back identity card photo" = "Vali ID-kaardi tagakülje foto"; + +"Select front identity card photo" = "Vali ID-kaardi esikülje foto"; + +"Selfie" = "Selfi"; + +"Selfie captures" = "Selfi pildistamine"; + +"Selfie captures are complete" = "Selfi on jäädvustatud"; + +"Take Photo" = "Pildista"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Teie isikut tõendava dokumendi pilte pole salvestatud. Kas soovite lahkuda?"; + +"There was an error accessing the camera." = "Kaamerale juurdepääsemisel esines tõrge."; + +"Try Again" = "Proovi uuesti"; + +"Try reduce glare and make ID visible" = "Proovige vähendada läiget ja veenduge, et ID oleks nähtav"; + +"Unable to establish a connection." = "Ühendust ei saa luua."; + +"Unsaved changes" = "Salvestamata muudatused"; + +"Upload" = "Üleslaadimine"; + +"Upload a Photo" = "Laadi foto üles"; + +"Upload your photo ID" = "Laadige üles fotoga isikut tõendav dokument"; + +"Uploading back identity card photo" = "ID-kaardi tagakülje foto üleslaadimine"; + +"Uploading front identity card photo" = "ID-kaardi esikülje foto üleslaadimine"; + +"Verify your identity" = "Kinnitage oma isik"; + +"We could not capture a high-quality image." = "Meil ei õnnestunud teha kõrge kvaliteediga pilti."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Vajame teie kaamera kasutamiseks luba. Andke rakenduse sätete kaudu juurdepääs kaamerale."; + +"Welcome" = "Tere tulemast"; + +"You can either try again or upload an image from your device." = "Võite kas uuesti proovida või pildi oma seadmest üles laadida."; + +"Your selfie images have not been saved. Do you want to leave?" = "Teie selfi pilte pole salvestatud. Kas soovite lahkuda?"; + +"driver's license" = "juhiluba"; + +"government-issued photo ID" = "riigi poolt väljastatud fotoga isikut tõendav dokument"; + +"passport" = "pass"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/fi.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/fi.lproj/Localizable.strings new file mode 100644 index 00000000..b374abe2 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/fi.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Seuraavassa vaiheessa otetaan automaattisesti muutama kuva sen vahvistamiseksi, että kyseessä olet sinä"; + +"Accepted forms of ID include" = "Hyväksyttyjä henkilöllisyystodistusmuotoja ovat"; + +"Alternatively, you may manually upload a photo of your identity document." = "Voit vaihtoehtoisesti ladata henkilöllisyysasiakirjan kuvan manuaalisesti."; + +"App Settings" = "Sovelluksen asetukset"; + +"Back identity card photo successfully uploaded" = "Henkilökortin takapuolen kuva ladattu onnistuneesti"; + +"Back of identity card" = "Henkilökortin takapuoli"; + +"Back of identity document" = "Henkilötodistuksen takapuoli"; + +"Camera permission" = "Kameran käyttöoikeus"; + +"Camera unavailable" = "Kamera ei käytössä"; + +"Capturing…" = "Otetaan kuvaa..."; + +"Choose File" = "Valitse tiedosto"; + +"Consent" = "Suostumus"; + +"Could not capture image" = "Kuvaa ei voitu ottaa"; + +"Date of Birth" = "Syntymäaika"; + +"Date of birth does not look valid" = "Syntymäaika ei näytä kelvolliselta"; + +"Details not visible" = "Tiedot eivät ole näkyvillä"; + +"Flip your identity card over to the other side" = "Käännä henkilökortti toisinpäin"; + +"Front identity card photo successfully uploaded" = "Henkilökortin etupuolen kuva ladattu onnistuneesti"; + +"Front of identity card" = "Henkilökortin etupuoli"; + +"Front of identity document" = "Henkilötodistuksen etupuoli"; + +"Get ready to scan your photo ID" = "Valmistaudu skannaamaan kuvallinen henkilöllisyystodistus"; + +"Get ready to take a selfie" = "Valmistaudu selfien ottoon"; + +"Go Back" = "Siirry takaisin"; + +"Hold still, scanning" = "Odota, skannaus kesken"; + +"I'm ready" = "Olen valmis"; + +"ID Number" = "Tunnusnumero"; + +"Individual CPF" = "Yksityishenkilön CPF"; + +"Keep ID level" = "Pidä henkilöllisyystodistus tasaisena"; + +"Last 4 of Social Security number" = "Sosiaaliturvatunnuksen 4 viimeistä merkkiä"; + +"Loading" = "Ladataan"; + +"Make sure all details are visible and focus" = "Varmista, että kaikki tiedot ovat näkyvissä ja tarkennettuja"; + +"Make sure you're in a well lit space." = "Varmista, että olet hyvin valaistussa tilassa."; + +"Move closer" = "Siirry lähemmäs"; + +"Move farther" = "Siirrä kauemmas"; + +"Move to a darker area" = "Siirry hämärämpään tilaan"; + +"Move to a well-lit area" = "Siirry hyvin valaistuun tilaan"; + +"NRIC or FIN" = "NRIC tai FIN"; + +"No document detected" = "Ei havaittuja asiakirjoja"; + +"Personal ID number" = "Henkilötunnus"; + +"Personal Information" = "Henkilötiedot"; + +"Phone Number" = "Puhelinnumero"; + +"Phone Verification" = "Vahvistus puhelimella"; + +"Photo Library" = "Kuvakirjasto"; + +"Please upload images of the front and back of your identity card" = "Lataa henkilökortin etu- ja takapuolen kuvat"; + +"Position your face in the center of the frame." = "Aseta kasvosi kuvan keskelle."; + +"Position your identity card in the center of the frame" = "Aseta henkilökortti kehyksen keskelle"; + +"Retake Photos" = "Ota kuvat uudelleen"; + +"Scan" = "Skannaa"; + +"Scanned" = "Skannattu"; + +"Select" = "Valitse"; + +"Select a location to upload the back of your identity document from" = "Valitse sijainti, josta haluat ladata henkilöllisyysasiakirjan takapuolen"; + +"Select a location to upload the front of your identity document from" = "Valitse sijainti, josta haluat ladata henkilöllisyysasiakirjan etupuolen"; + +"Select back identity card photo" = "Valitse henkilökortin takapuolen kuva"; + +"Select front identity card photo" = "Valitse henkilökortin etupuolen kuva"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfiet"; + +"Selfie captures are complete" = "Selfien ottaminen suortitettu"; + +"Take Photo" = "Ota kuva"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Henkilöllisyysasiakirjan kuvia ei ole tallennettu. Haluatko poistua?"; + +"There was an error accessing the camera." = "Kameran käytössä tapahtui virhe."; + +"Try Again" = "Yritä uudelleen"; + +"Try reduce glare and make ID visible" = "Yritä vähentää häikäisyä niin että henkilöllisyystodistus näkyy"; + +"Unable to establish a connection." = "Yhteyttä ei voida muodostaa."; + +"Unsaved changes" = "Tallentamattomat muutokset"; + +"Upload" = "Lataa"; + +"Upload a Photo" = "Lataa kuva"; + +"Upload your photo ID" = "Lataa kuvallinen henkilöllisyystodistus"; + +"Uploading back identity card photo" = "Ladataan henkilökortin takapuolen kuvaa"; + +"Uploading front identity card photo" = "Ladataan henkilökortin etupuolen kuvaa"; + +"Verify your identity" = "Vahvista henkilöllisyytesi"; + +"We could not capture a high-quality image." = "Emme kyenneet ottamaan korkealaatuista kuvaa."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Tarvitsemme kameran käyttöoikeuden. Salli käyttöoikeus sovelluksen asetuksissa."; + +"Welcome" = "Tervetuloa"; + +"You can either try again or upload an image from your device." = "Voit joko yrittää uudelleen tai ladata kuvan laitteestasi."; + +"Your selfie images have not been saved. Do you want to leave?" = "Selfiekuviasi ei ole tallennettu. Haluatko poistua?"; + +"driver's license" = "ajokortti"; + +"government-issued photo ID" = "valtion myöntämä kuvallinen henkilöllisyystodistus"; + +"passport" = "passi"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/fil.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/fil.lproj/Localizable.strings new file mode 100644 index 00000000..00510f09 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/fil.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Awtomatikong kukunin ang ilang larawan sa susunod na hakbang upang patunayan na ikaw nga ito"; + +"Accepted forms of ID include" = "Kasama sa mga tinatanggap na uri ng ID"; + +"Alternatively, you may manually upload a photo of your identity document." = "Bilang alternatibo, maaari kang manu-manong mag-upload ng larawan ng dokumento ng iyong pagkakakilanlan."; + +"App Settings" = "Mga Setting ng App"; + +"Back identity card photo successfully uploaded" = "Tagumpay na na-upload ang larawan ng likod ng kard ng pagkakakilanlan"; + +"Back of identity card" = "Likod ng kard ng pagkakakilanlan"; + +"Back of identity document" = "Likod ng dokumento ng pagkakakilanlan"; + +"Camera permission" = "Pahintulot ng kamera"; + +"Camera unavailable" = "Hindi magamit ang kamera"; + +"Capturing…" = "Kinukunan..."; + +"Choose File" = "Pumili ng File"; + +"Consent" = "Pahintulot"; + +"Could not capture image" = "Hindi makunan ng imahe"; + +"Date of Birth" = "Petsa ng Kapanganakan"; + +"Date of birth does not look valid" = "Mukhang hindi balido ang petsa ng kapanganakan"; + +"Details not visible" = "Hindi nakikita ang mga detalye"; + +"Flip your identity card over to the other side" = "Baliktarin ang iyong kard ng pagkakakilanlan"; + +"Front identity card photo successfully uploaded" = "Tagumpay na na-upload ang larawan ng harap ng kard ng pagkakakilanlan"; + +"Front of identity card" = "Harap ng card ng pagkakakilanlan"; + +"Front of identity document" = "Harap ng dokumento ng pagkakakilanlan"; + +"Get ready to scan your photo ID" = "Maghandang i-scan ang iyong photo ID"; + +"Get ready to take a selfie" = "Maghanda na kumuha ng selfie"; + +"Go Back" = "Bumalik"; + +"Hold still, scanning" = "Huwag gumalaw, ini-iscan"; + +"I'm ready" = "Handa na ako"; + +"ID Number" = "Numero ng ID"; + +"Individual CPF" = "Indibidwal na CPF"; + +"Keep ID level" = "Panatilihin ang lebel ng ID"; + +"Last 4 of Social Security number" = "Huling 4 ng numero ng Social Security"; + +"Loading" = "Niloload"; + +"Make sure all details are visible and focus" = "Siguraduhing nakikita at nakapokus ang lahat ng mga detalye"; + +"Make sure you're in a well lit space." = "Siguraduhing ikaw ay nasa maliwanag na lugar."; + +"Move closer" = "Ilapit"; + +"Move farther" = "Ilayo pa"; + +"Move to a darker area" = "Lumipat sa mas madilim na lugar"; + +"Move to a well-lit area" = "Lumipat sa maliwanag na lugar"; + +"NRIC or FIN" = "NRIC o FIN"; + +"No document detected" = "Walang nakitang dokumento"; + +"Personal ID number" = "Numero ng Personal na ID"; + +"Personal Information" = "Personal na Impormasyon"; + +"Phone Number" = "Numero ng Telepono"; + +"Phone Verification" = "Pag-verify sa Telepono"; + +"Photo Library" = "Library ng Larawan"; + +"Please upload images of the front and back of your identity card" = "Mangyaring i-upload ang mga imahe ng harap at likod ng iyong card ng pagkakakilanlan"; + +"Position your face in the center of the frame." = "Iposisyon ang iyong mukha sa gitna ng frame."; + +"Position your identity card in the center of the frame" = "Iposisyon ang iyong kard ng pagkakakilanlan sa gitna ng frame"; + +"Retake Photos" = "Kunan Muli Ang Mga Larawan"; + +"Scan" = "I-scan"; + +"Scanned" = "Na-scan na"; + +"Select" = "Piliin"; + +"Select a location to upload the back of your identity document from" = "Pumili ng lokasyon kung saan i-a-upload ang likod ng iyong dokumento ng pagkakakilanlan"; + +"Select a location to upload the front of your identity document from" = "Pumili ng lokasyon kung saan i-a-upload ang harap ng iyong dokumento ng pagkakakilanlan"; + +"Select back identity card photo" = "Pumili ng larawan ng likod ng kard ng pagkakakilanlan"; + +"Select front identity card photo" = "Pumili ng larawan ng harap ng kard ng pagkakakilanlan"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Mga pagkuha ng selfie"; + +"Selfie captures are complete" = "Kumpleto na ang mga pagkuha ng selfie"; + +"Take Photo" = "Kumuha ng larawan"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Hindi na-save ang mga imahe ng iyong dokumento ng pagkakakilanlan. Gusto mo bang umalis?"; + +"There was an error accessing the camera." = "Nagkaroon ng error sa pag-access sa kamera"; + +"Try Again" = "Subukan Muli"; + +"Try reduce glare and make ID visible" = "Subukang bawasan ang pagkasilaw at gawing nakikita ang ID"; + +"Unable to establish a connection." = "Hindi mapagtibay ang koneksiyon."; + +"Unsaved changes" = "Mga hindi na-save na pagbabago"; + +"Upload" = "I-upload"; + +"Upload a Photo" = "Mag-upload ng Larawan"; + +"Upload your photo ID" = "I-upload ang iyong photo ID"; + +"Uploading back identity card photo" = "Ina-upload ang larawan ng likod ng card ng pagkakakilanlan"; + +"Uploading front identity card photo" = "Ina-upload ang larawan ng harap ng kard ng pagkakakilanlan"; + +"Verify your identity" = "Patunayan ang iyong pagkakakilanlan"; + +"We could not capture a high-quality image." = "Hindi kami makakuha ng mataas na kalidad na imahe."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Kailangan namin ng pahintulot na gamitin ang iyong kamera. Mangyaring payagan ang pag-access ng kamera sa mga setting ng app."; + +"Welcome" = "Maligayang bati"; + +"You can either try again or upload an image from your device." = "Puwede mong subukan muli o mag-upload ng imahe mula sa iyong device."; + +"Your selfie images have not been saved. Do you want to leave?" = "Hindi pa na-save Ang iyong mga imaheng selfie. Gusto mo bang umalis?"; + +"driver's license" = "lisensya sa pagmamaneho"; + +"government-issued photo ID" = "inisyu ng gobyerno na photo ID"; + +"passport" = "pasaporte"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/fr-CA.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..020e238d --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/fr-CA.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "À l'étape suivante, nous allons prendre automatiquement quelques photos afin de vérifier votre identité"; + +"Accepted forms of ID include" = "Pièces d'identité acceptées"; + +"Alternatively, you may manually upload a photo of your identity document." = "Vous pouvez également téléverser manuellement une photo de votre pièce d'identité."; + +"App Settings" = "Paramètres de l'application"; + +"Back identity card photo successfully uploaded" = "Photo du verso de la carte d'identité téléversée"; + +"Back of identity card" = "Verso de la carte d'identité"; + +"Back of identity document" = "Verso de la pièce d'identité"; + +"Camera permission" = "Autorisation d'accès à l'appareil photo"; + +"Camera unavailable" = "Appareil photo non disponible"; + +"Capturing…" = "Capture en cours..."; + +"Choose File" = "Choisir un fichier"; + +"Consent" = "Consentement"; + +"Could not capture image" = "Impossible de prendre une photo"; + +"Date of Birth" = "Date de naissance"; + +"Date of birth does not look valid" = "La date de naissance ne semble pas valide"; + +"Details not visible" = "Détails non visibles"; + +"Flip your identity card over to the other side" = "Retournez votre carte d'identité"; + +"Front identity card photo successfully uploaded" = "Photo du recto de la carte d'identité téléversée"; + +"Front of identity card" = "Recto de la carte d'identité"; + +"Front of identity document" = "Recto de la pièce d'identité"; + +"Get ready to scan your photo ID" = "Préparez-vous à scanner votre pièce d'identité"; + +"Get ready to take a selfie" = "Préparez-vous à vous prendre en photo"; + +"Go Back" = "Retour"; + +"Hold still, scanning" = "Ne bougez pas, numérisation en cours"; + +"I'm ready" = "Tout est prêt"; + +"ID Number" = "Numéro d'identification"; + +"Individual CPF" = "Numéro CPF"; + +"Keep ID level" = "Maintenez votre pièce d'identité horizontale"; + +"Last 4 of Social Security number" = "4 derniers chiffres du numéro de sécurité sociale"; + +"Loading" = "Chargement en cours"; + +"Make sure all details are visible and focus" = "Assurez-vous que tous les détails sont nets et visibles"; + +"Make sure you're in a well lit space." = "Installez-vous dans un endroit bien éclairé."; + +"Move closer" = "Rapprochez le document"; + +"Move farther" = "Éloignez le document"; + +"Move to a darker area" = "Prenez votre photo dans un endroit plus sombre"; + +"Move to a well-lit area" = "Prenez votre photo dans un endroit bien éclairé"; + +"NRIC or FIN" = "Numéro NRIC ou FIN"; + +"No document detected" = "Aucun document détecté"; + +"Personal ID number" = "Numéro d'identification personnel"; + +"Personal Information" = "Données personnelles"; + +"Phone Number" = "Numéro de téléphone"; + +"Phone Verification" = "Vérification du numéro de téléphone"; + +"Photo Library" = "Bibliothèque de photos"; + +"Please upload images of the front and back of your identity card" = "Veuillez téléverser une photo du recto et du verso de votre carte d'identité"; + +"Position your face in the center of the frame." = "Placez votre visage au centre du cadre."; + +"Position your identity card in the center of the frame" = "Placez votre carte d'identité au centre du cadre"; + +"Retake Photos" = "Reprendre des photos"; + +"Scan" = "Numériser"; + +"Scanned" = "Numérisé"; + +"Select" = "Sélectionner"; + +"Select a location to upload the back of your identity document from" = "Sélectionnez l'emplacement à partir duquel vous souhaitez téléverser le verso de votre pièce d'identité"; + +"Select a location to upload the front of your identity document from" = "Sélectionnez l'emplacement à partir duquel vous souhaitez téléverser le recto de votre pièce d'identité"; + +"Select back identity card photo" = "Sélectionner le verso de la carte d'identité"; + +"Select front identity card photo" = "Sélectionner le recto de la carte d'identité"; + +"Selfie" = "Égoportrait"; + +"Selfie captures" = "Captures d'égoportraits"; + +"Selfie captures are complete" = "Les captures d'égoportraits sont terminées"; + +"Take Photo" = "Prendre une photo"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Les photos de votre pièce d'identité n'ont pas été enregistrées. Voulez-vous quitter?"; + +"There was an error accessing the camera." = "Une erreur est survenue lors de l'accès à l'appareil photo."; + +"Try Again" = "Réessayer"; + +"Try reduce glare and make ID visible" = "Essayez de réduire la luminosité et assurez-vous que la pièce d'identité est visible"; + +"Unable to establish a connection." = "Connexion impossible."; + +"Unsaved changes" = "Modifications non enregistrées"; + +"Upload" = "Téléverser"; + +"Upload a Photo" = "Téléverser une photo"; + +"Upload your photo ID" = "Téléverser votre pièce d'identité avec photo"; + +"Uploading back identity card photo" = "Téléversement de la photo du verso de la carte d'identité"; + +"Uploading front identity card photo" = "Téléversement de la photo du recto de la pièce d'identité"; + +"Verify your identity" = "Confirmer votre identité"; + +"We could not capture a high-quality image." = "Impossible de prendre une photo de bonne qualité."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Pour que nous puissions accéder à votre appareil photo, vous devez nous donner votre autorisation dans les paramètres de l'application."; + +"Welcome" = "Bienvenue"; + +"You can either try again or upload an image from your device." = "Vous pouvez réessayer ou téléverser une photo à partir de votre appareil."; + +"Your selfie images have not been saved. Do you want to leave?" = "Vos égoportraits n'ont pas été enregistrés. Voulez-vous quitter?"; + +"driver's license" = "permis de conduire"; + +"government-issued photo ID" = "pièce d'identité officielle avec photo"; + +"passport" = "passeport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/fr.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/fr.lproj/Localizable.strings new file mode 100644 index 00000000..df73a5fe --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/fr.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "À l'étape suivante, nous prendrons quelques photos de façon automatique afin de vérifier votre identité"; + +"Accepted forms of ID include" = "Pièces d'identité acceptées"; + +"Alternatively, you may manually upload a photo of your identity document." = "Vous pouvez également charger manuellement une photo de votre pièce d’identité."; + +"App Settings" = "Paramètres de l'application"; + +"Back identity card photo successfully uploaded" = "Photo du verso de la carte d'identité chargée"; + +"Back of identity card" = "Verso de la carte d'identité"; + +"Back of identity document" = "Verso de la pièce d'identité"; + +"Camera permission" = "Autorisation d'accès à l'appareil photo"; + +"Camera unavailable" = "Appareil photo non disponible"; + +"Capturing…" = "Capture en cours..."; + +"Choose File" = "Choisir un fichier"; + +"Consent" = "Consentement"; + +"Could not capture image" = "Impossible de prendre une photo"; + +"Date of Birth" = "Date de naissance"; + +"Date of birth does not look valid" = "La date de naissance ne semble pas valide"; + +"Details not visible" = "Détails non visibles"; + +"Flip your identity card over to the other side" = "Retournez votre carte d'identité"; + +"Front identity card photo successfully uploaded" = "Photo du recto de la carte d'identité chargée"; + +"Front of identity card" = "Recto de la carte d'identité"; + +"Front of identity document" = "Recto de la pièce d'identité"; + +"Get ready to scan your photo ID" = "Préparez-vous à scanner votre pièce d'identité"; + +"Get ready to take a selfie" = "Préparez-vous à vous prendre en photo"; + +"Go Back" = "Retour"; + +"Hold still, scanning" = "Maintenez le document en place, la numérisation est en cours"; + +"I'm ready" = "Tout est prêt"; + +"ID Number" = "Numéro d'identification"; + +"Individual CPF" = "Numéro CPF"; + +"Keep ID level" = "Maintenez votre pièce d'identité horizontale"; + +"Last 4 of Social Security number" = "4 derniers chiffres du numéro de sécurité sociale"; + +"Loading" = "Chargement en cours"; + +"Make sure all details are visible and focus" = "Assurez-vous que tous les détails sont nets et visibles"; + +"Make sure you're in a well lit space." = "Installez-vous dans un endroit bien éclairé."; + +"Move closer" = "Rapprochez le document"; + +"Move farther" = "Éloignez le document"; + +"Move to a darker area" = "Prenez votre photo dans un endroit plus sombre"; + +"Move to a well-lit area" = "Prenez votre photo dans un endroit bien éclairé"; + +"NRIC or FIN" = "Numéro NRIC ou FIN"; + +"No document detected" = "Aucun document détecté"; + +"Personal ID number" = "Numéro d'identification personnel"; + +"Personal Information" = "Informations personnelles"; + +"Phone Number" = "Numéro de téléphone"; + +"Phone Verification" = "Vérification du numéro de téléphone"; + +"Photo Library" = "Bibliothèque de photos"; + +"Please upload images of the front and back of your identity card" = "Veuillez charger une photo du recto et du verso de votre carte d'identité"; + +"Position your face in the center of the frame." = "Placez votre visage au centre du cadre."; + +"Position your identity card in the center of the frame" = "Placez votre carte d'identité au centre du cadre"; + +"Retake Photos" = "Reprendre des photos"; + +"Scan" = "Numériser"; + +"Scanned" = "Numérisé"; + +"Select" = "Sélectionner"; + +"Select a location to upload the back of your identity document from" = "Sélectionnez l'emplacement à partir duquel vous souhaitez charger le verso de votre pièce d'identité"; + +"Select a location to upload the front of your identity document from" = "Sélectionnez l'emplacement à partir duquel vous souhaitez charger le recto de votre pièce d'identité"; + +"Select back identity card photo" = "Sélectionner la photo du verso de la carte d'identité"; + +"Select front identity card photo" = "Sélectionner la photo du recto de la carte d'identité"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Captures de vos selfies"; + +"Selfie captures are complete" = "Les captures de vos selfies sont terminées"; + +"Take Photo" = "Prendre une photo"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Les images de votre pièce d'identité n'ont pas été enregistrées. Voulez-vous quitter ?"; + +"There was an error accessing the camera." = "Une erreur est survenue lors de l'accès à l'appareil photo."; + +"Try Again" = "Réessayer"; + +"Try reduce glare and make ID visible" = "Essayez de réduire la luminosité et assurez-vous que la pièce d'identité est visible"; + +"Unable to establish a connection." = "Connexion impossible."; + +"Unsaved changes" = "Modifications non enregistrées"; + +"Upload" = "Charger"; + +"Upload a Photo" = "Charger une photo"; + +"Upload your photo ID" = "Charger votre document d'identité avec photo"; + +"Uploading back identity card photo" = "Chargement de la photo du verso de la carte d'identité"; + +"Uploading front identity card photo" = "Chargement de la photo du recto de la carte d'identité"; + +"Verify your identity" = "Vérifier votre identité"; + +"We could not capture a high-quality image." = "Impossible de prendre une photo de bonne qualité."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Pour que nous puissions accéder à votre appareil photo, vous devez nous donner votre autorisation dans les paramètres de l'application."; + +"Welcome" = "Bienvenue"; + +"You can either try again or upload an image from your device." = "Vous pouvez réessayer ou charger une image à partir de votre appareil."; + +"Your selfie images have not been saved. Do you want to leave?" = "Vos selfies n'ont pas été enregistrés. Voulez-vous quitter ?"; + +"driver's license" = "permis de conduire"; + +"government-issued photo ID" = "pièce d'identité officielle avec photo"; + +"passport" = "passeport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/hr.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/hr.lproj/Localizable.strings new file mode 100644 index 00000000..e9265aaa --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/hr.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "U sljedećem koraku automatski će se snimiti nekoliko fotografija kako bi se potvrdilo da ste to vi"; + +"Accepted forms of ID include" = "Prihvaćeni oblici identifikacijskih dokumenata uključuju"; + +"Alternatively, you may manually upload a photo of your identity document." = "Umjesto toga možete ručno prenijeti fotografiju svog osobnog dokumenta."; + +"App Settings" = "Postavke aplikacije"; + +"Back identity card photo successfully uploaded" = "Fotografija stražnje strane osobne iskaznice je uspješno učitana"; + +"Back of identity card" = "Stražnja strana osobne iskaznice"; + +"Back of identity document" = "Stražnja strana identifikacijskog dokumenta"; + +"Camera permission" = "Dozvole kamere"; + +"Camera unavailable" = "Kamera nije dostupna"; + +"Capturing…" = "Snimanje…"; + +"Choose File" = "Odaberi datoteku"; + +"Consent" = "Privola"; + +"Could not capture image" = "Nismo uspjeli snimiti fotografiju"; + +"Date of Birth" = "Datum rođenja"; + +"Date of birth does not look valid" = "Datum rođenja ne izgleda ispravno"; + +"Details not visible" = "Pojedinosti nisu vidljive"; + +"Flip your identity card over to the other side" = "Okrenite svoju osobnu iskaznicu na drugu stranu"; + +"Front identity card photo successfully uploaded" = "Fotografija prednje strane osobne iskaznice uspješno je učitana"; + +"Front of identity card" = "Prednja strana osobne iskaznice"; + +"Front of identity document" = "Prednja strana identifikacijskog dokumenta"; + +"Get ready to scan your photo ID" = "Pripremite se za skeniranje svog osobnog dokumenta s fotografijom"; + +"Get ready to take a selfie" = "Pripremite se za snimanje selfija"; + +"Go Back" = "Vrati se natrag"; + +"Hold still, scanning" = "Ne pomičite se, skeniranje u tijeku"; + +"I'm ready" = "Spreman sam"; + +"ID Number" = "ID broj"; + +"Individual CPF" = "CPF porezni broj"; + +"Keep ID level" = "Držite osobni dokument ravno"; + +"Last 4 of Social Security number" = "4 zadnja broja socijalnog osiguranja"; + +"Loading" = "Učitavanje"; + +"Make sure all details are visible and focus" = "Provjerite jesu li svi podaci vidljivi i fokusirajte dokument"; + +"Make sure you're in a well lit space." = "Provjerite jeste li u dobro osvijetljenom prostoru."; + +"Move closer" = "Pomaknite bliže"; + +"Move farther" = "Pomaknite dalje"; + +"Move to a darker area" = "Pomaknite se u manje osvijetljeni prostor"; + +"Move to a well-lit area" = "Pomaknite se u dobro osvijetljeni prostor"; + +"NRIC or FIN" = "NRIC ili FIN"; + +"No document detected" = "Nije otkriven dokument"; + +"Personal ID number" = "Osobni identifikacijski broj"; + +"Personal Information" = "Osobni podaci"; + +"Phone Number" = "Telefonski broj"; + +"Phone Verification" = "Provjera uz pomoć telefona"; + +"Photo Library" = "Biblioteka fotografija"; + +"Please upload images of the front and back of your identity card" = "Molimo vas učitajte slike prednje i stražnje strane vaše osobne iskaznice"; + +"Position your face in the center of the frame." = "Postavite svoje lice u središte okvira."; + +"Position your identity card in the center of the frame" = "Postavite svoju osobnu iskaznicu u sredinu okvira"; + +"Retake Photos" = "Ponovo snimi fotografije"; + +"Scan" = "Skenirani dokument"; + +"Scanned" = "Skenirano"; + +"Select" = "Odaberi"; + +"Select a location to upload the back of your identity document from" = "Odaberite lokaciju s koje ćete učitati stražnju stranu svog dokumenta"; + +"Select a location to upload the front of your identity document from" = "Odaberite lokaciju s koje ćete učitati prednju stranu svog dokumenta"; + +"Select back identity card photo" = "Odaberite fotografiju stražnje strane osobne iskaznice"; + +"Select front identity card photo" = "Odaberite fotografiju prednje strane osobne iskaznice"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Snimanje selfie slika"; + +"Selfie captures are complete" = "Snimanje selfi slika je završeno"; + +"Take Photo" = "Snimi fotografiju"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Slike vašeg osobnog dokumenta nisu spremljene. Želite li otići?"; + +"There was an error accessing the camera." = "Došlo je do pogreške pri pristupanju kameri."; + +"Try Again" = "Pokušaj ponovno"; + +"Try reduce glare and make ID visible" = "Pokušajte smanjiti odsjaj i pobrinite se da je osobni dokument vidljiv."; + +"Unable to establish a connection." = "Nije moguće uspostaviti vezu."; + +"Unsaved changes" = "Nespremljene promjene"; + +"Upload" = "Učitaj"; + +"Upload a Photo" = "Učitaj fotografiju"; + +"Upload your photo ID" = "Učitajte svoj identifikacijski dokument s fotografijom"; + +"Uploading back identity card photo" = "Učitavanje fotografije stražnje strane osobne iskaznice"; + +"Uploading front identity card photo" = "Učitavanje fotografije prednje strane osobne iskaznice"; + +"Verify your identity" = "Potvrdite svoj identitet"; + +"We could not capture a high-quality image." = "Nismo uspjeli snimiti fotografiju visoke kvalitete."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Potrebna nam je dozvola za uporabu kamere. Molimo dozvolite pristup kameri u postavkama aplikacije."; + +"Welcome" = "Dobrodošli"; + +"You can either try again or upload an image from your device." = "Možete pokušati ponovno ili prenijeti sliku sa svog uređaja."; + +"Your selfie images have not been saved. Do you want to leave?" = "Vaše selfie slike nisu pohranjene. Želite li napustiti?"; + +"driver's license" = "vozačka dozvola"; + +"government-issued photo ID" = "Državni identifikacijski dokument s fotografijom"; + +"passport" = "putovnica"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/hu.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/hu.lproj/Localizable.strings new file mode 100644 index 00000000..78822046 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/hu.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Automatikusan elkészítünk néhány fényképet a következő lépés során, hogy ellenőrizhessük a személyazonosságát"; + +"Accepted forms of ID include" = "Elfogadott igazolványok, többek között"; + +"Alternatively, you may manually upload a photo of your identity document." = "Emellett lehetősége van arra is, hogy fotót töltsön fel az azonosító okmányáról."; + +"App Settings" = "Alkalmazásbeállítások"; + +"Back identity card photo successfully uploaded" = "Személyazonosító igazolvány hátlapfotója sikeresen feltöltve"; + +"Back of identity card" = "Személyazonosító igazolvány hátlapja"; + +"Back of identity document" = "Személyazonosító okmány hátulja"; + +"Camera permission" = "Kameraengedély"; + +"Camera unavailable" = "A kamera nem érhető el"; + +"Capturing…" = "Felvétel..."; + +"Choose File" = "Fájl kiválasztása"; + +"Consent" = "Hozzájárulás"; + +"Could not capture image" = "Nem sikerült felvételt készíteni"; + +"Date of Birth" = "Születési idő"; + +"Date of birth does not look valid" = "A születési idő megadása kötelező"; + +"Details not visible" = "A részletek nem láthatók"; + +"Flip your identity card over to the other side" = "Fordítsa személyazonosító igazolványát a másik oldalára"; + +"Front identity card photo successfully uploaded" = "Személyazonosító igazolvány előlapfotója sikeresen feltöltve"; + +"Front of identity card" = "Személyazonosító igazolvány előlapja"; + +"Front of identity document" = "Személyazonosító okmány eleje"; + +"Get ready to scan your photo ID" = "Készítse elő fényképes igazolványát a beolvasásra"; + +"Get ready to take a selfie" = "Készüljön fel egy szelfi elkészítésére"; + +"Go Back" = "Visszalépés"; + +"Hold still, scanning" = "Ne mozgassa, beolvasás folyamatban"; + +"I'm ready" = "Készen állok"; + +"ID Number" = "Azonosítószám"; + +"Individual CPF" = "Személyes CPF"; + +"Keep ID level" = "Tartsa egyenesen az igazolványt"; + +"Last 4 of Social Security number" = "A társadalombiztosítási szám utolsó 4 számjegye"; + +"Loading" = "Betöltés folyamatban"; + +"Make sure all details are visible and focus" = "Győződjön meg arról, hogy minden részlet látható és fókuszban van."; + +"Make sure you're in a well lit space." = "Válasszon jól megvilágított helyet."; + +"Move closer" = "Helyezze közelebb"; + +"Move farther" = "Helyezze távolabb"; + +"Move to a darker area" = "Menjen sötétebb helyre"; + +"Move to a well-lit area" = "Menjen egy jól megvilágított helyre"; + +"NRIC or FIN" = "NRIC vagy FIN"; + +"No document detected" = "Nem észlelhető a dokumentum"; + +"Personal ID number" = "Személyazonosító szám"; + +"Personal Information" = "Személyes adatok"; + +"Phone Number" = "Telefonszám"; + +"Phone Verification" = "Telefonos ellenőrzés"; + +"Photo Library" = "Képek"; + +"Please upload images of the front and back of your identity card" = "Töltse fel a személyazonosító igazolványának előlapját és hátlapját"; + +"Position your face in the center of the frame." = "Igazítsa az arcát a kép közepére."; + +"Position your identity card in the center of the frame" = "Igazítsa személyazonosító igazolványát a kép közepére"; + +"Retake Photos" = "Fényképek megismétlése"; + +"Scan" = "Beolvasás"; + +"Scanned" = "Beolvasva"; + +"Select" = "Kiválasztás"; + +"Select a location to upload the back of your identity document from" = "Válassza ki, hogy honnan szeretné feltölteni a személyazonosító okmányának hátlapját"; + +"Select a location to upload the front of your identity document from" = "Válassza ki, hogy honnan szeretné feltölteni a személyazonosító okmányának előlapját"; + +"Select back identity card photo" = "Személyazonosító igazolvány hátlapfotójának kiválasztása"; + +"Select front identity card photo" = "Személyazonosító igazolvány előlapfotójának kiválasztása"; + +"Selfie" = "Szelfi"; + +"Selfie captures" = "Szelfi készítése"; + +"Selfie captures are complete" = "Szelfi felvétel kész"; + +"Take Photo" = "Fénykép készítése"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Nem mentette azonosító okmányának képeit. Biztosan kilép?"; + +"There was an error accessing the camera." = "Hiba történt a kamerához való hozzáférés során"; + +"Try Again" = "Újrapróbálkozás"; + +"Try reduce glare and make ID visible" = "Próbálja csökkenteni a csillogást, hogy jól látható legyen az igazolvány"; + +"Unable to establish a connection." = "Nem létesíthető kapcsolat."; + +"Unsaved changes" = "Mentetlen módosítások"; + +"Upload" = "Feltöltés"; + +"Upload a Photo" = "Fotó feltöltése"; + +"Upload your photo ID" = "Fényképes azonosító igazolvány feltöltése"; + +"Uploading back identity card photo" = "Személyazonosító igazolvány hátlapfotójának feltöltése"; + +"Uploading front identity card photo" = "Személyazonosító igazolvány előlapfotójának feltöltése"; + +"Verify your identity" = "A személyazonosság igazolása"; + +"We could not capture a high-quality image." = "Nem tudtunk jó minőségű képet készíteni."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Engedélyre van szükségünk, hogy hozzáférjünk a kamerához. Engedélyezze a kamerához való hozzáférést az alkalmazásbeállításokban."; + +"Welcome" = "Üdvözöljük!"; + +"You can either try again or upload an image from your device." = "Próbálkozhat újra vagy feltölthet egy képet eszközéről is."; + +"Your selfie images have not been saved. Do you want to leave?" = "Nincs mentve a szelfi, biztos kilép?"; + +"driver's license" = "vezetői engedély"; + +"government-issued photo ID" = "Állam által kiadott fényképes igazolvány"; + +"passport" = "útlevél"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/id.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/id.lproj/Localizable.strings new file mode 100644 index 00000000..2dd455a3 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/id.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Beberapa foto akan diambil secara otomatis pada langkah berikutnya untuk memverifikasi diri Anda"; + +"Accepted forms of ID include" = "Bentuk identifikasi yang diterima antara lain"; + +"Alternatively, you may manually upload a photo of your identity document." = "Atau, Anda dapat mengunggah foto dokumen identitas secara manual"; + +"App Settings" = "Pengaturan Aplikasi"; + +"Back identity card photo successfully uploaded" = "Foto bagian belakang kartu identitas mengemudi berhasil diunggah"; + +"Back of identity card" = "Bagian belakang kartu identitas"; + +"Back of identity document" = "Bagian belakang dokumen identitas"; + +"Camera permission" = "Izin kamera"; + +"Camera unavailable" = "Kamera tidak tersedia"; + +"Capturing…" = "Mengambil gambar..."; + +"Choose File" = "Pilih File"; + +"Consent" = "Persetujuan"; + +"Could not capture image" = "Tidak dapat mengambil gambar"; + +"Date of Birth" = "Tanggal Lahir"; + +"Date of birth does not look valid" = "Tanggal lahir sepertinya tidak valid"; + +"Details not visible" = "Detail tidak terlihat"; + +"Flip your identity card over to the other side" = "Balikkan kartu identitas Anda ke sisi yang lain"; + +"Front identity card photo successfully uploaded" = "Foto bagian depan kartu identitas berhasil diunggah"; + +"Front of identity card" = "Bagian depan kartu identitas"; + +"Front of identity document" = "Bagian depan dokumen identitas"; + +"Get ready to scan your photo ID" = "Siapkan pemindaian identitas berfoto Anda"; + +"Get ready to take a selfie" = "Bersiaplah untuk mengambil swafoto"; + +"Go Back" = "Kembali"; + +"Hold still, scanning" = "Jangan bergerak, memindai"; + +"I'm ready" = "Saya siap"; + +"ID Number" = "Nomor Identifikasi"; + +"Individual CPF" = "CPF perorangan"; + +"Keep ID level" = "Sejajarkan identitas"; + +"Last 4 of Social Security number" = "4 angka terakhir Nomor Jaminan Sosial"; + +"Loading" = "Memuat"; + +"Make sure all details are visible and focus" = "Pastikan seluruh detail dapat dilihat dan dalam fokus."; + +"Make sure you're in a well lit space." = "Pastikan Anda berada di ruang yang cukup terang."; + +"Move closer" = "Mendekat"; + +"Move farther" = "Jauhkan"; + +"Move to a darker area" = "Pindah ke tempat yang lebih gelap"; + +"Move to a well-lit area" = "Pindahkan ke tempat yang cukup terang"; + +"NRIC or FIN" = "NRIC atau FIN"; + +"No document detected" = "Tidak ada dokumen yang terdeteksi"; + +"Personal ID number" = "Nomor identifikasi pribadi"; + +"Personal Information" = "Informasi Pribadi"; + +"Phone Number" = "Nomor Telepon"; + +"Phone Verification" = "Verifikasi Telepon"; + +"Photo Library" = "Pustaka Foto"; + +"Please upload images of the front and back of your identity card" = "Unggah gambar bagian depan dan belakang kartu identitas Anda"; + +"Position your face in the center of the frame." = "Posisikan wajah Anda di tengah bingkai."; + +"Position your identity card in the center of the frame" = "Posisikan kartu identitas Anda di tengah bingkai"; + +"Retake Photos" = "Ambil Ulang Foto"; + +"Scan" = "Pindai"; + +"Scanned" = "Dipindai"; + +"Select" = "Pilih"; + +"Select a location to upload the back of your identity document from" = "Pilih lokasi untuk mengunggah bagian belakang dokumen identitas Anda"; + +"Select a location to upload the front of your identity document from" = "Pilih lokasi untuk mengunggah bagian depan dokumen identitas Anda"; + +"Select back identity card photo" = "Pilih foto bagian belakang kartu identitas"; + +"Select front identity card photo" = "Pilih foto bagian depan kartu identitas"; + +"Selfie" = "Swafoto"; + +"Selfie captures" = "Pengambilan gambar swafoto"; + +"Selfie captures are complete" = "Pengambilan gambar swafoto selesai"; + +"Take Photo" = "Ambil Foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Gambar dokumen identitas Anda belum disimpan. Apakah Anda ingin keluar?"; + +"There was an error accessing the camera." = "Ada kesalahan saat mengakses kamera."; + +"Try Again" = "Coba Lagi"; + +"Try reduce glare and make ID visible" = "Coba kurangi silau cahaya dan pastikan identitas terlihat"; + +"Unable to establish a connection." = "Tidak dapat membuat koneksi."; + +"Unsaved changes" = "Perubahan tak tersimpan"; + +"Upload" = "Unggah"; + +"Upload a Photo" = "Unggah Foto"; + +"Upload your photo ID" = "Unggah ID berfoto Anda"; + +"Uploading back identity card photo" = "Mengunggah foto bagian belakang kartu identitas"; + +"Uploading front identity card photo" = "Mengunggah foto bagian depan kartu identitas"; + +"Verify your identity" = "Verifikasikan identitas Anda"; + +"We could not capture a high-quality image." = "Kami tidak dapat mengambil gambar berkualitas tinggi."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Kami memerlukan izin Anda untuk menggunakan kamera. Harap izinkan akses kamera di pengaturan aplikasi."; + +"Welcome" = "Selamat datang"; + +"You can either try again or upload an image from your device." = "Anda dapat mencoba lagi atau mengunggah gambar dari perangkat."; + +"Your selfie images have not been saved. Do you want to leave?" = "Gambar swafoto Anda belum disimpan. Apakah Anda ingin keluar?"; + +"driver's license" = "izin mengemudi"; + +"government-issued photo ID" = "identitas berfoto yang diterbitkan pemerintah"; + +"passport" = "paspor"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/it.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/it.lproj/Localizable.strings new file mode 100644 index 00000000..f1d512f5 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/it.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Al prossimo passaggio verranno scattate automaticamente alcune foto per verificare la tua identità"; + +"Accepted forms of ID include" = "Forme di identificazione accettate"; + +"Alternatively, you may manually upload a photo of your identity document." = "In alternativa, puoi caricare manualmente una foto del tuo documento di identità."; + +"App Settings" = "Impostazioni app"; + +"Back identity card photo successfully uploaded" = "Foto del retro della carta di identità caricata"; + +"Back of identity card" = "Retro del documento di identità"; + +"Back of identity document" = "Retro documento di identità"; + +"Camera permission" = "Autorizzazione fotocamera"; + +"Camera unavailable" = "Fotocamera non disponibile"; + +"Capturing…" = "Acquisizione in corso..."; + +"Choose File" = "Scegli file"; + +"Consent" = "Consenti"; + +"Could not capture image" = "Non è stato possibile acquisire l'immagine"; + +"Date of Birth" = "Data di nascita"; + +"Date of birth does not look valid" = "La data di nascita non è valida"; + +"Details not visible" = "Dettagli non visibili"; + +"Flip your identity card over to the other side" = "Gira sull'altro lato la carta di identità"; + +"Front identity card photo successfully uploaded" = "Foto del fronte della carta di identità caricata"; + +"Front of identity card" = "Fronte della carta di identità"; + +"Front of identity document" = "Fronte documento di identità"; + +"Get ready to scan your photo ID" = "Preparati a scansionare il tuo documento di identità con foto"; + +"Get ready to take a selfie" = "Preparati a scattare un selfie"; + +"Go Back" = "Indietro"; + +"Hold still, scanning" = "Tienilo fermo. Scansione in corso"; + +"I'm ready" = "Tutto pronto"; + +"ID Number" = "Numero documento di identità"; + +"Individual CPF" = "CPF personale"; + +"Keep ID level" = "Allinea il documento di identità all'angolo destro"; + +"Last 4 of Social Security number" = "Ultime quattro cifre del Social Security Number (SSN)"; + +"Loading" = "Caricamento in corso"; + +"Make sure all details are visible and focus" = "Assicurati che tutti i dettagli siano visibili e a fuoco"; + +"Make sure you're in a well lit space." = "Assicurati che l'illuminazione sia adeguata."; + +"Move closer" = "Avvicinalo"; + +"Move farther" = "Allontanalo"; + +"Move to a darker area" = "Sposta il documento in un punto meno illuminato."; + +"Move to a well-lit area" = "Sposta il documento in modo che l'illuminazione sia adeguata"; + +"NRIC or FIN" = "NRIC o FIN"; + +"No document detected" = "Nessun documento rilevato"; + +"Personal ID number" = "Numero documento di identità personale"; + +"Personal Information" = "Informazioni personali"; + +"Phone Number" = "Numero di telefono"; + +"Phone Verification" = "Verifica del numero di telefono"; + +"Photo Library" = "Galleria foto"; + +"Please upload images of the front and back of your identity card" = "Carica un'immagine fronte-retro del tuo documento di identità"; + +"Position your face in the center of the frame." = "Posiziona il volto al centro della cornice."; + +"Position your identity card in the center of the frame" = "Posiziona la carta di identità al centro della cornice"; + +"Retake Photos" = "Scatta di nuovo le foto"; + +"Scan" = "Scansiona"; + +"Scanned" = "Scansionato"; + +"Select" = "Seleziona"; + +"Select a location to upload the back of your identity document from" = "Seleziona una posizione da cui caricare il retro del tuo documento di identità"; + +"Select a location to upload the front of your identity document from" = "Seleziona una posizione da cui caricare il fronte del tuo documento di identità"; + +"Select back identity card photo" = "Seleziona la foto del retro della carta di identità"; + +"Select front identity card photo" = "Seleziona la foto del fronte della carta di identità"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Acquisizioni di selfie"; + +"Selfie captures are complete" = "L'acquisizione dei selfie è completa"; + +"Take Photo" = "Scatta foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Le immagini del tuo documento di identità non sono state salvate. Vuoi uscire?"; + +"There was an error accessing the camera." = "Si è verificato un errore durante l'accesso alla fotocamera."; + +"Try Again" = "Riprova"; + +"Try reduce glare and make ID visible" = "Cerca di ridurre i riflessi e assicurati che il documento di identità sia visibile."; + +"Unable to establish a connection." = "Impossibile stabilire una connessione."; + +"Unsaved changes" = "Modifiche non salvate"; + +"Upload" = "Carica"; + +"Upload a Photo" = "Carica una foto"; + +"Upload your photo ID" = "Carica il tuo documento d'identità con foto"; + +"Uploading back identity card photo" = "Caricamento della foto del retro della carta di identità"; + +"Uploading front identity card photo" = "Caricamento della foto del fronte della carta di identità"; + +"Verify your identity" = "Verifica la tua identità"; + +"We could not capture a high-quality image." = "Non è stato possibile acquisire un'immagine di alta qualità."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Devi autorizzare l'utilizzo della tua fotocamera. Concedi l'accesso alla fotocamera nelle impostazioni dell'app."; + +"Welcome" = "Ti diamo il benvenuto"; + +"You can either try again or upload an image from your device." = "Puoi riprovare o caricare un'immagine dal tuo dispositivo."; + +"Your selfie images have not been saved. Do you want to leave?" = "I tuoi selfie non sono stati salvati. Vuoi uscire?"; + +"driver's license" = "patente di guida"; + +"government-issued photo ID" = "documento identificativo ufficiale con foto"; + +"passport" = "passaporto"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/ja.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/ja.lproj/Localizable.strings new file mode 100644 index 00000000..9af339fa --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/ja.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "次のステップで写真が数枚撮影され、本人確認が行われます。"; + +"Accepted forms of ID include" = "身分証明書として受け付けられる書式は次のとおりです"; + +"Alternatively, you may manually upload a photo of your identity document." = "または、身分証明書の写真を手動でアップロードすることもできます。"; + +"App Settings" = "アプリの設定"; + +"Back identity card photo successfully uploaded" = "身分証明書の裏面の写真のアップロードが完了しました"; + +"Back of identity card" = "身分証明書の裏面"; + +"Back of identity document" = "本人確認書類の裏面"; + +"Camera permission" = "カメラの使用許可"; + +"Camera unavailable" = "カメラを利用できません"; + +"Capturing…" = "キャプチャーしています..."; + +"Choose File" = "ファイルを選択"; + +"Consent" = "同意する"; + +"Could not capture image" = "画像をキャプチャーできませんでした"; + +"Date of Birth" = "生年月日"; + +"Date of birth does not look valid" = "生年月日が無効のようです"; + +"Details not visible" = "詳細が見えません"; + +"Flip your identity card over to the other side" = "身分証明書を裏返してください"; + +"Front identity card photo successfully uploaded" = "身分証明書の表面の写真のアップロードが完了しました"; + +"Front of identity card" = "身分証明書の表面"; + +"Front of identity document" = "本人確認書類の表面"; + +"Get ready to scan your photo ID" = "写真付き身分証明書の直接撮影を準備する"; + +"Get ready to take a selfie" = "顔写真の撮影準備をする"; + +"Go Back" = "戻る"; + +"Hold still, scanning" = "動かさないでください、スキャンしています"; + +"I'm ready" = "準備完了"; + +"ID Number" = "ID 番号"; + +"Individual CPF" = "個人 CPF"; + +"Keep ID level" = "身分証明書を水平に保持してください"; + +"Last 4 of Social Security number" = "社会保障番号の末尾 4 桁"; + +"Loading" = "読み込み中"; + +"Make sure all details are visible and focus" = "すべての詳細が表示され、焦点が合っていることを確認してください"; + +"Make sure you're in a well lit space." = "明るい場所にいることを確認してください。"; + +"Move closer" = "近付けてください"; + +"Move farther" = "少し遠ざけてください"; + +"Move to a darker area" = "もっと暗い場所に移動してください"; + +"Move to a well-lit area" = "明るい場所に移動してください"; + +"NRIC or FIN" = "NRIC または FIN"; + +"No document detected" = "書類を検出できませんでした"; + +"Personal ID number" = "身分証明書番号"; + +"Personal Information" = "個人情報"; + +"Phone Number" = "電話番号"; + +"Phone Verification" = "電話番号の確認"; + +"Photo Library" = "フォトライブラリー"; + +"Please upload images of the front and back of your identity card" = "身分証明書の表裏両面の画像をアップロードしてください"; + +"Position your face in the center of the frame." = "フレームの中央に顔を置いてください。"; + +"Position your identity card in the center of the frame" = "身分証明書をフレームの中央に置いてください"; + +"Retake Photos" = "写真を撮り直す"; + +"Scan" = "スキャン"; + +"Scanned" = "スキャン終了"; + +"Select" = "選択"; + +"Select a location to upload the back of your identity document from" = "身分証明書の裏面をどこからアップロードするのかを選択してください"; + +"Select a location to upload the front of your identity document from" = "身分証明書の表面をどこからアップロードするのかを選択してください"; + +"Select back identity card photo" = "身分証明書の裏面の写真を選択"; + +"Select front identity card photo" = "身分証明書の表面の写真を選択"; + +"Selfie" = "顔写真"; + +"Selfie captures" = "顔写真の記録"; + +"Selfie captures are complete" = "顔写真の記録が完了しました"; + +"Take Photo" = "写真を撮る"; + +"The images of your identity document have not been saved. Do you want to leave?" = "身分証明書の画像が保存されていません。終了しますか?"; + +"There was an error accessing the camera." = "カメラへのアクセス中にエラーが発生しました。"; + +"Try Again" = "やり直す"; + +"Try reduce glare and make ID visible" = "光の反射を抑えて、身分証明書が見えるようにしてください"; + +"Unable to establish a connection." = "接続を確立できません"; + +"Unsaved changes" = "保存されていない変更"; + +"Upload" = "アップロード"; + +"Upload a Photo" = "写真をアップロード"; + +"Upload your photo ID" = "写真付き身分証明書をアップロードする"; + +"Uploading back identity card photo" = "身分証明書の裏面をアップロードしています"; + +"Uploading front identity card photo" = "身分証明書の表面をアップロードしています"; + +"Verify your identity" = "本人確認を行う"; + +"We could not capture a high-quality image." = "高画質の画像をキャプチャーできませんでした。"; + +"We need permission to use your camera. Please allow camera access in app settings." = "カメラへのアクセス許可が必要です。アプリの設定でカメラへのアクセスを許可してください。"; + +"Welcome" = "初期画面"; + +"You can either try again or upload an image from your device." = "もう一度お試しいただくか、デバイスから画像をアプロードしてください。"; + +"Your selfie images have not been saved. Do you want to leave?" = "顔写真の画像が保存されていません。終了しますか?"; + +"driver's license" = "運転免許証"; + +"government-issued photo ID" = "政府発行の写真付き身分証明書"; + +"passport" = "パスポート"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/ko.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/ko.lproj/Localizable.strings new file mode 100644 index 00000000..7f199c39 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/ko.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "다음 단계에서 본인임을 확인하기 위해 몇 장의 사진이 자동으로 촬영됩니다."; + +"Accepted forms of ID include" = "허용되는 신분증 형식은 다음과 같음"; + +"Alternatively, you may manually upload a photo of your identity document." = "또는 신분증 사진을 직접 업로드하실 수도 있습니다."; + +"App Settings" = "앱 설정"; + +"Back identity card photo successfully uploaded" = "신분증 뒷면 사진 업로드 성공"; + +"Back of identity card" = "신분증 뒷면"; + +"Back of identity document" = "신분증 뒷면"; + +"Camera permission" = "카메라 권한"; + +"Camera unavailable" = "카메라 사용 불가"; + +"Capturing…" = "캡처 중…"; + +"Choose File" = "파일 선택"; + +"Consent" = "동의"; + +"Could not capture image" = "이미지를 캡처할 수 없음"; + +"Date of Birth" = "생년월일"; + +"Date of birth does not look valid" = "생년월일이 유효하지 않습니다."; + +"Details not visible" = "세부사항이 보이지 않음"; + +"Flip your identity card over to the other side" = "신분증 반대쪽 면이 보이게 뒤집으십시오."; + +"Front identity card photo successfully uploaded" = "신분증 앞면 사진 업로드 성공"; + +"Front of identity card" = "신분증 앞면"; + +"Front of identity document" = "신분증 앞면"; + +"Get ready to scan your photo ID" = "사진이 있는 신분증 스캔 준비"; + +"Get ready to take a selfie" = "셀카 촬영 준비"; + +"Go Back" = "돌아가기"; + +"Hold still, scanning" = "움직이지 마십시오. 스캔하는 중입니다."; + +"I'm ready" = "준비됨"; + +"ID Number" = "ID 번호"; + +"Individual CPF" = "개별 CPF"; + +"Keep ID level" = "신분증 수준 유지"; + +"Last 4 of Social Security number" = "사회 보장 번호의 마지막 4자리 숫자"; + +"Loading" = "로드 중"; + +"Make sure all details are visible and focus" = "모든 세부사항이 보이고 초점이 맞춰져 있는지 확인하십시오"; + +"Make sure you're in a well lit space." = "공간의 조명이 밝은지 확인하십시오."; + +"Move closer" = "더 가까이"; + +"Move farther" = "더 멀리 이동하십시오"; + +"Move to a darker area" = "조명이 어두운 곳으로 이동해 주십시오"; + +"Move to a well-lit area" = "조명이 밝은 곳으로 이동"; + +"NRIC or FIN" = "NRIC 또는 FIN"; + +"No document detected" = "신분증을 감지할 수 없음"; + +"Personal ID number" = "개인 ID 번호"; + +"Personal Information" = "개인 정보"; + +"Phone Number" = "전화번호"; + +"Phone Verification" = "전화 확인"; + +"Photo Library" = "사진 라이브러리"; + +"Please upload images of the front and back of your identity card" = "신분증의 앞면과 뒷면 이미지를 업로드하십시오."; + +"Position your face in the center of the frame." = "프레임 중앙에 얼굴을 놓으십시오."; + +"Position your identity card in the center of the frame" = "프레임 중앙에 신분증을 놓으십시오."; + +"Retake Photos" = "사진 다시 찍기"; + +"Scan" = "스캔"; + +"Scanned" = "스캔 완료"; + +"Select" = "선택"; + +"Select a location to upload the back of your identity document from" = "신분 증명서 뒷면을 업로드할 위치 선택 -"; + +"Select a location to upload the front of your identity document from" = "신분 증명서 앞면을 업로드할 위치 선택 -"; + +"Select back identity card photo" = "신분증 뒷면 사진 선택"; + +"Select front identity card photo" = "신분증 앞면 사진 선택"; + +"Selfie" = "셀카"; + +"Selfie captures" = "캡처된 셀카"; + +"Selfie captures are complete" = "셀카 캡처 완료"; + +"Take Photo" = "사진 촬영"; + +"The images of your identity document have not been saved. Do you want to leave?" = "신분 증명서 이미지가 저장되지 않았습니다. 종료하시겠습니까?"; + +"There was an error accessing the camera." = "카메라에 액세스하는 도중 오류가 발생했습니다."; + +"Try Again" = "다시 시도"; + +"Try reduce glare and make ID visible" = "반사광을 줄이고 신분증이 보이도록 해 주십시오"; + +"Unable to establish a connection." = "연결을 설정할 수 없습니다."; + +"Unsaved changes" = "저장되지 않은 변경 사항"; + +"Upload" = "업로드"; + +"Upload a Photo" = "사진 업로드"; + +"Upload your photo ID" = "사진 ID 업로드"; + +"Uploading back identity card photo" = "신분증 뒷면 사진 업로드하는 중"; + +"Uploading front identity card photo" = "신분증 앞면 사진 업로드하는 중"; + +"Verify your identity" = "ID 확인"; + +"We could not capture a high-quality image." = "고품질 이미지를 캡처할 수 없습니다."; + +"We need permission to use your camera. Please allow camera access in app settings." = "카메라 사용 권한이 필요합니다. 앱 설정에서 카메라 액세스를 허용해 주십시오."; + +"Welcome" = "환영합니다."; + +"You can either try again or upload an image from your device." = "다시 시도하거나 디바이스에 있는 이미지를 업로드하실 수 있습니다."; + +"Your selfie images have not been saved. Do you want to leave?" = "셀카 이미지가 저장되지 않았습니다. 종료하시겠습니까?"; + +"driver's license" = "운전 면허증"; + +"government-issued photo ID" = "정부에서 발급한 사진이 있는 신분증"; + +"passport" = "여권"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/lt-LT.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..88e5c678 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/lt-LT.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Sekančiame žingsnyje, siekiant patvirtinti jūsų tapatybę, bus padarytos kelios jūsų nuotraukos"; + +"Accepted forms of ID include" = "Priimtos asmens tapatybės dokumento formos apima"; + +"Alternatively, you may manually upload a photo of your identity document." = "Arba galite patys įkelti tapatybės dokumento nuotrauką."; + +"App Settings" = "Programos nustatymai"; + +"Back identity card photo successfully uploaded" = "Sėkmingai įkelta asmens tapatybės kortelės galinės pusės nuotrauka"; + +"Back of identity card" = "Asmens tapatybės kortelės galinė pusė"; + +"Back of identity document" = "Tapatybės dokumento galinė pusė"; + +"Camera permission" = "Fotoaparato leidimas"; + +"Camera unavailable" = "Fotoaparatas nepasiekiamas"; + +"Capturing…" = "Fiksuojama..."; + +"Choose File" = "Pasirinkite failą"; + +"Consent" = "Sutikti"; + +"Could not capture image" = "Nepavyko užfiksuoti vaizdo"; + +"Date of Birth" = "Gimimo data"; + +"Date of birth does not look valid" = "Gimimo data neatrodo tinkama"; + +"Details not visible" = "Nematyti detalių"; + +"Flip your identity card over to the other side" = "Apverskite asmens tapatybės kortelę kita puse"; + +"Front identity card photo successfully uploaded" = "Sėkmingai įkelta asmens tapatybės kortelės priekinės pusės nuotrauka"; + +"Front of identity card" = "Asmens tapatybės kortelės priekinė pusė"; + +"Front of identity document" = "Tapatybės dokumento priekinė pusė"; + +"Get ready to scan your photo ID" = "Pasiruoškite nuskaityti savo asmens tapatybės dokumento nuotrauką"; + +"Get ready to take a selfie" = "Pasiruoškite nusifotografuoti"; + +"Go Back" = "Atgal"; + +"Hold still, scanning" = "Išlaikyti nejudinant, nuskaitoma"; + +"I'm ready" = "Esu pasiruošęs"; + +"ID Number" = "Identifikacinis numeris"; + +"Individual CPF" = "Asmens CPF"; + +"Keep ID level" = "Sulygiuokite tapatybės dokumentą"; + +"Last 4 of Social Security number" = "Paskutiniai 4 socialinio draudimo numerio skaitmenys"; + +"Loading" = "Įkeliama"; + +"Make sure all details are visible and focus" = "Įsitikinkite, kad visos detalės yra matomos ir sufokusuotos"; + +"Make sure you're in a well lit space." = "Įsitikinkite, kad esate gerai apšviestoje erdvėje."; + +"Move closer" = "Pastumkite arčiau"; + +"Move farther" = "Patraukite toliau"; + +"Move to a darker area" = "Pereikite į tamsesnę vietą"; + +"Move to a well-lit area" = "Padėkite gerai apšviestoje vietoje"; + +"NRIC or FIN" = "NRIC arba FIN"; + +"No document detected" = "Neaptiktas joks dokumentas"; + +"Personal ID number" = "Asmens kodas"; + +"Personal Information" = "Asmeninė informacija"; + +"Phone Number" = "Telefono numeris"; + +"Phone Verification" = "Telefono patvirtinimas"; + +"Photo Library" = "Nuotraukų biblioteka"; + +"Please upload images of the front and back of your identity card" = "Įkelkite asmens tapatybės kortelės priekinės ir galinės pusės vaizdus"; + +"Position your face in the center of the frame." = "Veidas turi būti rėmelio centre."; + +"Position your identity card in the center of the frame" = "Asmens tapatybės kortelė turi būti rėmelio centre"; + +"Retake Photos" = "Perfotografuoti"; + +"Scan" = "Nuskaityti"; + +"Scanned" = "Nuskaityta"; + +"Select" = "Pasirinkti"; + +"Select a location to upload the back of your identity document from" = "Pasirinkite vietą, iš kur įkelsite asmens tapatybės dokumento galinės pusės vaizdą"; + +"Select a location to upload the front of your identity document from" = "Pasirinkite vietą, iš kur įkelsite asmens tapatybės dokumento priekinės pusės vaizdą"; + +"Select back identity card photo" = "Pasirinkite asmens tapatybės kortelės galinės pusės nuotrauką"; + +"Select front identity card photo" = "Pasirinkite asmens tapatybės kortelės priekinės pusės nuotrauką"; + +"Selfie" = "Asmenukė"; + +"Selfie captures" = "Asmenukių įrašai"; + +"Selfie captures are complete" = "Baigti asmenukių įrašai"; + +"Take Photo" = "Fotografuoti"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Jūsų asmens tapatybės dokumento vaizdai neįrašyti. Ar norite išeiti?"; + +"There was an error accessing the camera." = "Bandant pasiekti fotoaparatą įvyko klaida."; + +"Try Again" = "Bandyti dar kartą"; + +"Try reduce glare and make ID visible" = "Pabandykite sumažinti atsispindėjimą ir padarykite tapatybės dokumentą matomą"; + +"Unable to establish a connection." = "Nepavyko prisijungti."; + +"Unsaved changes" = "Neįrašyti keitimai"; + +"Upload" = "Įkelti"; + +"Upload a Photo" = "Įkelti nuotrauką"; + +"Upload your photo ID" = "Įkelkite savo asmens dokumento nuotrauką"; + +"Uploading back identity card photo" = "Įkeliama asmens tapatybės kortelės galinės pusės nuotrauka"; + +"Uploading front identity card photo" = "Įkeliama asmens tapatybės kortelės priekinės pusės nuotrauka"; + +"Verify your identity" = "Patvirtinkite tapatybę"; + +"We could not capture a high-quality image." = "Nepavyko užfiksuoti aukštos kokybės vaizdo."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Mums reikia leidimo naudoti jūsų fotoaparatą. Suteikite priegą prie fotoaparato programos nustatymuose."; + +"Welcome" = "Pasveikinimas"; + +"You can either try again or upload an image from your device." = "Galite bandyti dar kartą arba įkelti vaizdą iš savo įrenginio."; + +"Your selfie images have not been saved. Do you want to leave?" = "Jūsų asmenukės vaizdai neįrašyti. Ar norite išeiti?"; + +"driver's license" = "vairuotojo pažymėjimas"; + +"government-issued photo ID" = "vyriausybės išduotas asmens tapatybės dokumentas su nuotrauka"; + +"passport" = "pasas"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/lv-LV.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..5a151043 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/lv-LV.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Nākamajā darbībā tiks automātiski uzņemti daži fotoattēli, lai pārbaudītu jūsu identitāti"; + +"Accepted forms of ID include" = "Apstiprinātās identifikācijas dokumenta formas ietver"; + +"Alternatively, you may manually upload a photo of your identity document." = "Vai arī varat manuāli augšupielādēt identifikācijas dokumenta fotoattēlu."; + +"App Settings" = "Lietotnes iestatījumi"; + +"Back identity card photo successfully uploaded" = "Identifikācijas kartes aizmugurējā puse ir veiksmīgi augšupielādēta"; + +"Back of identity card" = "Identifikācijas kartes aizmugure"; + +"Back of identity document" = "Identifikācijas dokumenta aizmugure"; + +"Camera permission" = "Kameras atļauja"; + +"Camera unavailable" = "Kamera nav pieejama"; + +"Capturing…" = "Uzņem attēlu…"; + +"Choose File" = "Izvēlieties failu"; + +"Consent" = "Piekrišana"; + +"Could not capture image" = "Neizdevās uzņemt attēlu"; + +"Date of Birth" = "Dzimšanas datums"; + +"Date of birth does not look valid" = "Dzimšanas datums neizskatās derīgs"; + +"Details not visible" = "Detaļas nav redzamas"; + +"Flip your identity card over to the other side" = "Apgrieziet identifikācijas karti uz otru pusi"; + +"Front identity card photo successfully uploaded" = "Identifikācijas kartes priekšpuse ir veiksmīgi augšupielādēta"; + +"Front of identity card" = "Identifikācijas kartes priekšpuse"; + +"Front of identity document" = "Identifikācijas dokumenta priekšpuse"; + +"Get ready to scan your photo ID" = "Sagatavojieties skenēt savu identifikācijas dokumentu ar fotogrāfiju"; + +"Get ready to take a selfie" = "Sagatavojieties uzņemt pašportretu"; + +"Go Back" = "Doties atpakaļ"; + +"Hold still, scanning" = "Nekustiniet, notiek skenēšana"; + +"I'm ready" = "Esmu gatavs"; + +"ID Number" = "ID numurs"; + +"Individual CPF" = "Fiziskas personas CPF"; + +"Keep ID level" = "Paturēt ID līmeni"; + +"Last 4 of Social Security number" = "Pēdējie 4 cipari no sociālās apdrošināšanas numura"; + +"Loading" = "Ielādē"; + +"Make sure all details are visible and focus" = "Pārliecinieties, vai visas detaļas ir redzamas un fokusētas."; + +"Make sure you're in a well lit space." = "Pārliecinieties, ka atrodaties labi apgaismotā vietā."; + +"Move closer" = "Novietojiet tuvāk"; + +"Move farther" = "Novietojiet tālāk"; + +"Move to a darker area" = "Pārvietojiet uz tumšāku vietu"; + +"Move to a well-lit area" = "Pārvietojieties uz labi apgaismotu vietu"; + +"NRIC or FIN" = "NRIC vai FIN"; + +"No document detected" = "Nav atpazīts dokuments"; + +"Personal ID number" = "Personas ID numurs"; + +"Personal Information" = "Personas dati"; + +"Phone Number" = "Tālruņa numurs"; + +"Phone Verification" = "Tālruņa verifikācija"; + +"Photo Library" = "Attēlu bibliotēka"; + +"Please upload images of the front and back of your identity card" = "Lūdzu, augšupielādējiet attēlus no jūsu identifikācijas kartes priekšpuses un aizmugures"; + +"Position your face in the center of the frame." = "Gādājiet, lai seja būtu kadra centrā."; + +"Position your identity card in the center of the frame" = "Gādājiet, lai identifikācijas karte būtu kadra centrā"; + +"Retake Photos" = "Uzņemt vēlreiz fotogrāfijas"; + +"Scan" = "Skenēt"; + +"Scanned" = "Skenēts"; + +"Select" = "Atlasīt"; + +"Select a location to upload the back of your identity document from" = "Atlasiet atrašanās vietu, kurā augšupielādēt jūsu identifikācijas dokumenta aizmuguri no"; + +"Select a location to upload the front of your identity document from" = "Atlasiet atrašanās vietu, kurā augšupielādēt jūsu identifikācijas dokumenta priekšpusi no"; + +"Select back identity card photo" = "Atlasīt identifikācijas kartes aizmugurējo fotoattēlu"; + +"Select front identity card photo" = "Atlasīt identifikācijas kartes priekšpuses fotoattēlu"; + +"Selfie" = "Pašportrets"; + +"Selfie captures" = "Pašportreta uzņēmumi"; + +"Selfie captures are complete" = "Pašportreta uzņēmumi ir pabeigti"; + +"Take Photo" = "Uzņemt attēlu"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Jūsu identifikācijas dokumenta attēli nav saglabāti. Vai vēlaties iziet?"; + +"There was an error accessing the camera." = "Radās kļūda, piekļūstot kamerai."; + +"Try Again" = "Mēģināt vēlreiz"; + +"Try reduce glare and make ID visible" = "Mēģiniet mazināt atspīdumu un pārliecinieties, ka ID ir redzams"; + +"Unable to establish a connection." = "Nevar izveidot savienojumu."; + +"Unsaved changes" = "Nesaglabātās izmaiņas"; + +"Upload" = "Augšupielādēt"; + +"Upload a Photo" = "Augšupielādēt fotoattēlu"; + +"Upload your photo ID" = "Augšupielādējiet savu ID dokumentu ar fotogrāfiju"; + +"Uploading back identity card photo" = "Augšupielādē identifikācijas kartes aizmugurējo fotoattēlu"; + +"Uploading front identity card photo" = "Augšupielādē identifikācijas kartes priekšpuses fotoattēlu"; + +"Verify your identity" = "Verificējiet savu identitāti"; + +"We could not capture a high-quality image." = "Neizdevās uzņemt augstas kvalitātes attēlu."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Lai izmantotu jūsu kameru, mums ir nepieciešama jūsu atļauja. Lūdzu, savos lietotnes iestatījumos iespējojiet kameras piekļuvi."; + +"Welcome" = "Laipni lūdzam"; + +"You can either try again or upload an image from your device." = "Varat mēģināt vēlreiz vai augšupielādēt attēlu no jūsu ierīces."; + +"Your selfie images have not been saved. Do you want to leave?" = "Jūsu pašportreta attēli nav saglabāti. Vai vēlaties iziet?"; + +"driver's license" = "autovadītāja apliecība"; + +"government-issued photo ID" = "valdības izsniegts identifikācijas dokuments ar fotogrāfiju"; + +"passport" = "pase"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/ms-MY.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..e6e416b8 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/ms-MY.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Beberapa foto akan diambil secara automatik pada langkah seterusnya untuk menentusahkan andalah orangnya"; + +"Accepted forms of ID include" = "Bentuk ID yang diterima termasuklah"; + +"Alternatively, you may manually upload a photo of your identity document." = "Sebagai alternatif, anda boleh memuat naik foto dokumen pengenalan anda secara manual."; + +"App Settings" = "Tetapan Aplikasi"; + +"Back identity card photo successfully uploaded" = "Foto bahagian belakang kad pengenalan berjaya dimuat naik"; + +"Back of identity card" = "Bahagian belakang kad pengenalan"; + +"Back of identity document" = "Bahagian belakang dokumen pengenalan"; + +"Camera permission" = "Keizinan kamera"; + +"Camera unavailable" = "Kamera tidak tersedia"; + +"Capturing…" = "Sedang ditangkap..."; + +"Choose File" = "Pilih Fail"; + +"Consent" = "Persetujuan"; + +"Could not capture image" = "Imej tidak dapat ditangkap"; + +"Date of Birth" = "Tarikh Lahir"; + +"Date of birth does not look valid" = "Tarikh lahir tampaknya tidak sah"; + +"Details not visible" = "Butiran tidak kelihatan"; + +"Flip your identity card over to the other side" = "Terbalikkan kad pengenalan anda ke sebelah satu lagi"; + +"Front identity card photo successfully uploaded" = "Foto bahagian hadapan kad pengenalan berjaya dimuat naik"; + +"Front of identity card" = "Bahagian hadapan kad pengenalan"; + +"Front of identity document" = "Bahagian hadapan dokumen pengenalan"; + +"Get ready to scan your photo ID" = "Bersedia untuk mengimbas ID berfoto anda"; + +"Get ready to take a selfie" = "Bersedia untuk mengambil swafoto"; + +"Go Back" = "Undur"; + +"Hold still, scanning" = "Jangan bergerak, pengimbasan sedang berlangsung"; + +"I'm ready" = "Saya sudah bersedia"; + +"ID Number" = "Nombor ID"; + +"Individual CPF" = "CPF Individu"; + +"Keep ID level" = "Pastikan ID mendatar"; + +"Last 4 of Social Security number" = "Empat digit terakhir nombor Keselamatan Sosial"; + +"Loading" = "Sedang dimuatkan"; + +"Make sure all details are visible and focus" = "Pastikan semua butiran dapat dilihat dan berada dalam fokus"; + +"Make sure you're in a well lit space." = "Pastikan anda berada di ruang yang cukup terang."; + +"Move closer" = "Beralih lebih dekat"; + +"Move farther" = "Beralih lebih jauh"; + +"Move to a darker area" = "Beralih ke kawasan yang lebih gelap"; + +"Move to a well-lit area" = "Beralih ke kawasan yang cukup terang"; + +"NRIC or FIN" = "NRIC atau FIN"; + +"No document detected" = "Tiada dokumen dikesan"; + +"Personal ID number" = "Nombor ID peribadi"; + +"Personal Information" = "Maklumat Peribadi"; + +"Phone Number" = "Nombor Telefon"; + +"Phone Verification" = "Penentusahan Telefon"; + +"Photo Library" = "Pustaka Foto"; + +"Please upload images of the front and back of your identity card" = "Sila muat naik imej hadapan dan belakang kad pengenalan anda"; + +"Position your face in the center of the frame." = "Letakkan muka anda di tengah bingkai"; + +"Position your identity card in the center of the frame" = "Letakkan kad pengenalan anda di tengah bingkai"; + +"Retake Photos" = "Ambil Semula Foto"; + +"Scan" = "Imbas"; + +"Scanned" = "Telah Diimbas"; + +"Select" = "Pilih"; + +"Select a location to upload the back of your identity document from" = "Pilih lokasi sumber untuk memuat naik bahagian belakang dokumen pengenalan anda"; + +"Select a location to upload the front of your identity document from" = "Pilih lokasi sumber untuk memuat naik bahagian hadapan dokumen pengenalan anda"; + +"Select back identity card photo" = "Pilih foto bahagian belakang kad pengenalan"; + +"Select front identity card photo" = "Pilih foto bahagian hadapan kad pengenalan"; + +"Selfie" = "Swafoto"; + +"Selfie captures" = "Tangkapan swafoto"; + +"Selfie captures are complete" = "Tangkapan swafoto selesai"; + +"Take Photo" = "Ambil Foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Imej dokumen pengenalan anda belum disimpan. Anda pasti ingin meninggalkannya?"; + +"There was an error accessing the camera." = "Ada ralat ketika mengakses kamera."; + +"Try Again" = "Cuba Lagi"; + +"Try reduce glare and make ID visible" = "Cuba kurangkan silau dan pastikan ID dapat dilihat"; + +"Unable to establish a connection." = "Sambungan tidak dapat diwujudkan."; + +"Unsaved changes" = "Perubahan tidak disimpan"; + +"Upload" = "Muat Naik"; + +"Upload a Photo" = "Muat Naik Foto"; + +"Upload your photo ID" = "Muat naik ID berfoto anda"; + +"Uploading back identity card photo" = "Foto bahagian belakang kad pengenalan sedang dimuat naik"; + +"Uploading front identity card photo" = "Foto bahagian hadapan kad pengenalan sedang dimuat naik"; + +"Verify your identity" = "Tentusahkan identiti anda"; + +"We could not capture a high-quality image." = "Kami tidak dapat menangkap imej berkualiti tinggi."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Kami perlukan keizinan untuk menggunakan kamera anda. Sila benarkan akses kamera dalam tetapan aplikasi."; + +"Welcome" = "Selamat datang"; + +"You can either try again or upload an image from your device." = "Anda boleh cuba lagi atau muat naik imej daripada peranti anda."; + +"Your selfie images have not been saved. Do you want to leave?" = "Imej swafoto anda belum lagi disimpan. Adakah anda ingin meninggalkannya?"; + +"driver's license" = "lesen memandu"; + +"government-issued photo ID" = "ID berfoto yang dikeluarkan oleh kerajaan"; + +"passport" = "pasport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/mt.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/mt.lproj/Localizable.strings new file mode 100644 index 00000000..0b9db24d --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/mt.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Fil-pass li jmiss se niġbdu ftit ritratti awtomatikament biex nivverifikaw li int min qed tgħid li int"; + +"Accepted forms of ID include" = "Ix-xorta ta' dokumenti tal-identità aċċettati"; + +"Alternatively, you may manually upload a photo of your identity document." = "Inkella tista' ttella' ritratt tad-dokument tal-identità tiegħek int stess."; + +"App Settings" = "Settings tal-App"; + +"Back identity card photo successfully uploaded" = "Ir-ritratt tan-naħa ta' wara tal-karta tal-identità ttella'"; + +"Back of identity card" = "In-naħa ta' wara tal-karta tal-identità"; + +"Back of identity document" = "In-naħa ta' wara tad-dokument tal-identità"; + +"Camera permission" = "Permess għall-kamera"; + +"Camera unavailable" = "Il-kamera mhux disponibbli"; + +"Capturing…" = "Qed nieħdu l-istessi..."; + +"Choose File" = "Agħżel Fajl"; + +"Consent" = "Kunsens"; + +"Could not capture image" = "Ma stajniex nieħdu r-ritratt"; + +"Date of Birth" = "Data tat-twelid"; + +"Date of birth does not look valid" = "Id-data tat-twelid ma tidhirx li hija tajba"; + +"Details not visible" = "Id-dettalji ma jidhrux"; + +"Flip your identity card over to the other side" = "Aqleb il-kard tal-identità tiegħek għan-naħa l-oħra"; + +"Front identity card photo successfully uploaded" = "Ir-ritratt tan-naħa ta' quddiem tal-kard tal-identità ttella' b'suċċes"; + +"Front of identity card" = "In-naħa ta' quddiem tal-karta tal-identità"; + +"Front of identity document" = "In-naħa ta' quddiem tad-dokument tal-identità"; + +"Get ready to scan your photo ID" = "Ħejji ruħek biex tiskennja d-dokument tal-identità bir-ritratt tiegħek"; + +"Get ready to take a selfie" = "Ħejji ruħek għall-istessu"; + +"Go Back" = "Mur Lura"; + +"Hold still, scanning" = "Tiċċaqlaqx, qed niskennjaw"; + +"I'm ready" = "Jien lest/a"; + +"ID Number" = "In-Numru tal-ID"; + +"Individual CPF" = "CPF personali"; + +"Keep ID level" = "Żomm id-dokument tal-identità ċatt"; + +"Last 4 of Social Security number" = "L-aħħar 4 ċifri tan-numru tas-Sigurtà Soċjali"; + +"Loading" = "Tielgħa"; + +"Make sure all details are visible and focus" = "Ara li d-dettalji kollha jidhru sewwa u huma ffukati"; + +"Make sure you're in a well lit space." = "Ara li tinsab f'post imdawwal sew."; + +"Move closer" = "Mexxih eqreb"; + +"Move farther" = "Ersaq lura"; + +"Move to a darker area" = "Ersaq f'post iktar mudlam"; + +"Move to a well-lit area" = "Mur f'post imdawwal sew"; + +"NRIC or FIN" = "NRIC jew FIN"; + +"No document detected" = "Ma nstab l-ebda dokument"; + +"Personal ID number" = "In-numru tal-ID personali"; + +"Personal Information" = "Dettalji personali"; + +"Phone Number" = "Numru tal-Mowbajl"; + +"Phone Verification" = "Verifika bil-Mowbajl"; + +"Photo Library" = "Ġabra tar-Ritratti"; + +"Please upload images of the front and back of your identity card" = "Jekk jogħġbok tella' r-ritratti tan-naħa ta' quddiem u ta' wara tal-karta tal-identità tiegħek"; + +"Position your face in the center of the frame." = "Qiegħed wiċċek f'nofs il-gwarniċ."; + +"Position your identity card in the center of the frame" = "Qiegħed il-karta tal-identità tiegħek f'nofs il-gwarniċ"; + +"Retake Photos" = "Erġa' ħu r-ritratti"; + +"Scan" = "Skennja"; + +"Scanned" = "Skennjat"; + +"Select" = "Agħżel"; + +"Select a location to upload the back of your identity document from" = "Agħżel il-post minn fejn tixtieq ittella' n-naħa ta' wara tad-dokument tal-identità"; + +"Select a location to upload the front of your identity document from" = "Agħżel il-post minn fejn tixtieq ittella' n-naħa ta' quddiem tad-dokument tal-identità"; + +"Select back identity card photo" = "Agħżel ir-ritratt tan-naħa ta' wara tal-karta tal-identità"; + +"Select front identity card photo" = "Agħżel ir-ritratt tan-naħa ta' quddiem tal-karta tal-identità"; + +"Selfie" = "Stessu"; + +"Selfie captures" = "Ġbid tal-istessu"; + +"Selfie captures are complete" = "L-istessi nġibdu"; + +"Take Photo" = "Ħu Ritratt"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Ir-ritratti tad-dokument tal-identità tiegħek ma ġewx issejvjati. Trid toħroġ xorta waħda?"; + +"There was an error accessing the camera." = "Seħħ żball meta aċċessajna l-kamera."; + +"Try Again" = "Erġa' Pprova"; + +"Try reduce glare and make ID visible" = "Ipprova naqqas id-dija u ara li d-dokument tal-identitá jidher sewwa"; + +"Unable to establish a connection." = "Ma nistgħux nagħmlu konnessjoni."; + +"Unsaved changes" = "Il-bidliet ma ġewx issejvjati"; + +"Upload" = "Tella'"; + +"Upload a Photo" = "Tella' Ritratt"; + +"Upload your photo ID" = "Tella' d-dokument tal-identità b'ritratt tiegħek"; + +"Uploading back identity card photo" = "Qed ittella' r-ritratt tan-naħa ta' wara tal-kartà tal-identità"; + +"Uploading front identity card photo" = "Qed ittella' r-ritratt tan-naħa ta' quddiem tal-karta tal-identità"; + +"Verify your identity" = "Ivverifika l-identità tiegħek"; + +"We could not capture a high-quality image." = "Ma stajniex nieħdu ritratt ta' kwalità tajba."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Neħtieġu l-permess biex bużaw il-kamera tiegħek. Jekk jogħġbok agħti aċċess għall-kamera mis-settings tal-apps."; + +"Welcome" = "Merħba"; + +"You can either try again or upload an image from your device." = "Tista' terġa' tipprova jew tagħżel li ttella' ritratt mit-tagħmir tiegħek."; + +"Your selfie images have not been saved. Do you want to leave?" = "L-istessi tiegħek għadhom ma ntrefgħux. Tixtieq toħroġ?"; + +"driver's license" = "liċenzja tas-sewqan"; + +"government-issued photo ID" = "dokument tal-identità bir-ritratt maħruġ mill-gvern"; + +"passport" = "passaport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/nb.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/nb.lproj/Localizable.strings new file mode 100644 index 00000000..ea38519f --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/nb.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Noen få bilder tas automatisk i neste trinn for å bekrefte at det er deg"; + +"Accepted forms of ID include" = "Godkjente former for ID inkluderer"; + +"Alternatively, you may manually upload a photo of your identity document." = "Alternativt kan du laste opp et bilde av identitetsdokumentet manuelt."; + +"App Settings" = "Appinnstillinger"; + +"Back identity card photo successfully uploaded" = "Bilde av baksiden av identitetskortet ble lastet opp"; + +"Back of identity card" = "Baksiden av identitetskortet"; + +"Back of identity document" = "Baksiden av identitetsdokumentet"; + +"Camera permission" = "Kameratilgang"; + +"Camera unavailable" = "Kamera utilgjengelig"; + +"Capturing…" = "Tar bilde …"; + +"Choose File" = "Velg fil"; + +"Consent" = "Samtykke"; + +"Could not capture image" = "Kunne ikke ta bilde"; + +"Date of Birth" = "Fødselsdato"; + +"Date of birth does not look valid" = "Fødselsdato ser ikke gyldig ut"; + +"Details not visible" = "Detaljer er ikke synlige"; + +"Flip your identity card over to the other side" = "Snu identitetskortet over til den andre siden"; + +"Front identity card photo successfully uploaded" = "Bilde av forsiden av identitetskortet ble lastet opp"; + +"Front of identity card" = "Forsiden av identitetskortet"; + +"Front of identity document" = "Forsiden av identitetsdokumentet"; + +"Get ready to scan your photo ID" = "Gjør deg klar til å skanne bilde-ID-en din"; + +"Get ready to take a selfie" = "Gjør deg klar til å ta en selfie"; + +"Go Back" = "Gå tilbake"; + +"Hold still, scanning" = "Vær rolig, skanner"; + +"I'm ready" = "Jeg er klar"; + +"ID Number" = "ID-nummer"; + +"Individual CPF" = "Individuell CPF"; + +"Keep ID level" = "Hold ID-en rett"; + +"Last 4 of Social Security number" = "Siste 4 sifre i personnummer"; + +"Loading" = "Laster inn"; + +"Make sure all details are visible and focus" = "Sørg for at alle detaljer er synlige og i fokus."; + +"Make sure you're in a well lit space." = "Sørg for at du befinner deg i et godt opplyst rom."; + +"Move closer" = "Flytt nærmere"; + +"Move farther" = "Flytt lenger unna"; + +"Move to a darker area" = "Flytt til et mørkere område"; + +"Move to a well-lit area" = "Flytt til et godt belyst område"; + +"NRIC or FIN" = "NRIC eller FIN"; + +"No document detected" = "Ingen dokumenter er oppdaget"; + +"Personal ID number" = "Personlig ID-nummer"; + +"Personal Information" = "Personopplysninger"; + +"Phone Number" = "Telefonnummer"; + +"Phone Verification" = "Telefonverifisering"; + +"Photo Library" = "Bildebibliotek"; + +"Please upload images of the front and back of your identity card" = "Last opp bilder av for- og baksiden av identitetskortet ditt"; + +"Position your face in the center of the frame." = "Plasser ansiktet midt i bildet."; + +"Position your identity card in the center of the frame" = "Plasser identitetskortet midt i bildet"; + +"Retake Photos" = "Ta bilder på nytt"; + +"Scan" = "Skann"; + +"Scanned" = "Skannet"; + +"Select" = "Velg"; + +"Select a location to upload the back of your identity document from" = "Velg en plassering du vil laste opp baksiden av identitetsdokumentet ditt fra"; + +"Select a location to upload the front of your identity document from" = "Velg en plassering du vil laste opp forsiden av identitetsdokumentet ditt fra"; + +"Select back identity card photo" = "Velg bilde av baksiden av identitetskortet"; + +"Select front identity card photo" = "Velg bilde av forsiden av identitetskortet"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfiebilder"; + +"Selfie captures are complete" = "Selfiebilder er fullført"; + +"Take Photo" = "Ta bilde"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Bildet av identitetsdokumentet er ikke lagret. Vil du forlate?"; + +"There was an error accessing the camera." = "Det oppstod en feil med å få tilgang til kameraet."; + +"Try Again" = "Prøv igjen"; + +"Try reduce glare and make ID visible" = "Prøv å redusere gjenskinn og gjør ID-en synlig"; + +"Unable to establish a connection." = "Kan ikke opprette en tilkobling."; + +"Unsaved changes" = "Ulagrede endringer"; + +"Upload" = "Last opp"; + +"Upload a Photo" = "Last opp et bilde"; + +"Upload your photo ID" = "Last opp din bilde-ID"; + +"Uploading back identity card photo" = "Laster opp bilde av baksiden av identitetskortet"; + +"Uploading front identity card photo" = "Laster opp bilde av forsiden av identitetskortet"; + +"Verify your identity" = "Verifiser identiteten din"; + +"We could not capture a high-quality image." = "Vi kunne ikke ta et bilde av høy kvalitet."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Vi trenger tillatelse for å bruke kameraet ditt. Gi tilgang til kameraet i appinnstillingene."; + +"Welcome" = "Velkommen"; + +"You can either try again or upload an image from your device." = "Du kan enten prøve på nytt eller laste opp et bilde fra enheten din."; + +"Your selfie images have not been saved. Do you want to leave?" = "Selfiebildene er ikke lagret. Vil du forlate?"; + +"driver's license" = "førerkort"; + +"government-issued photo ID" = "offentlig godkjente foto-ID"; + +"passport" = "pass"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/nl.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/nl.lproj/Localizable.strings new file mode 100644 index 00000000..404f43ab --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/nl.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "In de volgende stap worden er automatisch een paar foto's gemaakt om te verifiëren dat jij het bent"; + +"Accepted forms of ID include" = "Geaccepteerde identiteitsbewijzen"; + +"Alternatively, you may manually upload a photo of your identity document." = "Als alternatief kun je ook handmatig een foto van je identiteitsbewijs uploaden."; + +"App Settings" = "App-instellingen"; + +"Back identity card photo successfully uploaded" = "Foto van achterkant van identiteitskaart is geüpload"; + +"Back of identity card" = "Achterkant van identiteitskaart"; + +"Back of identity document" = "Achterkant van identiteitsbewijs"; + +"Camera permission" = "Toestemming voor camera"; + +"Camera unavailable" = "Camera niet beschikbaar"; + +"Capturing…" = "Selfie wordt gemaakt..."; + +"Choose File" = "Bestand kiezen"; + +"Consent" = "Toestaan"; + +"Could not capture image" = "Niet gelukt om afbeelding vast te leggen"; + +"Date of Birth" = "Geboortedatum"; + +"Date of birth does not look valid" = "Geboortedatum lijkt ongeldig te zijn"; + +"Details not visible" = "Details niet zichtbaar"; + +"Flip your identity card over to the other side" = "Draai je identiteitskaart om, zodat de andere kant zichtbaar is"; + +"Front identity card photo successfully uploaded" = "Foto van voorkant van identiteitskaart is geüpload"; + +"Front of identity card" = "Voorkant van identiteitskaart"; + +"Front of identity document" = "Voorkant van identiteitsbewijs"; + +"Get ready to scan your photo ID" = "Bereid je voor om je foto-ID te scannen"; + +"Get ready to take a selfie" = "Maak je klaar om een selfie te nemen"; + +"Go Back" = "Terug"; + +"Hold still, scanning" = "Niet bewegen, het document wordt gescand"; + +"I'm ready" = "Ik ben klaar"; + +"ID Number" = "Identificatienummer"; + +"Individual CPF" = "CPF natuurlijk persoon"; + +"Keep ID level" = "Houd het identiteitsbewijs recht"; + +"Last 4 of Social Security number" = "Laatste vier cijfers van socialezekerheidsnummer"; + +"Loading" = "Bezig met laden"; + +"Make sure all details are visible and focus" = "Zorg dat alle details goed zichtbaar en in focus zijn"; + +"Make sure you're in a well lit space." = "Zorg dat er voldoende belichting is."; + +"Move closer" = "Dichterbij houden"; + +"Move farther" = "Verder weg houden"; + +"Move to a darker area" = "Ga naar een donkerdere plek"; + +"Move to a well-lit area" = "Zorg dat er voldoende belichting is"; + +"NRIC or FIN" = "NRIC of FIN"; + +"No document detected" = "Geen document gedetecteerd"; + +"Personal ID number" = "Persoonlijk identificatienummer"; + +"Personal Information" = "Persoonlijke gegevens"; + +"Phone Number" = "Telefoonnummer"; + +"Phone Verification" = "Telefonische verificatie"; + +"Photo Library" = "Fotobibliotheek"; + +"Please upload images of the front and back of your identity card" = "Upload afbeeldingen van de voor- en achterkant van je identiteitskaart"; + +"Position your face in the center of the frame." = "Houd je gezicht in het midden van het frame."; + +"Position your identity card in the center of the frame" = "Plaats je identiteitskaart midden in het frame"; + +"Retake Photos" = "Nieuwe foto's maken"; + +"Scan" = "Scannen"; + +"Scanned" = "Gescand"; + +"Select" = "Selecteren"; + +"Select a location to upload the back of your identity document from" = "Selecteer een locatie vanwaar je de achterkant van je identiteitsbewijs wilt uploaden"; + +"Select a location to upload the front of your identity document from" = "Selecteer een locatie vanwaar je de voorkant van je identiteitsbewijs wilt uploaden"; + +"Select back identity card photo" = "Foto van achterkant van identiteitskaart selecteren"; + +"Select front identity card photo" = "Foto van voorkant van identiteitskaart selecteren"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfieopnamen"; + +"Selfie captures are complete" = "Selfieopnamen zijn voltooid"; + +"Take Photo" = "Foto maken"; + +"The images of your identity document have not been saved. Do you want to leave?" = "De afbeeldingen van je identiteitsbewijs zijn niet opgeslagen. Wil je afsluiten?"; + +"There was an error accessing the camera." = "Fout bij het verkrijgen van toegang tot de camera."; + +"Try Again" = "Opnieuw proberen"; + +"Try reduce glare and make ID visible" = "Probeer de glans te verminderen en zorg dat de ID zichtbaar is"; + +"Unable to establish a connection." = "Kan geen verbinding maken"; + +"Unsaved changes" = "Niet-opgeslagen wijzigingen"; + +"Upload" = "Uploaden"; + +"Upload a Photo" = "Een foto uploaden"; + +"Upload your photo ID" = "Je identiteitsbewijs uploaden"; + +"Uploading back identity card photo" = "Foto van achterkant van identiteitskaart uploaden"; + +"Uploading front identity card photo" = "Foto van voorkant van identiteitskaart uploaden"; + +"Verify your identity" = "Je identiteit verifiëren"; + +"We could not capture a high-quality image." = "We kunnen geen afbeelding in hoge kwaliteit vastleggen."; + +"We need permission to use your camera. Please allow camera access in app settings." = "We hebben toestemming nodig om je camera te gebruiken. Sta dit toe via de app-instellingen."; + +"Welcome" = "Welkom"; + +"You can either try again or upload an image from your device." = "Probeer het nogmaals of upload een afbeelding van je apparaat."; + +"Your selfie images have not been saved. Do you want to leave?" = "De afbeeldingen van je selfie zijn niet opgeslagen. Wil je afsluiten?"; + +"driver's license" = "rijbewijs"; + +"government-issued photo ID" = "Overheids-ID met foto"; + +"passport" = "paspoort"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/nn-NO.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..d7ae0f61 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/nn-NO.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "I neste trinn tek vi automatisk nokre få bilete av deg for å stadfeste at det er deg"; + +"Accepted forms of ID include" = "Godkjente formar for ID inkluderer"; + +"Alternatively, you may manually upload a photo of your identity document." = "Alternativt kan du laste opp eit bilete av ID-dokumentet ditt manuelt."; + +"App Settings" = "Appinnstillingar"; + +"Back identity card photo successfully uploaded" = "Bilete av baksida av identifikasjonskort lasta opp"; + +"Back of identity card" = "Baksida av ID-kort"; + +"Back of identity document" = "Baksida av identifikasjonsdokumentet"; + +"Camera permission" = "Kameraløyve"; + +"Camera unavailable" = "Kamera ikkje tilgjengeleg"; + +"Capturing…" = "Tar bilete ..."; + +"Choose File" = "Vel fil"; + +"Consent" = "Samtykke"; + +"Could not capture image" = "Kunne ikkje ta bilete"; + +"Date of Birth" = "Fødselsdato"; + +"Date of birth does not look valid" = "Fødselsdato ser ikkje gyldig ut"; + +"Details not visible" = "Detaljar ikkje synlege"; + +"Flip your identity card over to the other side" = "Snu ID-kortet ditt"; + +"Front identity card photo successfully uploaded" = "Bilete av framsida av identifikasjonskort lasta opp"; + +"Front of identity card" = "Framsida av identifikasjonskort"; + +"Front of identity document" = "Framsida av identifikasjonsdokumentet"; + +"Get ready to scan your photo ID" = "Gjør deg klar til å skanne bilde av ID-en din"; + +"Get ready to take a selfie" = "Gjer deg klar til å ta eit bilete av deg sjølv"; + +"Go Back" = "Gå tilbake"; + +"Hold still, scanning" = "Hold i ro, skannar"; + +"I'm ready" = "Eg er klar"; + +"ID Number" = "ID-nummer"; + +"Individual CPF" = "Individuell CPF"; + +"Keep ID level" = "Hald ID-en på rett line"; + +"Last 4 of Social Security number" = "Siste 4 tak i personnummeret"; + +"Loading" = "Lastar"; + +"Make sure all details are visible and focus" = "Sørg for at alle detaljar er synlege og i fokus"; + +"Make sure you're in a well lit space." = "Sørg for at du er på ein godt opplyst stad."; + +"Move closer" = "Flytt nærmare"; + +"Move farther" = "Flytt lenger vekk"; + +"Move to a darker area" = "Flytt til ein mørkare stad"; + +"Move to a well-lit area" = "Flytt til ein godt opplyst stad"; + +"NRIC or FIN" = "NRIC eller FIN"; + +"No document detected" = "Inkje dokument funne"; + +"Personal ID number" = "Personleg ID-nummer"; + +"Personal Information" = "Personleg informasjon"; + +"Phone Number" = "Telefonnummer"; + +"Phone Verification" = "Telefonstadfesting"; + +"Photo Library" = "Biletebibliotek"; + +"Please upload images of the front and back of your identity card" = "Last opp bilete av framsida og bakside av identifikasjonskortet ditt"; + +"Position your face in the center of the frame." = "Posisjoner andletet ditt i midten av biletet."; + +"Position your identity card in the center of the frame" = "Posisjoner ID-kortet ditt i midten av biletet"; + +"Retake Photos" = "Ta bilete på nytt"; + +"Scan" = "Skann"; + +"Scanned" = "Skanna"; + +"Select" = "Vel"; + +"Select a location to upload the back of your identity document from" = "Vel ei plassering du vil laste opp baksida av ID-dokumentet ditt frå"; + +"Select a location to upload the front of your identity document from" = "Vel ei plassering du vil laste opp framsida av ID-dokumentet ditt frå"; + +"Select back identity card photo" = "Vel bilete for baksida av ID-kort"; + +"Select front identity card photo" = "Vel bilete for framsida av ID-kort"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfiebilete"; + +"Selfie captures are complete" = "Selfiebilete blei tatt"; + +"Take Photo" = "Ta bilete"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Bileta av ID-dokumentet ditt har ikkje blitt lagra. Vil du avslutte?"; + +"There was an error accessing the camera." = "Det oppstod ein feil ved tilgang til kameraet."; + +"Try Again" = "Prøv igjen"; + +"Try reduce glare and make ID visible" = "Prøv å redusera gjenskinn og gjera ID-en synleg"; + +"Unable to establish a connection." = "Kan ikkje opprette tilkopling."; + +"Unsaved changes" = "Ulagra endringar"; + +"Upload" = "Last opp"; + +"Upload a Photo" = "Last opp eit bilete"; + +"Upload your photo ID" = "Last opp legitimasjon med foto"; + +"Uploading back identity card photo" = "Lastar opp baksida av ID-kort"; + +"Uploading front identity card photo" = "Lastar opp framsida av ID-kort"; + +"Verify your identity" = "Stadfest identiteten din"; + +"We could not capture a high-quality image." = "Vi kunne ikkje ta eit bilete i høg kvalitet."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Vi treng løyve for å bruke kameraet ditt. Gi tilgang til kameraet i appinnstillingane."; + +"Welcome" = "Velkomen"; + +"You can either try again or upload an image from your device." = "Du kan anten prøve igjen eller laste opp eit bilete frå eininga di."; + +"Your selfie images have not been saved. Do you want to leave?" = "Selfiebileta dine blei ikkje lagra. Vil du avslutte?"; + +"driver's license" = "førarkort"; + +"government-issued photo ID" = "offentleg godkjente foto-ID"; + +"passport" = "pass"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/pl-PL.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..2f699d2b --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/pl-PL.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "W kolejnym kroku zostanie zrobionych kilka zdjęć, aby potwierdzić Twoją tożsamość"; + +"Accepted forms of ID include" = "Akceptowane formy dokumentu tożsamości to"; + +"Alternatively, you may manually upload a photo of your identity document." = "Można też ręcznie przesłać zdjęcie dokumentu tożsamości."; + +"App Settings" = "Ustawienia aplikacji"; + +"Back identity card photo successfully uploaded" = "Przesłano zdjęcie tylnej strony dowodu tożsamości"; + +"Back of identity card" = "Tylna strona dokumentu tożsamości"; + +"Back of identity document" = "Tylna strona dokumentu tożsamości"; + +"Camera permission" = "Zezwolenie dla aparatu"; + +"Camera unavailable" = "Aparat niedostępny"; + +"Capturing…" = "Przechwytywanie…"; + +"Choose File" = "Wybierz plik"; + +"Consent" = "Zgoda"; + +"Could not capture image" = "Nie można wykonać zdjęcia"; + +"Date of Birth" = "Data urodzenia"; + +"Date of birth does not look valid" = "Data urodzenia wygląda na nieprawidłową"; + +"Details not visible" = "Szczegóły nie są widoczne"; + +"Flip your identity card over to the other side" = "Obróć dowód tożsamości na drugą stronę"; + +"Front identity card photo successfully uploaded" = "Przesłano zdjęcie przedniej strony dowodu tożsamości"; + +"Front of identity card" = "Przednia strona dokumentu tożsamości"; + +"Front of identity document" = "Przednia strona dokumentu tożsamości"; + +"Get ready to scan your photo ID" = "Przygotuj się na skanowanie dokumentu tożsamości ze zdjęciem"; + +"Get ready to take a selfie" = "Przygotuj się do zrobienia selfie"; + +"Go Back" = "Wróć"; + +"Hold still, scanning" = "Zaczekaj, trwa skanowanie"; + +"I'm ready" = "Gotowe"; + +"ID Number" = "Numer identyfikacyjny"; + +"Individual CPF" = "CPF osoby fizycznej"; + +"Keep ID level" = "Utrzymaj dokument tożsamości w poziomie"; + +"Last 4 of Social Security number" = "Ostatnie 4 cyfry numeru ubezpieczenia społecznego"; + +"Loading" = "Wczytywanie"; + +"Make sure all details are visible and focus" = "Upewnij się, że wszystkie szczegóły są widoczne i ostre"; + +"Make sure you're in a well lit space." = "Upewnij się, że znajdujesz się w dobrze oświetlonym miejscu."; + +"Move closer" = "Przybliż"; + +"Move farther" = "Oddal"; + +"Move to a darker area" = "Przenieś się do ciemniejszego miejsca"; + +"Move to a well-lit area" = "Przenieś się do dobrze oświetlonego miejsca"; + +"NRIC or FIN" = "NRIC lub FIN"; + +"No document detected" = "Nie wykryto żadnego dokumentu"; + +"Personal ID number" = "Numer dowodu tożsamości"; + +"Personal Information" = "Dane osobowe"; + +"Phone Number" = "Numer telefonu"; + +"Phone Verification" = "Weryfikacja numeru telefonu"; + +"Photo Library" = "Biblioteka zdjęć"; + +"Please upload images of the front and back of your identity card" = "Prześlij zdjęcia przedniej i tylnej strony dokumentu tożsamości"; + +"Position your face in the center of the frame." = "Umieść twarz w centrum kadru."; + +"Position your identity card in the center of the frame" = "Umieść dowód tożsamości w centrum kadru"; + +"Retake Photos" = "zrób zdjęcia ponownie"; + +"Scan" = "Skanowanie"; + +"Scanned" = "Zeskanowano"; + +"Select" = "Wybierz"; + +"Select a location to upload the back of your identity document from" = "Wybierz lokalizację, z której chcesz przesłać tylną stronę dokumentu tożsamości"; + +"Select a location to upload the front of your identity document from" = "Wybierz lokalizację, z której chcesz przesłać przednią stronę dokumentu tożsamości"; + +"Select back identity card photo" = "Wybierz zdjęcie tyłu dowodu tożsamości"; + +"Select front identity card photo" = "Wybierz zdjęcie tyłu dowodu tożsamości"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Przechwytywanie selfie"; + +"Selfie captures are complete" = "Zakończono przechwytywanie selfie"; + +"Take Photo" = "Zrób zdjęcie"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Nie zapisano zdjęć Twojego dowodu tożsamości. Czy chcesz wyjść?"; + +"There was an error accessing the camera." = "Wystąpił błąd dostępu do aparatu."; + +"Try Again" = "Spróbuj ponownie"; + +"Try reduce glare and make ID visible" = "Spróbuj zmniejszyć oświetlenie i upewnij się, że dokument tożsamości jest widoczny."; + +"Unable to establish a connection." = "Nie można nawiązać połączenia."; + +"Unsaved changes" = "Niezapisane zmiany"; + +"Upload" = "Prześlij"; + +"Upload a Photo" = "Prześlij zdjęcie"; + +"Upload your photo ID" = "Prześlij dokument tożsamości ze zdjęciem"; + +"Uploading back identity card photo" = "Przesyłanie zdjęcia tyłu dowodu tożsamości"; + +"Uploading front identity card photo" = "Przesyłanie zdjęcia przodu dowodu tożsamości"; + +"Verify your identity" = "Zweryfikuj tożsamość"; + +"We could not capture a high-quality image." = "Nie można zarejestrować obrazu wysokiej jakości."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Potrzebujemy pozwolenia na używanie Twojego aparatu. Zezwól w ustawieniach aplikacji na dostęp do aparatu."; + +"Welcome" = "Witamy"; + +"You can either try again or upload an image from your device." = "Możesz spróbować ponownie lub przesłać obraz z urządzenia."; + +"Your selfie images have not been saved. Do you want to leave?" = "Nie zapisano Twoich selfie. Czy chcesz wyjść?"; + +"driver's license" = "prawo jazdy"; + +"government-issued photo ID" = "wydany przez rząd dokument tożsamości ze zdjęciem"; + +"passport" = "paszport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/pt-BR.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..5463e243 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/pt-BR.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Algumas fotos serão tiradas automaticamente na próxima etapa para verificar sua identidade"; + +"Accepted forms of ID include" = "Os tipos aceitos de documento de identificação incluem"; + +"Alternatively, you may manually upload a photo of your identity document." = "Você também pode carregar manualmente uma foto do documento de identificação."; + +"App Settings" = "Ajustes do aplicativo"; + +"Back identity card photo successfully uploaded" = "Foto do verso da carteira de identidade carregada"; + +"Back of identity card" = "Verso da identidade"; + +"Back of identity document" = "Verso do documento de identificação"; + +"Camera permission" = "Permissão para câmera"; + +"Camera unavailable" = "Câmera indisponível"; + +"Capturing…" = "Capturando…"; + +"Choose File" = "Escolher arquivo"; + +"Consent" = "Autorizar"; + +"Could not capture image" = "Não foi possível capturar a imagem"; + +"Date of Birth" = "Data de nascimento"; + +"Date of birth does not look valid" = "A data de nascimento não é válida"; + +"Details not visible" = "Detalhes não visíveis"; + +"Flip your identity card over to the other side" = "Vire a carteira de identidade para o outro lado"; + +"Front identity card photo successfully uploaded" = "Foto da frente da carteira de identidade carregada"; + +"Front of identity card" = "Frente da identidade"; + +"Front of identity document" = "Frente do documento de identificação"; + +"Get ready to scan your photo ID" = "Prepare-se para digitalizar seu documento de identificação com foto"; + +"Get ready to take a selfie" = "Prepare-se para tirar uma selfie"; + +"Go Back" = "Voltar"; + +"Hold still, scanning" = "Segure firme. Digitalizando."; + +"I'm ready" = "Tudo pronto"; + +"ID Number" = "Número da identificação"; + +"Individual CPF" = "CPF"; + +"Keep ID level" = "Mantenha o documento nivelado"; + +"Last 4 of Social Security number" = "Últimos 4 dígitos do Social Security Number"; + +"Loading" = "Carregando"; + +"Make sure all details are visible and focus" = "Confira se todos os detalhes estão visíveis e em foco."; + +"Make sure you're in a well lit space." = "Escolha um espaço bem iluminado."; + +"Move closer" = "Aproxime"; + +"Move farther" = "Afaste"; + +"Move to a darker area" = "Mova para uma área mais escura"; + +"Move to a well-lit area" = "Mova para uma área bem iluminada"; + +"NRIC or FIN" = "NRIC ou FIN"; + +"No document detected" = "Nenhum documento detectado"; + +"Personal ID number" = "Número de identificação pessoal"; + +"Personal Information" = "Dados pessoais"; + +"Phone Number" = "Número de telefone"; + +"Phone Verification" = "Verificação de telefone"; + +"Photo Library" = "Fotos"; + +"Please upload images of the front and back of your identity card" = "Carregue imagens (frente e verso) do seu documento de identidade"; + +"Position your face in the center of the frame." = "Posicione seu rosto no centro do quadro."; + +"Position your identity card in the center of the frame" = "Posicione a carteira de identidade no centro do quadro"; + +"Retake Photos" = "Tirar fotos novamente"; + +"Scan" = "Digitalizar"; + +"Scanned" = "Digitalizado"; + +"Select" = "Selecionar"; + +"Select a location to upload the back of your identity document from" = "Selecione um local para carregar o verso do documento de identidade"; + +"Select a location to upload the front of your identity document from" = "Selecione um local para carregar a frente do documento de identidade"; + +"Select back identity card photo" = "Selecionar foto do verso da carteira de identidade"; + +"Select front identity card photo" = "Selecionar foto da frente da carteira de identidade"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Capturas de selfie"; + +"Selfie captures are complete" = "As capturas de selfie foram concluídas"; + +"Take Photo" = "Tirar foto"; + +"The images of your identity document have not been saved. Do you want to leave?" = "As imagens do documento de identificação não foram salvas. Deseja sair?"; + +"There was an error accessing the camera." = "Erro ao acessar a câmera."; + +"Try Again" = "Tentar novamente"; + +"Try reduce glare and make ID visible" = "Tente reduzir o brilho e tornar o documento visível"; + +"Unable to establish a connection." = "Não foi possível estabelecer a conexão."; + +"Unsaved changes" = "Alterações não salvas"; + +"Upload" = "Carregar"; + +"Upload a Photo" = "Carregar uma foto"; + +"Upload your photo ID" = "Carregue um documento de identificação com foto"; + +"Uploading back identity card photo" = "Carregar foto do verso da carteira de identidade"; + +"Uploading front identity card photo" = "Carregar foto da frente da carteira de identidade"; + +"Verify your identity" = "Verifique sua identidade"; + +"We could not capture a high-quality image." = "Não foi possível capturar uma imagem com qualidade."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Autorize o acesso à câmera nas configurações do aplicativo."; + +"Welcome" = "Olá"; + +"You can either try again or upload an image from your device." = "Você pode tentar novamente ou carregar uma imagem do dispositivo."; + +"Your selfie images have not been saved. Do you want to leave?" = "As imagens da sua selfie não foram salvas. Quer sair?"; + +"driver's license" = "carteira de habilitação"; + +"government-issued photo ID" = "documento de identificação com foto emitida pelo governo"; + +"passport" = "passaporte"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/pt-PT.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..98f75bfa --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/pt-PT.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Serão tiradas algumas fotografias automaticamente no passo seguinte para verificar a sua identidade"; + +"Accepted forms of ID include" = "Formas de documento de identificação aceites incluem"; + +"Alternatively, you may manually upload a photo of your identity document." = "Em alternativa, pode carregar manualmente uma fotografia do seu documento de identificação."; + +"App Settings" = "Definições da aplicação"; + +"Back identity card photo successfully uploaded" = "Fotografia do verso do cartão de identidade carregada com sucesso"; + +"Back of identity card" = "Verso do cartão de identidade"; + +"Back of identity document" = "Verso do documento de identificação"; + +"Camera permission" = "Permissão da câmara"; + +"Camera unavailable" = "Câmara indisponível"; + +"Capturing…" = "A captar…"; + +"Choose File" = "Escolher ficheiro"; + +"Consent" = "Autorizar"; + +"Could not capture image" = "Não foi possível capturar a imagem"; + +"Date of Birth" = "Data de nascimento"; + +"Date of birth does not look valid" = "A data de nascimento não parece ser válida"; + +"Details not visible" = "Detalhes não visíveis"; + +"Flip your identity card over to the other side" = "Vire o seu cartão de identidade para o outro lado"; + +"Front identity card photo successfully uploaded" = "Fotografia da frente do cartão de identidade carregada com sucesso"; + +"Front of identity card" = "Frente do cartão de identidade"; + +"Front of identity document" = "Frente do documento de identificação"; + +"Get ready to scan your photo ID" = "Prepare-se para digitalizar o seu documento de identificação com fotografia"; + +"Get ready to take a selfie" = "Prepare-se para tirar uma selfie"; + +"Go Back" = "Voltar"; + +"Hold still, scanning" = "Segure com firmeza, a capturar"; + +"I'm ready" = "Estou pronto"; + +"ID Number" = "Número de identificação"; + +"Individual CPF" = "CPF particular"; + +"Keep ID level" = "Mantenha o documento de identificação nivelado"; + +"Last 4 of Social Security number" = "Últimos 4 dígitos do número da segurança social"; + +"Loading" = "A carregar"; + +"Make sure all details are visible and focus" = "Certifique-se de que todas as informações estão visíveis e focadas"; + +"Make sure you're in a well lit space." = "Certifique-se de que se encontra num espaço bem iluminado."; + +"Move closer" = "Aproxime o documento"; + +"Move farther" = "Afaste a câmara"; + +"Move to a darker area" = "Desloque-se para um espaço mais escuro"; + +"Move to a well-lit area" = "Desloque-se para um espaço bem iluminado."; + +"NRIC or FIN" = "NRIC ou FIN"; + +"No document detected" = "Nenhum documento detetado"; + +"Personal ID number" = "Número de identificação pessoal"; + +"Personal Information" = "Informações pessoais"; + +"Phone Number" = "Número de telefone"; + +"Phone Verification" = "Verificação de telefone"; + +"Photo Library" = "Biblioteca de fotografias"; + +"Please upload images of the front and back of your identity card" = "Carregue imagens da parte da frente e do verso do seu cartão de identidade"; + +"Position your face in the center of the frame." = "Posicione o seu rosto no centro da moldura."; + +"Position your identity card in the center of the frame" = "Posicione o seu cartão de identidade no centro da moldura"; + +"Retake Photos" = "Voltar a tirar fotografias"; + +"Scan" = "Digitalizar"; + +"Scanned" = "Digitalizado"; + +"Select" = "Selecionar"; + +"Select a location to upload the back of your identity document from" = "Selecione uma localização a partir da qual pretende carregar o verso do seu documento de identidade"; + +"Select a location to upload the front of your identity document from" = "Selecione uma localização a partir da qual pretende carregar a frente do seu documento de identidade"; + +"Select back identity card photo" = "Selecione a fotografia do verso do seu cartão de identidade"; + +"Select front identity card photo" = "Selecione a fotografia da frente do seu cartão de identidade."; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Capturas de selfie"; + +"Selfie captures are complete" = "Capturas de selfie concluídas"; + +"Take Photo" = "Tirar fotografia"; + +"The images of your identity document have not been saved. Do you want to leave?" = "As imagens do seu documento de identificação não foram guardadas. Tem a certeza que quer sair?"; + +"There was an error accessing the camera." = "Ocorreu um erro ao aceder à câmara."; + +"Try Again" = "Tentar novamente"; + +"Try reduce glare and make ID visible" = "Tente reduzir o brilho para tornar a identificação visível"; + +"Unable to establish a connection." = "Não é possível estabelecer ligação."; + +"Unsaved changes" = "Alterações não guardadas"; + +"Upload" = "Carregar"; + +"Upload a Photo" = "Carregar fotografia"; + +"Upload your photo ID" = "Carregar o seu documento de identificação com fotografia"; + +"Uploading back identity card photo" = "A carregar a fotografia do verso do cartão de identidade"; + +"Uploading front identity card photo" = "A carregar a fotografia da frente do cartão de identidade"; + +"Verify your identity" = "Verifique a sua identidade"; + +"We could not capture a high-quality image." = "Não foi possível captar uma imagem de alta qualidade."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Precisamos de permissão para utilizar a sua câmara. Permita o acesso à câmara nas definições da aplicação."; + +"Welcome" = "Bem-vindo"; + +"You can either try again or upload an image from your device." = "Pode tentar novamente ou carregar uma imagem a partir do seu dispositivo."; + +"Your selfie images have not been saved. Do you want to leave?" = "As suas imagens de selfie não foram guardadas. Pretende sair?"; + +"driver's license" = "carta de condução"; + +"government-issued photo ID" = "documento de identificação com fotografia, emitido pelo governo"; + +"passport" = "passaporte"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/ro-RO.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..b300587a --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/ro-RO.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Câteva fotografii vor fi efectuate automat la următorul pas pentru a verifica dacă sunteți dvs."; + +"Accepted forms of ID include" = "Formele de identificare acceptate includ"; + +"Alternatively, you may manually upload a photo of your identity document." = "Alternativ, puteți încărca manual o fotografie a documentului dvs. de identitate."; + +"App Settings" = "Setările aplicației"; + +"Back identity card photo successfully uploaded" = "Fotografia verso-ului cărții de identitate a fost încărcată cu succes"; + +"Back of identity card" = "Verso-ul cărții de identitate"; + +"Back of identity document" = "Spatele documentului de identitate"; + +"Camera permission" = "Permisiune cameră"; + +"Camera unavailable" = "Cameră indisponibilă"; + +"Capturing…" = "Se captează…"; + +"Choose File" = "Selectare fișier"; + +"Consent" = "Consimțământ"; + +"Could not capture image" = "Nu s-a putut capta imaginea"; + +"Date of Birth" = "Data nașterii"; + +"Date of birth does not look valid" = "Data nașterii pare a nu fi validă"; + +"Details not visible" = "Datele nu sunt vizibile"; + +"Flip your identity card over to the other side" = "Întoarceți cartea de identitate pe partea cealaltă"; + +"Front identity card photo successfully uploaded" = "Fotografia părții frontale a cărții de identitate a fost încărcată cu succes"; + +"Front of identity card" = "Partea frontală a cărții de identitate"; + +"Front of identity document" = "Partea frontală a documentului de identitate"; + +"Get ready to scan your photo ID" = "Pregătiți-vă să vă scanați documentul de identitate cu fotografie"; + +"Get ready to take a selfie" = "Pregătiți-vă pentru a efectua un selfie"; + +"Go Back" = "Înapoi"; + +"Hold still, scanning" = "Nu mișcați, se scanează"; + +"I'm ready" = "Sunt gata"; + +"ID Number" = "Număr de identificare"; + +"Individual CPF" = "CPF persoană fizică"; + +"Keep ID level" = "Țineți documentul de identitate drept"; + +"Last 4 of Social Security number" = "Ultimele 4 cifre ale numărului de securitate socială"; + +"Loading" = "Se încarcă"; + +"Make sure all details are visible and focus" = "Asigurați-vă că toate detaliile sunt vizibile și focalizați"; + +"Make sure you're in a well lit space." = "Asigurați-vă că vă aflați într-un spațiu bine luminat."; + +"Move closer" = "Apropiați"; + +"Move farther" = "Depărtați"; + +"Move to a darker area" = "Mutați-vă într-o zonă mai întunecată"; + +"Move to a well-lit area" = "Mutați-vă într-o zonă bine luminată"; + +"NRIC or FIN" = "NRIC sau FIN"; + +"No document detected" = "Nu s-a detectat niciun document"; + +"Personal ID number" = "Număr de identificare personal"; + +"Personal Information" = "Informații cu caracter personal"; + +"Phone Number" = "Număr de telefon"; + +"Phone Verification" = "Verificare număr de telefon"; + +"Photo Library" = "Galerie foto"; + +"Please upload images of the front and back of your identity card" = "Vă rugăm să încărcați imagini cu partea frontală și verso-ul cărții dvs. de identitate"; + +"Position your face in the center of the frame." = "Poziționați-vă fața în centrul cadrului."; + +"Position your identity card in the center of the frame" = "Poziționați-vă cartea de identitate în centrul cadrului"; + +"Retake Photos" = "Faceți din nou fotografii"; + +"Scan" = "Scanare"; + +"Scanned" = "Scanat"; + +"Select" = "Selectare"; + +"Select a location to upload the back of your identity document from" = "Selectați o locație din care să încărcați verso-ului documentului dvs. de identitate"; + +"Select a location to upload the front of your identity document from" = "Selectați o locație din care să încărcați partea frontală a documentului dvs. de identitate"; + +"Select back identity card photo" = "Selectați fotografia verso-ului cărții de identitate"; + +"Select front identity card photo" = "Selectați fotografia părții frontale a cărții de identitate"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Capturi de tip selfie"; + +"Selfie captures are complete" = "Capturile de tip selfie sunt complete"; + +"Take Photo" = "Faceți o fotografie"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Imaginile documentului dvs. de identitate nu au fost salvate. Doriți să ieșiți?"; + +"There was an error accessing the camera." = "A apărut o eroare la accesarea camerei."; + +"Try Again" = "Încercați din nou"; + +"Try reduce glare and make ID visible" = "Încercați să reduceți strălucirea și să faceți vizibil documentul de identitate"; + +"Unable to establish a connection." = "Nu s-a putut stabili o conexiune."; + +"Unsaved changes" = "Modificări nesalvate"; + +"Upload" = "Încărcare"; + +"Upload a Photo" = "Încărcați o fotografie"; + +"Upload your photo ID" = "Încărcați documentul de identitate cu fotografie"; + +"Uploading back identity card photo" = "Se încarcă fotografia verso-ului cărții de identitate"; + +"Uploading front identity card photo" = "Se încarcă fotografia părții frontale a cărții de identitate"; + +"Verify your identity" = "Verificați-vă identitatea"; + +"We could not capture a high-quality image." = "Nu am putut captura o imagine de înaltă calitate."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Avem nevoie de permisiunea de a utiliza camera dvs. Vă rugăm să permiteți accesul la cameră în setările aplicației."; + +"Welcome" = "Bine ați venit"; + +"You can either try again or upload an image from your device." = "Puteți să încercați din nou sau să încărcați o imagine de pe dispozitivul dvs."; + +"Your selfie images have not been saved. Do you want to leave?" = "Fotografiile dvs. de tip selfie nu au fost salvate. Doriți să ieșiți?"; + +"driver's license" = "permis de conducere"; + +"government-issued photo ID" = "document de identitate cu fotografie emis de guvern"; + +"passport" = "pașaport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/ru.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/ru.lproj/Localizable.strings new file mode 100644 index 00000000..2e7443d2 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/ru.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "На следующем этапе будет автоматически сделано несколько фотографий: это нужно, чтобы подтвердить вашу личность"; + +"Accepted forms of ID include" = "Принимаются следующие виды удостоверений"; + +"Alternatively, you may manually upload a photo of your identity document." = "В качестве альтернативы вы можете загрузить фотографию своего удостоверения личности."; + +"App Settings" = "Настройки приложения"; + +"Back identity card photo successfully uploaded" = "Фотография оборотной стороны удостоверения личности успешно отправлена"; + +"Back of identity card" = "Оборот удостоверения личности"; + +"Back of identity document" = "Оборотная сторона документа, удостоверяющего личность"; + +"Camera permission" = "Разрешение на доступ к камере"; + +"Camera unavailable" = "Камера недоступна"; + +"Capturing…" = "Идет съемка..."; + +"Choose File" = "Выберите файл"; + +"Consent" = "Согласие"; + +"Could not capture image" = "Не удалось получить изображение"; + +"Date of Birth" = "Дата рождения"; + +"Date of birth does not look valid" = "Похоже, дата рождения недействительна"; + +"Details not visible" = "Детали не видны"; + +"Flip your identity card over to the other side" = "Переверните удостоверение личности другой стороной"; + +"Front identity card photo successfully uploaded" = "Фотография лицевой стороны удостоверения личности успешно отправлена"; + +"Front of identity card" = "Лицевая сторона удостоверения личности"; + +"Front of identity document" = "Лицевая сторона документа, удостоверяющего личность"; + +"Get ready to scan your photo ID" = "Приготовьтесь сканировать удостоверение личности с фотографией"; + +"Get ready to take a selfie" = "Приготовьтесь сделать селфи"; + +"Go Back" = "Назад"; + +"Hold still, scanning" = "Держите документ неподвижно, идет сканирование"; + +"I'm ready" = "Готово"; + +"ID Number" = "Номер удостоверения личности"; + +"Individual CPF" = "Личный номер CPF"; + +"Keep ID level" = "Держите документ ровно"; + +"Last 4 of Social Security number" = "Последние 4 цифры номера социального страхования"; + +"Loading" = "Идет загрузка"; + +"Make sure all details are visible and focus" = "Проследите, чтобы все детали были видны и в фокусе"; + +"Make sure you're in a well lit space." = "Позаботьтесь о хорошем освещении."; + +"Move closer" = "Придвиньтесь поближе"; + +"Move farther" = "Отодвиньтесь подальше"; + +"Move to a darker area" = "Перейдите в более темное место"; + +"Move to a well-lit area" = "Переместитесь в хорошо освещенное место"; + +"NRIC or FIN" = "Номер NRIC или FIN"; + +"No document detected" = "Документ не найден"; + +"Personal ID number" = "Номер документа, удостоверяющего личность"; + +"Personal Information" = "Персональная информация"; + +"Phone Number" = "Номер телефона"; + +"Phone Verification" = "Проверка по телефону"; + +"Photo Library" = "Библиотека фотографий"; + +"Please upload images of the front and back of your identity card" = "Отправьте изображения лицевой стороны и оборота вашего удостоверения личности"; + +"Position your face in the center of the frame." = "Расположите лицо по центру рамки"; + +"Position your identity card in the center of the frame" = "Поместите удостоверение личности в центр рамки"; + +"Retake Photos" = "Повторные фото"; + +"Scan" = "Сканировать"; + +"Scanned" = "Сканировано"; + +"Select" = "Выбрать"; + +"Select a location to upload the back of your identity document from" = "Выберите папку с изображением оборота вашего документа, удостоверяющего личность, для отправки"; + +"Select a location to upload the front of your identity document from" = "Выберите папку с изображением лицевой стороны вашего документа, удостоверяющего личность, для отправки"; + +"Select back identity card photo" = "Выбрать фотографию оборотной стороны удостоверения личности"; + +"Select front identity card photo" = "Выбрать фотографию лицевой стороны удостоверения личности"; + +"Selfie" = "Селфи"; + +"Selfie captures" = "Съемка селфи"; + +"Selfie captures are complete" = "Съемка селфи выполнена"; + +"Take Photo" = "Сделать фото"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Изображения вашего удостоверения личности не сохранены. Вы все-таки хотите выйти?"; + +"There was an error accessing the camera." = "При попытке доступа к камере произошла ошибка."; + +"Try Again" = "Повторите попытку"; + +"Try reduce glare and make ID visible" = "Постарайтесь, чтобы не было бликов и чтобы документ был хорошо виден"; + +"Unable to establish a connection." = "Не удалось установить соединение."; + +"Unsaved changes" = "Несохраненные изменения"; + +"Upload" = "Отправить"; + +"Upload a Photo" = "Отправьте фотографию"; + +"Upload your photo ID" = "Отправьте удостоверение личности с фото"; + +"Uploading back identity card photo" = "Отправляется фотография оборотной стороны удостоверения личности"; + +"Uploading front identity card photo" = "Отправляется фотография лицевой стороны удостоверения личности"; + +"Verify your identity" = "Подтвердите свою личность"; + +"We could not capture a high-quality image." = "Не удалось получить качественное изображение."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Нам требуется разрешение на доступ к камере. Разрешите доступ к камере в настройках приложения."; + +"Welcome" = "Добро пожаловать"; + +"You can either try again or upload an image from your device." = "Попробуйте еще раз или загрузите изображение со своего устройства."; + +"Your selfie images have not been saved. Do you want to leave?" = "Ваши селфи-изображения не были сохранены. Хотите выйти?"; + +"driver's license" = "водительские права"; + +"government-issued photo ID" = "государственное удостоверение личности с фотографией"; + +"passport" = "паспорт"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/sk-SK.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..8d24abc1 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/sk-SK.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "V ďalšom kroku sa automaticky urobí pár fotografií na overenie, že ste to vy."; + +"Accepted forms of ID include" = "K prijateľným formám dokladu totožnosti patria"; + +"Alternatively, you may manually upload a photo of your identity document." = "Prípadne môžete nahrať fotografiu vášho dokladu totožnosti."; + +"App Settings" = "Nastavenia aplikácie"; + +"Back identity card photo successfully uploaded" = "Fotografia zadnej strany preukazu totožnosti bola úspešne nahraná"; + +"Back of identity card" = "Zadná strana preukazu totožnosti"; + +"Back of identity document" = "Zadná strana dokladu totožnosti"; + +"Camera permission" = "Povolenie fotoaparátu"; + +"Camera unavailable" = "Fotoaparát je nedostupný"; + +"Capturing…" = "Zaznamenáva sa..."; + +"Choose File" = "Vybrať súbor"; + +"Consent" = "Súhlas"; + +"Could not capture image" = "Nepodarilo sa nasnímať fotografiu"; + +"Date of Birth" = "Dátum narodenia"; + +"Date of birth does not look valid" = "Zdá sa, že dátum narodenia je neplatný"; + +"Details not visible" = "Podrobnosti nie sú viditeľné"; + +"Flip your identity card over to the other side" = "Otočte svoj preukaz totožnosti na druhú stranu"; + +"Front identity card photo successfully uploaded" = "Fotografia prednej strany preukazu totožnosti bola úspešne nahraná"; + +"Front of identity card" = "Predná strana preukazu totožnosti"; + +"Front of identity document" = "Predná strana dokladu totožnosti"; + +"Get ready to scan your photo ID" = "Pripravte sa na naskenovanie dokladu totožnosti s fotografiou"; + +"Get ready to take a selfie" = "Pripravte sa na nasnímanie selfie"; + +"Go Back" = "Späť"; + +"Hold still, scanning" = "Držte pokojne, skenuje sa"; + +"I'm ready" = "Som pripravený/-á"; + +"ID Number" = "Identifikačné číslo"; + +"Individual CPF" = "Individuálne CPF"; + +"Keep ID level" = "Udržiavajte úroveň dokladu totožnosti"; + +"Last 4 of Social Security number" = "Posledné 4 číslice čísla sociálneho poistenia"; + +"Loading" = "Nahrávanie"; + +"Make sure all details are visible and focus" = "Uistite sa, že sú všetky podrobnosti viditeľné a správne zaostrené"; + +"Make sure you're in a well lit space." = "Uistite sa, že ste v dobre osvetlenom priestore."; + +"Move closer" = "Prisuňte"; + +"Move farther" = "Posuňte ďalej"; + +"Move to a darker area" = "Presuňte sa do tmavšieho priestoru"; + +"Move to a well-lit area" = "Presuňte sa do dobre osvetleného priestoru"; + +"NRIC or FIN" = "NRIC alebo FIN"; + +"No document detected" = "Nerozpoznal sa žiadny dokument"; + +"Personal ID number" = "Osobné identifikačné číslo"; + +"Personal Information" = "Osobné informácie"; + +"Phone Number" = "Telefónne číslo"; + +"Phone Verification" = "Telefonické overenie"; + +"Photo Library" = "Knižnica fotografií"; + +"Please upload images of the front and back of your identity card" = "Nahrajte fotografie prednej a zadnej strany preukazu totožnosti"; + +"Position your face in the center of the frame." = "Umiestnite tvár do stredu rámika."; + +"Position your identity card in the center of the frame" = "Umiestnite svoj preukaz totožnosti do stredu rámika"; + +"Retake Photos" = "Znova odfotiť"; + +"Scan" = "Skenovať"; + +"Scanned" = "Naskenované"; + +"Select" = "Zvoliť"; + +"Select a location to upload the back of your identity document from" = "Vyberte umiestnenie, z ktorého chcete nahrať zadnú stranu dokladu totožnosti"; + +"Select a location to upload the front of your identity document from" = "Vyberte umiestnenie, z ktorého chcete nahrať prednú stranu dokladu totožnosti"; + +"Select back identity card photo" = "Vyberte fotografiu zadnej strany preukazu totožnosti"; + +"Select front identity card photo" = "Vyberte fotografiu prednej strany preukazu totožnosti"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfie zábery"; + +"Selfie captures are complete" = "Selfie zábery sú dokončené"; + +"Take Photo" = "Odfotiť"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Obrázky vášho dokladu totožnosti sa neuložili. Chcete odísť?"; + +"There was an error accessing the camera." = "Pri prístupe k fotoaparátu sa vyskytla chyba."; + +"Try Again" = "Skúste to znova"; + +"Try reduce glare and make ID visible" = "Skúste obmedziť odlesky a zviditeľnite doklad totožnosti"; + +"Unable to establish a connection." = "Nepodarilo sa vytvoriť spojenie."; + +"Unsaved changes" = "Neuložené zmeny"; + +"Upload" = "Nahrať"; + +"Upload a Photo" = "Nahrať fotografiu"; + +"Upload your photo ID" = "Nahrajte doklad totožnosti s fotografiou"; + +"Uploading back identity card photo" = "Nahrávanie fotografie zo zadnej strany preukazu totožnosti"; + +"Uploading front identity card photo" = "Nahrávanie fotografie z prednej strany preukazu totožnosti"; + +"Verify your identity" = "Overiť totožnosť"; + +"We could not capture a high-quality image." = "Nepodarilo sa nasnímať obrázok vo vysokej kvalite."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Potrebujeme povolenie na použitie vášho fotoaparátu. Povoľte prístup k fotoaparátu v nastaveniach aplikácie."; + +"Welcome" = "Vitajte"; + +"You can either try again or upload an image from your device." = "Môžete to skúsiť znova alebo nahrať obrázok zo svojho zariadenia."; + +"Your selfie images have not been saved. Do you want to leave?" = "Obrázky selfie sa neuložili. Chcete odísť?"; + +"driver's license" = "vodičský preukaz"; + +"government-issued photo ID" = "doklad totožnosti s fotografiou vydaný štátom"; + +"passport" = "pas"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/sl-SI.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..286cb44d --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/sl-SI.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "V naslednjem koraku bo samodejno posnetih nekaj fotografij, da bomo lahko preverili vašo identiteto."; + +"Accepted forms of ID include" = "Med sprejemljive oblike osebnih dokumentov spadajo"; + +"Alternatively, you may manually upload a photo of your identity document." = "Fotografijo svojega osebnega dokumenta pa lahko tudi ročno naložite."; + +"App Settings" = "Nastavitve aplikacije"; + +"Back identity card photo successfully uploaded" = "Fotografija hrbtne strani osebne izkaznice je bila uspešno naložena"; + +"Back of identity card" = "Hrbtna stran osebne izkaznice"; + +"Back of identity document" = "Hrbtna stran osebnega dokumenta"; + +"Camera permission" = "Dovoljenje za kamero"; + +"Camera unavailable" = "Kamera ni na voljo"; + +"Capturing…" = "Zajemanje ..."; + +"Choose File" = "Izberite datoteko"; + +"Consent" = "Soglasje"; + +"Could not capture image" = "Slike ni bilo mogoče posneti"; + +"Date of Birth" = "Datum rojstva"; + +"Date of birth does not look valid" = "Videti je, da datum rojstva ni veljaven"; + +"Details not visible" = "Podrobnosti niso vidne"; + +"Flip your identity card over to the other side" = "Obrnite osebno izkaznico na drugo stran"; + +"Front identity card photo successfully uploaded" = "Fotografija sprednje strani osebne izkaznice je bila uspešno naložena"; + +"Front of identity card" = "Sprednja stran osebne izkaznice"; + +"Front of identity document" = "Sprednja stran osebnega dokumenta"; + +"Get ready to scan your photo ID" = "Pripravite se na optično branje osebnega dokumenta s fotografijo"; + +"Get ready to take a selfie" = "Pripravite se na zajem selfija"; + +"Go Back" = "Nazaj"; + +"Hold still, scanning" = "Optično branje – držite pri miru"; + +"I'm ready" = "Pripravljen(-a) sem"; + +"ID Number" = "Številka osebnega dokumenta"; + +"Individual CPF" = "CPF posameznika"; + +"Keep ID level" = "Osebni dokument naj ostane raven"; + +"Last 4 of Social Security number" = "Zadnje 4 števke številke socialnega zavarovanja"; + +"Loading" = "Nalaganje"; + +"Make sure all details are visible and focus" = "Prepričajte se, da so vse podrobnosti vidne in izostrene"; + +"Make sure you're in a well lit space." = "Poskrbite, da ste v dobro osvetljenem prostoru."; + +"Move closer" = "Približajte"; + +"Move farther" = "Oddaljite"; + +"Move to a darker area" = "Pojdite v temnejši prostor"; + +"Move to a well-lit area" = "Pojdite v dobro osvetljen prostor"; + +"NRIC or FIN" = "NRIC ali FIN"; + +"No document detected" = "Zaznan ni bil noben dokument"; + +"Personal ID number" = "Številka osebne izkaznice"; + +"Personal Information" = "Osebni podatki"; + +"Phone Number" = "Telefonska številka"; + +"Phone Verification" = "Preverjanje po telefonu"; + +"Photo Library" = "Knjižnica fotografij"; + +"Please upload images of the front and back of your identity card" = "Naložite sliki sprednje in hrbtne strani osebne izkaznice"; + +"Position your face in the center of the frame." = "Zagotovite, da je vaš obraz na sredini okvira."; + +"Position your identity card in the center of the frame" = "Zagotovite, da je osebna izkaznica na sredini okvirja"; + +"Retake Photos" = "Znova posnemi fotografije"; + +"Scan" = "Optično preberi"; + +"Scanned" = "Optično prebrano"; + +"Select" = "Izberi"; + +"Select a location to upload the back of your identity document from" = "Izberite mesto, s katerega želite naložiti hrbtno stran osebnega dokumenta"; + +"Select a location to upload the front of your identity document from" = "Izberite mesto, s katerega želite naložiti sprednjo stran osebnega dokumenta"; + +"Select back identity card photo" = "Izberite fotografijo hrbtne strani osebne izkaznice"; + +"Select front identity card photo" = "Izberite fotografijo sprednje strani osebne izkaznice"; + +"Selfie" = "Selfi"; + +"Selfie captures" = "Posnetki selfija"; + +"Selfie captures are complete" = "Selfiji so posneti"; + +"Take Photo" = "Fotografiraj"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Slike vašega osebnega dokumenta niso shranjene. Ali želite zapreti to stran?"; + +"There was an error accessing the camera." = "Pri dostopu do kamere je prišlo do napake."; + +"Try Again" = "Poskusi znova"; + +"Try reduce glare and make ID visible" = "Poskusite zmanjšati bleščanje in se prepričajte, da je osebni dokument viden"; + +"Unable to establish a connection." = "Povezave ni mogoče vzpostaviti."; + +"Unsaved changes" = "Neshranjene spremembe"; + +"Upload" = "Naloži"; + +"Upload a Photo" = "Naloži fotografijo"; + +"Upload your photo ID" = "Naložite osebni dokument s svojo fotografijo"; + +"Uploading back identity card photo" = "Nalaganje fotografije hrbtne strani osebne izkaznice"; + +"Uploading front identity card photo" = "Nalaganje fotografije sprednje strani osebne izkaznice"; + +"Verify your identity" = "Preverite svojo identiteto"; + +"We could not capture a high-quality image." = "Ni bilo mogoče zajeti visokokakovostne slike."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Potrebujemo dovoljenje za uporabo vaše kamere. Dovolite dostop do kamere v nastavitvah aplikacije."; + +"Welcome" = "Dobrodošli"; + +"You can either try again or upload an image from your device." = "Poskusite znova ali pa naložite sliko iz svoje naprave."; + +"Your selfie images have not been saved. Do you want to leave?" = "Slike vašega selfija niso shranjene. Ali želite zapreti to stran?"; + +"driver's license" = "vozniško dovoljenje"; + +"government-issued photo ID" = "uradni osebni dokument s fotografijo"; + +"passport" = "potni list"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/sv.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/sv.lproj/Localizable.strings new file mode 100644 index 00000000..3ddd4b5e --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/sv.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Ett par foton kommer att tas automatiskt i nästa steg för att verifiera att det är du"; + +"Accepted forms of ID include" = "Godkända former av id-handlingar inkluderar"; + +"Alternatively, you may manually upload a photo of your identity document." = "Du kan även manuellt ladda upp ett foto av din id-handling."; + +"App Settings" = "Appinställningar"; + +"Back identity card photo successfully uploaded" = "Bilden på id-kortets baksida har laddats upp"; + +"Back of identity card" = "Baksida av id-kort"; + +"Back of identity document" = "Id-handlingens baksida"; + +"Camera permission" = "Kamerabehörighet"; + +"Camera unavailable" = "Kamera ej tillgänglig"; + +"Capturing…" = "Tar bild …"; + +"Choose File" = "Välj fil"; + +"Consent" = "Samtycke"; + +"Could not capture image" = "Det gick inte att ta bilden"; + +"Date of Birth" = "Födelsedatum"; + +"Date of birth does not look valid" = "Födelsedatumet verkar inte vara giltig"; + +"Details not visible" = "Detaljer syns inte"; + +"Flip your identity card over to the other side" = "Vänd på id-handlingen"; + +"Front identity card photo successfully uploaded" = "Bilden på id-kortets framsida har laddats upp"; + +"Front of identity card" = "Framsida av id-kort"; + +"Front of identity document" = "Id-handlingens framsida"; + +"Get ready to scan your photo ID" = "Gör dig redo att skanna ditt foto-id"; + +"Get ready to take a selfie" = "Gör dig redo att ta en selfie"; + +"Go Back" = "Gå tillbaka"; + +"Hold still, scanning" = "Rör dig inte, dokumentet skannas in"; + +"I'm ready" = "Jag är redo"; + +"ID Number" = "ID-nummer"; + +"Individual CPF" = "Personligt CPF"; + +"Keep ID level" = "Se till att id-handlingen ligger rakt"; + +"Last 4 of Social Security number" = "Fyra sista siffrorna i socialförsäkringsnumret"; + +"Loading" = "Laddar"; + +"Make sure all details are visible and focus" = "Se till att alla uppgifter är synliga och i fokus"; + +"Make sure you're in a well lit space." = "Se till att du befinner dig på en plats med bra belysning."; + +"Move closer" = "Flytta närmare"; + +"Move farther" = "Flytta längre bort"; + +"Move to a darker area" = "Flytta till en mörkare plats"; + +"Move to a well-lit area" = "Flytta till en plats med bra belysning"; + +"NRIC or FIN" = "NRIC eller FIN"; + +"No document detected" = "Inget dokument identifierades"; + +"Personal ID number" = "Personligt ID-nummer"; + +"Personal Information" = "Personuppgifter"; + +"Phone Number" = "Telefonnummer"; + +"Phone Verification" = "Telefonverifiering"; + +"Photo Library" = "Fotobibliotek"; + +"Please upload images of the front and back of your identity card" = "Ladda upp bilderna på fram- och baksidan av ditt id-kort"; + +"Position your face in the center of the frame." = "Placera ansiktet i mitten av ramen."; + +"Position your identity card in the center of the frame" = "Placera id-kortet i mitten av ramen"; + +"Retake Photos" = "Ta bilden på nytt"; + +"Scan" = "Skanna dokument"; + +"Scanned" = "Dokumentet har skannats"; + +"Select" = "Välj"; + +"Select a location to upload the back of your identity document from" = "Välj från vilken plats du vill ladda upp baksidan på din id-handling"; + +"Select a location to upload the front of your identity document from" = "Välj från vilken plats du vill ladda upp framsidan på din id-handling"; + +"Select back identity card photo" = "Välj en bild på id-kortets baksida"; + +"Select front identity card photo" = "Välj en bild på id-kortets framsida"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Selfiebilder"; + +"Selfie captures are complete" = "Selfiebilderna har tagits"; + +"Take Photo" = "Ta bild"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Bilderna av din id-handling har inte sparats. Vill du avsluta?"; + +"There was an error accessing the camera." = "Ett fel uppstod vid åtkomst till kameran."; + +"Try Again" = "Försök igen"; + +"Try reduce glare and make ID visible" = "Försök att minska bländningen och se till att id-handlingen är synlig"; + +"Unable to establish a connection." = "Det gick inte att upprätta en anslutning."; + +"Unsaved changes" = "Ändringarna har inte sparats"; + +"Upload" = "Ladda upp"; + +"Upload a Photo" = "Ladda upp en bild"; + +"Upload your photo ID" = "Ladda upp ditt foto-id"; + +"Uploading back identity card photo" = "Laddar upp bild på id-kortets baksida"; + +"Uploading front identity card photo" = "Laddar upp bild på id-kortets framsida"; + +"Verify your identity" = "Verifiera din identitet"; + +"We could not capture a high-quality image." = "Det gick inte att ta en bild av hög kvalitet."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Vi behöver behörighet att använda din kamera. Tillåt kameraåtkomst i appens inställningar."; + +"Welcome" = "Välkommen"; + +"You can either try again or upload an image from your device." = "Du kan antingen försöka igen eller ladda upp en bild från din enhet."; + +"Your selfie images have not been saved. Do you want to leave?" = "Dina selfies har inte sparats. Vill du avsluta?"; + +"driver's license" = "körkort"; + +"government-issued photo ID" = "myndighetsutfärdad id-handling med foto"; + +"passport" = "pass"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/tr.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/tr.lproj/Localizable.strings new file mode 100644 index 00000000..f096c545 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/tr.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Gerçekten siz olduğunuzu doğrulamak için sonraki adımda otomatik olarak birkaç fotoğraf çekilecek"; + +"Accepted forms of ID include" = "Kabul edilen kimlik türleri kapsamı"; + +"Alternatively, you may manually upload a photo of your identity document." = "Kimlik belgenizin fotoğrafını manuel olarak da yükleyebilirsiniz."; + +"App Settings" = "Uygulama Ayarları"; + +"Back identity card photo successfully uploaded" = "Kimliğin arka yüz fotoğrafı başarıyla yüklendi"; + +"Back of identity card" = "Kimliğin arka yüzü"; + +"Back of identity document" = "Kimlik belgesinin arka yüzü"; + +"Camera permission" = "Kamera izni"; + +"Camera unavailable" = "Kamera kullanılamıyor"; + +"Capturing…" = "Çekiliyor..."; + +"Choose File" = "Dosya Seç"; + +"Consent" = "Onay"; + +"Could not capture image" = "Fotoğraf alınamadı"; + +"Date of Birth" = "Doğum Tarihi"; + +"Date of birth does not look valid" = "Doğum tarihi geçerli görünmüyor"; + +"Details not visible" = "Ayrıntılar görünmüyor"; + +"Flip your identity card over to the other side" = "Kimliğinizin diğer yüzünü çevirin"; + +"Front identity card photo successfully uploaded" = "Kimliğin ön yüz fotoğrafı başarıyla yüklendi"; + +"Front of identity card" = "Kimliğin ön yüzü"; + +"Front of identity document" = "Kimlik belgesinin ön yüzü"; + +"Get ready to scan your photo ID" = "Fotoğraflı kimliğinizi taramak için hazırlanın"; + +"Get ready to take a selfie" = "Selfie çekmeye hazırlanın"; + +"Go Back" = "Geri Git"; + +"Hold still, scanning" = "Bekleyin, taranıyor"; + +"I'm ready" = "Hazırım"; + +"ID Number" = "Kimlik Numarası"; + +"Individual CPF" = "Bireysel CPF"; + +"Keep ID level" = "Kimlik seviyesini koruyun"; + +"Last 4 of Social Security number" = "Sosyal Güvenlik numarasının son 4 hanesi"; + +"Loading" = "Yükleniyor"; + +"Make sure all details are visible and focus" = "Tüm detayların görünür ve odak dahilinde olduğundan emin olun"; + +"Make sure you're in a well lit space." = "İyi ışık alan bir alanda olduğunuzdan emin olun."; + +"Move closer" = "Yakınlaştırın"; + +"Move farther" = "Uzaklaştırın"; + +"Move to a darker area" = "Daha karanlık bir yere geçin"; + +"Move to a well-lit area" = "İyi aydınlatılmış bir yere geçin"; + +"NRIC or FIN" = "NRIC veya FIN"; + +"No document detected" = "Belge tespit edilmedi"; + +"Personal ID number" = "Kişisel kimlik numarası"; + +"Personal Information" = "Kişisel Bilgiler"; + +"Phone Number" = "Telefon Numarası"; + +"Phone Verification" = "Telefon doğrulama"; + +"Photo Library" = "Fotoğraf Kitaplığı"; + +"Please upload images of the front and back of your identity card" = "Lütfen kimlik belgenizin ön ve arka yüzlerinin fotoğraflarını yükleyin"; + +"Position your face in the center of the frame." = "Yüzünüzü çerçevenin ortasına yerleştirin."; + +"Position your identity card in the center of the frame" = "Kimliğinizi çerçevenin ortasına yerleştirin"; + +"Retake Photos" = "Fotoğrafları Tekrar Çek"; + +"Scan" = "Tara"; + +"Scanned" = "Tarandı"; + +"Select" = "Seç"; + +"Select a location to upload the back of your identity document from" = "Kimlik belgenizin arka yüzünü yükleyeceğiniz konumu seçin"; + +"Select a location to upload the front of your identity document from" = "Kimlik belgenizin ön yüzünü yükleyeceğiniz konumu seçin"; + +"Select back identity card photo" = "Kimliğin arka yüz fotoğrafını seç"; + +"Select front identity card photo" = "Kimliğin ön yüz fotoğrafını seç"; + +"Selfie" = "Özçekim"; + +"Selfie captures" = "Özçekim çekimleri"; + +"Selfie captures are complete" = "Özçekim çekimleri tamamlandı"; + +"Take Photo" = "Fotoğraf Çek"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Kimlik belgenizin fotoğrafları kaydedilmedi. Ayrılmak istiyor musunuz?"; + +"There was an error accessing the camera." = "Kameraya erişilirken bir hata oluştu."; + +"Try Again" = "Tekrar Dene"; + +"Try reduce glare and make ID visible" = "Parlamayı azaltmaya ve kimlik kartını görünür hale getirmeye çalışın"; + +"Unable to establish a connection." = "Bağlantı kurulamıyor."; + +"Unsaved changes" = "Kaydedilmemiş değişiklikler"; + +"Upload" = "Yükle"; + +"Upload a Photo" = "Fotoğraf Yükle"; + +"Upload your photo ID" = "Fotoğraflı kimliğinizi yükleyin"; + +"Uploading back identity card photo" = "Kimliğin arka yüz fotoğrafı yükleniyor"; + +"Uploading front identity card photo" = "Kimliğin ön yüz fotoğrafı yükleniyor"; + +"Verify your identity" = "Kimliğinizi doğrulayın"; + +"We could not capture a high-quality image." = "Yüksek kaliteli bir fotoğraf yakalayamadık."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Kameranızı kullanmamız için izniniz gerekiyor. Lütfen uygulama ayarlarından kamera erişimine izin verin."; + +"Welcome" = "Hoş Geldiniz"; + +"You can either try again or upload an image from your device." = "Tekrar deneyebilir veya cihazınızdan bir fotoğraf yükleyebilirsiniz."; + +"Your selfie images have not been saved. Do you want to leave?" = "Özçekim görüntüleriniz kaydedilmedi. Çıkmak istiyor musunuz?"; + +"driver's license" = "ehliyet"; + +"government-issued photo ID" = "devlet tarafından verilen fotoğraflı kimlik"; + +"passport" = "pasaport"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/vi.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/vi.lproj/Localizable.strings new file mode 100644 index 00000000..59cde163 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/vi.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "Một vài ảnh sẽ được chụp tự động ở bước tiếp theo để xác minh đó là bạn"; + +"Accepted forms of ID include" = "Các loại ID được chấp nhận bao gồm"; + +"Alternatively, you may manually upload a photo of your identity document." = "Ngoài ra, bạn có thể tự tải lên ảnh giấy tờ tùy thân của mình."; + +"App Settings" = "Cài đặt Ứng dụng"; + +"Back identity card photo successfully uploaded" = "Đã thành công tải lên ảnh mặt sau thẻ căn cước"; + +"Back of identity card" = "Mặt sau thẻ căn cước"; + +"Back of identity document" = "Mặt sau giấy tờ nhận dạng"; + +"Camera permission" = "Cấp quyền máy ảnh"; + +"Camera unavailable" = "Máy ảnh không khả dụng"; + +"Capturing…" = "Đang chụp…"; + +"Choose File" = "Chọn tệp"; + +"Consent" = "Chấp thuận"; + +"Could not capture image" = "Không thể chụp ảnh"; + +"Date of Birth" = "Ngày sinh"; + +"Date of birth does not look valid" = "Ngày sinh có vẻ không hợp lệ"; + +"Details not visible" = "Không thấy rõ thông tin chi tiết"; + +"Flip your identity card over to the other side" = "Lật thẻ căn cước của bạn sang mặt khác"; + +"Front identity card photo successfully uploaded" = "Đã thành công tải lên ảnh mặt trước thẻ căn cước"; + +"Front of identity card" = "Mặt trước thẻ căn cước"; + +"Front of identity document" = "Mặt trước giấy tờ nhận dạng"; + +"Get ready to scan your photo ID" = "Chuẩn bị quét ID có ảnh"; + +"Get ready to take a selfie" = "Sẵn sàng để tự chụp ảnh"; + +"Go Back" = "Quay lại"; + +"Hold still, scanning" = "Giữ yên, đang quét"; + +"I'm ready" = "Đã sẵn sàng"; + +"ID Number" = "Số ID"; + +"Individual CPF" = "CPF cá nhân"; + +"Keep ID level" = "Giữ ID cân bằng"; + +"Last 4 of Social Security number" = "4 số cuối của số An Sinh Xã Hội"; + +"Loading" = "Đang tải"; + +"Make sure all details are visible and focus" = "Đảm bảo tất cả các thông tin nhìn rõ và được lấy nét"; + +"Make sure you're in a well lit space." = "Hãy đảm bảo bạn đang ở trong không gian đủ ánh sáng."; + +"Move closer" = "Đến gần hơn"; + +"Move farther" = "Lùi xa hơn"; + +"Move to a darker area" = "Di chuyển đến khu vực tối hơn"; + +"Move to a well-lit area" = "Di chuyển đến không gian đủ ánh sáng"; + +"NRIC or FIN" = "NRIC hoặc FIN"; + +"No document detected" = "Không phát hiện tài liệu"; + +"Personal ID number" = "Số ID Cá nhân"; + +"Personal Information" = "Thông tin cá nhân"; + +"Phone Number" = "Số điện thoại"; + +"Phone Verification" = "Xác minh Số điện thoại"; + +"Photo Library" = "Thư viện ảnh"; + +"Please upload images of the front and back of your identity card" = "Vui lòng tải lên hình ảnh mặt trước và mặt sau của thẻ căn cước"; + +"Position your face in the center of the frame." = "Đặt khuôn mặt của bạn ở giữa khung hình."; + +"Position your identity card in the center of the frame" = "Đặt thẻ căn cước của bạn ở giữa khung hình"; + +"Retake Photos" = "Chụp lại ảnh"; + +"Scan" = "Quét"; + +"Scanned" = "Đã quét"; + +"Select" = "Chọn"; + +"Select a location to upload the back of your identity document from" = "Chọn một vị trí để tải lên mặt sau giấy tờ tùy thân của bạn"; + +"Select a location to upload the front of your identity document from" = "Chọn một vị trí để tải lên mặt trước giấy tờ tùy thân của bạn"; + +"Select back identity card photo" = "Chọn ảnh mặt sau thẻ căn cước"; + +"Select front identity card photo" = "Chọn ảnh mặt trước thẻ căn cước"; + +"Selfie" = "Selfie"; + +"Selfie captures" = "Chụp ảnh selfie"; + +"Selfie captures are complete" = "Chụp ảnh selfie đã hoàn tất"; + +"Take Photo" = "Chụp ảnh"; + +"The images of your identity document have not been saved. Do you want to leave?" = "Ảnh của giấy tờ tùy thân của bạn chưa được lưu. Bạn có muốn rời đi không?"; + +"There was an error accessing the camera." = "Đã xảy ra lỗi khi truy cập máy ảnh."; + +"Try Again" = "Hãy thử lại"; + +"Try reduce glare and make ID visible" = "Hãy thử giảm độ chói và đảm bảo ID được nhìn rõ"; + +"Unable to establish a connection." = "Không thể thiết lập kết nối."; + +"Unsaved changes" = "Thay đổi chưa lưu"; + +"Upload" = "Tải lên"; + +"Upload a Photo" = "Tải lên ảnh"; + +"Upload your photo ID" = "Tải lên ID có ảnh của bạn"; + +"Uploading back identity card photo" = "Đang tải lên ảnh mặt sau thẻ căn cước"; + +"Uploading front identity card photo" = "Đang tải lên ảnh mặt trước thẻ căn cước"; + +"Verify your identity" = "Xác minh danh tính của bạn"; + +"We could not capture a high-quality image." = "Chúng tôi không thể chụp ảnh có chất lượng cao."; + +"We need permission to use your camera. Please allow camera access in app settings." = "Chúng tôi cần được cấp quyền để sử dụng máy ảnh của bạn. Vui lòng cho phép truy cập máy ảnh trong phần cài đặt ứng dụng."; + +"Welcome" = "Chào mừng"; + +"You can either try again or upload an image from your device." = "Bạn có thể thử lại hoặc tải lên hình ảnh từ thiết bị của mình."; + +"Your selfie images have not been saved. Do you want to leave?" = "Ảnh selfie của bạn chưa được lưu. Bạn có muốn rời khỏi?"; + +"driver's license" = "bằng lái xe"; + +"government-issued photo ID" = "giấy tờ tùy thân có ảnh do chính phủ cấp"; + +"passport" = "hộ chiếu"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/zh-HK.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..47b23b3f --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/zh-HK.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "下一步將自動拍攝一些照片來驗證是否是您本人"; + +"Accepted forms of ID include" = "接受的身份證件類型包括"; + +"Alternatively, you may manually upload a photo of your identity document." = "您也可以手動上載您的身份證件的照片。"; + +"App Settings" = "應用設定"; + +"Back identity card photo successfully uploaded" = "身份證背面照片已成功上載"; + +"Back of identity card" = "身份證背面"; + +"Back of identity document" = "身份證件背面"; + +"Camera permission" = "相機許可"; + +"Camera unavailable" = "相機不可用"; + +"Capturing…" = "正在拍攝..."; + +"Choose File" = "選擇文件"; + +"Consent" = "許可"; + +"Could not capture image" = "無法捕獲圖片"; + +"Date of Birth" = "出生日期"; + +"Date of birth does not look valid" = "出生日期看似無效"; + +"Details not visible" = "細節不可見"; + +"Flip your identity card over to the other side" = "將身份證翻到另一面"; + +"Front identity card photo successfully uploaded" = "身份證正面照片已成功上載"; + +"Front of identity card" = "身份證正面"; + +"Front of identity document" = "身份證件背面"; + +"Get ready to scan your photo ID" = "準備好掃描您帶照片的身份證件"; + +"Get ready to take a selfie" = "準備好自拍"; + +"Go Back" = "返回"; + +"Hold still, scanning" = "保持不動,正在掃描"; + +"I'm ready" = "我準備好了"; + +"ID Number" = "證件號碼"; + +"Individual CPF" = "個人 CPF"; + +"Keep ID level" = "保持證件水平"; + +"Last 4 of Social Security number" = "社會安全號碼後 4 位"; + +"Loading" = "正在載入"; + +"Make sure all details are visible and focus" = "確保所有細節是否清晰可見且對焦"; + +"Make sure you're in a well lit space." = "確保光線充足。"; + +"Move closer" = "請靠近一點"; + +"Move farther" = "請離遠一點"; + +"Move to a darker area" = "移動到較暗區域"; + +"Move to a well-lit area" = "移到光線良好處"; + +"NRIC or FIN" = "NRIC 或 FIN"; + +"No document detected" = "未檢測到證件"; + +"Personal ID number" = "個人身份證件號碼"; + +"Personal Information" = "個人資訊"; + +"Phone Number" = "電話號碼"; + +"Phone Verification" = "電話驗證"; + +"Photo Library" = "照片庫"; + +"Please upload images of the front and back of your identity card" = "請上載您的身份證正反面的照片"; + +"Position your face in the center of the frame." = "將臉放在畫面正中間。"; + +"Position your identity card in the center of the frame" = "將身份證放在畫面正中間"; + +"Retake Photos" = "重新拍照"; + +"Scan" = "掃描"; + +"Scanned" = "已掃描"; + +"Select" = "選擇"; + +"Select a location to upload the back of your identity document from" = "選擇一個位置,從那裡您上載您的身份證的背面"; + +"Select a location to upload the front of your identity document from" = "選擇一個位置,從那裡您上載您的身份證的正面"; + +"Select back identity card photo" = "選擇身份證背面照片"; + +"Select front identity card photo" = "選擇身份證正面照片"; + +"Selfie" = "自拍照"; + +"Selfie captures" = "自拍照拍攝"; + +"Selfie captures are complete" = "已完成自拍照拍攝"; + +"Take Photo" = "拍照"; + +"The images of your identity document have not been saved. Do you want to leave?" = "尚未保存您的身份證件的照片。想要離開嗎?"; + +"There was an error accessing the camera." = "訪問相機時發生了錯誤。"; + +"Try Again" = "重試"; + +"Try reduce glare and make ID visible" = "嘗試減少眩光並使身份證件可見"; + +"Unable to establish a connection." = "無法建立連接。"; + +"Unsaved changes" = "未保存的更改"; + +"Upload" = "上載"; + +"Upload a Photo" = "上載一張照片"; + +"Upload your photo ID" = "上載您帶照片的身份證件"; + +"Uploading back identity card photo" = "正在上載身份證背面照片"; + +"Uploading front identity card photo" = "正在上載身份證正面照片"; + +"Verify your identity" = "驗證您的身份"; + +"We could not capture a high-quality image." = "我們未能捕捉到高品質圖片。"; + +"We need permission to use your camera. Please allow camera access in app settings." = "我們需要使用您的相機的許可。請在應用設定中允許使用相機。"; + +"Welcome" = "歡迎"; + +"You can either try again or upload an image from your device." = "您可以重試或從設備上載一張照片。"; + +"Your selfie images have not been saved. Do you want to leave?" = "尚未保存您的自拍照。想要離開嗎?"; + +"driver's license" = "駕駛執照"; + +"government-issued photo ID" = "政府簽發的帶照片身份證"; + +"passport" = "護照"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..a817aaeb --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "下一步将自动拍摄一些照片来验证是否是您本人"; + +"Accepted forms of ID include" = "接受的身份证件包括"; + +"Alternatively, you may manually upload a photo of your identity document." = "您也可以手动上传您的身份证件的照片。"; + +"App Settings" = "应用设置"; + +"Back identity card photo successfully uploaded" = "身份证背面照片已成功上传"; + +"Back of identity card" = "身份证背面"; + +"Back of identity document" = "身份证件背面"; + +"Camera permission" = "相机许可"; + +"Camera unavailable" = "相机不可用"; + +"Capturing…" = "正在拍摄..."; + +"Choose File" = "选择文件"; + +"Consent" = "许可"; + +"Could not capture image" = "无法拍摄图片"; + +"Date of Birth" = "出生日期"; + +"Date of birth does not look valid" = "出生日期看似无效"; + +"Details not visible" = "细节不可见"; + +"Flip your identity card over to the other side" = "将身份证翻到另一面"; + +"Front identity card photo successfully uploaded" = "身份证正面照片已成功上传"; + +"Front of identity card" = "身份证正面"; + +"Front of identity document" = "身份证件正面"; + +"Get ready to scan your photo ID" = "准备好扫描您带照片的身份证件"; + +"Get ready to take a selfie" = "准备好自拍"; + +"Go Back" = "返回"; + +"Hold still, scanning" = "保持不动,正在扫描"; + +"I'm ready" = "我准备好了"; + +"ID Number" = "证件号码"; + +"Individual CPF" = "个人 CPF"; + +"Keep ID level" = "保持证件水平"; + +"Last 4 of Social Security number" = "社会保障号码后 4 位"; + +"Loading" = "正在加载"; + +"Make sure all details are visible and focus" = "确保所有细节清晰可见且对焦"; + +"Make sure you're in a well lit space." = "确保光线充足。"; + +"Move closer" = "请靠近一点"; + +"Move farther" = "请放远一点"; + +"Move to a darker area" = "移动到较暗区域"; + +"Move to a well-lit area" = "移到光线良好处"; + +"NRIC or FIN" = "NRIC 或 FIN"; + +"No document detected" = "未检测到证件"; + +"Personal ID number" = "个人身份证件号码"; + +"Personal Information" = "个人信息"; + +"Phone Number" = "电话号码"; + +"Phone Verification" = "电话验证"; + +"Photo Library" = "照片库"; + +"Please upload images of the front and back of your identity card" = "请上传您的身份证正反面的照片"; + +"Position your face in the center of the frame." = "将脸放在画面正中间。"; + +"Position your identity card in the center of the frame" = "将身份证放在画面正中间"; + +"Retake Photos" = "重新拍照"; + +"Scan" = "扫描"; + +"Scanned" = "已扫描"; + +"Select" = "选择"; + +"Select a location to upload the back of your identity document from" = "选择一个位置,从那里您上传您的身份证的背面"; + +"Select a location to upload the front of your identity document from" = "选择一个位置,从那里您上传您的身份证的正面"; + +"Select back identity card photo" = "选择身份证背面照片"; + +"Select front identity card photo" = "选择身份证正面照片"; + +"Selfie" = "自拍照"; + +"Selfie captures" = "自拍照拍摄"; + +"Selfie captures are complete" = "已完成自拍照拍摄"; + +"Take Photo" = "拍照"; + +"The images of your identity document have not been saved. Do you want to leave?" = "尚未保存您的身份证件的照片。想要离开吗?"; + +"There was an error accessing the camera." = "访问相机时发生了错误。"; + +"Try Again" = "重试"; + +"Try reduce glare and make ID visible" = "尝试减少眩光并使身份证件可见"; + +"Unable to establish a connection." = "无法建立连接。"; + +"Unsaved changes" = "未保存的更改"; + +"Upload" = "上传"; + +"Upload a Photo" = "上传一张照片"; + +"Upload your photo ID" = "上传您的带照片身份证件"; + +"Uploading back identity card photo" = "正在上传身份证背面照片"; + +"Uploading front identity card photo" = "正在上传身份证正面照片"; + +"Verify your identity" = "验证您的身份"; + +"We could not capture a high-quality image." = "我们未能捕捉到高质量图片。"; + +"We need permission to use your camera. Please allow camera access in app settings." = "我们需要使用您的相机的许可。请在应用设置中允许使用相机。"; + +"Welcome" = "欢迎"; + +"You can either try again or upload an image from your device." = "您可以重试或从设备上传一张照片。"; + +"Your selfie images have not been saved. Do you want to leave?" = "尚未保存您的自拍照。想要离开吗?"; + +"driver's license" = "驾驶证"; + +"government-issued photo ID" = "政府签发的带照片身份证"; + +"passport" = "护照"; diff --git a/StripeIdentity/StripeIdentity/Resources/Localizations/zh-Hant.lproj/Localizable.strings b/StripeIdentity/StripeIdentity/Resources/Localizations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..cc3a6dbb --- /dev/null +++ b/StripeIdentity/StripeIdentity/Resources/Localizations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,155 @@ +"A few photos will be taken automatically on the next step to verify it's you" = "下一步將自動拍攝一些相片來驗證是否是您本人"; + +"Accepted forms of ID include" = "接受的身分證件類型包括"; + +"Alternatively, you may manually upload a photo of your identity document." = "您也可以手動上傳您的身分證件的照片。"; + +"App Settings" = "應用設定"; + +"Back identity card photo successfully uploaded" = "身份證背面照片已成功上傳"; + +"Back of identity card" = "身分證背面"; + +"Back of identity document" = "身分證件背面"; + +"Camera permission" = "攝像機許可"; + +"Camera unavailable" = "相機不可用"; + +"Capturing…" = "正在拍攝..."; + +"Choose File" = "選擇檔案"; + +"Consent" = "許可"; + +"Could not capture image" = "無法捕獲圖片"; + +"Date of Birth" = "出生日期"; + +"Date of birth does not look valid" = "出生日期看似無效"; + +"Details not visible" = "細節不可見"; + +"Flip your identity card over to the other side" = "將身份證翻到另一面"; + +"Front identity card photo successfully uploaded" = "身份證正面照片已成功上傳"; + +"Front of identity card" = "身分證正面"; + +"Front of identity document" = "身分證件正面"; + +"Get ready to scan your photo ID" = "準備好掃描您帶照片的身分證件"; + +"Get ready to take a selfie" = "準備好自拍"; + +"Go Back" = "返回"; + +"Hold still, scanning" = "保持不動,正在掃描"; + +"I'm ready" = "我準備好了"; + +"ID Number" = "證件號碼"; + +"Individual CPF" = "個人 CPF"; + +"Keep ID level" = "保持證件水平"; + +"Last 4 of Social Security number" = "社會安全號碼後 4 位"; + +"Loading" = "正在載入"; + +"Make sure all details are visible and focus" = "確保所有細節是否清晰可見且對焦"; + +"Make sure you're in a well lit space." = "確保光線充足。"; + +"Move closer" = "請靠近一點"; + +"Move farther" = "請離遠一點"; + +"Move to a darker area" = "移動到較暗區域"; + +"Move to a well-lit area" = "移到光線良好處"; + +"NRIC or FIN" = "NRIC 或 FIN"; + +"No document detected" = "未檢測到證件"; + +"Personal ID number" = "個人身分證件號碼"; + +"Personal Information" = "個人資訊"; + +"Phone Number" = "電話號碼"; + +"Phone Verification" = "電話驗證"; + +"Photo Library" = "照片庫"; + +"Please upload images of the front and back of your identity card" = "請上傳您的身分證正反面的照片"; + +"Position your face in the center of the frame." = "將臉放在畫面正中間。"; + +"Position your identity card in the center of the frame" = "將身份證放在畫面正中間"; + +"Retake Photos" = "重新拍照"; + +"Scan" = "掃描"; + +"Scanned" = "已掃描"; + +"Select" = "選擇"; + +"Select a location to upload the back of your identity document from" = "選擇一個位置,從那裡您上傳您的身分證的背面"; + +"Select a location to upload the front of your identity document from" = "選擇一個位置,從那裡您上傳您的身分證的正面"; + +"Select back identity card photo" = "選擇身份證背面照片"; + +"Select front identity card photo" = "選擇身份證正面照片"; + +"Selfie" = "自拍照"; + +"Selfie captures" = "自拍照拍攝"; + +"Selfie captures are complete" = "已完成自拍照拍攝"; + +"Take Photo" = "拍照"; + +"The images of your identity document have not been saved. Do you want to leave?" = "尚未保存您的身份證件的照片。想要離開嗎?"; + +"There was an error accessing the camera." = "訪問相機時發生了錯誤。"; + +"Try Again" = "重試"; + +"Try reduce glare and make ID visible" = "嘗試減少眩光並使身分證件可見"; + +"Unable to establish a connection." = "無法建立連接。"; + +"Unsaved changes" = "未保存的更改"; + +"Upload" = "上傳"; + +"Upload a Photo" = "上傳一張照片"; + +"Upload your photo ID" = "上傳您帶照片的身分證件"; + +"Uploading back identity card photo" = "正在上傳身份證背面照片"; + +"Uploading front identity card photo" = "正在上傳身份證正面照片"; + +"Verify your identity" = "驗證您的身份"; + +"We could not capture a high-quality image." = "我們未能捕捉到高品質圖片。"; + +"We need permission to use your camera. Please allow camera access in app settings." = "我們需要使用您的相機的許可。請在應用設定中允許使用相機。"; + +"Welcome" = "歡迎"; + +"You can either try again or upload an image from your device." = "您可以重試或從裝置上傳一張照片。"; + +"Your selfie images have not been saved. Do you want to leave?" = "尚未保存您的自拍照。想要離開嗎?"; + +"driver's license" = "駕駛執照"; + +"government-issued photo ID" = "政府簽發的帶照片身分證件"; + +"passport" = "護照"; diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentScanner+API.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentScanner+API.swift new file mode 100644 index 00000000..6437e8b6 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentScanner+API.swift @@ -0,0 +1,45 @@ +// +// DocumentScanner+API.swift +// StripeIdentity +// +// Created by Mel Ludowise on 4/14/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import Vision + +extension DocumentScanner.Configuration { + // TODO: collect historical data and update the threshold from server. + static let defaultBlurThreshold: Decimal = 0.0 + + init( + from capturePageConfig: StripeAPI.VerificationPageStaticContentDocumentCapturePage, + for locale: Locale = .autoupdatingCurrent + ) { + self.init( + idDetectorMinScore: capturePageConfig.models.idDetectorMinScore.floatValue, + idDetectorMinIOU: capturePageConfig.models.idDetectorMinIou.floatValue, + motionBlurMinIOU: capturePageConfig.motionBlurMinIou.floatValue, + motionBlurMinDuration: TimeInterval(capturePageConfig.motionBlurMinDuration) / 1000, + backIdCardBarcodeSymbology: capturePageConfig.symbology(for: locale), + backIdCardBarcodeTimeout: TimeInterval(capturePageConfig.iosIdCardBackBarcodeTimeout) + / 1000, + blurThreshold: (capturePageConfig.blurThreshold ?? DocumentScanner.Configuration.defaultBlurThreshold).floatValue, + highResImageCorpPadding: capturePageConfig.highResImageCropPadding + ) + } +} + +extension StripeAPI.VerificationPageStaticContentDocumentCapturePage { + func symbology(for locale: Locale) -> VNBarcodeSymbology? { + guard let regionCode = locale.regionCode, + let symbologyString = iosIdCardBackCountryBarcodeSymbologies[regionCode] + else { + return nil + } + + return VNBarcodeSymbology(fromStringValue: symbologyString) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentType+StripeIdentity.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentType+StripeIdentity.swift new file mode 100644 index 00000000..05535319 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentType+StripeIdentity.swift @@ -0,0 +1,21 @@ +// +// DocumentType+StripeIdentity.swift +// StripeIdentity +// +// Created by Mel Ludowise on 1/11/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension DocumentType { + var hasBack: Bool { + switch self { + case .passport: + return false + case .drivingLicense, + .idCard: + return true + } + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentUploader+API.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentUploader+API.swift new file mode 100644 index 00000000..7bffce4d --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/DocumentUploader+API.swift @@ -0,0 +1,65 @@ +// +// DocumentUploader+API.swift +// StripeIdentity +// +// Created by Mel Ludowise on 1/6/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCameraCore +import UIKit + +extension IdentityImageUploader.Configuration { + init( + from capturePageConfig: StripeAPI.VerificationPageStaticContentDocumentCapturePage + ) { + self.init( + filePurpose: capturePageConfig.filePurpose, + highResImageCompressionQuality: capturePageConfig.highResImageCompressionQuality, + highResImageCropPadding: capturePageConfig.highResImageCropPadding, + highResImageMaxDimension: capturePageConfig.highResImageMaxDimension, + lowResImageCompressionQuality: capturePageConfig.lowResImageCompressionQuality, + lowResImageMaxDimension: capturePageConfig.lowResImageMaxDimension + ) + } +} + +extension StripeAPI.VerificationPageDataDocumentFileData { + init( + documentScannerOutput: DocumentScannerOutput?, + highResImage: String, + lowResImage: String?, + exifMetadata: CameraExifMetadata?, + uploadMethod: FileUploadMethod, + forceConfirm: Bool = false + ) { + // TODO(mludowise|IDPROD-3269): Encode additional properties from scanner output + let scores = documentScannerOutput?.idDetectorOutput.allClassificationScores + self.init( + backScore: scores?[.idCardBack].map { TwoDecimalFloat($0) }, + brightnessValue: exifMetadata?.brightnessValue.map { TwoDecimalFloat(double: $0) }, + cameraLensModel: exifMetadata?.lensModel, + exposureDuration: documentScannerOutput?.cameraProperties.map { + Int($0.exposureDuration.seconds * 1000) + }, + exposureIso: documentScannerOutput?.cameraProperties.map { + TwoDecimalFloat($0.exposureISO) + }, + focalLength: exifMetadata?.focalLength.map { TwoDecimalFloat(double: $0) }, + frontCardScore: scores?[.idCardFront].map { TwoDecimalFloat($0) }, + highResImage: highResImage, + invalidScore: scores?[.invalid].map { TwoDecimalFloat($0) }, + iosBarcodeDecoded: documentScannerOutput?.barcode?.hasBarcode, + iosBarcodeSymbology: documentScannerOutput?.barcode?.symbology.stringValue, + iosTimeToFindBarcode: documentScannerOutput?.barcode.map { + Int($0.timeTryingToFindBarcode * 1000) + }, + isVirtualCamera: documentScannerOutput?.cameraProperties?.isVirtualDevice, + lowResImage: lowResImage, + passportScore: scores?[.passport].map { TwoDecimalFloat($0) }, + uploadMethod: uploadMethod, + forceConfirm: forceConfirm + ) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/FaceScanner+API.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/FaceScanner+API.swift new file mode 100644 index 00000000..e81937cd --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/FaceScanner+API.swift @@ -0,0 +1,28 @@ +// +// FaceScanner+API.swift +// StripeIdentity +// +// Created by Mel Ludowise on 6/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension FaceScanner.Configuration { + init( + from selfiePageConfig: StripeAPI.VerificationPageStaticContentSelfiePage + ) { + self.init( + faceDetectorMinScore: selfiePageConfig.models.faceDetectorMinScore.floatValue, + faceDetectorMinIOU: selfiePageConfig.models.faceDetectorMinIou.floatValue, + maxCenteredThreshold: .init( + x: selfiePageConfig.maxCenteredThresholdX, + y: selfiePageConfig.maxCenteredThresholdY + ), + minEdgeThreshold: selfiePageConfig.minEdgeThreshold, + minCoverageThreshold: selfiePageConfig.minCoverageThreshold, + maxCoverageThreshold: selfiePageConfig.maxCoverageThreshold + ) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/IdentityAPIClient.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/IdentityAPIClient.swift new file mode 100644 index 00000000..d657b228 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/IdentityAPIClient.swift @@ -0,0 +1,175 @@ +// +// IdentityAPIClient.swift +// StripeIdentity +// +// Created by Mel Ludowise on 10/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +protocol IdentityAPIClient: AnyObject { + var verificationSessionId: String { get } + var apiVersion: Int { get set } + + func getIdentityVerificationPage() -> Promise + + func updateIdentityVerificationPageData( + updating verificationData: StripeAPI.VerificationPageDataUpdate + ) -> Promise + + func submitIdentityVerificationPage() -> Promise + + func uploadImage( + _ image: UIImage, + compressionQuality: CGFloat, + purpose: String, + fileName: String + ) -> Future + + func verifyTestVerificationSession( + simulateDelay: Bool + ) -> Promise + + func unverifyTestVerificationSession( + simulateDelay: Bool + ) -> Promise + + func generatePhoneOtp() -> Promise + + func cannotPhoneVerifyOtp() -> Promise +} + +final class IdentityAPIClientImpl: IdentityAPIClient { + /// The latest production-ready version of the VerificationPages API that the + /// SDK is capable of using. + /// + /// - Note: Update this value when a new API version is ready for use in production. + static let productionApiVersion: Int = 6 + + var betas: Set { + return ["identity_client_api=v\(apiVersion)"] + } + + let apiClient: STPAPIClient + let verificationSessionId: String + + /// The VerificationPages API version used to make all API requests. + /// + /// - Note: This should only be modified when testing endpoints not yet in production. + var apiVersion = IdentityAPIClientImpl.productionApiVersion { + didSet { + apiClient.betas = betas + } + } + + private init( + verificationSessionId: String, + apiClient: STPAPIClient + ) { + self.verificationSessionId = verificationSessionId + self.apiClient = apiClient + } + + convenience init( + verificationSessionId: String, + ephemeralKeySecret: String + ) { + self.init( + verificationSessionId: verificationSessionId, + apiClient: STPAPIClient(publishableKey: ephemeralKeySecret) + ) + apiClient.betas = betas + apiClient.appInfo = STPAPIClient.shared.appInfo + } + + func getIdentityVerificationPage() -> Promise { + return apiClient.get( + resource: APIEndpointVerificationPage(id: verificationSessionId), + parameters: ["app_identifier": Bundle.main.bundleIdentifier ?? ""] + ) + } + + func updateIdentityVerificationPageData( + updating verificationData: StripeAPI.VerificationPageDataUpdate + ) -> Promise { + return apiClient.post( + resource: APIEndpointVerificationPageData(id: verificationSessionId), + object: verificationData + ) + } + + func submitIdentityVerificationPage() -> Promise { + return apiClient.post( + resource: APIEndpointVerificationPageSubmit(id: verificationSessionId), + parameters: [:] + ) + } + + func uploadImage( + _ image: UIImage, + compressionQuality: CGFloat, + purpose: String, + fileName: String + ) -> Future { + return apiClient.uploadImageAndGetMetrics( + image, + compressionQuality: compressionQuality, + purpose: purpose, + fileName: fileName, + ownedBy: verificationSessionId + ) + } + + func verifyTestVerificationSession(simulateDelay: Bool) -> Promise { + return apiClient.post( + resource: APIEndpointVerificationPageTestingVerify(id: verificationSessionId), + parameters: ["simulate_delay": simulateDelay] + ) + } + + func unverifyTestVerificationSession(simulateDelay: Bool) -> Promise { + return apiClient.post( + resource: APIEndpointVerificationPageTestingUnverify(id: verificationSessionId), + parameters: ["simulate_delay": simulateDelay] + ) + } + + func generatePhoneOtp() -> StripeCore.Promise { + return apiClient.post( + resource: APIEndpointVerificationPagePhoneOtpGenerate(id: verificationSessionId), + parameters: [:] + ) + } + + func cannotPhoneVerifyOtp() -> StripeCore.Promise { + return apiClient.post( + resource: APIEndpointVerificationPagePhoneOtpCannotVerify(id: verificationSessionId), + parameters: [:] + ) + } +} + +private func APIEndpointVerificationPage(id: String) -> String { + return "identity/verification_pages/\(id)" +} +private func APIEndpointVerificationPageData(id: String) -> String { + return "identity/verification_pages/\(id)/data" +} +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" +} +private func APIEndpointVerificationPagePhoneOtpGenerate(id: String) -> String { + return "identity/verification_pages/\(id)/phone_otp/generate" +} +private func APIEndpointVerificationPagePhoneOtpCannotVerify(id: String) -> String { + return "identity/verification_pages/\(id)/phone_otp/cannot_verify" +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/DocumentType.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/DocumentType.swift new file mode 100644 index 00000000..13162e7e --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/DocumentType.swift @@ -0,0 +1,16 @@ +// +// DocumentType.swift +// StripeIdentity +// +// Created by Mel Ludowise on 2/18/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// +import Foundation + +enum DocumentType: String, Encodable, CaseIterable, Equatable { + // NOTE: The declaration order determines the default order these + // are displayed in the UI on the document selection screen + case drivingLicense = "driving_license" + case idCard = "id_card" + case passport +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/TruncatedDecimal.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/TruncatedDecimal.swift new file mode 100644 index 00000000..5e7661d3 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/TruncatedDecimal.swift @@ -0,0 +1,61 @@ +// +// TruncatedDecimal.swift +// StripeIdentity +// +// Created by Mel Ludowise on 2/9/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Truncates the decimal number to a specific number of decimal places when +/// encoding it. +protocol TruncatedDecimal: Codable, Equatable { + /// The value type this decimal is wrapping (e.g. Float, Double, CGFloat) + associatedtype ValueType: (FloatingPoint & CVarArg & Codable & LosslessStringConvertible) + + /// The number of decimal digits that should be encoded + static var numberOfDecimalDigits: UInt { get } + + /// The wrapped value + var value: ValueType { get } + + init(_ value: ValueType) +} + +// MARK: - Codable + +extension TruncatedDecimal { + init( + from decoder: Decoder + ) throws { + self.init(try ValueType(from: decoder)) + } + + func encode(to encoder: Encoder) throws { + // Because STPAPIClient always encodes as form data, we can use a + // string-encoding to format the number to the correct decimal places + let string = String(format: "%.\(Self.numberOfDecimalDigits)f", value) + try string.encode(to: encoder) + } +} + +// MARK: - TwoDecimalFloat +/// Truncates a float to 2 decimal places when encoding it +struct TwoDecimalFloat: TruncatedDecimal { + static let numberOfDecimalDigits: UInt = 2 + + let value: Float + + init( + _ value: Float + ) { + self.value = value + } + + init( + double: Double + ) { + self.init(Float(double)) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPage.swift new file mode 100644 index 00000000..b2296b32 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPage.swift @@ -0,0 +1,58 @@ +// +// VerificationPage.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + /// A VerificationPage contains the static content and initial state that is required for Stripe Identity's native mobile SDKs to render the verification flow. + + struct VerificationPage: Decodable, Equatable { + enum Status: String, Codable, Equatable { + case canceled = "canceled" + case processing = "processing" + case requiresInput = "requires_input" + case verified = "verified" + } + let biometricConsent: VerificationPageStaticContentConsentPage + let documentCapture: VerificationPageStaticContentDocumentCapturePage + let documentSelect: VerificationPageStaticContentDocumentSelectPage + let individual: VerificationPageStaticContentIndividualPage + let countryNotListed: VerificationPageStaticContentCountryNotListedPage + let individualWelcome: VerificationPageStaticContentIndividualWelcomePage + let phoneOtp: VerificationPageStaticContentPhoneOtpPage? + /// The short-lived URL that can be used in the case that the client cannot support the VerificationSession. + let fallbackUrl: String + /// Unique identifier for the object. + let id: String + /// Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. + let livemode: Bool + let requirements: VerificationPageRequirements + /// Static content for the selfie page + let selfie: VerificationPageStaticContentSelfiePage? + /// Status of the associated VerificationSession. + let status: Status + /// If true, the associated VerificationSession has been submitted for processing. + let submitted: Bool + let success: VerificationPageStaticContentTextPage + /// If true, the client cannot support the VerificationSession. + let unsupportedClient: Bool + let bottomsheet: [String: VerificationPageStaticContentBottomSheetContent]? + /// session ID to identify experiement exposure + let userSessionId: String + let experiments: [VerificationPageStaticContentExperiment] + + } + +} + +extension StripeAPI.VerificationPage { + func copyWithNewMissings(newMissings: Set) -> 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, bottomsheet: self.bottomsheet, userSessionId: self.userSessionId, experiments: self.experiments) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageFieldType.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageFieldType.swift new file mode 100644 index 00000000..888f76d0 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageFieldType.swift @@ -0,0 +1,30 @@ +// +// VerificationPageFieldType.swift +// StripeIdentity +// +// Created by Mel Ludowise on 2/26/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension StripeAPI { + enum VerificationPageFieldType: String, Codable, Equatable, CaseIterable { + case biometricConsent = "biometric_consent" + case face = "face" + case idDocumentBack = "id_document_back" + case idDocumentFront = "id_document_front" + case idNumber = "id_number" + case dob = "dob" + case name = "name" + case address = "address" + case phoneNumber = "phone_number" + case phoneOtp = "phone_otp" + } +} + +extension StripeAPI.VerificationPageFieldType { + func supportsForceConfirm() -> Bool { + return self == .idDocumentFront || self == .idDocumentBack + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageIconType.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageIconType.swift new file mode 100644 index 00000000..2ee9ac80 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageIconType.swift @@ -0,0 +1,53 @@ +// +// VerificationPageIconType.swift +// StripeIdentity +// +// Created by Chen Cen on 9/14/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +extension StripeAPI { + enum VerificationPageIconType: String, Codable, Equatable, CaseIterable { + case cloud = "cloud" + case document = "document" + case createIdentityVerification = "create_identity_verification" + case lock = "lock" + case moved = "moved" + case wallet = "wallet" + case camera = "camera" + case disputeProtection = "dispute_protection" + case phone = "phone" + } +} + +extension StripeAPI.VerificationPageIconType { + func makeImage() -> UIImage { + switch self { + case .cloud: + return Image.iconCloud.makeImage().withTintColor(IdentityUI.darkIconColor) + case .document: + return Image.iconDocument.makeImage().withTintColor(IdentityUI.darkIconColor) + case .createIdentityVerification: + return Image.iconCreateIdentityVerification.makeImage().withTintColor(IdentityUI.darkIconColor) + case .lock: + return Image.iconLock.makeImage().withTintColor(IdentityUI.darkIconColor) + case .moved: + return Image.iconMoved.makeImage().withTintColor(IdentityUI.darkIconColor) + case .wallet: + return Image.iconWallet.makeImage().withTintColor(IdentityUI.darkIconColor) + case .camera: + return Image.iconCameraClassic.makeImage().withTintColor(IdentityUI.darkIconColor) + case .disputeProtection: + return Image.iconDisputeProtection.makeImage().withTintColor(IdentityUI.darkIconColor) + case .phone: + return Image.iconPhone.makeImage().withTintColor(IdentityUI.darkIconColor) + } + } + + func makeImageView() -> UIImageView { + return UIImageView(image: self.makeImage()) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageRequirements.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageRequirements.swift new file mode 100644 index 00000000..3a866e7e --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageRequirements.swift @@ -0,0 +1,17 @@ +// +// VerificationPageRequirements.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageRequirements: Decodable, Equatable { + let missing: Set + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticConsentLineContent.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticConsentLineContent.swift new file mode 100644 index 00000000..fdda37d5 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticConsentLineContent.swift @@ -0,0 +1,18 @@ +// +// VerificationPageStaticConsentLineContent.swift +// StripeIdentity +// +// Created by Chen Cen on 9/20/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticConsentLineContent: Decodable, Equatable { + let icon: VerificationPageIconType + let content: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentBottomSheetContent.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentBottomSheetContent.swift new file mode 100644 index 00000000..932bcafa --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentBottomSheetContent.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentBottomSheetContent.swift +// StripeIdentity +// +// Created by Chen Cen on 9/14/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentBottomSheetContent: Decodable, Equatable { + let bottomsheetId: String + let title: String? + let lines: [VerificationPageStaticContentBottomSheetLineContent] + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentBottomSheetLineContent.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentBottomSheetLineContent.swift new file mode 100644 index 00000000..a65ea09a --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentBottomSheetLineContent.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentBottomSheetLineContent.swift +// StripeIdentity +// +// Created by Chen Cen on 9/14/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentBottomSheetLineContent: Decodable, Equatable { + let icon: VerificationPageIconType? + let title: String + let content: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentConsentPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentConsentPage.swift new file mode 100644 index 00000000..b556ad09 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentConsentPage.swift @@ -0,0 +1,22 @@ +// +// VerificationPageStaticContentConsentPage.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentConsentPage: Decodable, Equatable { + let acceptButtonText: String + let declineButtonText: String + let privacyPolicy: String + let title: String? + let scrollToContinueButtonText: String + let lines: [VerificationPageStaticConsentLineContent] + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentCountryNotListedPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentCountryNotListedPage.swift new file mode 100644 index 00000000..44cb799d --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentCountryNotListedPage.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentCountryNotListedPage.swift +// StripeIdentity +// +// Created by Chen Cen on 2/1/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageStaticContentCountryNotListedPage: Decodable, Equatable { + let title: String + let body: String + let cancelButtonText: String + let idFromOtherCountryTextButtonText: String + let addressFromOtherCountryTextButtonText: String + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentCaptureModels.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentCaptureModels.swift new file mode 100644 index 00000000..0464ac17 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentCaptureModels.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentDocumentCaptureModels.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentDocumentCaptureModels: Decodable, Equatable { + let idDetectorMinIou: Decimal + let idDetectorMinScore: Decimal + let idDetectorUrl: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentCapturePage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentCapturePage.swift new file mode 100644 index 00000000..7c713f12 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentCapturePage.swift @@ -0,0 +1,31 @@ +// +// VerificationPageStaticContentDocumentCapturePage.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import CoreGraphics +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentDocumentCapturePage: Decodable, Equatable { + let autocaptureTimeout: Int + let filePurpose: String + let highResImageCompressionQuality: CGFloat + let highResImageCropPadding: CGFloat + let highResImageMaxDimension: Int + let iosIdCardBackBarcodeTimeout: Int + let iosIdCardBackCountryBarcodeSymbologies: [String: String] + let lowResImageCompressionQuality: CGFloat + let lowResImageMaxDimension: Int + let models: VerificationPageStaticContentDocumentCaptureModels + let motionBlurMinDuration: Int + let motionBlurMinIou: Decimal + let requireLiveCapture: Bool + let blurThreshold: Decimal? + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentSelectPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentSelectPage.swift new file mode 100644 index 00000000..a9b81a5f --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentDocumentSelectPage.swift @@ -0,0 +1,20 @@ +// +// VerificationPageStaticContentDocumentSelectPage.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentDocumentSelectPage: Decodable, Equatable { + let body: String? + let buttonText: String + let idDocumentTypeAllowlist: [String: String] + let title: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentExperiment.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentExperiment.swift new file mode 100644 index 00000000..645e55f6 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentExperiment.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentExperiment.swift +// StripeIdentity +// +// Created by Chen Cen on 3/8/24. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentExperiment: Decodable, Equatable { + let experimentName: String + let eventName: String + let eventMetadata: [String: String] + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentIndividualPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentIndividualPage.swift new file mode 100644 index 00000000..c54e5f8a --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentIndividualPage.swift @@ -0,0 +1,22 @@ +// +// VerificationPageStaticContentIndividualPage.swift +// StripeIdentity +// +// Created by Chen Cen on 1/27/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentIndividualPage: Decodable, Equatable { + let addressCountries: [String: String] + let buttonText: String + let title: String + let idNumberCountries: [String: String] + let idNumberCountryNotListedTextButtonText: String + let addressCountryNotListedTextButtonText: String + let phoneNumberCountries: [String: String] + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentIndividualWelcomePage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentIndividualWelcomePage.swift new file mode 100644 index 00000000..359ae05b --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentIndividualWelcomePage.swift @@ -0,0 +1,21 @@ +// +// VerificationPageStaticContentIndividualWelcomePage.swift +// StripeIdentity +// +// Created by Chen Cen on 2/14/23. +// + +import Foundation + +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentIndividualWelcomePage: Decodable, Equatable { + let getStartedButtonText: String + let privacyPolicy: String + let title: String + let lines: [VerificationPageStaticConsentLineContent] + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentPhoneOtpPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentPhoneOtpPage.swift new file mode 100644 index 00000000..6cf41677 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentPhoneOtpPage.swift @@ -0,0 +1,23 @@ +// +// VerificationPageStaticContentPhoneOtpPage.swift +// StripeIdentity +// +// Created by Chen Cen on 6/14/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentPhoneOtpPage: Decodable, Equatable { + let title: String + let body: String + let redactedPhoneNumber: String? + let errorOtpMessage: String + let resendButtonText: String + let cannotVerifyButtonText: String + let otpLength: Int + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentSelfieModels.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentSelfieModels.swift new file mode 100644 index 00000000..de6138f3 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentSelfieModels.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentSelfieModels.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentSelfieModels: Decodable, Equatable { + let faceDetectorMinIou: Decimal + let faceDetectorMinScore: Decimal + let faceDetectorUrl: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentSelfiePage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentSelfiePage.swift new file mode 100644 index 00000000..68f4e61b --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentSelfiePage.swift @@ -0,0 +1,34 @@ +// +// VerificationPageStaticContentSelfiePage.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import CoreGraphics +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentSelfiePage: Decodable, Equatable { + let autocaptureTimeout: Int + let filePurpose: String + let highResImageCompressionQuality: CGFloat + let highResImageCropPadding: CGFloat + let highResImageMaxDimension: Int + let lowResImageCompressionQuality: CGFloat + let lowResImageMaxDimension: Int + let maxCenteredThresholdX: CGFloat + let maxCenteredThresholdY: CGFloat + let maxCoverageThreshold: CGFloat + let minCoverageThreshold: CGFloat + let minEdgeThreshold: CGFloat + let models: VerificationPageStaticContentSelfieModels + let numSamples: Int + let sampleInterval: Int + let trainingConsentText: String + let blurThreshold: Decimal? + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentTextPage.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentTextPage.swift new file mode 100644 index 00000000..ce7ee0a7 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPage/VerificationPageStaticContentTextPage.swift @@ -0,0 +1,19 @@ +// +// VerificationPageStaticContentTextPage.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageStaticContentTextPage: Decodable, Equatable { + let body: String + let buttonText: String + let title: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageData.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageData.swift new file mode 100644 index 00000000..942a23e6 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageData.swift @@ -0,0 +1,42 @@ +// +// VerificationPageData.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + /// VerificationPageData contains the state of a verification, including what information needs to be collected to complete the verification flow. + + struct VerificationPageData: Decodable, Equatable { + typealias Status = VerificationPage.Status + + /// Unique identifier for the object. + let id: String + let requirements: VerificationPageDataRequirements + /// Status of the associated VerificationSession. + 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 + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageDataRequirementError.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageDataRequirementError.swift new file mode 100644 index 00000000..85555bd8 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageDataRequirementError.swift @@ -0,0 +1,21 @@ +// +// VerificationPageDataRequirementError.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageDataRequirementError: Decodable, Equatable { + let backButtonText: String? + let body: String + let continueButtonText: String? + let requirement: VerificationPageFieldType + let title: String + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageDataRequirements.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageDataRequirements.swift new file mode 100644 index 00000000..4b15ccc2 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageData/VerificationPageDataRequirements.swift @@ -0,0 +1,18 @@ +// +// VerificationPageDataRequirements.swift +// +// Generated by swagger-codegen +// https://github.com/swagger-api/swagger-codegen +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + + struct VerificationPageDataRequirements: Decodable, Equatable { + let errors: [VerificationPageDataRequirementError] + let missing: Set + } + +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/RequiredInternationalAddress.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/RequiredInternationalAddress.swift new file mode 100644 index 00000000..72d5d28f --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/RequiredInternationalAddress.swift @@ -0,0 +1,20 @@ +// +// RequiredInternationalAddress.swift +// StripeIdentity +// +// Created by Chen Cen on 1/27/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct RequiredInternationalAddress: Encodable, Equatable { + let line1: String + let line2: String? + let city: String? + let postalCode: String? + let state: String? + let country: String? + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageClearData.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageClearData.swift new file mode 100644 index 00000000..86a780f3 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageClearData.swift @@ -0,0 +1,42 @@ +// +// VerificationPageClearData.swift +// StripeIdentity +// +// Created by Mel Ludowise on 3/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageClearData: Encodable, Equatable { + let biometricConsent: Bool? + let face: Bool? + let idDocumentBack: Bool? + let idDocumentFront: Bool? + let idNumber: Bool? + let dob: Bool? + let name: Bool? + let address: Bool? + let phoneOtp: Bool? + } +} + +extension StripeAPI.VerificationPageClearData { + init( + clearFields fields: Set + ) { + self.init( + biometricConsent: fields.contains(.biometricConsent), + face: fields.contains(.face), + idDocumentBack: fields.contains(.idDocumentBack), + idDocumentFront: fields.contains(.idDocumentFront), + idNumber: fields.contains(.idNumber), + dob: fields.contains(.dob), + name: fields.contains(.name), + address: fields.contains(.address), + phoneOtp: fields.contains(.phoneOtp) + ) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageCollectedData.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageCollectedData.swift new file mode 100644 index 00000000..0398fc67 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageCollectedData.swift @@ -0,0 +1,151 @@ +// +// VerificationPageCollectedData.swift +// StripeIdentity +// +// Created by Mel Ludowise on 2/26/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageCollectedData: Encodable, Equatable { + + private(set) var biometricConsent: Bool? + private(set) var face: VerificationPageDataFace? + private(set) var idDocumentBack: VerificationPageDataDocumentFileData? + private(set) var idDocumentFront: VerificationPageDataDocumentFileData? + private(set) var idNumber: VerificationPageDataIdNumber? + private(set) var dob: VerificationPageDataDob? + private(set) var name: VerificationPageDataName? + private(set) var address: RequiredInternationalAddress? + private(set) var phone: VerificationPageDataPhone? + private(set) var phoneOtp: String? + + init( + biometricConsent: Bool? = nil, + face: VerificationPageDataFace? = nil, + idDocumentBack: VerificationPageDataDocumentFileData? = nil, + idDocumentFront: VerificationPageDataDocumentFileData? = nil, + idNumber: VerificationPageDataIdNumber? = nil, + dob: VerificationPageDataDob? = nil, + name: VerificationPageDataName? = nil, + address: RequiredInternationalAddress? = nil, + phone: VerificationPageDataPhone? = nil, + phoneOtp: String? = nil + ) { + self.biometricConsent = biometricConsent + self.face = face + self.idDocumentBack = idDocumentBack + self.idDocumentFront = idDocumentFront + self.idNumber = idNumber + self.dob = dob + self.name = name + self.address = address + self.phone = phone + self.phoneOtp = phoneOtp + } + } +} + +/// All mutating functions needs to pass all values explicitly to the new object, as the default value would be nil. +extension StripeAPI.VerificationPageCollectedData { + /// Returns a new `VerificationPageCollectedData`, merging the data from this + /// one with the provided one. + func merging( + _ otherData: StripeAPI.VerificationPageCollectedData + ) -> StripeAPI.VerificationPageCollectedData { + return StripeAPI.VerificationPageCollectedData( + biometricConsent: otherData.biometricConsent ?? self.biometricConsent, + face: otherData.face ?? self.face, + idDocumentBack: otherData.idDocumentBack ?? self.idDocumentBack, + idDocumentFront: otherData.idDocumentFront ?? self.idDocumentFront, + idNumber: otherData.idNumber ?? self.idNumber, + dob: otherData.dob ?? self.dob, + name: otherData.name ?? self.name, + address: otherData.address ?? self.address, + phone: otherData.phone ?? self.phone, + phoneOtp: otherData.phoneOtp ?? self.phoneOtp + ) + } + + /// Merges the data from the provided `VerificationPageCollectedData` into this one. + mutating func merge(_ otherData: StripeAPI.VerificationPageCollectedData) { + self = self.merging(otherData) + } + + mutating func clearData(field: StripeAPI.VerificationPageFieldType) { + switch field { + case .biometricConsent: + self.biometricConsent = nil + case .face: + self.face = nil + case .idDocumentBack: + self.idDocumentBack = nil + case .idDocumentFront: + self.idDocumentFront = nil + case .idNumber: + self.idNumber = nil + case .dob: + self.dob = nil + case .name: + self.name = nil + case .address: + self.address = nil + case .phoneNumber: + self.phone = nil + case .phoneOtp: + self.phoneOtp = nil + } + } + + /// Helper to determine the front document score for analytics purposes + var frontDocumentScore: TwoDecimalFloat? { + // return the larger of the two + guard let frontCardScore = idDocumentFront?.frontCardScore?.value, let passportScore = idDocumentFront?.passportScore?.value else + { + return nil + } + if frontCardScore > passportScore { + return idDocumentFront?.frontCardScore + } else { + return idDocumentFront?.passportScore + } + } + + var collectedTypes: Set { + var ret = Set() + if self.biometricConsent != nil { + ret.insert(.biometricConsent) + } + if self.face != nil { + ret.insert(.face) + } + if self.idDocumentBack != nil { + ret.insert(.idDocumentBack) + } + if self.idDocumentFront != nil { + ret.insert(.idDocumentFront) + } + if self.idNumber != nil { + ret.insert(.idNumber) + } + if self.dob != nil { + ret.insert(.dob) + } + if self.name != nil { + ret.insert(.name) + } + if self.address != nil { + ret.insert(.address) + } + if self.phone != nil { + ret.insert(.phoneNumber) + } + if self.phoneOtp != nil { + ret.insert(.phoneOtp) + } + return ret + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataDob.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataDob.swift new file mode 100644 index 00000000..35a738b3 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataDob.swift @@ -0,0 +1,17 @@ +// +// VerificationPageDataDob.swift +// StripeIdentity +// +// Created by Chen Cen on 1/27/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataDob: Encodable, Equatable { + let day: String? + let month: String? + let year: String? + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataDocumentFileData.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataDocumentFileData.swift new file mode 100644 index 00000000..f772f1b7 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataDocumentFileData.swift @@ -0,0 +1,59 @@ +// +// VerificationPageDataDocumentFileData.swift +// StripeIdentity +// +// Created by Mel Ludowise on 12/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataDocumentFileData: Encodable, Equatable { + + enum FileUploadMethod: String, Encodable, Equatable { + /// Document image was auto-captured from the camera feed using ML models + case autoCapture = "auto_capture" + /// Document was uploaded from the file system + case fileUpload = "file_upload" + /// Document image was captured from the camera feed manually + case manualCapture = "manual_capture" + } + + /// If auto-captured, probability score of 'back' result from ML model. + let backScore: TwoDecimalFloat? + let brightnessValue: TwoDecimalFloat? + let cameraLensModel: String? + let exposureDuration: Int? + let exposureIso: TwoDecimalFloat? + let focalLength: TwoDecimalFloat? + /// If auto-captured, probability score of 'front_id' result from ML model. + let frontCardScore: TwoDecimalFloat? + /// File ID of uploaded image. If user auto-captured, this will be cropped to the bounds of the document. + let highResImage: String + /// If auto-captured, probability score of 'invalid' result from ML model. + let invalidScore: TwoDecimalFloat? + let iosBarcodeDecoded: Bool? + let iosBarcodeSymbology: String? + let iosTimeToFindBarcode: Int? + let isVirtualCamera: Bool? + /// If auto-captured, file ID of uploaded un-cropped image. + let lowResImage: String? + /// If auto-captured, probability score of 'passport' result from ML model. + let passportScore: TwoDecimalFloat? + /// Method of getting the document image + let uploadMethod: FileUploadMethod + /// If true, force confirm from backend without actually checking the uploaded image. + private(set) var forceConfirm: Bool? + + } +} + +extension StripeAPI.VerificationPageDataDocumentFileData { + /// Copy the data with a different forceConfirm value + func withForceConfirm(_ forceConfirm: Bool) -> StripeAPI.VerificationPageDataDocumentFileData { + var copy = self + copy.forceConfirm = forceConfirm + return copy + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataFace.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataFace.swift new file mode 100644 index 00000000..c0ea8e54 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataFace.swift @@ -0,0 +1,48 @@ +// +// VerificationPageDataFace.swift +// StripeIdentity +// +// Created by Mel Ludowise on 6/10/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataFace: Encodable, Equatable { + + /// File ID of uploaded image for best selfie frame. This will be cropped to the bounds of the face in the image. + let bestHighResImage: String + /// File ID of uploaded image for best selfie frame. This will be un-cropped. + let bestLowResImage: String + /// File ID of uploaded image for first selfie frame. This will be cropped to the bounds of the face in the image. + let firstHighResImage: String + /// File ID of uploaded image for first selfie frame. This will be un-cropped. + let firstLowResImage: String + /// File ID of uploaded image for last selfie frame. This will be cropped to the bounds of the face in the image. + let lastHighResImage: String + /// File ID of uploaded image for last selfie frame. This will be un-cropped. + let lastLowResImage: String + /// FaceDetector score for the best selfie frame. + let bestFaceScore: TwoDecimalFloat + /// Variance of the FaceDetector scores over all selfie frames. + let faceScoreVariance: TwoDecimalFloat + /// The total number of selfie frames taken. + let numFrames: Int + /// Camera brightness value for the best selfie frame. + let bestBrightnessValue: TwoDecimalFloat? + /// Camera lens model for the best selfie frame. + let bestCameraLensModel: String? + /// Camera exposure duration for the best selfie frame. + let bestExposureDuration: Int? + /// Camera exposure ISO for the best selfie frame + let bestExposureIso: TwoDecimalFloat? + /// Camera focal length for the best selfie frame. + let bestFocalLength: TwoDecimalFloat? + /// If the best selfie frame was taken by a virtual camera. + let bestIsVirtualCamera: Bool? + /// Whether the user consents for their selfie to be used for training purposes + let trainingConsent: Bool + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataIdNumber.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataIdNumber.swift new file mode 100644 index 00000000..294e8601 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataIdNumber.swift @@ -0,0 +1,17 @@ +// +// VerificationPageDataIdNumber.swift +// StripeIdentity +// +// Created by Chen Cen on 1/27/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataIdNumber: Encodable, Equatable { + let country: String? + let partialValue: String? + let value: String? + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataName.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataName.swift new file mode 100644 index 00000000..2901644e --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataName.swift @@ -0,0 +1,16 @@ +// +// VerificationPageDataName.swift +// StripeIdentity +// +// Created by Chen Cen on 1/27/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataName: Encodable, Equatable { + let firstName: String? + let lastName: String? + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataPhone.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataPhone.swift new file mode 100644 index 00000000..eea738c2 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataPhone.swift @@ -0,0 +1,16 @@ +// +// VerificationPageDataPhone.swift +// StripeIdentity +// +// Created by Chen Cen on 6/12/23. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataPhone: Encodable, Equatable { + let country: String? + let phoneNumber: String? + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataUpdate.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataUpdate.swift new file mode 100644 index 00000000..9a0a608e --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/Models/VerificationPageDataUpdate/VerificationPageDataUpdate.swift @@ -0,0 +1,18 @@ +// +// VerificationPageDataUpdate.swift +// StripeIdentity +// +// Created by Mel Ludowise on 11/2/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension StripeAPI { + struct VerificationPageDataUpdate: Encodable, Equatable { + + let clearData: VerificationPageClearData? + let collectedData: VerificationPageCollectedData? + } +} diff --git a/StripeIdentity/StripeIdentity/Source/API Bindings/SelfieUploader+API.swift b/StripeIdentity/StripeIdentity/Source/API Bindings/SelfieUploader+API.swift new file mode 100644 index 00000000..0e27e052 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/API Bindings/SelfieUploader+API.swift @@ -0,0 +1,64 @@ +// +// SelfieUploader+API.swift +// StripeIdentity +// +// Created by Mel Ludowise on 6/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCameraCore +@_spi(STP) import StripeCore +import UIKit + +extension IdentityImageUploader.Configuration { + init( + from selfiePageConfig: StripeAPI.VerificationPageStaticContentSelfiePage + ) { + self.init( + filePurpose: selfiePageConfig.filePurpose, + highResImageCompressionQuality: selfiePageConfig.highResImageCompressionQuality, + highResImageCropPadding: selfiePageConfig.highResImageCropPadding, + highResImageMaxDimension: selfiePageConfig.highResImageMaxDimension, + lowResImageCompressionQuality: selfiePageConfig.lowResImageCompressionQuality, + lowResImageMaxDimension: selfiePageConfig.lowResImageMaxDimension + ) + } +} + +extension StripeAPI.VerificationPageDataFace { + init( + uploadedFiles: SelfieUploader.FileData, + capturedImages: FaceCaptureData, + bestFrameExifMetadata: CameraExifMetadata?, + trainingConsent: Bool + ) { + self.init( + bestHighResImage: uploadedFiles.bestHighResFile.id, + bestLowResImage: uploadedFiles.bestLowResFile.id, + firstHighResImage: uploadedFiles.firstHighResFile.id, + firstLowResImage: uploadedFiles.firstLowResFile.id, + lastHighResImage: uploadedFiles.lastHighResFile.id, + lastLowResImage: uploadedFiles.lastLowResFile.id, + bestFaceScore: .init(capturedImages.bestMiddle.scannerOutput.faceScore), + faceScoreVariance: .init(capturedImages.faceScoreVariance), + numFrames: capturedImages.numSamples, + bestBrightnessValue: bestFrameExifMetadata?.brightnessValue.map { + TwoDecimalFloat(double: $0) + }, + bestCameraLensModel: bestFrameExifMetadata?.lensModel, + bestExposureDuration: capturedImages.bestMiddle.scannerOutput.cameraProperties.map { + Int($0.exposureDuration.seconds * 1000) + }, + bestExposureIso: capturedImages.bestMiddle.scannerOutput.cameraProperties.map { + TwoDecimalFloat($0.exposureISO) + }, + bestFocalLength: bestFrameExifMetadata?.focalLength.map { + TwoDecimalFloat(double: $0) + }, + bestIsVirtualCamera: capturedImages.bestMiddle.scannerOutput.cameraProperties? + .isVirtualDevice, + trainingConsent: trainingConsent + ) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/Analytics/IdentityAnalyticsClient.swift b/StripeIdentity/StripeIdentity/Source/Analytics/IdentityAnalyticsClient.swift new file mode 100644 index 00000000..7b846249 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/Analytics/IdentityAnalyticsClient.swift @@ -0,0 +1,534 @@ +// +// IdentityAnalyticsClient.swift +// StripeIdentity +// +// Created by Mel Ludowise on 6/7/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +enum IdentityAnalyticsClientError: AnalyticLoggableErrorV2 { + /// `startTrackingTimeToScreen` was called twice in a row without calling + /// `stopTrackingTimeToScreenAndLogIfNeeded` + case timeToScreenAlreadyStarted( + alreadyStartedForScreen: IdentityAnalyticsClient.ScreenName?, + requestedForScreen: IdentityAnalyticsClient.ScreenName? + ) + + func analyticLoggableSerializeForLogging() -> [String: Any] { + var payload: [String: Any] = [ + "domain": (self as NSError).domain, + ] + switch self { + case .timeToScreenAlreadyStarted(let alreadyStartedForScreen, let requestedForScreen): + payload["type"] = "timeToScreenAlreadyStarted" + if let alreadyStartedForScreen = alreadyStartedForScreen { + payload["previous_tracked_screen"] = alreadyStartedForScreen.rawValue + } + if let requestedForScreen = requestedForScreen { + payload["new_tracked_screen"] = requestedForScreen.rawValue + } + } + return payload + } +} + +/// Wrapper for AnalyticsClient that formats Identity-specific analytics +final class IdentityAnalyticsClient { + + enum EventName: String { + // MARK: UI + case sheetPresented = "sheet_presented" + case sheetClosed = "sheet_closed" + case verificationFailed = "verification_failed" + case verificationCanceled = "verification_canceled" + case verificationSucceeded = "verification_succeeded" + case screenAppeared = "screen_presented" + case cameraError = "camera_error" + case cameraPermissionDenied = "camera_permission_denied" + case cameraPermissionGranted = "camera_permission_granted" + case documentCaptureTimeout = "document_timeout" + case selfieCaptureTimeout = "selfie_timeout" + // MARK: Performance + case averageFPS = "average_fps" + case modelPerformance = "model_performance" + case imageUpload = "image_upload" + case timeToScreen = "time_to_screen" + // MARK: Errors + case genericError = "generic_error" + // MARK: Experiment + case experimentExposure = "preloaded_experiment_retrieved" + } + + enum ScreenName: String { + case biometricConsent = "consent" + case documentCapture = "live_capture" + case documentFileUpload = "file_upload" + case documentWarmup = "document_warmup" + case selfieCapture = "selfie" + case selfieWarmup = "selfie_warmup" + case success = "confirmation" + case individual = "individual" + case phoneOtp = "phone_otp" + case individual_welcome = "individual_welcome" + case error = "error" + case countryNotListed = "country_not_listed" + case debug = "debug" + } + + /// Name of the scanner logged in scanning performance events + enum ScannerName: String { + case document + case selfie + } + + static let sharedAnalyticsClient = AnalyticsClientV2( + clientId: "mobile-identity-sdk", + origin: "stripe-identity-ios" + ) + + let verificationSessionId: String + let analyticsClient: AnalyticsClientV2Protocol + + /// Total number of times the front of the document was attempted to be scanned. + private(set) var numDocumentFrontScanAttempts = 0 + + /// Total number of times the back of the document was attempted to be scanned. + private(set) var numDocumentBackScanAttempts = 0 + + /// Total number of times a selfie was attempted to be scanned. + private(set) var numSelfieScanAttempts = 0 + + /// Tracks the start time for `timeToScreen` analytic + private(set) var timeToScreenStartTime: Date? + /// The last screen transitioned to for `timeToScreen` analytic + private(set) var timeToScreenFromScreen: ScreenName? + + private(set) var blurScoreFront: Float? + private(set) var blurScoreBack: Float? + + init( + verificationSessionId: String, + analyticsClient: AnalyticsClientV2Protocol = IdentityAnalyticsClient.sharedAnalyticsClient + ) { + self.verificationSessionId = verificationSessionId + self.analyticsClient = analyticsClient + } + + // MARK: - UI Events + + /// Increments the number of times a scan was initiated for the specified side of the document + func countDidStartDocumentScan(for side: DocumentSide) { + switch side { + case .front: + numDocumentFrontScanAttempts += 1 + case .back: + numDocumentBackScanAttempts += 1 + } + } + + /// Increments the number of times a scan was initiated for a selfie + func countDidStartSelfieScan() { + numSelfieScanAttempts += 1 + } + + func updateBlurScore(_ blurScore: Float, for side: DocumentSide) { + if side == .front { + blurScoreFront = blurScore + } else { + blurScoreBack = blurScore + } + } + + private func logAnalytic( + _ eventName: EventName, + metadata: [String: Any], + verificationPage: StripeAPI.VerificationPage? + ) { + if let verificationPage = verificationPage { + let userSessionId = verificationPage.userSessionId + let experiments = verificationPage.experiments + + for exp in experiments { + if exp.eventName == eventName.rawValue && + (exp.eventMetadata.allSatisfy { (key, value) in + return metadata[key] as? String == value + }) { + analyticsClient.log( + eventName: EventName.experimentExposure.rawValue, + parameters: [ + "arb_id": userSessionId, + "experiment_retrieved": exp.experimentName, + ] + ) + } + } + } + + analyticsClient.log( + eventName: eventName.rawValue, + parameters: [ + "verification_session": verificationSessionId, + "event_metadata": metadata, + ] + ) + } + + /// Logs an event when the verification sheet is presented + func logSheetPresented(sheetController: VerificationSheetControllerProtocol) { + logAnalytic( + .sheetPresented, + metadata: [:], + verificationPage: try? sheetController.verificationPageResponse?.get() + ) + } + + /// Logs a closed, failed, or canceled analytic events, depending on the result + func logSheetClosedFailedOrCanceled( + result: IdentityVerificationSheet.VerificationFlowResult, + sheetController: VerificationSheetControllerProtocol, + filePath: StaticString = #filePath, + line: UInt = #line + ) { + switch result { + case .flowCompleted: + logSheetClosed( + sessionResult: "flow_complete", + sheetController: sheetController + ) + + case .flowCanceled: + logVerificationCanceled( + sheetController: sheetController + ) + logSheetClosed( + sessionResult: "flow_canceled", + sheetController: sheetController + ) + + case .flowFailed(let error): + logVerificationFailed( + sheetController: sheetController, + error: error, + filePath: filePath, + line: line + ) + } + } + + /// Helper to create metadata common to both failed, canceled, and succeed analytic events + private func failedCanceledSucceededCommonMetadataPayload( + sheetController: VerificationSheetControllerProtocol + ) -> [String: Any] { + var metadata: [String: Any] = [:] + + if let verificationPage = try? sheetController.verificationPageResponse?.get() { + metadata["require_selfie"] = verificationPage.requirements.missing.contains(.face) + metadata["from_fallback_url"] = verificationPage.unsupportedClient + } + if let frontUploadMethod = sheetController.collectedData.idDocumentFront?.uploadMethod { + metadata["doc_front_upload_type"] = frontUploadMethod.rawValue + } + if let backUploadMethod = sheetController.collectedData.idDocumentBack?.uploadMethod { + metadata["doc_back_upload_type"] = backUploadMethod.rawValue + } + + return metadata + } + + /// Logs an event when the verification sheet is closed + private func logSheetClosed(sessionResult: String, sheetController: VerificationSheetControllerProtocol) { + logAnalytic( + .sheetClosed, + metadata: [ + "session_result": sessionResult + ], + verificationPage: try? sheetController.verificationPageResponse?.get() + ) + } + + /// Logs an event when verification sheet fails + private func logVerificationFailed( + sheetController: VerificationSheetControllerProtocol, + error: Error, + filePath: StaticString, + line: UInt + ) { + var metadata = failedCanceledSucceededCommonMetadataPayload( + sheetController: sheetController + ) + metadata["error"] = AnalyticsClientV2.serialize( + error: error, + filePath: filePath, + line: line + ) + + logAnalytic(.verificationFailed, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Logs an event when verification sheet is canceled + private func logVerificationCanceled( + sheetController: VerificationSheetControllerProtocol + ) { + var metadata = failedCanceledSucceededCommonMetadataPayload( + sheetController: sheetController + ) + if let lastScreen = sheetController.flowController.analyticsLastScreen { + metadata["last_screen_name"] = lastScreen.analyticsScreenName.rawValue + } + + logAnalytic(.verificationCanceled, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Logs an event when verification sheet succeeds + func logVerificationSucceeded( + sheetController: VerificationSheetControllerProtocol + ) { + var metadata = failedCanceledSucceededCommonMetadataPayload( + sheetController: sheetController + ) + + metadata["doc_front_retry_times"] = max(0, numDocumentFrontScanAttempts - 1) + metadata["doc_back_retry_times"] = max(0, numDocumentBackScanAttempts - 1) + metadata["selfie_retry_times"] = max(0, numSelfieScanAttempts - 1) + + if let frontScore = sheetController.collectedData.frontDocumentScore { + metadata["doc_front_model_score"] = frontScore.value + } + if let backScore = sheetController.collectedData.idDocumentBack?.backScore { + metadata["doc_back_model_score"] = backScore.value + } + if let bestFaceScore = sheetController.collectedData.face?.bestFaceScore { + metadata["selfie_model_score"] = bestFaceScore.value + } + if let blurScoreFront = blurScoreFront { + metadata["doc_front_blur_score"] = blurScoreFront + } + if let blurScoreBack = blurScoreBack { + metadata["doc_back_blur_score"] = blurScoreBack + } + + logAnalytic(.verificationSucceeded, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Logs an event when a screen is presented + func logScreenAppeared( + screenName: ScreenName, + sheetController: VerificationSheetControllerProtocol + ) { + let metadata: [String: Any] = [ + "screen_name": screenName.rawValue, + ] + + logAnalytic(.screenAppeared, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Logs an event when a camera error occurs + func logCameraError( + sheetController: VerificationSheetControllerProtocol, + error: Error, + filePath: StaticString = #filePath, + line: UInt = #line + ) { + var metadata: [String: Any] = [:] + metadata["error"] = AnalyticsClientV2.serialize( + error: error, + filePath: filePath, + line: line + ) + logAnalytic(.cameraError, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Logs either a permission denied or granted event when the camera permissions are checked prior to starting a camera session + func logCameraPermissionsChecked( + sheetController: VerificationSheetControllerProtocol, + isGranted: Bool? + ) { + let eventName: EventName = + (isGranted == true) ? .cameraPermissionGranted : .cameraPermissionDenied + + logAnalytic(eventName, metadata: [:], verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Logs an event when document capture times out + func logDocumentCaptureTimeout( + documentSide: DocumentSide, + sheetController: VerificationSheetControllerProtocol + ) { + logAnalytic( + .documentCaptureTimeout, + metadata: [ + "side": documentSide.rawValue, + ], + verificationPage: try? sheetController.verificationPageResponse?.get() + ) + } + + /// Logs an event when selfie capture times out + func logSelfieCaptureTimeout(sheetController: VerificationSheetControllerProtocol) { + logAnalytic(.selfieCaptureTimeout, metadata: [:], verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + // MARK: - Performance Events + + /// Logs the a scan's average number of frames per seconds processed + func logAverageFramesPerSecond( + averageFPS: Double, + numFrames: Int, + scannerName: ScannerName, + sheetController: VerificationSheetControllerProtocol + ) { + logAnalytic( + .averageFPS, + metadata: [ + "type": scannerName.rawValue, + "value": averageFPS, + "frames": numFrames, + ], + verificationPage: try? sheetController.verificationPageResponse?.get() + ) + } + + /// Logs the average inference and post-processing times for every ML model used for one scan + func logModelPerformance( + mlModelMetricsTrackers: [MLDetectorMetricsTrackerProtocol], + sheetController: VerificationSheetControllerProtocol + ) { + mlModelMetricsTrackers.forEach { metricsTracker in + // Cache values to avoid weakly capturing performanceTracker + let modelName = metricsTracker.modelName + + metricsTracker.getPerformanceMetrics(completeOn: .main) { averageMetrics, numFrames in + guard numFrames > 0 else { return } + self.logModelPerformance( + modelName: modelName, + averageMetrics: averageMetrics, + numFrames: numFrames, + sheetController: sheetController + ) + } + } + } + + /// Logs an ML model's average inference and post-process time during a scan + private func logModelPerformance( + modelName: String, + averageMetrics: MLDetectorMetricsTracker.Metrics, + numFrames: Int, + sheetController: VerificationSheetControllerProtocol + ) { + logAnalytic( + .modelPerformance, + metadata: [ + "ml_model": modelName, + "inference": averageMetrics.inference.milliseconds, + "postprocess": averageMetrics.postProcess.milliseconds, + "frames": numFrames, + ], + verificationPage: try? sheetController.verificationPageResponse?.get() + ) + } + + /// Logs the time it takes to upload an image along with its file size and compression quality + func logImageUpload( + timeToUpload: TimeInterval, + compressionQuality: CGFloat, + fileId: String, + fileName: String, + fileSizeBytes: Int, + sheetController: VerificationSheetControllerProtocol + ) { + // NOTE: File size is logged in kB + let metadata: [String: Any] = [ + "value": timeToUpload.milliseconds, + "id": fileId, + "compression_quality": compressionQuality, + "file_name": fileName, + "file_size": fileSizeBytes / 1024, + ] + + logAnalytic(.imageUpload, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + /// Tracks the time when a user taps a button to continue to the next screen. + /// Should be followed by a call to `stopTrackingTimeToScreenAndLogIfNeeded` + /// when the next screen appears. + func startTrackingTimeToScreen( + from fromScreen: ScreenName?, + sheetController: VerificationSheetControllerProtocol + ) { + if timeToScreenStartTime != nil { + logGenericError( + error: IdentityAnalyticsClientError.timeToScreenAlreadyStarted( + alreadyStartedForScreen: timeToScreenFromScreen, + requestedForScreen: fromScreen + ), + sheetController: sheetController + ) + } + timeToScreenStartTime = Date() + timeToScreenFromScreen = fromScreen + } + + /// Logs the time it takes for a screen to appear after the user takes an + /// action to proceed to the next screen in the flow. + /// If `startTrackingTimeToScreen` was not called before calling this method, + /// an analytic is not logged. + func stopTrackingTimeToScreenAndLogIfNeeded( + to toScreen: ScreenName, + sheetController: VerificationSheetControllerProtocol + ) { + let endTime = Date() + + defer { + // Reset state properties + self.timeToScreenStartTime = nil + self.timeToScreenFromScreen = nil + } + + // This method could be called unnecessarily from `viewDidAppear` in the + // case that the view controller was presenting another screen that was + // dismissed or the back button was used. Only log an analytic if there's + // `startTrackingTimeToScreen` was called. + guard let startTime = timeToScreenStartTime, + timeToScreenFromScreen != toScreen + else { + return + } + + var metadata: [String: Any] = [ + "value": endTime.timeIntervalSince(startTime).milliseconds, + "to_screen_name": toScreen.rawValue, + ] + if let fromScreen = timeToScreenFromScreen { + metadata["from_screen_name"] = fromScreen.rawValue + } + + logAnalytic(.timeToScreen, metadata: metadata, verificationPage: try? sheetController.verificationPageResponse?.get()) + } + + // MARK: - Error Events + + /// Logs when an error occurs. + func logGenericError( + error: Error, + filePath: StaticString = #filePath, + line: UInt = #line, + sheetController: VerificationSheetControllerProtocol + ) { + logAnalytic( + .genericError, + metadata: [ + "error_details": AnalyticsClientV2.serialize( + error: error, + filePath: filePath, + line: line + ), + ], + verificationPage: try? sheetController.verificationPageResponse?.get() + ) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/Categories/Array+StripeIdentity.swift b/StripeIdentity/StripeIdentity/Source/Categories/Array+StripeIdentity.swift new file mode 100644 index 00000000..d33d16cb --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/Categories/Array+StripeIdentity.swift @@ -0,0 +1,33 @@ +// +// Array+StripeIdentity.swift +// StripeIdentity +// +// Created by Mel Ludowise on 5/10/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +// Borrowed from https://stackoverflow.com/a/47210788/4133371 +extension Array { + func sum(with transform: (Element) -> T) -> T { + return self.reduce(0) { partialResult, element in + return partialResult + transform(element) + } + } + + func average(with transform: (Element) -> T) -> T { + return sum(with: transform) / T(self.count) + } + + func standardDeviation(with transform: (Element) -> T) -> T { + let mean = average(with: transform) + + let v: T = reduce(0) { partialResult, element in + let distanceToMean = transform(element) - mean + return partialResult + (distanceToMean * distanceToMean) + } + + return sqrt(v / (T(self.count) - 1)) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/Categories/CGImage+StripeIdentity.swift b/StripeIdentity/StripeIdentity/Source/Categories/CGImage+StripeIdentity.swift new file mode 100644 index 00000000..93a143ef --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/Categories/CGImage+StripeIdentity.swift @@ -0,0 +1,119 @@ +// +// CGImage+StripeIdentity.swift +// StripeIdentity +// +// Created by Mel Ludowise on 12/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import CoreGraphics +@_spi(STP) import StripeCore +import UIKit + +enum STPCGImageError: String, Error { + /// The image could not be cropped + case unableToCrop + /// The image could not be scaled down + case unableToScaleDown +} + +extension CGImage { + + enum CropPaddingComputationMethod { + /// The pixel crop padding is a function of the maximum width or height of the image + case maxImageWidthOrHeight + /// The pixel crop padding is a function of the width of the region of interest + case regionWidth + } + + /// Crops the image to a given region of interest plus padding. + /// + /// - Parameters: + /// - normalizedRegion: A rect, in image coordinates, defining the area to add padding to and then crop, where origin is bottom-left. + /// - cropPadding: A value, ranging between 0–1, that is added as padding to the region of interest. + /// - computationMethod: The method which the crop padding pixel value is computed from. + /// + /// - Returns: An image cropped to the given specifications. + /// + /// - Throws: STPCGImageError if the image could not be cropped + /// + /// - Note: + /// The pixel value of the padding added to region of interest is defined as `cropPadding * max(width, height)`. + func cropping( + toNormalizedRegion normalizedRegion: CGRect, + withPadding cropPadding: CGFloat, + computationMethod: CropPaddingComputationMethod + ) throws -> CGImage { + guard + let image = cropping( + to: computePixelCropArea( + normalizedRegion: normalizedRegion, + pixelPadding: computePixelPadding( + padding: cropPadding, + normalizedRegion: normalizedRegion, + computationMethod: computationMethod + ) + ) + ) + else { + throw STPCGImageError.unableToCrop + } + return image + } + + func computePixelPadding( + padding: CGFloat, + normalizedRegion: CGRect, + computationMethod: CropPaddingComputationMethod + ) -> CGFloat { + switch computationMethod { + case .maxImageWidthOrHeight: + return padding * CGFloat(max(width, height)) + case .regionWidth: + return padding * normalizedRegion.width * CGFloat(width) + } + } + + func computePixelCropArea( + normalizedRegion: CGRect, + pixelPadding: CGFloat + ) -> CGRect { + let pixelRegionOfInterest = CGRect( + x: normalizedRegion.minX * CGFloat(width), + y: normalizedRegion.minY * CGFloat(height), + width: normalizedRegion.width * CGFloat(width), + height: normalizedRegion.height * CGFloat(height) + ) + return pixelRegionOfInterest.insetBy( + dx: -pixelPadding, + dy: -pixelPadding + ) + } + + /// Scales the image, maintaining its aspect ratio, to a maximum dimension. + /// If the image size is already smaller than the given dimension, it will + /// maintain its original dimension. + /// + /// - Parameter maxPixelDimension: The maximum dimensions, in pixels, the returned image should be. + /// + /// - Returns: An image scaled down to the max dimensions. + /// + /// - Throws: STPCGImageError if the image could not be scaled + func scaledDown( + toMaxPixelDimension maxPixelDimension: CGSize + ) throws -> CGImage { + let scale = computeScale(maxPixelDimension: maxPixelDimension) + guard let image = UIImage(cgImage: self).resized(to: scale)?.cgImage else { + throw STPCGImageError.unableToScaleDown + } + return image + } + + func computeScale( + maxPixelDimension: CGSize + ) -> CGFloat { + let horizontalScale = min(maxPixelDimension.width, CGFloat(width)) / CGFloat(width) + let verticalScale = min(maxPixelDimension.height, CGFloat(height)) / CGFloat(height) + return min(horizontalScale, verticalScale) + } +} diff --git a/StripeIdentity/StripeIdentity/Source/Categories/MLMultiArray+StripeIdentity.swift b/StripeIdentity/StripeIdentity/Source/Categories/MLMultiArray+StripeIdentity.swift new file mode 100644 index 00000000..715245c5 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/Categories/MLMultiArray+StripeIdentity.swift @@ -0,0 +1,16 @@ +// +// MLMultiArray+StripeIdentity.swift +// StripeIdentity +// +// Created by Mel Ludowise on 1/26/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import CoreML +import Foundation + +extension MLMultiArray { + subscript(key: [Int]) -> NSNumber { + return self[key.map { NSNumber(value: $0) }] + } +} diff --git a/StripeIdentity/StripeIdentity/Source/Categories/NSAttributedString+HTML.swift b/StripeIdentity/StripeIdentity/Source/Categories/NSAttributedString+HTML.swift new file mode 100644 index 00000000..9ae4c930 --- /dev/null +++ b/StripeIdentity/StripeIdentity/Source/Categories/NSAttributedString+HTML.swift @@ -0,0 +1,273 @@ +// +// NSAttributedString+HTML.swift +// StripeIdentity +// +// Created by Mel Ludowise on 2/1/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +/// Specifies how to style HTML used to generate an NSAttributedString. +struct HTMLStyle { + static let `default` = HTMLStyle( + bodyFont: UIFont.preferredFont(forTextStyle: .body, weight: .regular), + bodyColor: .label, + h1Font: UIFont.preferredFont(forTextStyle: .title1), + h2Font: UIFont.preferredFont(forTextStyle: .title2), + h3Font: UIFont.preferredFont(forTextStyle: .title3), + h4Font: UIFont.preferredFont(forTextStyle: .headline), + h5Font: UIFont.preferredFont(forTextStyle: .subheadline), + h6Font: UIFont.preferredFont(forTextStyle: .footnote), + isLinkUnderlined: false + ) + + let bodyFont: UIFont + let bodyColor: UIColor? + + let h1Font: UIFont? + let h1Color: UIColor? + + let h2Font: UIFont? + let h2Color: UIColor? + + let h3Font: UIFont? + let h3Color: UIColor? + + let h4Font: UIFont? + let h4Color: UIColor? + + let h5Font: UIFont? + let h5Color: UIColor? + + let h6Font: UIFont? + let h6Color: UIColor? + + let isLinkUnderlined: Bool + let shouldCenterText: Bool + + let linkColor: UIColor? + + let lineHeightMultiple: CGFloat + + init( + bodyFont: UIFont, + bodyColor: UIColor? = nil, + h1Font: UIFont? = nil, + h1Color: UIColor? = nil, + h2Font: UIFont? = nil, + h2Color: UIColor? = nil, + h3Font: UIFont? = nil, + h3Color: UIColor? = nil, + h4Font: UIFont? = nil, + h4Color: UIColor? = nil, + h5Font: UIFont? = nil, + h5Color: UIColor? = nil, + h6Font: UIFont? = nil, + h6Color: UIColor? = nil, + isLinkUnderlined: Bool = false, + shouldCenterText: Bool = false, + linkColor: UIColor? = nil, + lineHeightMultiple: CGFloat = 1 + ) { + self.bodyFont = bodyFont + self.bodyColor = bodyColor + self.h1Font = h1Font + self.h1Color = h1Color + self.h2Font = h2Font + self.h2Color = h2Color + self.h3Font = h3Font + self.h3Color = h3Color + self.h4Font = h4Font + self.h4Color = h4Color + self.h5Font = h5Font + self.h5Color = h5Color + self.h6Font = h6Font + self.h6Color = h6Color + self.isLinkUnderlined = isLinkUnderlined + self.shouldCenterText = shouldCenterText + self.linkColor = linkColor + self.lineHeightMultiple = lineHeightMultiple + } + + fileprivate static func cssText( + _ cssName: String, + font: UIFont?, + color: UIColor?, + shouldCenterText: Bool + ) -> String { + guard font != nil || color != nil else { + return "" + } + + let fontAttributes = font.map { font -> String in + // If the specified font is the same family as the system font, + // then use "-apple-system" instead. Otherwise, the html renderer will + // only use the non-bold variation of the system font, breaking any bold + // font configurations. + var familyName = font.familyName + if familyName == UIFont.systemFont(ofSize: font.pointSize).familyName { + familyName = "-apple-system" + } + + let fontWeight = + font.fontDescriptor.symbolicTraits.contains(.traitBold) + ? "bold" + : "regular" + + return """ + font-family: "\(familyName)"; + font-size: \(font.pointSize); + font-weight: "\(fontWeight)"; + """ + } + + let colorAttributes = color.map { color -> String in + return "color: \(color.cssValue);" + } + + let centerAttribute = shouldCenterText ? "text-align: center;" : "" + + return """ + \(cssName) { + \(fontAttributes ?? "") + \(colorAttributes ?? "") + \(centerAttribute) + } + """ + } + + /// Constructs a style HTML tag from the properties of this HTMLStyle + fileprivate var styleElementText: String { + var text = " + """ + + return text + } +} + +extension NSAttributedString { + static func createHtmlString(htmlText: String, style: HTMLStyle) throws -> NSAttributedString { + let mutableHtmlString = try NSMutableAttributedString( + htmlText: htmlText, style: style) + + if style.lineHeightMultiple != 1 { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineHeightMultiple = style.lineHeightMultiple + paragraphStyle.headIndent = 1 + paragraphStyle.firstLineHeadIndent = 0 + mutableHtmlString.addAttribute( + .paragraphStyle, + value: paragraphStyle, + range: NSRange(location: 0, length: (mutableHtmlString.length)) + ) + } + return mutableHtmlString + } +} + +private extension NSMutableAttributedString { + /// Initializes an NSAttributedString from HTML with the specified style. + /// + /// - Note: + /// By default, when an attributed string is built from HTML, the font defaults + /// to Times New Roman with 11pt font. Setting a font on the UILabel or + /// UITextView displaying the attributed string does not override the font. + /// + /// This initializer wraps the HTML string in a ` + + + + + +
+
+ + + """ +} diff --git a/StripePayments/StripePayments/Source/Captcha/HCaptchaLog.swift b/StripePayments/StripePayments/Source/Captcha/HCaptchaLog.swift new file mode 100644 index 00000000..69a8c71a --- /dev/null +++ b/StripePayments/StripePayments/Source/Captcha/HCaptchaLog.swift @@ -0,0 +1,68 @@ +// +// HCaptchaEvent.swift +// HCaptcha +// +// Copyright © 2023 HCaptcha. All rights reserved. +// + +import Foundation + +/** Internal SDK logger level + */ +enum HCaptchaLogLevel: Int, CustomStringConvertible { + case debug = 0 + case warning = 1 + case error = 2 + + var description: String { + switch self { + case .debug: + return "Debug" + case .warning: + return "Warning" + case .error: + return "Error" + } + } +} + +/** Internal SDK logger + */ +internal class HCaptchaLogger { + static var minLevel: HCaptchaLogLevel = .debug + + static func debug(_ message: String, _ args: CVarArg...) { + log(level: .debug, message: message, args: args) + } + + static func warn(_ message: String, _ args: CVarArg...) { + log(level: .warning, message: message, args: args) + } + + static func error(_ message: String, _ args: CVarArg...) { + log(level: .error, message: message, args: args) + } + + static func log(level: HCaptchaLogLevel, message: String, args: [CVarArg]) { +#if DEBUG + guard level.rawValue >= minLevel.rawValue else { + return + } + + let formattedMessage = String(format: message, arguments: args) + let logMessage = "\(timestamp) \(threadId) HCaptcha/\(level.description): \(formattedMessage)" + + print(logMessage) +#endif + } + + private static var timestamp: String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.sss" + return dateFormatter.string(from: Date()) + } + + private static var threadId: String { + return Thread.isMainThread ? "main" : "\(pthread_self())" + } +} diff --git a/StripePayments/StripePayments/Source/Captcha/HCaptchaResult.swift b/StripePayments/StripePayments/Source/Captcha/HCaptchaResult.swift new file mode 100644 index 00000000..1f053350 --- /dev/null +++ b/StripePayments/StripePayments/Source/Captcha/HCaptchaResult.swift @@ -0,0 +1,55 @@ +// +// HCaptchaWebViewManager.swift +// HCaptcha +// +// Created by Flávio Caetano on 06/03/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +import Foundation + +/** The HCaptcha result. + + This may contain the validation token on success, or an error that may have occurred. + */ +@objc +class HCaptchaResult: NSObject { + + /// Result token + let token: String? + + /// Result error + let error: HCaptchaError? + + /// Manager + let manager: HCaptchaWebViewManager + + internal init (_ manager: HCaptchaWebViewManager, token: String? = nil, error: HCaptchaError? = nil) { + self.manager = manager + self.token = token + self.error = error + } + + /** + - returns: The validation token uppon success. + + Tries to unwrap the Result and retrieve the token if it's successful. + + - Throws: `HCaptchaError` + */ + @objc + func dematerialize() throws -> String { + manager.resultHandled = true + + if let token = self.token { + return token + } + + if let error = self.error { + throw error + } + + assertionFailure("Impossible state result must be initialized with token or error") + return "" + } +} diff --git a/StripePayments/StripePayments/Source/Captcha/HCaptchaURLOpener.swift b/StripePayments/StripePayments/Source/Captcha/HCaptchaURLOpener.swift new file mode 100644 index 00000000..6439e6b1 --- /dev/null +++ b/StripePayments/StripePayments/Source/Captcha/HCaptchaURLOpener.swift @@ -0,0 +1,39 @@ +// +// HCaptchaURLOpener.swift +// HCaptcha +// +// Copyright © 2022 HCaptcha. All rights reserved. +// + +import Foundation +import UIKit + +/** + * The protocol for a contractor which can handle/open URLs in an external viewer/browser + */ +internal protocol HCaptchaURLOpener { + /** + Return true if url can be handled + - parameter url: The URL to be checked + */ + func canOpenURL(_ url: URL) -> Bool + + /** + Handle passed url + - parameter url: The URL to be checked + */ + func openURL(_ url: URL) +} + +/** + * UIApplication based implementation + */ +internal class HCapchaAppURLOpener: HCaptchaURLOpener { + func canOpenURL(_ url: URL) -> Bool { + return UIApplication.shared.canOpenURL(url) + } + + func openURL(_ url: URL) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } +} diff --git a/StripePayments/StripePayments/Source/Captcha/HCaptchaWebViewManager+WKNavigationDelegate.swift b/StripePayments/StripePayments/Source/Captcha/HCaptchaWebViewManager+WKNavigationDelegate.swift new file mode 100644 index 00000000..962577f6 --- /dev/null +++ b/StripePayments/StripePayments/Source/Captcha/HCaptchaWebViewManager+WKNavigationDelegate.swift @@ -0,0 +1,45 @@ +// +// HCaptchaWebViewManager+WKNavigationDelegate.swift +// HCaptcha +// +// Copyright © 2024 HCaptcha. All rights reserved. +// + +import Foundation +import WebKit + +extension HCaptchaWebViewManager: WKNavigationDelegate { + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + if navigationAction.targetFrame == nil, let url = navigationAction.request.url, urlOpener.canOpenURL(url) { + urlOpener.openURL(url) + return .cancel + } + return .allow + } + + /// Tells the delegate that an error occurred during navigation. + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + Log.debug("WebViewManager.webViewDidFail with \(error)") + completion?(HCaptchaResult(self, error: .unexpected(error))) + } + + /// Tells the delegate that an error occurred during the early navigation process. + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + Log.debug("WebViewManager.webViewDidFailProvisionalNavigation with \(error)") + completion?(HCaptchaResult(self, error: .unexpected(error))) + } + + /// Tells the delegate that the web view’s content process was terminated. + func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { + Log.debug("WebViewManager.webViewWebContentProcessDidTerminate") + let kHCaptchaErrorWebViewProcessDidTerminate = -1 + let kHCaptchaErrorDomain = "com.hcaptcha.sdk-ios" + let error = NSError(domain: kHCaptchaErrorDomain, + code: kHCaptchaErrorWebViewProcessDidTerminate, + userInfo: [ + NSLocalizedDescriptionKey: "WebView web content process did terminate", + NSLocalizedRecoverySuggestionErrorKey: "Call HCaptcha.reset()", ]) + completion?(HCaptchaResult(self, error: .unexpected(error))) + didFinishLoading = false + } +} diff --git a/StripePayments/StripePayments/Source/Captcha/HCaptchaWebViewManager.swift b/StripePayments/StripePayments/Source/Captcha/HCaptchaWebViewManager.swift new file mode 100644 index 00000000..7d9d9e91 --- /dev/null +++ b/StripePayments/StripePayments/Source/Captcha/HCaptchaWebViewManager.swift @@ -0,0 +1,394 @@ +// +// HCaptchaWebViewManager.swift +// HCaptcha + +import Foundation +import WebKit + +/** Handles comunications with the webview containing the HCaptcha challenge. + */ +internal class HCaptchaWebViewManager: NSObject { + enum JSCommand: String { + case execute = "execute();" + case reset = "reset();" + } + + typealias Log = HCaptchaLogger + + fileprivate struct Constants { + static let BotUserAgent = "bot/2.1" + } + + fileprivate let webViewInitSize = CGSize(width: 1, height: 1) + +#if DEBUG + /// Forces the challenge to be explicitly displayed. + var forceVisibleChallenge = false { + didSet { + // Also works on iOS < 9 + webView.performSelector( + onMainThread: Selector(("_setCustomUserAgent:")), + with: forceVisibleChallenge ? Constants.BotUserAgent : nil, + waitUntilDone: true + ) + } + } + + /// Allows validation stubbing for testing + var shouldSkipForTests = false +#endif + + /// True if validation token was dematerialized + internal var resultHandled: Bool = false + + /// Sends the result message + var completion: ((HCaptchaResult) -> Void)? + + /// Called (currently) when a challenge becomes visible + var onEvent: ((HCaptchaEvent, Any?) -> Void)? + + /// Notifies the JS bundle has finished loading + var onDidFinishLoading: (() -> Void)? { + didSet { + if didFinishLoading { + onDidFinishLoading?() + } + } + } + + /// Configures the webview for display when required + var configureWebView: ((WKWebView) -> Void)? + + /// The dispatch token used to ensure `configureWebView` is only called once. + var configureWebViewDispatchToken = UUID() + + /// If the HCaptcha should be reset when it errors + var shouldResetOnError = true + + /// The JS message recoder + fileprivate var decoder: HCaptchaDecoder! + + /// Indicates if the script has already been loaded by the `webView` + internal var didFinishLoading = false { + didSet { + if didFinishLoading { + onDidFinishLoading?() + } + } + } + + /// Stop async webView configuration + private var stopInitWebViewConfiguration = false + + /// The observer for `.UIWindowDidBecomeVisible` + fileprivate var observer: NSObjectProtocol? + + /// Base URL for WebView + fileprivate var baseURL: URL! + + /// Actual HTML + fileprivate var formattedHTML: String! + + /// Keep error If it happens before validate call + fileprivate var lastError: HCaptchaError? + + /// The webview that executes JS code + lazy var webView: WKWebView = { + let debug = Log.minLevel == .debug + let webview = WKWebView( + frame: CGRect(origin: CGPoint.zero, size: webViewInitSize), + configuration: self.buildConfiguration() + ) + webview.accessibilityIdentifier = "webview" + webview.accessibilityTraits = UIAccessibilityTraits.link + webview.isHidden = true + if debug { + if #available(iOS 16.4, *) { + webview.perform(Selector(("setInspectable:")), with: true) + } + webview.evaluateJavaScript("navigator.userAgent") { (result, _) in + Log.debug("WebViewManager WKWebView UserAgent: \(result ?? "nil")") + } + } + Log.debug("WebViewManager WKWebView instance created") + + return webview + }() + + /// Responsible for external link handling + internal let urlOpener: HCaptchaURLOpener + + /** + - parameters: + - html: The HTML string to be loaded onto the webview + - apiKey: The hCaptcha sitekey + - baseURL: The URL configured with the sitekey + - endpoint: The JS API endpoint to be loaded onto the HTML file. + - size: Size of visible area + - orientation: Of the widget + - rqdata: Custom supplied challenge data + - theme: Widget theme, value must be valid JS Object or String with brackets + */ + init(html: String, + apiKey: String, + baseURL: URL, + endpoint: URL, + size: HCaptchaSize, + orientation: HCaptchaOrientation, + rqdata: String?, + theme: String, + urlOpener: HCaptchaURLOpener = HCapchaAppURLOpener()) { + Log.debug("WebViewManager.init") + self.urlOpener = urlOpener + super.init() + self.baseURL = baseURL + self.decoder = HCaptchaDecoder { [weak self] result in + self?.handle(result: result) + } + DispatchQueue.global(qos: .userInitiated).async { + let debugInfo = HCaptchaDebugInfo.json + Log.debug("WebViewManager.init after debug") + self.formattedHTML = String(format: html, arguments: ["apiKey": apiKey, + "endpoint": endpoint.absoluteString, + "size": size.rawValue, + "orientation": orientation.rawValue, + "rqdata": rqdata ?? "", + "theme": theme, + "debugInfo": debugInfo, + ]) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + guard !self.stopInitWebViewConfiguration else { return } + + self.setupWebview(html: self.formattedHTML, url: baseURL) + } + } + } + + /** + - parameter view: The view that should present the webview. + + Starts the challenge validation + */ + func validate(on view: UIView) { + Log.debug("WebViewManager.validate on: \(view)") + resultHandled = false + +#if DEBUG + guard !shouldSkipForTests else { + completion?(HCaptchaResult(self, token: "")) + return + } +#endif + view.addSubview(webView) + if self.didFinishLoading && (webView.bounds.size == CGSize.zero || webView.bounds.size == webViewInitSize) { + self.doConfigureWebView() + } + + executeJS(command: .execute) + } + + /// Stops the execution of the webview + func stop() { + Log.debug("WebViewManager.stop") + stopInitWebViewConfiguration = true + webView.stopLoading() + resultHandled = true + } + + /** + Resets the HCaptcha. + + The reset is achieved by calling `ghcaptcha.reset()` on the JS API. + */ + func reset() { + Log.debug("WebViewManager.reset") + configureWebViewDispatchToken = UUID() + stopInitWebViewConfiguration = false + resultHandled = false + if didFinishLoading { + executeJS(command: .reset) + didFinishLoading = false + } else if let formattedHTML = self.formattedHTML { + setupWebview(html: formattedHTML, url: baseURL) + } + } +} + +// MARK: - Private Methods + +/** Private methods for HCaptchaWebViewManager + */ +fileprivate extension HCaptchaWebViewManager { + /** + - returns: An instance of `WKWebViewConfiguration` + + Creates a `WKWebViewConfiguration` to be added to the `WKWebView` instance. + */ + func buildConfiguration() -> WKWebViewConfiguration { + let controller = WKUserContentController() + controller.add(decoder, name: "hcaptcha") + + let conf = WKWebViewConfiguration() + conf.userContentController = controller + + return conf + } + + /** + - parameter result: A `HCaptchaDecoder.Result` with the decoded message. + + Handles the decoder results received from the webview + */ + func handle(result: HCaptchaDecoder.Result) { + Log.debug("WebViewManager.handleResult: \(result)") + + guard !resultHandled else { + Log.debug("WebViewManager.handleResult skip as handled") + return + } + + switch result { + case .token(let token): + completion?(HCaptchaResult(self, token: token)) + case .error(let error): + handle(error: error) + onEvent?(.error, error) + case .showHCaptcha: webView.isHidden = false + case .didLoad: didLoad() + case .onOpen: onEvent?(.open, nil) + case .onExpired: onEvent?(.expired, nil) + case .onChallengeExpired: onEvent?(.challengeExpired, nil) + case .onClose: onEvent?(.close, nil) + case .log: break + } + } + + private func handle(error: HCaptchaError) { + if error == .sessionTimeout { + if shouldResetOnError, let view = webView.superview { + reset() + validate(on: view) + } else { + completion?(HCaptchaResult(self, error: error)) + } + } else { + if let completion = completion { + completion(HCaptchaResult(self, error: error)) + } else { + lastError = error + } + } + } + + private func didLoad() { + Log.debug("WebViewManager.didLoad") + if completion != nil { + executeJS(command: .execute, didLoad: true) + } + didFinishLoading = true + self.doConfigureWebView() + } + + private func doConfigureWebView() { + Log.debug("WebViewManager.doConfigureWebView") + if configureWebView != nil { + DispatchQueue.once(token: configureWebViewDispatchToken) { [weak self] in + guard let `self` = self else { return } + self.configureWebView?(self.webView) + } + } + } + + /** + - parameters: + - html: The embedded HTML file + - url: The base URL given to the webview + + Adds the webview to a valid UIView and loads the initial HTML file + */ + func setupWebview(html: String, url: URL) { + #if os(visionOS) + let windows = UIApplication.shared.connectedScenes + .compactMap { ($0 as? UIWindowScene)?.windows } + .flatMap { $0 } + .sorted { firstWindow, _ in firstWindow.isKeyWindow } + let window = windows.first + #else + let window = UIApplication.shared.windows.first { $0.isKeyWindow } + #endif + if let window { + setupWebview(on: window, html: html, url: url) + } else { + observer = NotificationCenter.default.addObserver( + forName: UIWindow.didBecomeVisibleNotification, + object: nil, + queue: nil + ) { [weak self] notification in + guard let window = notification.object as? UIWindow else { return } + guard let slf = self else { return } + slf.setupWebview(on: window, html: html, url: url) + } + } + } + + /** + - parameters: + - window: The window in which to add the webview + - html: The embedded HTML file + - url: The base URL given to the webview + + Adds the webview to a valid UIView and loads the initial HTML file + */ + func setupWebview(on window: UIWindow, html: String, url: URL) { + Log.debug("WebViewManager.setupWebview") + if webView.superview == nil { + window.addSubview(webView) + } + webView.loadHTMLString(html, baseURL: url) + if webView.navigationDelegate == nil { + webView.navigationDelegate = self + } + + if let observer = observer { + NotificationCenter.default.removeObserver(observer) + } + } + + /** + - parameters: + - command: The JavaScript command to be executed + - didLoad: True if didLoad event already occured + + Executes the JS command that loads the HCaptcha challenge. This method has no effect if the webview hasn't + finished loading. + */ + func executeJS(command: JSCommand, didLoad: Bool = false) { + Log.debug("WebViewManager.executeJS: \(command)") + guard didLoad else { + if let error = lastError { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + Log.debug("WebViewManager complete with pendingError: \(error)") + + self.completion?(HCaptchaResult(self, error: error)) + self.lastError = nil + } + if error == .networkError { + Log.debug("WebViewManager reloads html after \(error) error") + self.webView.loadHTMLString(formattedHTML, baseURL: baseURL) + } + } + return + } + webView.evaluateJavaScript(command.rawValue) { [weak self] _, error in + if let error = error { + self?.decoder.send(error: .unexpected(error)) + } + } + } + + func executeJS(command: JSCommand) { + executeJS(command: command, didLoad: self.didFinishLoading) + } +} diff --git a/StripePayments/StripePayments/Source/Captcha/String+Dict.swift b/StripePayments/StripePayments/Source/Captcha/String+Dict.swift new file mode 100644 index 00000000..ab0e4f35 --- /dev/null +++ b/StripePayments/StripePayments/Source/Captcha/String+Dict.swift @@ -0,0 +1,32 @@ +// +// String+Dict.swift +// HCaptcha +// +// Created by Flávio Caetano on 10/10/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +import Foundation + +extension String { + /** + - parameters: + - format: The string to be formatted. + - arguments: A dictionary containing the which keys should be replaced by which values. + - returns: A formatted string + + Parses a format string using a dictionary of arguments + + Replaces occurrences of `"${key}"` with their respective values. + + ``` + String(format: "Hello, ${user}", ["user": "Flavio"]) // Hello, Flavio + ``` + */ + init(format: String, arguments: [String: CustomStringConvertible]) { + self.init(describing: arguments.reduce(format) + { (format: String, args: (key: String, value: CustomStringConvertible)) -> String in + format.replacingOccurrences(of: "${\(args.key)}", with: args.value.description) + }) + } +} diff --git a/StripePayments/StripePayments/Source/Categories/Enums+CustomStringConvertible.swift b/StripePayments/StripePayments/Source/Categories/Enums+CustomStringConvertible.swift new file mode 100644 index 00000000..4db2961e --- /dev/null +++ b/StripePayments/StripePayments/Source/Categories/Enums+CustomStringConvertible.swift @@ -0,0 +1,877 @@ +// +// Enums+CustomStringConvertible.swift +// Stripe +// +// Autogenerated by generate_objc_enum_string_values.rb +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +/// :nodoc: +extension STPBankAccountHolderType: CustomStringConvertible { + public var description: String { + switch self { + case .company: + return "company" + case .individual: + return "individual" + } + } +} + +/// :nodoc: +extension STPBankAccountStatus: CustomStringConvertible { + public var description: String { + switch self { + case .errored: + return "errored" + case .new: + return "new" + case .validated: + return "validated" + case .verificationFailed: + return "verificationFailed" + case .verified: + return "verified" + } + } +} + +/// :nodoc: +extension STPCardBrand: CustomStringConvertible { + public var description: String { + switch self { + case .JCB: + return "JCB" + case .amex: + return "amex" + case .dinersClub: + return "dinersClub" + case .discover: + return "discover" + case .mastercard: + return "mastercard" + case .unionPay: + return "unionPay" + case .cartesBancaires: + return "cartesBancaires" + case .unknown: + return "unknown" + case .visa: + return "visa" + } + } +} + +/// :nodoc: +extension STPCardFundingType: CustomStringConvertible { + public var description: String { + switch self { + case .credit: + return "credit" + case .debit: + return "debit" + case .other: + return "other" + case .prepaid: + return "prepaid" + } + } +} + +/// :nodoc: +extension STPCollectBankAccountError: CustomStringConvertible { + public var description: String { + switch self { + case .financialConnectionsSDKNotLinked: + return "financialConnectionsSDKNotLinked" + case .invalidClientSecret: + return "invalidClientSecret" + case .unexpectedError: + return "unexpectedError" + } + } +} + +/// :nodoc: +extension STPConnectAccountBusinessType: CustomStringConvertible { + public var description: String { + switch self { + case .company: + return "company" + case .individual: + return "individual" + case .none: + return "nil" + } + } +} + +/// :nodoc: +extension STPFPXBankBrand: CustomStringConvertible { + public var description: String { + switch self { + case .BSN: + return "BSN" + case .CIMB: + return "CIMB" + case .HSBC: + return "HSBC" + case .KFH: + return "KFH" + case .RHB: + return "RHB" + case .UOB: + return "UOB" + case .affinBank: + return "affinBank" + case .allianceBank: + return "allianceBank" + case .ambank: + return "ambank" + case .bankIslam: + return "bankIslam" + case .bankMuamalat: + return "bankMuamalat" + case .bankRakyat: + return "bankRakyat" + case .hongLeongBank: + return "hongLeongBank" + case .maybank2E: + return "maybank2E" + case .maybank2U: + return "maybank2U" + case .ocbc: + return "ocbc" + case .publicBank: + return "publicBank" + case .standardChartered: + return "standardChartered" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPFilePurpose: CustomStringConvertible { + public var description: String { + switch self { + case .disputeEvidence: + return "disputeEvidence" + case .identityDocument: + return "identityDocument" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPIntentActionType: CustomStringConvertible { + public var description: String { + return self.stringValue + } +} + +/// :nodoc: +extension STPIntentActionUseStripeSDKType: CustomStringConvertible { + public var description: String { + switch self { + case .threeDS2Fingerprint: + return "threeDS2Fingerprint" + case .threeDS2Redirect: + return "threeDS2Redirect" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPKlarnaLineItemType: CustomStringConvertible { + public var description: String { + switch self { + case .SKU: + return "SKU" + case .shipping: + return "shipping" + case .tax: + return "tax" + } + } +} + +/// :nodoc: +extension STPKlarnaPaymentMethods: CustomStringConvertible { + public var description: String { + switch self { + case .installments: + return "installments" + case .none: + return "none" + case .payIn4: + return "payIn4" + case .payIn4OrInstallments: + return "payIn4OrInstallments" + } + } +} + +/// :nodoc: +extension STPMandateCustomerAcceptanceType: CustomStringConvertible { + public var description: String { + switch self { + case .offline: + return "offline" + case .online: + return "online" + } + } +} + +/// :nodoc: +extension STPMicrodepositType: CustomStringConvertible { + public var description: String { + switch self { + case .amounts: + return "amounts" + case .descriptorCode: + return "descriptorCode" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentHandlerActionStatus: CustomStringConvertible { + public var description: String { + switch self { + case .canceled: + return "canceled" + case .failed: + return "failed" + case .succeeded: + return "succeeded" + } + } +} + +/// :nodoc: +extension STPPaymentHandlerErrorCode: CustomStringConvertible { + public var description: String { + switch self { + case .intentStatusErrorCode: + return "intentStatusErrorCode" + case .invalidClientSecret: + return "invalidClientSecret" + case .noConcurrentActionsErrorCode: + return "noConcurrentActionsErrorCode" + case .notAuthenticatedErrorCode: + return "notAuthenticatedErrorCode" + case .paymentErrorCode: + return "paymentErrorCode" + case .requiredAppNotAvailable: + return "requiredAppNotAvailable" + case .requiresAuthenticationContextErrorCode: + return "requiresAuthenticationContextErrorCode" + case .requiresPaymentMethodErrorCode: + return "requiresPaymentMethodErrorCode" + case .stripe3DS2ErrorCode: + return "stripe3DS2ErrorCode" + case .timedOutErrorCode: + return "timedOutErrorCode" + case .unsupportedAuthenticationErrorCode: + return "unsupportedAuthenticationErrorCode" + case .unexpectedErrorCode: + return "unexpectedErrorCode" + case .missingReturnURL: + return "missingReturnURL" + } + } +} + +/// :nodoc: +extension STPPaymentIntentActionType: CustomStringConvertible { + public var description: String { + switch self { + case .redirectToURL: + return "redirectToURL" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentIntentCaptureMethod: CustomStringConvertible { + public var description: String { + switch self { + case .automatic: + return "automatic" + case .manual: + return "manual" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentIntentConfirmationMethod: CustomStringConvertible { + public var description: String { + switch self { + case .automatic: + return "automatic" + case .manual: + return "manual" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentIntentLastPaymentErrorType: CustomStringConvertible { + public var description: String { + switch self { + case .api: + return "api" + case .apiConnection: + return "apiConnection" + case .authentication: + return "authentication" + case .card: + return "card" + case .idempotency: + return "idempotency" + case .invalidRequest: + return "invalidRequest" + case .rateLimit: + return "rateLimit" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentIntentSetupFutureUsage: CustomStringConvertible { + public var description: String { + switch self { + case .none: + return "none" + case .offSession: + return "offSession" + case .onSession: + return "onSession" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +@available( + *, + deprecated, + message: "Use STPIntentActionType instead", + renamed: "STPIntentActionType" +) +extension STPPaymentIntentSourceActionType: CustomStringConvertible { + public var description: String { + switch self { + case .authorizeWithURL: + return "authorizeWithURL" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentIntentStatus: CustomStringConvertible { + public var description: String { + switch self { + case .canceled: + return "canceled" + case .processing: + return "processing" + case .requiresAction: + return "requiresAction" + case .requiresCapture: + return "requiresCapture" + case .requiresConfirmation: + return "requiresConfirmation" + case .requiresPaymentMethod: + return "requiresPaymentMethod" + case .requiresSource: + return "requiresSource" + case .requiresSourceAction: + return "requiresSourceAction" + case .succeeded: + return "succeeded" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentMethodCardCheckResult: CustomStringConvertible { + public var description: String { + switch self { + case .failed: + return "failed" + case .pass: + return "pass" + case .unavailable: + return "unavailable" + case .unchecked: + return "unchecked" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentMethodCardWalletType: CustomStringConvertible { + public var description: String { + switch self { + case .amexExpressCheckout: + return "amexExpressCheckout" + case .applePay: + return "applePay" + case .googlePay: + return "googlePay" + case .masterpass: + return "masterpass" + case .samsungPay: + return "samsungPay" + case .link: + return "link" + case .unknown: + return "unknown" + case .visaCheckout: + return "visaCheckout" + } + } +} + +/// :nodoc: +extension STPPaymentMethodType: CustomStringConvertible { + public var description: String { + switch self { + case .AUBECSDebit: + return "AUBECSDebit" + case .EPS: + return "EPS" + case .FPX: + return "FPX" + case .OXXO: + return "OXXO" + case .SEPADebit: + return "SEPADebit" + case .UPI: + return "UPI" + case .USBankAccount: + return "USBankAccount" + case .affirm: + return "affirm" + case .afterpayClearpay: + return "afterpayClearpay" + case .alipay: + return "alipay" + case .bacsDebit: + return "bacsDebit" + case .bancontact: + return "bancontact" + case .blik: + return "blik" + case .boleto: + return "boleto" + case .card: + return "card" + case .cardPresent: + return "cardPresent" + case .giropay: + return "giropay" + case .grabPay: + return "grabPay" + case .iDEAL: + return "iDEAL" + case .klarna: + return "klarna" + case .link: + return "link" + case .netBanking: + return "netBanking" + case .payPal: + return "payPal" + case .przelewy24: + return "przelewy24" + case .sofort: + return "sofort" + case .unknown: + return "unknown" + case .weChatPay: + return "weChatPay" + case .cashApp: + return "cashApp" + case .swish: + return "swish" + case .twint: + return "TWINT" + case .paynow, .zip, .revolutPay, .mobilePay, .amazonPay, .alma, .konbini, .promptPay, .sunbit, .billie, .satispay, .crypto: + // `description` is the value used when this type is converted to a string for debugging purposes, just use the display name. + return displayName + case .multibanco: + return "multibanco" + } + } +} + +/// :nodoc: +extension STPPaymentMethodUSBankAccountHolderType: CustomStringConvertible { + public var description: String { + switch self { + case .company: + return "company" + case .individual: + return "individual" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentMethodUSBankAccountType: CustomStringConvertible { + public var description: String { + switch self { + case .checking: + return "checking" + case .savings: + return "savings" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPPaymentStatus: CustomStringConvertible { + public var description: String { + switch self { + case .error: + return "error" + case .success: + return "success" + case .userCancellation: + return "userCancellation" + } + } +} + +/// :nodoc: +extension STPPinStatus: CustomStringConvertible { + public var description: String { + switch self { + case .ephemeralKeyError: + return "ephemeralKeyError" + case .errorVerificationAlreadyRedeemed: + return "errorVerificationAlreadyRedeemed" + case .errorVerificationCodeIncorrect: + return "errorVerificationCodeIncorrect" + case .errorVerificationExpired: + return "errorVerificationExpired" + case .errorVerificationTooManyAttempts: + return "errorVerificationTooManyAttempts" + case .success: + return "success" + case .unknownError: + return "unknownError" + } + } +} + +/// :nodoc: +extension STPRedirectContextError: CustomStringConvertible { + public var description: String { + switch self { + case .appRedirectError: + return "appRedirectError" + } + } +} + +/// :nodoc: +extension STPRedirectContextState: CustomStringConvertible { + public var description: String { + switch self { + case .cancelled: + return "cancelled" + case .completed: + return "completed" + case .inProgress: + return "inProgress" + case .notStarted: + return "notStarted" + } + } +} + +/// :nodoc: +extension STPSetupIntentLastSetupErrorType: CustomStringConvertible { + public var description: String { + switch self { + case .API: + return "API" + case .apiConnection: + return "apiConnection" + case .authentication: + return "authentication" + case .card: + return "card" + case .idempotency: + return "idempotency" + case .invalidRequest: + return "invalidRequest" + case .rateLimit: + return "rateLimit" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSetupIntentStatus: CustomStringConvertible { + public var description: String { + switch self { + case .canceled: + return "canceled" + case .processing: + return "processing" + case .requiresAction: + return "requiresAction" + case .requiresConfirmation: + return "requiresConfirmation" + case .requiresPaymentMethod: + return "requiresPaymentMethod" + case .succeeded: + return "succeeded" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSetupIntentUsage: CustomStringConvertible { + public var description: String { + switch self { + case .none: + return "none" + case .offSession: + return "offSession" + case .onSession: + return "onSession" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSourceCard3DSecureStatus: CustomStringConvertible { + public var description: String { + switch self { + case .`optional`: + return "`optional`" + case .`required`: + return "`required`" + case .notSupported: + return "notSupported" + case .recommended: + return "recommended" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSourceFlow: CustomStringConvertible { + public var description: String { + switch self { + case .codeVerification: + return "codeVerification" + case .none: + return "none" + case .receiver: + return "receiver" + case .redirect: + return "redirect" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSourceRedirectStatus: CustomStringConvertible { + public var description: String { + switch self { + case .failed: + return "failed" + case .notRequired: + return "notRequired" + case .pending: + return "pending" + case .succeeded: + return "succeeded" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSourceStatus: CustomStringConvertible { + public var description: String { + switch self { + case .canceled: + return "canceled" + case .chargeable: + return "chargeable" + case .consumed: + return "consumed" + case .failed: + return "failed" + case .pending: + return "pending" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSourceType: CustomStringConvertible { + public var description: String { + switch self { + case .EPS: + return "EPS" + case .P24: + return "P24" + case .SEPADebit: + return "SEPADebit" + case .alipay: + return "alipay" + case .bancontact: + return "bancontact" + case .card: + return "card" + case .giropay: + return "giropay" + case .iDEAL: + return "iDEAL" + case .klarna: + return "klarna" + case .multibanco: + return "multibanco" + case .sofort: + return "sofort" + case .threeDSecure: + return "threeDSecure" + case .unknown: + return "unknown" + case .weChatPay: + return "weChatPay" + } + } +} + +/// :nodoc: +extension STPSourceUsage: CustomStringConvertible { + public var description: String { + switch self { + case .reusable: + return "reusable" + case .singleUse: + return "singleUse" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPSourceVerificationStatus: CustomStringConvertible { + public var description: String { + switch self { + case .failed: + return "failed" + case .pending: + return "pending" + case .succeeded: + return "succeeded" + case .unknown: + return "unknown" + } + } +} + +/// :nodoc: +extension STPThreeDSButtonTitleStyle: CustomStringConvertible { + public var description: String { + switch self { + case .`default`: + return "`default`" + case .lowercase: + return "lowercase" + case .sentenceCapitalized: + return "sentenceCapitalized" + case .uppercase: + return "uppercase" + } + } +} + +/// :nodoc: +extension STPThreeDSCustomizationButtonType: CustomStringConvertible { + public var description: String { + switch self { + case .`continue`: + return "`continue`" + case .cancel: + return "cancel" + case .next: + return "next" + case .resend: + return "resend" + case .submit: + return "submit" + } + } +} + +/// :nodoc: +extension STPTokenType: CustomStringConvertible { + public var description: String { + switch self { + case .PII: + return "PII" + case .account: + return "account" + case .bankAccount: + return "bankAccount" + case .card: + return "card" + case .cvcUpdate: + return "cvcUpdate" + } + } +} diff --git a/StripePayments/StripePayments/Source/Helpers/STPBINController.swift b/StripePayments/StripePayments/Source/Helpers/STPBINController.swift new file mode 100644 index 00000000..9d5fd378 --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/STPBINController.swift @@ -0,0 +1,431 @@ +// +// STPBINController.swift +// StripePayments +// +// Created by Jack Flintermann on 5/24/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public struct STPBINRange: Decodable, Equatable { + @_spi(STP) public let panLength: UInt + @_spi(STP) public let brand: STPCardBrand + @_spi(STP) public let accountRangeLow: String + @_spi(STP) public let accountRangeHigh: String + @_spi(STP) public let country: String? + + private enum CodingKeys: String, CodingKey { + case panLength = "pan_length" + case brand = "brand" + case accountRangeLow = "account_range_low" + case accountRangeHigh = "account_range_high" + case country = "country" + } + + @_spi(STP) public init( + from decoder: Decoder + ) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.panLength = try container.decode(UInt.self, forKey: .panLength) + let brandString = try container.decode(String.self, forKey: .brand) + self.brand = STPCard.brand(from: brandString) + self.accountRangeLow = try container.decode(String.self, forKey: .accountRangeLow) + self.accountRangeHigh = try container.decode(String.self, forKey: .accountRangeHigh) + self.country = try? container.decode(String.self, forKey: .country) + self.isHardcoded = false + } + + @_spi(STP) public init( + panLength: UInt, + brand: STPCardBrand, + accountRangeLow: String, + accountRangeHigh: String, + country: String? + ) { + self.panLength = panLength + self.brand = brand + self.accountRangeLow = accountRangeLow + self.accountRangeHigh = accountRangeHigh + self.country = country + self.isHardcoded = true + } + + /// indicates bin range was included in the SDK (rather than downloaded from edge service) + @_spi(STP) public var isHardcoded: Bool +} + +extension STPBINRange { + /// Number matching strategy: Truncate the longer of the two numbers (theirs and our + /// bounds) to match the length of the shorter one, then do numerical compare. + func matchesNumber(_ number: String) -> Bool { + + var withinLowRange = false + var withinHighRange = false + + if number.count < (accountRangeLow.count) { + withinLowRange = + Int(number) ?? 0 >= Int( + (accountRangeLow as NSString?)?.substring(to: number.count) ?? "" + ) + ?? 0 + } else { + withinLowRange = + Int((number as NSString).substring(to: accountRangeLow.count)) ?? 0 >= Int( + accountRangeLow + ) + ?? 0 + } + + if number.count < (accountRangeHigh.count) { + withinHighRange = + Int(number) ?? 0 <= Int( + (accountRangeHigh as NSString?)?.substring(to: number.count) ?? "" + ) ?? 0 + } else { + withinHighRange = + Int((number as NSString).substring(to: accountRangeHigh.count)) ?? 0 <= Int( + accountRangeHigh + ) ?? 0 + } + + return withinLowRange && withinHighRange + } + + func compare(_ other: STPBINRange) -> ComparisonResult { + let result = NSNumber(value: accountRangeLow.count).compare( + NSNumber(value: other.accountRangeLow.count) + ) + + // If they are the same range unknown brands go first. + if result == .orderedSame { + if brand == .unknown && other.brand != .unknown { + return .orderedAscending + } else if brand != .unknown && other.brand == .unknown { + return .orderedDescending + } + } + + return result + + } +} + +private let CardMetadataURL = URL(string: "https://api.stripe.com/edge-internal/card-metadata")! + +extension STPBINRange { + struct STPBINRangeResponse: Decodable { + var data: [STPBINRange] + } + + typealias BINRangeCompletionBlock = (Result) -> Void + + /// Converts a PKPayment object into a Stripe token using the Stripe API. + /// - Parameters: + /// - payment: The user's encrypted payment information as returned from a PKPaymentAuthorizationController. Cannot be nil. + /// - completion: The callback to run with the returned Stripe token (and any errors that may have occurred). + static func retrieve( + apiClient: STPAPIClient = .shared, + forPrefix binPrefix: String, + completion: @escaping BINRangeCompletionBlock + ) { + assert(binPrefix.count == 6, "Requests can only be made with 6-digit binPrefixes.") + // not adding explicit handling for above assert as endpoint will error anyway + let params = [ + "bin_prefix": binPrefix + ] + + apiClient.get(url: CardMetadataURL, parameters: params, completion: completion) + } +} + +@_spi(STP) public typealias STPRetrieveBINRangesCompletionBlock = (Result<[STPBINRange], Error>) -> + Void + +@_spi(STP) public class STPBINController { + @_spi(STP) public static let shared = STPBINController() + + /// For testing + @_spi(STP) public func reset() { + _performSync { + sAllRanges = STPBINController.STPBINRangeInitialRanges + } + } + + @_spi(STP) public func isLoadingCardMetadata(forPrefix binPrefix: String) -> Bool { + var isLoading = false + self._retrievalQueue.sync(execute: { + let binPrefixKey = binPrefix.stp_safeSubstring(to: kPrefixLengthForMetadataRequest) + isLoading = sPendingRequests[binPrefixKey] != nil + }) + return isLoading + } + + @_spi(STP) public func allRanges() -> [STPBINRange] { + var ret: [STPBINRange]? + self._performSync(withAllRangesLock: { + ret = sAllRanges + }) + return ret ?? [] + } + + @_spi(STP) public func binRanges(forNumber number: String) -> [STPBINRange] { + return self.allRanges().filter { (binRange) -> Bool in + binRange.matchesNumber(number) + } + } + + @_spi(STP) public func binRanges(for brand: STPCardBrand) -> [STPBINRange] { + return self.allRanges().filter { (binRange) -> Bool in + binRange.brand == brand + } + } + + @_spi(STP) public func mostSpecificBINRange(forNumber number: String) -> STPBINRange { + let validRanges = self.allRanges().filter { (range) -> Bool in + range.matchesNumber(number) + } + return validRanges.sorted { (r1, r2) -> Bool in + if number.isEmpty { + // empty numbers should always best match to unknown brand + if r1.brand == .unknown && r2.brand != .unknown { + return true + } else if r1.brand != .unknown && r2.brand == .unknown { + return false + } + } + return r1.compare(r2) == .orderedAscending + }.last! + } + + @_spi(STP) public func maxCardNumberLength() -> Int { + return kMaxCardNumberLength + } + + /// Returns the shortest possible card number length for the brand + @_spi(STP) public func minCardNumberLength(for brand: STPCardBrand) -> Int { + switch brand { + case .visa, .amex, .mastercard, .discover, .JCB, .dinersClub, .cartesBancaires: + return allRanges().reduce(Int.max) { currentMinimum, range in + if range.brand == brand { + return min(currentMinimum, Int(range.panLength)) + } else { + return currentMinimum + } + } + case .unionPay: + return 16 + case .unknown: + return 13 + } + } + + @_spi(STP) public func minLengthForFullBINRange() -> Int { + return kPrefixLengthForMetadataRequest + } + + /// This is basically a wrapper around: + /// + /// 1. Does BIN have variable length pans, i.e. do we need to call the metadata service + /// 2. If yes, have we already gotten a response from the metadata service + @_spi(STP) public func hasBINRanges(forPrefix binPrefix: String) -> Bool { + if self.isInvalidBINPrefix(binPrefix) { + return true // we won't fetch any more info for this prefix + } + // if we know a card has a static length, we don't need to ask the BIN service + if !self.isVariableLengthBINPrefix(binPrefix) { + return true + } + var hasBINRanges = false + self._retrievalQueue.sync(execute: { + let binPrefixKey = binPrefix.stp_safeSubstring(to: kPrefixLengthForMetadataRequest) + hasBINRanges = + (binPrefixKey.count) == kPrefixLengthForMetadataRequest + && sRetrievedRanges[binPrefixKey] != nil + }) + return hasBINRanges + } + + func isInvalidBINPrefix(_ binPrefix: String) -> Bool { + let firstFive = binPrefix.stp_safeSubstring(to: kPrefixLengthForMetadataRequest - 1) + return (self.mostSpecificBINRange(forNumber: firstFive)).brand == .unknown + } + + /// This will asynchronously check if we have already fetched metadata for this prefix and if we have not will + /// issue a network request to retrieve it if possible. + /// - Parameter recordErrorsAsSuccess: An unfortunate toggle for behavior that STPCardFormView/STPPaymentCardTextField depends on. See https://jira.corp.stripe.com/browse/MOBILESDK-724 + /// - Parameter onlyFetchForVariableLengthBINs: Only hit the network if a BIN is known to be variable length. (e.g. UnionPay). + /// If this is disabled, we will *always* fetch and cache BIN information for the passed BIN. + /// Use caution when disabling this: The BIN length information coming from the service may not be correct, which will + /// cause issues when validating PAN length. + @_spi(STP) public func retrieveBINRanges( + forPrefix binPrefix: String, + recordErrorsAsSuccess: Bool = true, + onlyFetchForVariableLengthBINs: Bool = true, + completion: @escaping STPRetrieveBINRangesCompletionBlock + ) { + self._retrievalQueue.async(execute: { + let binPrefixKey = binPrefix.stp_safeSubstring(to: kPrefixLengthForMetadataRequest) + if self.sRetrievedRanges[binPrefixKey] != nil + || (binPrefixKey.count) < kPrefixLengthForMetadataRequest + || self.isInvalidBINPrefix(binPrefixKey) + || (onlyFetchForVariableLengthBINs && !self.isVariableLengthBINPrefix(binPrefix)) + { + // if we already have a metadata response or the binPrefix isn't long enough to make a request, + // or we know that this is not a valid BIN prefix + // or we know this isn't a BIN prefix that could contain variable length BINs + // return the bin ranges we already have on device + DispatchQueue.main.async(execute: { + completion(.success(self.binRanges(forNumber: binPrefix))) + }) + } else if self.sPendingRequests[binPrefixKey] != nil { + // A request for this prefix is already in flight, add the completion block to sPendingRequests + if let sPendingRequest = self.sPendingRequests[binPrefixKey] { + self.sPendingRequests[binPrefixKey] = sPendingRequest + [completion] + } + } else { + + self.sPendingRequests[binPrefixKey] = [completion] + + STPBINRange.retrieve( + forPrefix: binPrefixKey, + completion: { result in + self._retrievalQueue.async(execute: { + let ranges = result.map { $0.data } + let completionBlocks = self.sPendingRequests[binPrefixKey] + + self.sPendingRequests.removeValue(forKey: binPrefixKey) + + if recordErrorsAsSuccess { + // The following is a comment for STPCardFormView/STPPaymentCardTextField: + // we'll record this response even if there was an error + // this will prevent our validation from getting stuck thinking we don't + // have enough info if the metadata service is down or unreachable + // Could improve this in the future with "smart" retries + self.sRetrievedRanges[binPrefixKey] = (try? ranges.get()) ?? [] + } else if let ranges = try? ranges.get(), !ranges.isEmpty { + self.sRetrievedRanges[binPrefixKey] = ranges + } + self._performSync(withAllRangesLock: { + self.sAllRanges = + self.sAllRanges + ((try? ranges.get()) ?? []) + }) + + if case .failure = ranges { + STPAnalyticsClient.sharedClient.logCardMetadataResponseFailure() + } + + DispatchQueue.main.async(execute: { + for block in completionBlocks ?? [] { + block(ranges) + } + }) + }) + } + ) + } + }) + } + + // MARK: - Class Utilities + + static let STPBINRangeInitialRanges: [STPBINRange] = { + let ranges: [(String, String, UInt, STPCardBrand)] = [ + // Unknown + ("", "", 19, .unknown), + // American Express + ("34", "34", 15, .amex), + ("37", "37", 15, .amex), + // Diners Club + ("30", "30", 16, .dinersClub), + ("36", "36", 14, .dinersClub), + ("38", "39", 16, .dinersClub), + // Discover + ("60", "60", 16, .discover), + ("64", "65", 16, .discover), + // JCB + ("35", "35", 16, .JCB), + // Mastercard + ("50", "59", 16, .mastercard), + ("22", "27", 16, .mastercard), + ("67", "67", 16, .mastercard), // Maestro + // UnionPay + ("62", "62", 16, .unionPay), + ("81", "81", 16, .unionPay), + // Include at least one known 19-digit BIN for maxLength + ("621598", "621598", 19, .unionPay), + // Visa + ("40", "49", 16, .visa), + ("413600", "413600", 13, .visa), + ("444509", "444509", 13, .visa), + ("444509", "444509", 13, .visa), + ("444550", "444550", 13, .visa), + ("450603", "450603", 13, .visa), + ("450617", "450617", 13, .visa), + ("450628", "450629", 13, .visa), + ("450636", "450636", 13, .visa), + ("450640", "450641", 13, .visa), + ("450662", "450662", 13, .visa), + ("463100", "463100", 13, .visa), + ("476142", "476142", 13, .visa), + ("476143", "476143", 13, .visa), + ("492901", "492902", 13, .visa), + ("492920", "492920", 13, .visa), + ("492923", "492923", 13, .visa), + ("492928", "492930", 13, .visa), + ("492937", "492937", 13, .visa), + ("492939", "492939", 13, .visa), + ("492960", "492960", 13, .visa), + ] + var binRanges: [STPBINRange] = [] + for range in ranges { + let binRange = STPBINRange.init( + panLength: range.2, + brand: range.3, + accountRangeLow: range.0, + accountRangeHigh: range.1, + country: nil + ) + binRanges.append(binRange) + } + return binRanges + }() + + var sAllRanges: [STPBINRange] = { + return STPBINRangeInitialRanges + }() + + let sAllRangesLockQueue: DispatchQueue = { + DispatchQueue(label: "com.stripe.STPBINRange.allRanges") + }() + + func _performSync(withAllRangesLock block: () -> Void) { + sAllRangesLockQueue.sync(execute: { + block() + }) + } + + // sPendingRequests contains the completion blocks for a given metadata request that we have not yet gotten a response for + var sPendingRequests: [String: [STPRetrieveBINRangesCompletionBlock]] = [:] + + // sRetrievedRanges tracks the bin prefixes for which we've already received metadata responses + var sRetrievedRanges: [String: [STPBINRange]] = [:] + + // _retrievalQueue protects access to the two above dictionaries, sSpendingRequests and sRetrievedRanges + let _retrievalQueue: DispatchQueue = { + return DispatchQueue(label: "com.stripe.retrieveBINRangesForPrefix") + }() + + func isVariableLengthBINPrefix(_ binPrefix: String) -> Bool { + guard !binPrefix.isEmpty else { + return false + } + let firstFive = binPrefix.stp_safeSubstring(to: kPrefixLengthForMetadataRequest - 1) + // Only UnionPay has variable-length cards at the moment. + return (self.mostSpecificBINRange(forNumber: firstFive)).brand == .unionPay + } +} + +private let kMaxCardNumberLength: Int = 19 +private let kPrefixLengthForMetadataRequest: Int = 6 diff --git a/StripePayments/StripePayments/Source/Helpers/STPBankAccountCollector.swift b/StripePayments/StripePayments/Source/Helpers/STPBankAccountCollector.swift new file mode 100644 index 00000000..4e87cffc --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/STPBankAccountCollector.swift @@ -0,0 +1,709 @@ +// +// STPBankAccountCollector.swift +// StripePayments +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/// Error codes generated by STPBankAccountCollector +@objc public enum STPCollectBankAccountError: Int { + + /// Error when using APIs that require the linking the StripeFinancialConnections SDK + @objc(STPCollectBankAccountErrorFinancialConnectionsSDKNotLinked) + case financialConnectionsSDKNotLinked + + /// Error when a secret can not be parsed to retrieve the ID + @objc(STPCollectBankAccountErrorInvalidClientSecret) + case invalidClientSecret + + /// Unexpected behavior in calling the API + @objc(STPCollectBankAccountErrorUnexpectedError) + case unexpectedError +} + +/// A class responsible for collecting bank account information +public class STPBankAccountCollector: NSObject { + + /// By default `sharedHandler` initializes with STPAPIClient.shared. + public var apiClient: STPAPIClient + + @objc(`init`) + @available(swift, deprecated: 0.0.1, obsoleted: 0.0.1, renamed: "init()") + public convenience override init() { + self.init(apiClient: STPAPIClient.shared) + } + + public init( + apiClient: STPAPIClient = .shared + ) { + self.apiClient = apiClient + } + + // MARK: Collect Bank Account - Payment Intent + public typealias STPCollectBankAccountForPaymentCompletionBlock = (STPPaymentIntent?, NSError?) + -> Void + + /// Presents a modal from the viewController to collect bank account + /// and if completed successfully, link your bank account to a PaymentIntent + /// - Parameters: + /// - clientSecret: Client secret of the payment intent + /// - params: Parameters for this call + /// - viewController: Presenting view controller that will present the modal + /// - completion: Completion block to be called on completion of the operation. + /// Upon success, the `STPPaymentIntent` instance will have an + /// expanded `paymentMethod` containing detailed payment method information + @objc(collectBankAccountForPaymentWithClientSecret:params:from:completion:) + public func collectBankAccountForPayment( + clientSecret: String, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + completion: @escaping STPCollectBankAccountForPaymentCompletionBlock + ) { + collectBankAccountForPayment( + clientSecret: clientSecret, + returnURL: nil, + params: params, + from: viewController, + completion: completion + ) + } + + /// Presents a modal from the viewController to collect bank account + /// and if completed successfully, link your bank account to a PaymentIntent + /// - Parameters: + /// - clientSecret: Client secret of the payment intent + /// - returnURL: A URL that redirects back to your app to be used to return after completing authentication in another app (such as bank app or Safari). + /// - params: Parameters for this call + /// - viewController: Presenting view controller that will present the modal + /// - completion: Completion block to be called on completion of the operation. + /// Upon success, the `STPPaymentIntent` instance will have an + /// expanded `paymentMethod` containing detailed payment method information + @objc(collectBankAccountForPaymentWithClientSecret:returnURL:params:from:completion:) + public func collectBankAccountForPayment( + clientSecret: String, + returnURL: String?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + completion: @escaping STPCollectBankAccountForPaymentCompletionBlock + ) { + collectBankAccountForPayment( + clientSecret: clientSecret, + returnURL: returnURL, + params: params, + from: viewController, + onEvent: nil, + completion: completion + ) + } + + /// Presents a modal from the viewController to collect bank account + /// and if completed successfully, link your bank account to a PaymentIntent + /// - Parameters: + /// - clientSecret: Client secret of the payment intent + /// - returnURL: A URL that redirects back to your app to be used to return after completing authentication in another app (such as bank app or Safari). + /// - params: Parameters for this call + /// - viewController: Presenting view controller that will present the modal + /// - onEvent: The `onEvent` closure is triggered upon the occurrence of specific events during the process of a user connecting their financial accounts. + /// - completion: Completion block to be called on completion of the operation. + /// Upon success, the `STPPaymentIntent` instance will have an + /// expanded `paymentMethod` containing detailed payment method information + public func collectBankAccountForPayment( + clientSecret: String, + returnURL: String?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + completion: @escaping STPCollectBankAccountForPaymentCompletionBlock + ) { + let paymentIntentID = STPPaymentIntent.id(fromClientSecret: clientSecret) + logCollectBankAccountStarted(type: .payment, intentID: paymentIntentID) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: (FinancialConnectionsSDKResult?, STPPaymentIntent?, NSError?) -> Void = { result, paymentIntent, error in + self.logCollectBankAccountFinished(type: .payment, intentID: paymentIntent?.stripeId, linkAccountSessionID: nil, financialConnectionsSDKResult: result, error: error) + completion(paymentIntent, error) + } + guard let paymentIntentID else { + completion(nil, nil, error(for: .invalidClientSecret)) + return + } + let financialConnectionsCompletion: + (FinancialConnectionsSDKResult?, LinkAccountSession?, NSError?) -> Void = { + result, + linkAccountSession, + error in + if let error { + completion(result, nil, error) + return + } + guard let result else { + completion(result, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "collectBankAccountForPayment() completed without a result")) + return + } + guard let linkAccountSession else { + completion(result, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "collectBankAccountForPayment() completed without a link account session")) + return + } + + switch result { + case .completed: + self.attachLinkAccountSessionToPaymentIntent( + paymentIntentID: paymentIntentID, + clientSecret: clientSecret, + linkAccountSession: linkAccountSession + ) { paymentIntent, error in + completion(result, paymentIntent, error) + } + case .cancelled: + self.apiClient.retrievePaymentIntent(withClientSecret: clientSecret) { + intent, + error in + if let intent { + completion(result, intent, nil) + } else if let error { + completion(result, nil, error as NSError) + } else { + completion(result, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "Canceled and retrieved PI without an error or intent")) + } + } + case .failed(let error): + completion(result, nil, error as NSError) + } + } + _collectBankAccountForPayment( + clientSecret: clientSecret, + returnURL: returnURL, + onEvent: onEvent, + params: params, + from: viewController, + financialConnectionsCompletion: financialConnectionsCompletion + ) + } + + @_spi(STP) public typealias CollectBankAccountCompletionBlock = (FinancialConnectionsSDKResult?, LinkAccountSession?, NSError?) -> Void + @_spi(STP) public func collectBankAccountForPayment( + clientSecret: String, + returnURL: String?, + additionalParameters: [String: Any] = [:], + elementsSessionContext: ElementsSessionContext?, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + financialConnectionsCompletion: @escaping ( + FinancialConnectionsSDKResult?, LinkAccountSession?, NSError? + ) -> Void + ) { + let paymentIntentID = STPPaymentIntent.id(fromClientSecret: clientSecret) + logCollectBankAccountStarted(type: .payment, intentID: paymentIntentID) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let financialConnectionsCompletion: (FinancialConnectionsSDKResult?, LinkAccountSession?, NSError?) -> Void = { result, linkAccountSession, error in + self.logCollectBankAccountFinished(type: .payment, intentID: paymentIntentID, linkAccountSessionID: linkAccountSession?.stripeID, financialConnectionsSDKResult: result, error: error) + financialConnectionsCompletion(result, linkAccountSession, error) + } + _collectBankAccountForPayment( + clientSecret: clientSecret, + returnURL: returnURL, + additionalParameters: additionalParameters, + elementsSessionContext: elementsSessionContext, + onEvent: onEvent, + params: params, + from: viewController, + financialConnectionsCompletion: financialConnectionsCompletion + ) + } + + private func _collectBankAccountForPayment( + clientSecret: String, + returnURL: String?, + additionalParameters: [String: Any] = [:], + elementsSessionContext: ElementsSessionContext? = nil, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + financialConnectionsCompletion: @escaping ( + FinancialConnectionsSDKResult?, LinkAccountSession?, NSError? + ) -> Void + ) { + guard + let financialConnectionsAPI = FinancialConnectionsSDKAvailability.financialConnections() + else { + assertionFailure("FinancialConnections SDK has not been linked into your project") + financialConnectionsCompletion(nil, nil, error(for: .financialConnectionsSDKNotLinked)) + return + } + + guard let paymentIntentID = STPPaymentIntent.id(fromClientSecret: clientSecret) else { + financialConnectionsCompletion(nil, nil, error(for: .invalidClientSecret)) + return + } + + let linkAccountSessionCallback: STPLinkAccountSessionBlock = { linkAccountSession, error in + if let error { + financialConnectionsCompletion(nil, nil, error as NSError) + return + } + guard let linkAccountSession else { + financialConnectionsCompletion( + nil, + nil, + self.error(for: .unexpectedError, loggingSafeErrorMessage: "createLinkAccountSession w/ PI called without an error or link account session") + ) + return + } + financialConnectionsAPI.presentFinancialConnectionsSheet( + apiClient: self.apiClient, + clientSecret: linkAccountSession.clientSecret, + returnURL: returnURL, + elementsSessionContext: elementsSessionContext, + onEvent: onEvent, + from: viewController + ) { result in + financialConnectionsCompletion(result, linkAccountSession, nil) + } + } + + apiClient.createLinkAccountSession( + paymentIntentID: paymentIntentID, + clientSecret: clientSecret, + paymentMethodType: params.paymentMethodParams.type, + customerName: params.paymentMethodParams.billingDetails?.name, + customerEmailAddress: params.paymentMethodParams.billingDetails?.email, + linkMode: elementsSessionContext?.linkMode, + additionalParameters: additionalParameters, + completion: linkAccountSessionCallback + ) + } + + // MARK: Helper + private func attachLinkAccountSessionToPaymentIntent( + paymentIntentID: String, + clientSecret: String, + linkAccountSession: LinkAccountSession, + completion: @escaping STPCollectBankAccountForPaymentCompletionBlock + ) { + STPAPIClient.shared.attachLinkAccountSession( + paymentIntentID: paymentIntentID, + linkAccountSessionID: linkAccountSession.stripeID, + clientSecret: clientSecret + ) { paymentIntent, error in + if let error { + completion( + nil, + error as NSError + ) + return + } + guard let paymentIntent = paymentIntent else { + completion(nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "attachLinkAccountSession() returned neither error nor PaymentIntent")) + return + } + completion(paymentIntent, nil) + } + } + + // MARK: Collect Bank Account - Setup Intent + public typealias STPCollectBankAccountForSetupCompletionBlock = (STPSetupIntent?, NSError?) -> + Void + + /// Presents a modal from the viewController to collect bank account + /// and if completed successfully, link your bank account to a SetupIntent + /// - Parameters: + /// - clientSecret: Client secret of the setup intent + /// - params: Parameters for this call + /// - viewController: Presenting view controller that will present the modal + /// - completion: Completion block to be called on completion of the operation. + /// Upon success, the `STPSetupIntent` instance will have an + /// expanded `paymentMethod` containing detailed payment method information + @objc(collectBankAccountForSetupWithClientSecret:params:from:completion:) + public func collectBankAccountForSetup( + clientSecret: String, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + completion: @escaping STPCollectBankAccountForSetupCompletionBlock + ) { + collectBankAccountForSetup( + clientSecret: clientSecret, + returnURL: nil, + params: params, + from: viewController, + completion: completion + ) + } + + /// Presents a modal from the viewController to collect bank account + /// and if completed successfully, link your bank account to a SetupIntent + /// - Parameters: + /// - clientSecret: Client secret of the setup intent + /// - returnURL: A URL that redirects back to your app to be used to return after completing authentication in another app (such as bank app or Safari). + /// - params: Parameters for this call + /// - viewController: Presenting view controller that will present the modal + /// - completion: Completion block to be called on completion of the operation. + /// Upon success, the `STPSetupIntent` instance will have an + /// expanded `paymentMethod` containing detailed payment method information + @objc(collectBankAccountForSetupWithClientSecret:returnURL:params:from:completion:) + public func collectBankAccountForSetup( + clientSecret: String, + returnURL: String?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + completion: @escaping STPCollectBankAccountForSetupCompletionBlock + ) { + collectBankAccountForSetup( + clientSecret: clientSecret, + returnURL: returnURL, + params: params, + from: viewController, + onEvent: nil, + completion: completion + ) + } + + /// Presents a modal from the viewController to collect bank account + /// and if completed successfully, link your bank account to a SetupIntent + /// - Parameters: + /// - clientSecret: Client secret of the setup intent + /// - returnURL: A URL that redirects back to your app to be used to return after completing authentication in another app (such as bank app or Safari). + /// - params: Parameters for this call + /// - viewController: Presenting view controller that will present the modal + /// - onEvent: The `onEvent` closure is triggered upon the occurrence of specific events during the process of a user connecting their financial accounts. + /// - completion: Completion block to be called on completion of the operation. + /// Upon success, the `STPSetupIntent` instance will have an + /// expanded `paymentMethod` containing detailed payment method information + public func collectBankAccountForSetup( + clientSecret: String, + returnURL: String?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + completion: @escaping STPCollectBankAccountForSetupCompletionBlock + ) { + let setupIntentID = STPSetupIntent.id(fromClientSecret: clientSecret) + logCollectBankAccountStarted(type: .setup, intentID: setupIntentID) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: (FinancialConnectionsSDKResult?, STPSetupIntent?, NSError?) -> Void = { result, setupIntent, error in + self.logCollectBankAccountFinished(type: .setup, intentID: setupIntent?.stripeID, linkAccountSessionID: nil, financialConnectionsSDKResult: result, error: error) + completion(setupIntent, error) + } + guard let setupIntentID else { + completion(nil, nil, error(for: .invalidClientSecret)) + return + } + let financialConnectionsCompletion: + (FinancialConnectionsSDKResult?, LinkAccountSession?, NSError?) -> Void = { + result, + linkAccountSession, + error in + if let error { + completion(result, nil, error as NSError) + return + } + guard let result else { + completion(result, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "collectBankAccountForSetup() completed without a result")) + return + } + guard let linkAccountSession else { + completion(result, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "collectBankAccountForSetup() completed without a link account session")) + return + } + switch result { + case .completed: + self.attachLinkAccountSessionToSetupIntent( + setupIntentID: setupIntentID, + clientSecret: clientSecret, + linkAccountSession: linkAccountSession + ) { setupIntent, error in + completion(result, setupIntent, error) + } + case .cancelled: + self.apiClient.retrieveSetupIntent(withClientSecret: clientSecret) { + intent, + error in + if let intent = intent { + completion(result, intent, nil) + } else if let error { + completion(result, nil, error as NSError) + } else { + completion(result, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "Canceled and retrieved SI without an error or intent")) + } + } + case .failed(let error): + completion(result, nil, error as NSError) + } + } + collectBankAccountForSetup( + clientSecret: clientSecret, + returnURL: returnURL, + onEvent: onEvent, + params: params, + from: viewController, + financialConnectionsCompletion: financialConnectionsCompletion + ) + } + + @_spi(STP) public func collectBankAccountForSetup( + clientSecret: String, + returnURL: String?, + additionalParameters: [String: Any] = [:], + elementsSessionContext: ElementsSessionContext? = nil, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + financialConnectionsCompletion: @escaping ( + FinancialConnectionsSDKResult?, LinkAccountSession?, NSError? + ) -> Void + ) { + let setupIntentID = STPSetupIntent.id(fromClientSecret: clientSecret) + logCollectBankAccountStarted(type: .setup, intentID: setupIntentID) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let financialConnectionsCompletion: (FinancialConnectionsSDKResult?, LinkAccountSession?, NSError?) -> Void = { result, linkAccountSession, error in + self.logCollectBankAccountFinished(type: .setup, intentID: setupIntentID, linkAccountSessionID: linkAccountSession?.stripeID, financialConnectionsSDKResult: result, error: error) + financialConnectionsCompletion(result, linkAccountSession, error) + } + _collectBankAccountForSetup( + clientSecret: clientSecret, + returnURL: returnURL, + additionalParameters: additionalParameters, + elementsSessionContext: elementsSessionContext, + onEvent: onEvent, + params: params, + from: viewController, + financialConnectionsCompletion: financialConnectionsCompletion + ) + } + + private func _collectBankAccountForSetup( + clientSecret: String, + returnURL: String?, + additionalParameters: [String: Any] = [:], + elementsSessionContext: ElementsSessionContext?, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + params: STPCollectBankAccountParams, + from viewController: UIViewController, + financialConnectionsCompletion: @escaping ( + FinancialConnectionsSDKResult?, LinkAccountSession?, NSError? + ) -> Void + ) { + guard + let financialConnectionsAPI = FinancialConnectionsSDKAvailability.financialConnections() + else { + assertionFailure("FinancialConnections SDK has not been linked into your project") + financialConnectionsCompletion(nil, nil, error(for: .financialConnectionsSDKNotLinked)) + return + } + guard let setupIntentID = STPSetupIntent.id(fromClientSecret: clientSecret) else { + financialConnectionsCompletion(nil, nil, error(for: .invalidClientSecret)) + return + } + let linkAccountSessionCallback: STPLinkAccountSessionBlock = { linkAccountSession, error in + if let error { + financialConnectionsCompletion(nil, nil, error as NSError) + return + } + guard let linkAccountSession else { + financialConnectionsCompletion( + nil, + nil, + self.error(for: .unexpectedError, loggingSafeErrorMessage: "createLinkAccountSession w/ SI called without an error or link account session") + ) + return + } + + financialConnectionsAPI.presentFinancialConnectionsSheet( + apiClient: self.apiClient, + clientSecret: linkAccountSession.clientSecret, + returnURL: returnURL, + elementsSessionContext: elementsSessionContext, + onEvent: onEvent, + from: viewController + ) { result in + financialConnectionsCompletion(result, linkAccountSession, nil) + } + } + apiClient.createLinkAccountSession( + setupIntentID: setupIntentID, + clientSecret: clientSecret, + paymentMethodType: params.paymentMethodParams.type, + customerName: params.paymentMethodParams.billingDetails?.name, + customerEmailAddress: params.paymentMethodParams.billingDetails?.email, + linkMode: elementsSessionContext?.linkMode, + additionalParameters: additionalParameters, + completion: linkAccountSessionCallback + ) + } + + // MARK: Helper + private func attachLinkAccountSessionToSetupIntent( + setupIntentID: String, + clientSecret: String, + linkAccountSession: LinkAccountSession, + completion: @escaping STPCollectBankAccountForSetupCompletionBlock + ) { + STPAPIClient.shared.attachLinkAccountSession( + setupIntentID: setupIntentID, + linkAccountSessionID: linkAccountSession.stripeID, + clientSecret: clientSecret + ) { setupIntent, error in + if let error { + completion(nil, error as NSError) + return + } + guard let setupIntent else { + completion(nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "attachLinkAccountSession() returned neither error nor SetupIntent")) + return + } + completion(setupIntent, nil) + } + } + + // MARK: - Collect Bank Account - Deferred Intent + @_spi(STP) public func collectBankAccountForDeferredIntent( + sessionId: String, + returnURL: String?, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + amount: Int?, + currency: String?, + onBehalfOf: String?, + additionalParameters: [String: Any] = [:], + elementsSessionContext: ElementsSessionContext?, + from viewController: UIViewController, + financialConnectionsCompletion: @escaping ( + FinancialConnectionsSDKResult?, LinkAccountSession?, NSError? + ) -> Void + ) { + logCollectBankAccountStarted(type: .deferred, intentID: nil) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let financialConnectionsCompletion: (FinancialConnectionsSDKResult?, LinkAccountSession?, NSError?) -> Void = { result, linkAccountSession, error in + self.logCollectBankAccountFinished(type: .deferred, intentID: nil, linkAccountSessionID: linkAccountSession?.stripeID, financialConnectionsSDKResult: result, error: error) + financialConnectionsCompletion(result, linkAccountSession, error) + } + + guard + let financialConnectionsAPI = FinancialConnectionsSDKAvailability.financialConnections() + else { + assertionFailure("FinancialConnections SDK has not been linked into your project") + financialConnectionsCompletion(nil, nil, error(for: .financialConnectionsSDKNotLinked)) + return + } + + apiClient.createLinkAccountSessionForDeferredIntent( + sessionId: sessionId, + amount: amount, + currency: currency, + onBehalfOf: onBehalfOf, + linkMode: elementsSessionContext?.linkMode, + additionalParameters: additionalParameters + ) { linkAccountSession, error in + if let error { + financialConnectionsCompletion(nil, nil, error as NSError) + return + } + guard let linkAccountSession else { + financialConnectionsCompletion(nil, nil, self.error(for: .unexpectedError, loggingSafeErrorMessage: "createLinkAccountSessionForDeferredIntent called without an error or link account session")) + return + } + financialConnectionsAPI.presentFinancialConnectionsSheet( + apiClient: self.apiClient, + clientSecret: linkAccountSession.clientSecret, + returnURL: returnURL, + elementsSessionContext: elementsSessionContext, + onEvent: onEvent, + from: viewController + ) { result in + financialConnectionsCompletion(result, linkAccountSession, nil) + } + } + } +} + +// MARK: - Error +extension STPBankAccountCollector { + private func error( + for errorCode: STPCollectBankAccountError, + loggingSafeErrorMessage: String? = nil + ) -> NSError { + var userInfo: [String: String] = [:] + switch errorCode { + case .financialConnectionsSDKNotLinked: + userInfo[STPError.errorMessageKey] = + "StripeFinancialConnections SDK has not been linked into your project" + case .invalidClientSecret: + userInfo[STPError.errorMessageKey] = "Unable to parse client secret" + case .unexpectedError: + userInfo[STPError.errorMessageKey] = loggingSafeErrorMessage + } + return STPBankAccountCollectorError(code: errorCode, loggingSafeUserInfo: userInfo) as NSError + } +} + +/// STPBankAccountCollector errors (i.e. errors that are created by the STPBankAccountCollector class and have a corresponding STPCollectBankAccountError) used to be NSErrors. +/// This struct exists so that these errors can be Swift errors to conform to AnalyticLoggableError, while still looking like the old NSErrors to users (i.e. same domain and code). +struct STPBankAccountCollectorError: Error, CustomNSError, AnalyticLoggableError { + // AnalyticLoggableError properties + let analyticsErrorType: String = errorDomain + let analyticsErrorCode: String + let additionalNonPIIErrorDetails: [String: Any] + + // CustomNSError properties, to not break old behavior when this was an NSError + static let errorDomain: String = "STPBankAccountCollectorErrorDomain" + let errorUserInfo: [String: Any] + let errorCode: Int + + init(code: STPCollectBankAccountError, loggingSafeUserInfo: [String: String]) { + errorCode = code.rawValue + // Set analytics error code to the description (e.g. "invalidClientSecret") + analyticsErrorCode = code.description + errorUserInfo = loggingSafeUserInfo + additionalNonPIIErrorDetails = loggingSafeUserInfo + } +} + +// MARK: - Analytic +extension STPBankAccountCollector { + fileprivate struct Analytic: StripeCore.Analytic { + let event: StripeCore.STPAnalyticEvent + let intentID: String? + let linkAccountSessionID: String? + let intentType: IntentType + let financialConnectionsSDKResult: FinancialConnectionsSDKResult? + let error: Error? + + var params: [String: Any] { + var params: [String: Any] = error?.serializeForV1Analytics() ?? [:] + params["intent_id"] = intentID + params["intent_type"] = intentType.rawValue + params["link_account_session_id"] = linkAccountSessionID + params["fc_sdk_result"] = { + switch financialConnectionsSDKResult { + case nil: + return nil + case .cancelled: + return "cancelled" + case .completed: + return "completed" + case .failed: + return "failed" + } + }() + return params + } + + } + enum IntentType: String { + case payment + case setup + case deferred + } + + func logCollectBankAccountStarted(type: IntentType, intentID: String?) { + let analytic = Analytic(event: .bankAccountCollectorStarted, intentID: intentID, linkAccountSessionID: nil, intentType: type, financialConnectionsSDKResult: nil, error: nil) + STPAnalyticsClient.sharedClient.log(analytic: analytic, apiClient: self.apiClient) + } + + func logCollectBankAccountFinished(type: IntentType, intentID: String?, linkAccountSessionID: String?, financialConnectionsSDKResult: FinancialConnectionsSDKResult?, error: Error?) { + let analytic = Analytic(event: .bankAccountCollectorFinished, intentID: intentID, linkAccountSessionID: linkAccountSessionID, intentType: type, financialConnectionsSDKResult: financialConnectionsSDKResult, error: error) + STPAnalyticsClient.sharedClient.log(analytic: analytic, apiClient: self.apiClient) + } +} diff --git a/StripePayments/StripePayments/Source/Helpers/STPBlocks.swift b/StripePayments/StripePayments/Source/Helpers/STPBlocks.swift new file mode 100644 index 00000000..22127017 --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/STPBlocks.swift @@ -0,0 +1,122 @@ +// +// STPBlocks.swift +// StripePayments +// +// Created by Jack Flintermann on 3/23/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +import PassKit + +/// An enum representing the status of a payment requested from the user. +@objc public enum STPPaymentStatus: Int { + /// The payment succeeded. + case success + /// The payment failed due to an unforeseen error, such as the user's Internet connection being offline. + case error + /// The user cancelled the payment (for example, by hitting "cancel" in the Apple Pay dialog). + case userCancellation +} + +/// A block that may optionally be called with an error. +/// - Parameter error: The error that occurred, if any. +public typealias STPErrorBlock = (Error?) -> Void +/// A block that contains a boolean success param and may optionally be called with an error. +/// - Parameters: +/// - success: Whether the task succeeded. +/// - error: The error that occurred, if any. +public typealias STPBooleanSuccessBlock = (Bool, Error?) -> Void +/// A callback to be run with a JSON response. +/// - Parameters: +/// - jsonResponse: The JSON response, or nil if an error occured. +/// - error: The error that occurred, if any. +public typealias STPJSONResponseCompletionBlock = ([AnyHashable: Any]?, Error?) -> Void +/// A callback to be run with a token response from the Stripe API. +/// - Parameters: +/// - token: The Stripe token from the response. Will be nil if an error occurs. - seealso: STPToken +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPTokenCompletionBlock = (STPToken?, Error?) -> Void +/// A callback to be run with a source response from the Stripe API. +/// - Parameters: +/// - source: The Stripe source from the response. Will be nil if an error occurs. - seealso: STPSource +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPSourceCompletionBlock = (STPSource?, Error?) -> Void +/// A callback to be run with a source or card response from the Stripe API. +/// - Parameters: +/// - source: The Stripe source from the response. Will be nil if an error occurs. - seealso: STPSourceProtocol +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPSourceProtocolCompletionBlock = (STPSourceProtocol?, Error?) -> Void +/// A callback to be run with a PaymentIntent response from the Stripe API. +/// - Parameters: +/// - paymentIntent: The Stripe PaymentIntent from the response. Will be nil if an error occurs. - seealso: STPPaymentIntent +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPPaymentIntentCompletionBlock = (STPPaymentIntent?, Error?) -> Void +/// A callback to be run with a PaymentIntent response from the Stripe API. +/// - Parameters: +/// - setupIntent: The Stripe SetupIntent from the response. Will be nil if an error occurs. - seealso: STPSetupIntent +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPSetupIntentCompletionBlock = (STPSetupIntent?, Error?) -> Void +/// A callback to be run with a PaymentMethod response from the Stripe API. +/// - Parameters: +/// - paymentMethod: The Stripe PaymentMethod from the response. Will be nil if an error occurs. - seealso: STPPaymentMethod +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPPaymentMethodCompletionBlock = (STPPaymentMethod?, Error?) -> Void +/// A callback to be run with an array of PaymentMethods response from the Stripe API. +/// - Parameters: +/// - paymentMethods: An array of PaymentMethod from the response. Will be nil if an error occurs. - seealso: STPPaymentMethod +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPPaymentMethodsCompletionBlock = ([STPPaymentMethod]?, Error?) -> Void + +/// A callback to be run with a file response from the Stripe API. +/// - Parameters: +/// - file: The Stripe file from the response. Will be nil if an error occurs. - seealso: STPFile +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPFileCompletionBlock = (STPFile?, Error?) -> Void +/// A callback to be run with a customer response from the Stripe API. +/// - Parameters: +/// - customer: The Stripe customer from the response, or nil if an error occurred. - seealso: STPCustomer +/// - error: The error returned from the response, or nil if none occurs. +public typealias STPCustomerCompletionBlock = (STPCustomer?, Error?) -> Void +/// An enum representing the success and error states of PIN management +@objc public enum STPPinStatus: Int { + /// The verification object was already redeemed + case success + /// The verification object was already redeemed + case errorVerificationAlreadyRedeemed + /// The one-time code was incorrect + case errorVerificationCodeIncorrect + /// The verification object was expired + case errorVerificationExpired + /// The verification object has been attempted too many times + case errorVerificationTooManyAttempts + /// An error occured while retrieving the ephemeral key + case ephemeralKeyError + /// An unknown error occured + case unknownError +} + +/// A callback to be run with a card PIN response from the Stripe API. +/// - Parameters: +/// - cardPin: The Stripe card PIN from the response. Will be nil if an error occurs. - seealso: STPIssuingCardPin +/// - status: The status to help you sort between different error state, or STPPinSuccess when succesful. - seealso: STPPinStatus for possible values. +/// - error: The error returned from the response, or nil if none occurs. - seealso: StripeError.h for possible values. +public typealias STPPinCompletionBlock = (STPIssuingCardPin?, STPPinStatus, Error?) -> Void +/// A callback to be run with a 3DS2 authenticate response from the Stripe API. +/// - Parameters: +/// - authenticateResponse: The Stripe AuthenticateResponse. Will be nil if an error occurs. - seealso: STP3DS2AuthenticateResponse +/// - error: The error returned from the response, or nil if none occurs. +typealias STP3DS2AuthenticateCompletionBlock = (STP3DS2AuthenticateResponse?, Error?) -> Void +/// A block called with a payment status and an optional error. +/// - Parameter error: The error that occurred, if any. +public typealias STPPaymentStatusBlock = (STPPaymentStatus, Error?) -> Void + +/// A callback to be run with an STPRadarSession +/// +/// - Parameters: +/// - radarSession: The RadarSession object. +/// - error: The error that occured, if any. +public typealias STPRadarSessionCompletionBlock = (STPRadarSession?, Error?) -> Void + +/// An empty block, called with no arguments, returning nothing. +public typealias STPVoidBlock = () -> Void diff --git a/StripePayments/StripePayments/Source/Helpers/STPCardValidator.swift b/StripePayments/StripePayments/Source/Helpers/STPCardValidator.swift new file mode 100644 index 00000000..4fe77d43 --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/STPCardValidator.swift @@ -0,0 +1,517 @@ +// +// STPCardValidator.swift +// StripePayments +// +// Created by Jack Flintermann on 7/15/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// These fields indicate whether a card field represents a valid value, invalid +/// value, or incomplete value. +@objc @frozen public enum STPCardValidationState: Int { + /// The field's contents are valid. For example, a valid, 16-digit card number. + /// Note that valid values may not be complete. For example: a US Zip code can + /// be 5 or 9 digits. A 5-digit code is Valid, but more text could be entered + /// to transition to incomplete again. American Express CVC codes can be 3 or + /// 4 digits and both will be treated as Valid. + case valid + /// The field's contents are invalid. For example, an expiration date + /// of "13/42". + case invalid + /// The field's contents are not currently valid, but could be by typing + /// additional characters. For example, a CVC of "1". + case incomplete +} + +/// This class contains static methods to validate card numbers, expiration dates, +/// and CVCs. For a list of test card numbers to use with this code, +/// see https://stripe.com/docs/testing +@objc(STPCardValidator) +public class STPCardValidator: NSObject { + /// Returns a copy of the passed string with all non-numeric characters removed. + @objc(sanitizedNumericStringForString:) + public class func sanitizedNumericString( + for string: String + ) + -> String + { + return stringByRemovingCharactersFromSet(string, CharacterSet.stp_invertedAsciiDigit) + } + + /// Returns a copy of the passed string with all characters removed that do not exist within a postal code. + @objc(sanitizedPostalStringForString:) + public class func sanitizedPostalString( + for string: String + ) + -> String + { + let sanitizedString = stringByRemovingCharactersFromSet( + string, + CharacterSet.stp_invertedPostalCode + ) + let sanitizedStringWithoutPunctuation = stringByRemovingCharactersFromSet( + sanitizedString, + CharacterSet(charactersIn: " -") + ) + if sanitizedStringWithoutPunctuation == "" { + // No postal codes begin with a space or -. If the user has only entered these characters, it was probably a typo. + return "" + } + return sanitizedString + } + + /// Whether or not the target string contains only numeric characters. + @objc(stringIsNumeric:) + public class func stringIsNumeric(_ string: String) -> Bool { + return + (string as NSString).rangeOfCharacter(from: CharacterSet.stp_invertedAsciiDigit) + .location + == NSNotFound + } + + /// Validates a card number, passed as a string. This will return + /// STPCardValidationStateInvalid for numbers that are too short or long, contain + /// invalid characters, do not pass Luhn validation, or (optionally) do not match + /// a number format issued by a major card brand. + /// - Parameters: + /// - cardNumber: The card number to validate. Ex. @"4242424242424242" + /// - validatingCardBrand: Whether or not to enforce that the number appears to + /// be issued by a major card brand (or could be). For example, no issuing card + /// network currently issues card numbers beginning with the digit 9; if an + /// otherwise correct-length and luhn-valid card number beginning with 9 + /// (example: 9999999999999995) were passed to this method, it would return + /// STPCardValidationStateInvalid if this parameter were YES and + /// STPCardValidationStateValid if this parameter were NO. If unsure, you should + /// use YES for this value. + /// - Returns: STPCardValidationStateValid if the number is valid, + /// STPCardValidationStateInvalid if the number is invalid, or + /// STPCardValidationStateIncomplete if the number is a substring of a valid + /// card (e.g. @"4242"). + @objc(validationStateForNumber:validatingCardBrand:) + public class func validationState( + forNumber cardNumber: String?, + validatingCardBrand: Bool + ) -> STPCardValidationState { + guard let cardNumber = cardNumber else { + return .incomplete + } + let sanitizedNumber = self.stringByRemovingSpaces(from: cardNumber) + if sanitizedNumber.count == 0 { + return .incomplete + } + if !self.stringIsNumeric(sanitizedNumber) { + return .invalid + } + let binRange = STPBINController.shared.mostSpecificBINRange(forNumber: sanitizedNumber) + if binRange.brand == .unknown && validatingCardBrand { + return .invalid + } + if sanitizedNumber.count == binRange.panLength { + let isValidLuhn = self.stringIsValidLuhn(sanitizedNumber) + if isValidLuhn { + if binRange.isHardcoded + && STPBINController.shared.isVariableLengthBINPrefix(sanitizedNumber) + { + // log that we didn't get a match in the metadata response so fell back to a hard coded response + STPAnalyticsClient.sharedClient.logCardMetadataMissingRange() + } + return .valid + } else { + return .invalid + } + } else if sanitizedNumber.count > binRange.panLength { + return .invalid + } else { + return .incomplete + } + } + + /// The card brand for a card number or substring thereof. + /// - Parameter cardNumber: A card number, or partial card number. For + /// example, @"4242", @"5555555555554444", or @"123". + /// - Returns: The brand for that card number. The example parameters would + /// return STPCardBrandVisa, STPCardBrandMasterCard, and + /// STPCardBrandUnknown, respectively. + @objc(brandForNumber:) + public class func brand(forNumber cardNumber: String) -> STPCardBrand { + let sanitizedNumber = self.sanitizedNumericString(for: cardNumber) + let brands = self.possibleBrands(forNumber: sanitizedNumber) + if brands.count == 1 { + return brands.first! + } + return .unknown + } + + /// The possible number lengths for cards associated with a card brand. For + /// example, Discover card numbers contain 16 characters, while American Express + /// cards contain 15 characters. + /// - Parameter brand: The brand to return lengths for. + /// - Returns: The set of possible lengths cards associated with that brand can be. + @objc(lengthsForCardBrand:) + public class func lengths(for brand: STPCardBrand) -> Set { + var set: Set = [] + let binRanges = STPBINController.shared.binRanges(for: brand) + for binRange in binRanges { + _ = set.insert(binRange.panLength) + } + return set + } + + /// The maximum possible length the number of a card associated with the specified + /// brand could be. + /// For example, Visa cards could be either 13 or 16 characters, so this method + /// would return 16 for the that card brand. + /// - Parameter brand: The brand to return the max length for. + /// - Returns: The maximum length card numbers associated with that brand could be. + @objc(maxLengthForCardBrand:) + public class func maxLength(for brand: STPCardBrand) -> Int { + var maxLength = -1 + for length in self.lengths(for: brand) { + if length > maxLength { + maxLength = Int(length) + } + } + return maxLength + } + + /// The length of the final grouping of digits to use when formatting a card number + /// for display. + /// For example, Visa cards display their final 4 numbers, e.g. "4242", while + /// American Express cards display their final 5 digits, e.g. "10005". + /// - Parameter brand: The brand to return the fragment length for. + /// - Returns: The final fragment length card numbers associated with that brand use. + @objc(fragmentLengthForCardBrand:) + public class func fragmentLength(for brand: STPCardBrand) -> Int { + return Int(self.cardNumberFormat(for: brand).last?.uintValue ?? 0) + } + + /// Validates an expiration month, passed as an (optionally 0-padded) string. + /// Example valid values are "3", "12", and "08". Example invalid values are "99", + /// "a", and "00". Incomplete values include "0" and "1". + /// - Parameter expirationMonth: A string representing a 2-digit expiration month for a + /// payment card. + /// - Returns: STPCardValidationStateValid if the month is valid, + /// STPCardValidationStateInvalid if the month is invalid, or + /// STPCardValidationStateIncomplete if the month is a substring of a valid + /// month (e.g. @"0" or @"1"). + @objc(validationStateForExpirationMonth:) + public class func validationState( + forExpirationMonth expirationMonth: String + ) + -> STPCardValidationState + { + + let sanitizedExpiration = self.stringByRemovingSpaces(from: expirationMonth) + + if !self.stringIsNumeric(sanitizedExpiration) { + return .invalid + } + + switch sanitizedExpiration.count { + case 0: + return .incomplete + case 1: + return ((sanitizedExpiration == "0") || (sanitizedExpiration == "1")) + ? .incomplete : .valid + case 2: + return (0 < Int(sanitizedExpiration) ?? 0 && Int(sanitizedExpiration) ?? 0 <= 12) + ? .valid : .invalid + default: + return .invalid + } + } + + /// Validates an expiration year, passed as a string representing the final + /// 2 digits of the year. + /// This considers the period between the current year until 2099 as valid times. + /// An example valid year value would be "16" (assuming the current year, as + /// determined by NSDate.date, is 2015). + /// Will return STPCardValidationStateInvalid for a month/year combination that + /// is earlier than the current date (i.e. @"15" and @"04" in October 2015). + /// Example invalid year values are "00", "a", and "13". Any 1-digit year string + /// will return STPCardValidationStateIncomplete. + /// - Parameters: + /// - expirationYear: A string representing a 2-digit expiration year for a + /// payment card. + /// - expirationMonth: A string representing a valid 2-digit expiration month + /// for a payment card. If the month is invalid + /// (see `validationStateForExpirationMonth`), this will + /// return STPCardValidationStateInvalid. + /// - Returns: STPCardValidationStateValid if the year is valid, + /// STPCardValidationStateInvalid if the year is invalid, or + /// STPCardValidationStateIncomplete if the year is a substring of a valid + /// year (e.g. @"1" or @"2"). + @objc(validationStateForExpirationYear:inMonth:) + public class func validationState( + forExpirationYear expirationYear: String, + inMonth expirationMonth: String + ) -> STPCardValidationState { + return self.validationState( + forExpirationYear: expirationYear, + inMonth: expirationMonth, + inCurrentYear: self.currentYear(), + currentMonth: self.currentMonth() + ) + } + + /// The max CVC length for a card brand (for example, American Express CVCs are + /// 4 digits, while all others are 3). + /// - Parameter brand: The brand to return the max CVC length for. + /// - Returns: The maximum length of CVC numbers for cards associated with that brand. + @objc(maxCVCLengthForCardBrand:) + public class func maxCVCLength(for brand: STPCardBrand) -> UInt { + switch brand { + case .amex, .unknown: + return 4 + default: + return 3 + } + } + + /// Validates a card's CVC, passed as a numeric string, for the given card brand. + /// - Parameters: + /// - cvc: the CVC to validate + /// - brand: the card brand (can be determined from the card's number + /// using `brandForNumber`) + /// - Returns: Whether the CVC represents a valid CVC for that card brand. For + /// example, would return STPCardValidationStateValid for @"123" and + /// STPCardBrandVisa, STPCardValidationStateValid for @"1234" and + /// STPCardBrandAmericanExpress, STPCardValidationStateIncomplete for @"12" and + /// STPCardBrandVisa, and STPCardValidationStateInvalid for @"12345" and any brand. + @objc(validationStateForCVC:cardBrand:) + public class func validationState( + forCVC cvc: String, + cardBrand brand: STPCardBrand + ) + -> STPCardValidationState + { + + if !self.stringIsNumeric(cvc) { + return .invalid + } + + let sanitizedCvc = self.sanitizedNumericString(for: cvc) + + let minLength = self.minCVCLength() + let maxLength = self.maxCVCLength(for: brand) + if sanitizedCvc.count < minLength { + return .incomplete + } else if sanitizedCvc.count > maxLength { + return .invalid + } else { + return .valid + } + } + + /// Validates the given card details. + /// - Parameter card: The card details to validate. + /// - Returns: STPCardValidationStateValid if all fields are valid, + /// STPCardValidationStateInvalid if any field is invalid, or + /// STPCardValidationStateIncomplete if all fields are either incomplete or valid. + @objc(validationStateForCard:) + public class func validationState(forCard card: STPCardParams) -> STPCardValidationState { + return self.validationState( + forCard: card, + inCurrentYear: self.currentYear(), + currentMonth: self.currentMonth() + ) + } + + class func stringByRemovingSpaces(from string: String) -> String { + let set = CharacterSet.whitespaces + return stringByRemovingCharactersFromSet(string, set) + } + + static func stringByRemovingCharactersFromSet(_ string: String, _ cs: CharacterSet) -> String { + let filtered = string.unicodeScalars.filter { !cs.contains($0) } + return String(String.UnicodeScalarView(filtered)) + } + + class func validationState( + forExpirationYear expirationYear: String, + inMonth expirationMonth: String, + inCurrentYear currentYear: Int, + currentMonth: Int + ) -> STPCardValidationState { + + let moddedYear = currentYear % 100 + + if !self.stringIsNumeric(expirationMonth) || !self.stringIsNumeric(expirationYear) { + return .invalid + } + + let sanitizedMonth = self.sanitizedNumericString(for: expirationMonth) + let sanitizedYear = self.sanitizedNumericString(for: expirationYear) + + switch sanitizedYear.count { + case 0, 1: + return .incomplete + case 2: + guard let yearInt = Int(sanitizedYear), let monthInt = Int(sanitizedMonth), self.validationState(forExpirationMonth: sanitizedMonth) != .invalid else { + return .invalid + } + if yearInt == moddedYear { + return monthInt >= currentMonth ? .valid : .invalid + } else { + return ((yearInt > moddedYear) && (yearInt - moddedYear <= 50)) ? .valid : .invalid + } + default: + return .invalid + } + } + + class func validationState( + forCard card: STPCardParams, + inCurrentYear currentYear: Int, + currentMonth: Int + ) -> STPCardValidationState { + let numberValidation = self.validationState( + forNumber: card.number ?? "", + validatingCardBrand: true + ) + let expMonthString = String(format: "%02lu", UInt(card.expMonth)) + let expMonthValidation = self.validationState(forExpirationMonth: expMonthString) + let expYearString = String(format: "%02lu", UInt(card.expYear) % 100) + let expYearValidation = self.validationState( + forExpirationYear: expYearString, + inMonth: expMonthString, + inCurrentYear: currentYear, + currentMonth: currentMonth + ) + let brand = self.brand(forNumber: card.number ?? "") + let cvcValidation = self.validationState(forCVC: card.cvc ?? "", cardBrand: brand) + + let states = [ + NSNumber(value: numberValidation.rawValue), + NSNumber(value: expMonthValidation.rawValue), + NSNumber(value: expYearValidation.rawValue), + NSNumber(value: cvcValidation.rawValue), + ] + var incomplete = false + for boxedState in states { + let state = STPCardValidationState(rawValue: boxedState.intValue) + if state == .invalid { + return state! + } else if state == .incomplete { + incomplete = true + } + } + return incomplete ? .incomplete : .valid + } + + @_spi(STP) public class func minCVCLength() -> Int { + return 3 + } + + class func possibleBrands(forNumber cardNumber: String) -> Set { + let binRanges = STPBINController.shared.binRanges(forNumber: cardNumber) + var brands = binRanges.map { $0.brand } + brands.removeAll { $0 == .unknown } + return Set(brands) + } + + // This is a bit of a hack: We want to fetch BIN information for Card Brand Choice, but some + // of the BIN length information coming from the service is incorrect: We're receiving a maximum + // length, but we really should receive a min-max range. + // We don't want to pollute the main STPBINController cache with this bad data. + // + // We currently prevent cache pollution with an `isVariableLengthBINPrefix` check in + // `retrieveBinRanges()`, but we'll bypass that check when using the CBC BIN controller. + static let cbcBinController = STPBINController() + + /// Returns available brands for the provided card details. + /// - Parameter card: The card details to validate. + /// - Parameter completion: Will be called with the set of available STPCardBrands or an error. + /// - seealso: https://stripe.com/docs/card-brand-choice + public class func possibleBrands(forCard cardParams: STPPaymentMethodCardParams, + completion: @escaping (Result, Error>) -> Void) { + guard let cardNumber = cardParams.number else { + // If the number is nil or empty, any brand is possible. + completion(.success(Set(STPCardBrand.allCases))) + return + } + possibleBrands(forNumber: cardNumber, completion: completion) + } + + public class func possibleBrands(forNumber cardNumber: String, + completion: @escaping (Result, Error>) -> Void) { + // Hardcoded test cards that are in our docs but not supported by the card metadata service + // https://stripe.com/docs/card-brand-choice#testing + let testCards: [String: [STPCardBrand]] = ["4000002500001001": [.cartesBancaires, .visa], + "5555552500001001": [.cartesBancaires, .mastercard], ] + + if let testBrands = testCards[cardNumber] { + completion(.success(Set(testBrands))) + return + } + + cbcBinController.retrieveBINRanges(forPrefix: cardNumber, recordErrorsAsSuccess: false, onlyFetchForVariableLengthBINs: false) { result in + switch result { + case .failure(let error): + completion(.failure(error)) + case .success: + let binRanges = cbcBinController.binRanges(forNumber: cardNumber) + let brands = binRanges.map { $0.brand } + .filter { $0 != .unknown } + completion(.success(Set(brands))) + } + } + } + + class func currentYear() -> Int { + let calendar = Calendar(identifier: .gregorian) + return (calendar.component(.year, from: Date())) % 100 + } + + class func currentMonth() -> Int { + let calendar = Calendar(identifier: .gregorian) + return calendar.component(.month, from: Date()) + } +} + +extension STPCardValidator { + class func cardNumberFormat(for brand: STPCardBrand) -> [NSNumber] { + switch brand { + case .amex: + return [NSNumber(value: 4), NSNumber(value: 6), NSNumber(value: 5)] + default: + return [NSNumber(value: 4), NSNumber(value: 4), NSNumber(value: 4), NSNumber(value: 4)] + } + } + + @_spi(STP) public class func cardNumberFormat(forCardNumber cardNumber: String) -> [NSNumber] { + let binRange = STPBINController.shared.mostSpecificBINRange(forNumber: cardNumber) + if binRange.brand == .dinersClub && binRange.panLength == 14 { + return [NSNumber(value: 4), NSNumber(value: 6), NSNumber(value: 4)] + } + + return self.cardNumberFormat(for: binRange.brand) + } + + @_spi(STP) public class func stringIsValidLuhn(_ number: String) -> Bool { + var odd = true + var sum = 0 + var digits: [String] = [] + + for i in 0.. 9 { + digit -= 9 + } + sum += digit + } + + return sum % 10 == 0 + } +} diff --git a/StripePayments/StripePayments/Source/Helpers/STPLocalizedString.swift b/StripePayments/StripePayments/Source/Helpers/STPLocalizedString.swift new file mode 100644 index 00000000..24ce47ac --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/STPLocalizedString.swift @@ -0,0 +1,16 @@ +// +// STPLocalizedString.swift +// StripePayments +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@inline(__always) func STPLocalizedString(_ key: String, _ comment: String?) -> String { + return STPLocalizationUtils.localizedStripeString( + forKey: key, + bundleLocator: StripePaymentsBundleLocator.self + ) +} diff --git a/StripePayments/StripePayments/Source/Helpers/STPPaymentConfirmation+SwiftUI.swift b/StripePayments/StripePayments/Source/Helpers/STPPaymentConfirmation+SwiftUI.swift new file mode 100644 index 00000000..3ab81761 --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/STPPaymentConfirmation+SwiftUI.swift @@ -0,0 +1,146 @@ +// +// STPPaymentConfirmation+SwiftUI.swift +// StripePayments +// +// Created by David Estes on 2/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import SafariServices +import SwiftUI + +struct ConfirmPaymentPresenter: UIViewControllerRepresentable { + @Binding var presented: Bool + let intentParams: ParamsType + let onCompletion: CompletionBlockType + + func makeCoordinator() -> Coordinator { + return Coordinator(parent: self) + } + + func makeUIViewController(context: Context) -> UIViewController { + return context.coordinator.uiViewController + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + context.coordinator.parent = self + context.coordinator.presented = presented + } + + class Coordinator: NSObject, STPAuthenticationContext { + func authenticationPresentingViewController() -> UIViewController { + // This is a bit of a hack: We traverse the view hierarchy looking for the most reasonable VC to present from. + // A VC hosted within a SwiftUI cell, for example, doesn't have a parent, so we need to find the UIWindow. + var presentingViewController = uiViewController.view.window?.rootViewController + presentingViewController = + presentingViewController?.presentedViewController ?? presentingViewController + ?? uiViewController + return presentingViewController ?? UIViewController() + } + + var parent: ConfirmPaymentPresenter + init( + parent: ConfirmPaymentPresenter + ) { + self.parent = parent + } + + let uiViewController = UIViewController() + + var presented: Bool = false { + didSet { + if oldValue != presented { + presented ? presentConfirmationSheet() : forciblyDismissConfirmationSheet() + } + } + } + + private func presentConfirmationSheet() { + if let params = self.parent.intentParams as? STPPaymentIntentParams, + let completion = self.parent.onCompletion + as? STPPaymentHandlerActionPaymentIntentCompletionBlock + { + STPPaymentHandler.sharedHandler.confirmPayment(params, with: self) { + (status, pi, error) in + self.parent.presented = false + completion(status, pi, error) + } + } else if let params = self.parent.intentParams as? STPSetupIntentConfirmParams, + let completion = self.parent.onCompletion + as? STPPaymentHandlerActionSetupIntentCompletionBlock + { + STPPaymentHandler.sharedHandler.confirmSetupIntent(params, with: self) { + (status, si, error) in + self.parent.presented = false + completion(status, si, error) + } + } else { + assert(false, "ConfirmPaymentPresenter was passed an invalid type.") + } + + } + + private func forciblyDismissConfirmationSheet() { + if let sfvc = self.authenticationPresentingViewController().presentedViewController + as? SFSafariViewController, + !sfvc.isBeingDismissed + { + self.authenticationPresentingViewController().dismiss(animated: true) + } + } + } +} + +extension View { + /// Confirm the payment, presenting a sheet for the user to confirm their payment if needed. + /// - Parameter isConfirmingPayment: A binding to whether the payment is being confirmed. This will present a sheet if needed. It will be updated to `false` after performing the payment confirmation. + /// - Parameter paymentIntentParams: A PaymentIntentParams to confirm. + /// - Parameter onCompletion: Called with the result of the payment after the payment confirmation is done and the sheet (if any) is dismissed. + public func paymentConfirmationSheet( + isConfirmingPayment: Binding, + paymentIntentParams: STPPaymentIntentParams, + onCompletion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) -> some View { + self.modifier( + ConfirmPaymentPresentationModifier( + isPresented: isConfirmingPayment, + intentParams: paymentIntentParams, + onCompletion: onCompletion + ) + ) + } + + /// Confirm the SetupIntent, presenting a sheet for the user to confirm if needed. + /// - Parameter isConfirmingSetupIntent: A binding to whether the SetupIntent is being confirmed. This will present a sheet if needed. It will be updated to `false` after performing the SetupIntent confirmation. + /// - Parameter paymentIntentParams: A SetupIntentParams to confirm. + /// - Parameter onCompletion: Called with the result of the SetupIntent confirmation after the confirmation is done and the sheet (if any) is dismissed. + public func setupIntentConfirmationSheet( + isConfirmingSetupIntent: Binding, + setupIntentParams: STPSetupIntentConfirmParams, + onCompletion: @escaping STPPaymentHandlerActionSetupIntentCompletionBlock + ) -> some View { + self.modifier( + ConfirmPaymentPresentationModifier( + isPresented: isConfirmingSetupIntent, + intentParams: setupIntentParams, + onCompletion: onCompletion + ) + ) + } +} + +struct ConfirmPaymentPresentationModifier: ViewModifier { + @Binding var isPresented: Bool + let intentParams: ParamsType + let onCompletion: CompletionBlockType + + func body(content: Content) -> some View { + content.background( + ConfirmPaymentPresenter( + presented: $isPresented, + intentParams: intentParams, + onCompletion: onCompletion + ) + ) + } +} diff --git a/StripePayments/StripePayments/Source/Helpers/StripePayments+Export.swift b/StripePayments/StripePayments/Source/Helpers/StripePayments+Export.swift new file mode 100644 index 00000000..2e67edd3 --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/StripePayments+Export.swift @@ -0,0 +1,10 @@ +// +// StripePayments+Export.swift +// StripePayments +// +// Created by David Estes on 7/6/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_exported import StripeCore diff --git a/StripePayments/StripePayments/Source/Helpers/StripePaymentsBundleLocator.swift b/StripePayments/StripePayments/Source/Helpers/StripePaymentsBundleLocator.swift new file mode 100644 index 00000000..d02bf278 --- /dev/null +++ b/StripePayments/StripePayments/Source/Helpers/StripePaymentsBundleLocator.swift @@ -0,0 +1,19 @@ +// +// StripePaymentsBundleLocator.swift +// StripePayments +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// :nodoc: +@_spi(STP) public final class StripePaymentsBundleLocator: BundleLocatorProtocol { + public static let internalClass: AnyClass = StripePaymentsBundleLocator.self + public static let bundleName = "StripePaymentsBundle" + #if SWIFT_PACKAGE + public static let spmResourcesBundle = Bundle.module + #endif + public static let resourcesBundle = StripePaymentsBundleLocator.computeResourcesBundle() +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/APIRequest.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/APIRequest.swift new file mode 100644 index 00000000..8c612fdf --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/APIRequest.swift @@ -0,0 +1,199 @@ +// +// APIRequest.swift +// StripePayments +// +// Created by Jack Flintermann on 10/14/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +let HTTPMethodPOST = "POST" +let HTTPMethodGET = "GET" +let HTTPMethodDELETE = "DELETE" +let JSONKeyObject = "object" + +/// - Note: The shape of this class is only for backwards compatibility with `STPAPIResponseDecodable` public API bindings. +/// If you're not dealing with `STPAPIResponseDecodable` objects, use STPAPIClient's `get`, `post`, etc. methods. +@_spi(STP) public class APIRequest: NSObject { + @_spi(STP) public typealias STPAPIResponseBlock = (ResponseType?, HTTPURLResponse?, Error?) -> + Void + + @_spi(STP) public class func post( + with apiClient: STPAPIClient, + endpoint: String, + additionalHeaders: [String: String] = [:], + parameters: [String: Any], + completion: @escaping STPAPIResponseBlock + ) { + // Build url + let url = apiClient.apiURL.appendingPathComponent(endpoint) + + // Setup request + var request = apiClient.configuredRequest(for: url, additionalHeaders: additionalHeaders) + request.httpMethod = HTTPMethodPOST + request.stp_setFormPayload(parameters) + + // Perform request + apiClient.urlSession.stp_performDataTask( + with: request as URLRequest, + completionHandler: { body, response, error in + self.parseResponse(response, body: body, error: error, completion: completion) + } + ) + } + + /// Async version + @_spi(STP) public class func post( + with apiClient: STPAPIClient, + endpoint: String, + additionalHeaders: [String: String] = [:], + parameters: [String: Any] + ) async throws -> (ResponseType) { + return try await withCheckedThrowingContinuation { continuation in + post(with: apiClient, endpoint: endpoint, additionalHeaders: additionalHeaders, parameters: parameters) { responseObject, _, error in + guard let responseObject else { + continuation.resume(throwing: error ?? NSError.stp_genericFailedToParseResponseError()) + return + } + continuation.resume(returning: responseObject) + } + } + } + + @_spi(STP) public class func getWith( + _ apiClient: STPAPIClient, + endpoint: String, + additionalHeaders: [String: String] = [:], + parameters: [String: Any], + completion: @escaping STPAPIResponseBlock + ) { + // Build url + let url = apiClient.apiURL.appendingPathComponent(endpoint) + + // Setup request + var request = apiClient.configuredRequest(for: url, additionalHeaders: additionalHeaders) + request.stp_addParameters(toURL: parameters) + request.httpMethod = HTTPMethodGET + + // Perform request + apiClient.urlSession.stp_performDataTask( + with: request as URLRequest, + completionHandler: { body, response, error in + self.parseResponse(response, body: body, error: error, completion: completion) + } + ) + } + + /// Async version + @_spi(STP) public class func getWith( + _ apiClient: STPAPIClient, + endpoint: String, + additionalHeaders: [String: String] = [:], + parameters: [String: Any] + ) async throws -> ResponseType { + return try await withCheckedThrowingContinuation { continuation in + getWith(apiClient, endpoint: endpoint, additionalHeaders: additionalHeaders, parameters: parameters) { responseObject, _, error in + guard let responseObject else { + continuation.resume(throwing: error ?? NSError.stp_genericFailedToParseResponseError()) + return + } + continuation.resume(returning: responseObject) + } + } + } + + @_spi(STP) public class func delete( + with apiClient: STPAPIClient, + endpoint: String, + additionalHeaders: [String: String] = [:], + parameters: [String: Any], + completion: @escaping STPAPIResponseBlock + ) { + // Build url + let url = apiClient.apiURL.appendingPathComponent(endpoint) + + // Setup request + var request = apiClient.configuredRequest(for: url, additionalHeaders: additionalHeaders) + request.stp_addParameters(toURL: parameters) + request.httpMethod = HTTPMethodDELETE + + // Perform request + apiClient.urlSession.stp_performDataTask( + with: request as URLRequest, + completionHandler: { body, response, error in + self.parseResponse(response, body: body, error: error, completion: completion) + } + ) + } + + class func parseResponse( + _ response: URLResponse?, + body: Data?, + error: Error?, + completion: @escaping (ResponseType?, HTTPURLResponse?, Error?) -> Void + ) { + // Derive HTTP URL response + var httpResponse: HTTPURLResponse? + if response is HTTPURLResponse { + httpResponse = response as? HTTPURLResponse + } + + // Wrap completion block with main thread dispatch + let safeCompletion: ((ResponseType?, Error?) -> Void) = { responseObject, responseError in + stpDispatchToMainThreadIfNecessary({ + completion(responseObject, httpResponse, responseError) + }) + } + + if error != nil { + // Forward NSURLSession error + return safeCompletion(nil, error) + } + + // Parse JSON response body + var jsonDictionary: [AnyHashable: Any]? + if let body = body { + do { + jsonDictionary = + try JSONSerialization.jsonObject(with: body, options: []) as? [AnyHashable: Any] + } catch { + + } + } + + // HACK: + // STPEmptyStripeResponse will always parse successfully and never return an error, as we're + // not looking at the HTTP error code or the error dictionary. + // I'm afraid this will cause issues if anyone is depending on the old behavior, so let's treat + // STPEmptyStripeResponse as special. + // We probably always want errors to override object deserialization: re-evaluate + // this hack when building the new API client. + if ResponseType.self == STPEmptyStripeResponse.self { + if let error: Error = + NSError.stp_error(fromStripeResponse: jsonDictionary, httpResponse: httpResponse) + { + safeCompletion(nil, error) + } else if let responseObject = ResponseType.decodedObject( + fromAPIResponse: jsonDictionary + ) { + safeCompletion(responseObject, nil) + } else { + safeCompletion(nil, NSError.stp_genericFailedToParseResponseError()) + } + return + } + // END OF STPEmptyStripeResponse HACK + + if let responseObject = ResponseType.decodedObject(fromAPIResponse: jsonDictionary) { + safeCompletion(responseObject, nil) + } else { + let error: Error = + NSError.stp_error(fromStripeResponse: jsonDictionary, httpResponse: httpResponse) + ?? NSError.stp_genericFailedToParseResponseError() + safeCompletion(nil, error) + } + } + +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STP3DS2AuthenticateResponse.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STP3DS2AuthenticateResponse.swift new file mode 100644 index 00000000..7c81409d --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STP3DS2AuthenticateResponse.swift @@ -0,0 +1,81 @@ +// +// STP3DS2AuthenticateResponse.swift +// StripePayments +// +// Created by Cameron Sabol on 5/22/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +enum STP3DS2AuthenticateResponseState: Int { + /// Unknown Authenticate Response state + case unknown = 0 + /// State indicating that a challenge flow needs to be applied + case challengeRequired + /// State indicating that the authentication succeeded + case succeeded +} + +class STP3DS2AuthenticateResponse: NSObject, STPAPIResponseDecodable { + private(set) var allResponseFields: [AnyHashable: Any] = [:] + /// The Authentication Response received from the Access Control Server + private(set) var authenticationResponse: STDSAuthenticationResponse? + /// When the 3DS2 Authenticate Response was created. + private(set) var created: Date? + /// Whether or not this Authenticate Response was created in livemode. + private(set) var livemode = false + /// The identifier for the Source associated with this Authenticate Response + private(set) var sourceID: String? + /// A fallback URL to redirect to instead of running native 3DS2 + private(set) var fallbackURL: URL? + /// The state of the authentication + private(set) var state: STP3DS2AuthenticateResponseState = .unknown + + override required init() { + super.init() + } + + class func decodedObject(fromAPIResponse response: [AnyHashable: Any]?) -> Self? { + guard let response = response else { + return nil + } + let dict = response.stp_dictionaryByRemovingNulls() + + let fallbackURL = dict.stp_url(forKey: "fallback_redirect_url") + + let authenticationResponseJSON = dict.stp_dictionary(forKey: "ares") + + var authenticationResponse: STDSAuthenticationResponse? + if let authenticationResponseJSON = authenticationResponseJSON { + authenticationResponse = STDSAuthenticationResponseFromJSON(authenticationResponseJSON) + } + if authenticationResponse == nil && fallbackURL == nil { + // we need at least one of ares or fallback_redirect_url + return nil + } + + let stateString = dict.stp_string(forKey: "state") + var state: STP3DS2AuthenticateResponseState = .unknown + if stateString == "succeeded" { + state = .succeeded + } else if stateString == "challenge_required" { + state = .challengeRequired + } + + let authResponse = self.init() + authResponse.authenticationResponse = authenticationResponse + authResponse.state = state + authResponse.created = dict.stp_date(forKey: "created") + authResponse.livemode = dict.stp_bool(forKey: "livemode", or: true) + authResponse.sourceID = dict.stp_string(forKey: "source") + authResponse.fallbackURL = fallbackURL + authResponse.allResponseFields = response + + return authResponse + } +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STPEmptyStripeResponse.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STPEmptyStripeResponse.swift new file mode 100644 index 00000000..a503e7e9 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STPEmptyStripeResponse.swift @@ -0,0 +1,30 @@ +// +// STPEmptyStripeResponse.swift +// StripePayments +// +// Created by Cameron Sabol on 6/11/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// An STPAPIResponseDecodable implementation to use for endpoints that don't +/// actually return objects, like /v1/3ds2/challenge_completed +@_spi(STP) public class STPEmptyStripeResponse: NSObject, STPAPIResponseDecodable { + @_spi(STP) public private(set) var allResponseFields: [AnyHashable: Any] = [:] + + required internal override init() { + super.init() + } + + @_spi(STP) public class func decodedObject( + fromAPIResponse response: [AnyHashable: Any]? + ) -> Self? { + let emptyResponse = self.init() + if let response = response { + emptyResponse.allResponseFields = response + } + + return emptyResponse + } +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STPFormEncoder.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STPFormEncoder.swift new file mode 100644 index 00000000..84783b6e --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STPFormEncoder.swift @@ -0,0 +1,97 @@ +// +// STPFormEncoder.swift +// StripePayments +// +// Created by Jack Flintermann on 1/8/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public class STPFormEncoder: NSObject { + @objc @_spi(STP) public class func dictionary( + forObject object: (NSObject & STPFormEncodable) + ) -> [String: Any] { + // returns [object root name : object.coded (eg [property name strings: property values)] + let keyPairs = self.keyPairDictionary(forObject: object) + let rootObjectName = type(of: object).rootObjectName() + if let rootObjectName = rootObjectName { + return [rootObjectName: keyPairs] + } else { + return keyPairs + } + } + + // MARK: - Internal + + /// Returns [Property name : Property's form encodable value] + private class func keyPairDictionary( + forObject object: (NSObject & STPFormEncodable) + ) + -> [String: + Any] + { + var keyPairs: [String: Any] = [:] + for (propertyName, formFieldName) in type(of: object).propertyNamesToFormFieldNamesMapping() + { + if let propertyValue = object.value(forKeyPath: propertyName) { + guard let propertyValue = propertyValue as? NSObject else { + assertionFailure() + continue + } + keyPairs[formFieldName] = formEncodableValue(for: propertyValue) + } + } + for (additionalFieldName, additionalFieldValue) in object.additionalAPIParameters { + guard let additionalFieldName = additionalFieldName as? String, + let additionalFieldValue = additionalFieldValue as? NSObject + else { + assertionFailure() + continue + } + keyPairs[additionalFieldName] = formEncodableValue(for: additionalFieldValue) + } + return keyPairs + } + + /// Expands object, and any subobjects, into key pair dictionaries if they are STPFormEncodable + private class func formEncodableValue(for object: NSObject) -> NSObject { + switch object { + case let object as NSObject & STPFormEncodable: + return self.keyPairDictionary(forObject: object) as NSObject + case let dict as NSDictionary: + let result = NSMutableDictionary(capacity: dict.count) + dict.enumerateKeysAndObjects({ key, value, _ in + if let key = key as? NSObject, // Don't all keys need to be Strings? + let value = value as? NSObject + { + result[formEncodableValue(for: key)] = formEncodableValue(for: value) + } else { + assertionFailure() // TODO remove + } + }) + return result + case let array as NSArray: + let result = NSMutableArray() + for element in array { + guard let element = element as? NSObject else { + assertionFailure() // TODO remove + continue + } + result.add(formEncodableValue(for: element)) + } + return result + case let set as NSSet: + let result = NSMutableSet() + for element in set { + guard let element = element as? NSObject else { + continue + } + result.add(self.formEncodableValue(for: element)) + } + return result + default: + return object + } + } +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STPIntentActionUseStripeSDK.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STPIntentActionUseStripeSDK.swift new file mode 100644 index 00000000..79ac9ac8 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STPIntentActionUseStripeSDK.swift @@ -0,0 +1,224 @@ +// +// STPIntentActionUseStripeSDK.swift +// StripePayments +// +// Created by Cameron Sabol on 5/15/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation + +@objc +enum STPIntentActionUseStripeSDKType: Int { + case unknown = 0 + case threeDS2Fingerprint + case threeDS2Redirect +} + +class STPIntentActionUseStripeSDK: NSObject { + + let allResponseFields: [AnyHashable: Any] + + let type: STPIntentActionUseStripeSDKType + + // MARK: - 3DS2 Fingerprint + let directoryServerName: String? + let directoryServerID: String? + + /// PEM encoded DS certificate + let directoryServerCertificate: String? + let rootCertificateStrings: [String]? + + /// A Visa-specific field + let directoryServerKeyID: String? + let serverTransactionID: String? + let threeDSSourceID: String? + + /// Publishable key to use for making authentication API calls (Link-specific) + let publishableKeyOverride: String? + let threeDS2IntentOverride: String? + + // MARK: - 3DS2 Redirect + let redirectURL: URL? + + private init( + type: STPIntentActionUseStripeSDKType, + directoryServerName: String?, + directoryServerID: String?, + directoryServerCertificate: String?, + rootCertificateStrings: [String]?, + directoryServerKeyID: String?, + serverTransactionID: String?, + threeDSSourceID: String?, + publishableKeyOverride: String?, + threeDS2IntentOverride: String?, + redirectURL: URL?, + allResponseFields: [AnyHashable: Any] + ) { + self.type = type + self.directoryServerName = directoryServerName + self.directoryServerID = directoryServerID + self.directoryServerCertificate = directoryServerCertificate + self.rootCertificateStrings = rootCertificateStrings + self.directoryServerKeyID = directoryServerKeyID + self.serverTransactionID = serverTransactionID + self.threeDSSourceID = threeDSSourceID + self.publishableKeyOverride = publishableKeyOverride + self.threeDS2IntentOverride = threeDS2IntentOverride + self.redirectURL = redirectURL + self.allResponseFields = allResponseFields + super.init() + } + + convenience init?( + encryptionInfo: [AnyHashable: Any], + directoryServerName: String?, + directoryServerKeyID: String?, + serverTransactionID: String?, + threeDSSourceID: String?, + publishableKeyOverride: String?, + threeDS2IntentOverride: String?, + allResponseFields: [AnyHashable: Any] + ) { + guard let certificate = encryptionInfo["certificate"] as? String, + !certificate.isEmpty, + let directoryServerID = encryptionInfo["directory_server_id"] as? String, + !directoryServerID.isEmpty, + let rootCertificates = encryptionInfo["root_certificate_authorities"] as? [String], + !rootCertificates.isEmpty + else { + return nil + } + self.init( + type: .threeDS2Fingerprint, + directoryServerName: directoryServerName, + directoryServerID: directoryServerID, + directoryServerCertificate: certificate, + rootCertificateStrings: rootCertificates, + directoryServerKeyID: directoryServerKeyID, + serverTransactionID: serverTransactionID, + threeDSSourceID: threeDSSourceID, + publishableKeyOverride: publishableKeyOverride, + threeDS2IntentOverride: threeDS2IntentOverride, + redirectURL: nil, + allResponseFields: allResponseFields + ) + } + + convenience init( + redirectURL: URL, + allResponseFields: [AnyHashable: Any] + ) { + var threeDSSourceID: String? + if redirectURL.lastPathComponent.hasPrefix("src_") { + threeDSSourceID = redirectURL.lastPathComponent + } + self.init( + type: .threeDS2Redirect, + directoryServerName: nil, + directoryServerID: nil, + directoryServerCertificate: nil, + rootCertificateStrings: nil, + directoryServerKeyID: nil, + serverTransactionID: nil, + threeDSSourceID: threeDSSourceID, + publishableKeyOverride: nil, + threeDS2IntentOverride: nil, + redirectURL: redirectURL, + allResponseFields: allResponseFields + ) + } + + convenience override init() { + self.init( + type: .unknown, + directoryServerName: nil, + directoryServerID: nil, + directoryServerCertificate: nil, + rootCertificateStrings: nil, + directoryServerKeyID: nil, + serverTransactionID: nil, + threeDSSourceID: nil, + publishableKeyOverride: nil, + threeDS2IntentOverride: nil, + redirectURL: nil, + allResponseFields: [:] + ) + } + + @objc override var description: String { + let props: [String] = [ + // Object + String(format: "%@: %p", String(describing: STPIntentActionUseStripeSDK.self), self), + // IntentActionUseStripeSDK details (alphabetical) + "directoryServer = \(String(describing: directoryServerName))", + "directoryServerID = \(String(describing: directoryServerID))", + "directoryServerKeyID = \(String(describing: directoryServerKeyID))", + "serverTransactionID = \(String(describing: serverTransactionID))", + "directoryServerCertificate = \(String(describing: (directoryServerCertificate?.count ?? 0 > 0 ? "" : nil)))", + "rootCertificateStrings = \(String(describing: (rootCertificateStrings?.count ?? 0 > 0 ? "" : nil)))", + "threeDSSourceID = \(String(describing: threeDSSourceID))", + "type = \(String(describing: allResponseFields["type"]))", + "redirectURL = \(String(describing: redirectURL))", + ] + + return "<\(props.joined(separator: "; "))>" + } +} + +/// :nodoc: +extension STPIntentActionUseStripeSDK: STPAPIResponseDecodable { + class func decodedObject(fromAPIResponse response: [AnyHashable: Any]?) -> Self? { + guard let dict = response, + let typeString = dict["type"] as? String + else { + return nil + } + + switch typeString { + case "stripe_3ds2_fingerprint": + if let encryptionInfo = dict["directory_server_encryption"] as? [AnyHashable: Any] { + return STPIntentActionUseStripeSDK( + encryptionInfo: encryptionInfo, + directoryServerName: dict["directory_server_name"] as? String, + directoryServerKeyID: encryptionInfo["key_id"] as? String, + serverTransactionID: dict["server_transaction_id"] as? String, + threeDSSourceID: dict["three_d_secure_2_source"] as? String, + publishableKeyOverride: dict["publishable_key"] as? String, + threeDS2IntentOverride: dict["three_d_secure_2_intent"] as? String, + allResponseFields: dict + ) as? Self + } else { + return nil + } + case "three_d_secure_redirect": + if let redirectURLString = dict["stripe_js"] as? String, + let redirectURL = URL(string: redirectURLString) + { + return STPIntentActionUseStripeSDK( + redirectURL: redirectURL, + allResponseFields: dict + ) + as? Self + } else { + return nil + } + + default: + return STPIntentActionUseStripeSDK( + type: .unknown, + directoryServerName: nil, + directoryServerID: nil, + directoryServerCertificate: nil, + rootCertificateStrings: nil, + directoryServerKeyID: nil, + serverTransactionID: nil, + threeDSSourceID: nil, + publishableKeyOverride: nil, + threeDS2IntentOverride: nil, + redirectURL: nil, + allResponseFields: dict + ) as? Self + } + } +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STPInternalAPIResponseDecodable.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STPInternalAPIResponseDecodable.swift new file mode 100644 index 00000000..822b6893 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STPInternalAPIResponseDecodable.swift @@ -0,0 +1,16 @@ +// +// STPInternalAPIResponseDecodable.swift +// StripePayments +// +// Created by Ben Guo on 5/23/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Objects that can be returned as part of a heterogenous API response +/// (e.g. cards and sources) should implement this protocol. +@objc protocol STPInternalAPIResponseDecodable: STPAPIResponseDecodable { + /// The object's type. This should match the `object` field in the API response. + func stripeObject() -> String +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STPPaymentMethodListDeserializer.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STPPaymentMethodListDeserializer.swift new file mode 100644 index 00000000..431782da --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STPPaymentMethodListDeserializer.swift @@ -0,0 +1,44 @@ +// +// STPPaymentMethodListDeserializer.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 5/16/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Deserializes the response returned from https://stripe.com/docs/api/payment_methods/list +@_spi(STP) public class STPPaymentMethodListDeserializer: NSObject, STPAPIResponseDecodable { + @_spi(STP) public var paymentMethods: [STPPaymentMethod]? + @_spi(STP) public private(set) var allResponseFields: [AnyHashable: Any] = [:] + + // MARK: STPAPIResponseDecodable + override required init() { + super.init() + } + + @_spi(STP) public class func decodedObject( + fromAPIResponse response: [AnyHashable: Any]? + ) -> Self? { + guard let response = response else { + return nil + } + let dict = response.stp_dictionaryByRemovingNulls() + // Required fields + guard let data = dict.stp_array(forKey: "data") as? [[AnyHashable: Any]] else { + return nil + } + + let paymentMethodsDeserializer = self.init() + var paymentMethods: [STPPaymentMethod] = [] + for paymentMethodJSON in data { + let paymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: paymentMethodJSON) + if let paymentMethod = paymentMethod { + paymentMethods.append(paymentMethod) + } + } + paymentMethodsDeserializer.paymentMethods = paymentMethods + return paymentMethodsDeserializer + } +} diff --git a/StripePayments/StripePayments/Source/Internal/API Bindings/STPSourcePoller.swift b/StripePayments/StripePayments/Source/Internal/API Bindings/STPSourcePoller.swift new file mode 100644 index 00000000..13ffa9f7 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/API Bindings/STPSourcePoller.swift @@ -0,0 +1,232 @@ +// +// STPSourcePoller.swift +// StripePayments +// +// Created by Ben Guo on 1/26/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +class STPSourcePoller: NSObject { + required init( + apiClient: STPAPIClient, + clientSecret: String, + sourceID: String, + timeout: TimeInterval, + completion: @escaping STPSourceCompletionBlock + ) { + self.apiClient = apiClient + self.sourceID = sourceID + self.clientSecret = clientSecret + self.completion = completion + pollInterval = DefaultPollInterval + self.timeout = timeout + startTime = Date() + retryCount = 0 + requestCount = 0 + pollingPaused = false + pollingStopped = false + super.init() + poll(after: 0, lastError: nil) + let notificationCenter = NotificationCenter.default + notificationCenter.addObserver( + self, + selector: #selector(restartPolling), + name: UIApplication.didBecomeActiveNotification, + object: nil + ) + notificationCenter.addObserver( + self, + selector: #selector(restartPolling), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + notificationCenter.addObserver( + self, + selector: #selector(pausePolling), + name: UIApplication.willResignActiveNotification, + object: nil + ) + notificationCenter.addObserver( + self, + selector: #selector(pausePolling), + name: UIApplication.didEnterBackgroundNotification, + object: nil + ) + } + + // Stops polling and cancels the request in progress. + func stopPolling() { + pollingStopped = true + if let timer = timer { + timer.invalidate() + self.timer = nil + } + } + + private weak var apiClient: STPAPIClient? + private var sourceID: String + private var clientSecret: String + private var completion: STPSourceCompletionBlock + private var latestSource: STPSource? + private var pollInterval: TimeInterval = 0.0 + private var timeout: TimeInterval = 0.0 + private var timer: Timer? + private var startTime: Date + private var retryCount = 0 + private var requestCount = 0 + private var pollingPaused = false + private var pollingStopped = false + + deinit { + NotificationCenter.default.removeObserver(self) + } + + func poll(after interval: TimeInterval, lastError error: Error?) { + let totalTime: TimeInterval = Date().timeIntervalSince(startTime) + let shouldTimeout = + requestCount > 0 + && ((totalTime) >= TimeInterval(min(timeout, MaxTimeout)) || retryCount >= MaxRetries) + if apiClient == nil || shouldTimeout { + cleanupAndFireCompletion( + with: latestSource, + error: error + ) + return + } + if pollingPaused || pollingStopped { + return + } + timer = Timer.scheduledTimer( + timeInterval: interval, + target: self, + selector: #selector(_poll), + userInfo: nil, + repeats: false + ) + } + + @objc func _poll() { + timer = nil + let application = UIApplication.shared + var bgTaskID: UIBackgroundTaskIdentifier = .invalid + bgTaskID = application.beginBackgroundTask(expirationHandler: { + application.endBackgroundTask(bgTaskID) + bgTaskID = .invalid + }) + apiClient?.retrieveSource( + withId: sourceID, + clientSecret: clientSecret, + responseCompletion: { source, response, error in + self._continue(with: source, response: response, error: error as NSError?) + self.requestCount += 1 + application.endBackgroundTask(bgTaskID) + bgTaskID = .invalid + } + ) + } + + func _continue( + with source: STPSource?, + response: HTTPURLResponse?, + error: NSError? + ) { + if let response = response { + let status = response.statusCode + if status >= 400 && status < 500 { + // Don't retry requests that 4xx + cleanupAndFireCompletion( + with: latestSource, + error: error + ) + } else if status == 200 { + pollInterval = DefaultPollInterval + retryCount = 0 + latestSource = source + if shouldContinuePollingSource(source) { + poll(after: pollInterval, lastError: nil) + } else { + cleanupAndFireCompletion( + with: latestSource, + error: nil + ) + } + } else { + // Backoff and increment retry count + pollInterval = TimeInterval(min(pollInterval * 2, MaxPollInterval)) + retryCount += 1 + poll(after: pollInterval, lastError: error) + } + } else { + // Retry if there's a connectivity error + if let error = error, + error.code == CFNetworkErrors.cfurlErrorNotConnectedToInternet.rawValue + || error.code == CFNetworkErrors.cfurlErrorNetworkConnectionLost.rawValue + { + retryCount += 1 + poll(after: pollInterval, lastError: error) + } else { + // Don't call completion if the request was cancelled + if let error = error, error.code != CFNetworkErrors.cfurlErrorCancelled.rawValue { + cleanupAndFireCompletion( + with: latestSource, + error: error + ) + } + stopPolling() + } + } + } + + func shouldContinuePollingSource(_ source: STPSource?) -> Bool { + if source == nil { + return false + } + return source?.status == .pending + } + + @objc func restartPolling() { + if pollingStopped { + return + } + pollingPaused = false + if timer == nil { + poll(after: 0, lastError: nil) + } + } + + // Pauses polling, without canceling the request in progress. + @objc func pausePolling() { + pollingPaused = true + if let timer = timer { + timer.invalidate() + self.timer = nil + } + } + + func cleanupAndFireCompletion( + with source: STPSource?, + error: Error? + ) { + if !pollingStopped { + DispatchQueue.main.async(execute: { + if error == nil && source == nil { + self.completion(nil, NSError.stp_genericConnectionError()) + } else { + self.completion(source, error) + } + }) + stopPolling() + } + } +} + +private let DefaultPollInterval: TimeInterval = 1.5 +private let MaxPollInterval: TimeInterval = 24 +// Stop polling after 5 minutes +private let MaxTimeout: TimeInterval = 60 * 5 +// Stop polling after 5 consecutive non-200 responses +private let MaxRetries: Int = 5 diff --git a/StripePayments/StripePayments/Source/Internal/Analytics/Analytic+Payments.swift b/StripePayments/StripePayments/Source/Internal/Analytics/Analytic+Payments.swift new file mode 100644 index 00000000..9b2f1966 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Analytics/Analytic+Payments.swift @@ -0,0 +1,57 @@ +// +// Analytic+Payments.swift +// StripePayments +// +// Created by Mel Ludowise on 5/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// A generic analytic type. +/// - NOTE: This should only be used to support legacy analytics. +/// Any new analytic events should create a new type and conform to `PaymentAnalytic`. +struct GenericPaymentAnalytic: PaymentAnalytic { + let event: STPAnalyticEvent + let paymentConfiguration: NSObject? + let additionalParams: [String: Any] +} + +/// Represents a generic payment error analytic +struct GenericPaymentErrorAnalytic: PaymentAnalytic { + let event: STPAnalyticEvent + let paymentConfiguration: NSObject? + let additionalParams: [String: Any] + let error: Error +} + +extension GenericPaymentAnalytic { + var params: [String: Any] { + var params = additionalParams + + params["company_name"] = Bundle.stp_applicationName() ?? "" + params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) + params["ocr_type"] = PaymentsSDKVariant.ocrTypeString + params["pay_var"] = PaymentsSDKVariant.variant + + if let paymentConfiguration = paymentConfiguration, + let analyticsSerializerClass = GenericPaymentAnalytic.STPBasicUIAnalyticsSerializerClass + { + let configurationDictionary = analyticsSerializerClass.serializeConfiguration( + paymentConfiguration + ) + params = params.merging(configurationDictionary) { (_, new) in new } + } + + return params + } + + static let STPBasicUIAnalyticsSerializerClass: STPAnalyticsSerializer.Type? = + NSClassFromString("Stripe.STPBasicUIAnalyticsSerializer") as? STPAnalyticsSerializer.Type + +} + +@_spi(STP) public protocol STPAnalyticsSerializer { + static func serializeConfiguration(_ configuration: NSObject) -> [String: String] +} diff --git a/StripePayments/StripePayments/Source/Internal/Analytics/STPAnalyticsClient+Payments.swift b/StripePayments/StripePayments/Source/Internal/Analytics/STPAnalyticsClient+Payments.swift new file mode 100644 index 00000000..f75f6fa5 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Analytics/STPAnalyticsClient+Payments.swift @@ -0,0 +1,382 @@ +// +// STPAnalyticsClient+Payments.swift +// StripePayments +// +// Created by Mel Ludowise on 5/26/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +// MARK: - Creation +extension STPAnalyticsClient { + func logTokenCreationAttempt( + with configuration: NSObject?, + tokenType: String? + ) { + log( + analytic: GenericPaymentAnalytic( + event: .tokenCreation, + paymentConfiguration: configuration, + additionalParams: [ + "token_type": tokenType ?? "unknown", + ] + ) + ) + } + + func logSourceCreationAttempt( + with configuration: NSObject?, + sourceType: String? + ) { + log( + analytic: GenericPaymentAnalytic( + event: .sourceCreation, + paymentConfiguration: configuration, + additionalParams: [ + "source_type": sourceType ?? "unknown", + ] + ) + ) + } + + func logPaymentMethodCreationAttempt( + with configuration: NSObject?, + paymentMethodType: String?, + apiClient: STPAPIClient + ) { + log( + analytic: GenericPaymentAnalytic( + event: .paymentMethodCreation, + paymentConfiguration: configuration, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown", + ] + ), + apiClient: apiClient + ) + } + + func logPaymentMethodUpdateAttempt( + with configuration: NSObject? + ) { + log( + analytic: GenericPaymentAnalytic( + event: .paymentMethodUpdate, + paymentConfiguration: configuration, + additionalParams: [:] + ) + ) + } +} + +// MARK: - Confirmation +extension STPAnalyticsClient { + func logPaymentIntentConfirmationAttempt( + with configuration: NSObject?, + paymentMethodType: String?, + apiClient: STPAPIClient + ) { + log( + analytic: GenericPaymentAnalytic( + event: .paymentMethodIntentCreation, + paymentConfiguration: configuration, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown", + ] + ), + apiClient: apiClient + ) + } + + func logSetupIntentConfirmationAttempt( + with configuration: NSObject?, + paymentMethodType: String?, + apiClient: STPAPIClient + ) { + log( + analytic: GenericPaymentAnalytic( + event: .setupIntentConfirmationAttempt, + paymentConfiguration: configuration, + additionalParams: [ + "source_type": paymentMethodType ?? "unknown", + ] + ), + apiClient: apiClient + ) + } +} + +// MARK: - 3DS2 Flow +extension STPAnalyticsClient { + func log3DS2AuthenticationRequestParamsFailed( + with configuration: NSObject?, + intentID: String, + error: NSError + ) { + log( + analytic: GenericPaymentErrorAnalytic( + event: ._3DS2AuthenticationRequestParamsFailed, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + ], + error: error + ) + ) + } + + func log3DS2AuthenticateAttempt( + with configuration: NSObject?, + intentID: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2AuthenticationAttempt, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + ] + ) + ) + } + + func log3DS2FrictionlessFlow( + with configuration: NSObject?, + intentID: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2FrictionlessFlow, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + ] + ) + ) + } + + func logURLRedirectNextAction( + with configuration: NSObject?, + intentID: String?, + usesWebAuthSession: Bool + ) { + logURLRedirectNextAction(with: configuration, intentID: intentID, usesWebAuthSession: usesWebAuthSession, isComplete: false) + } + + func logURLRedirectNextActionCompleted( + with configuration: NSObject?, + intentID: String?, + usesWebAuthSession: Bool + ) { + logURLRedirectNextAction(with: configuration, intentID: intentID, usesWebAuthSession: usesWebAuthSession, isComplete: true) + } + + func logURLRedirectNextAction( + with configuration: NSObject?, + intentID: String?, + usesWebAuthSession: Bool, + isComplete: Bool + ) { + var params: [String: Any] = ["redirect_type": usesWebAuthSession ? "SFVC" : "ASWAS"] + if let intentID { + params["intent_id"] = intentID + } + log( + analytic: GenericPaymentAnalytic( + event: isComplete ? .urlRedirectNextActionCompleted : .urlRedirectNextAction, + paymentConfiguration: configuration, + additionalParams: params + ) + ) + } + + func log3DS2ChallengeFlowPresented( + with configuration: NSObject?, + intentID: String, + uiType: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2ChallengeFlowPresented, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + "3ds2_ui_type": uiType, + ] + ) + ) + } + + func log3DS2ChallengeFlowTimedOut( + with configuration: NSObject?, + intentID: String, + uiType: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2ChallengeFlowTimedOut, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + "3ds2_ui_type": uiType, + ] + ) + ) + } + + func log3DS2ChallengeFlowUserCanceled( + with configuration: NSObject?, + intentID: String, + uiType: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2ChallengeFlowUserCanceled, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + "3ds2_ui_type": uiType, + ] + ) + ) + } + + func log3DS2RedirectUserCanceled( + with configuration: NSObject?, + intentID: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2RedirectUserCanceled, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + ] + ) + ) + } + + func log3DS2ChallengeFlowCompleted( + with configuration: NSObject?, + intentID: String, + uiType: String + ) { + log( + analytic: GenericPaymentAnalytic( + event: ._3DS2ChallengeFlowCompleted, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + "3ds2_ui_type": uiType, + ] + ) + ) + } + + func log3DS2ChallengeFlowErrored( + with configuration: NSObject?, + intentID: String, + error: NSError + ) { + log( + analytic: GenericPaymentErrorAnalytic( + event: ._3DS2ChallengeFlowErrored, + paymentConfiguration: configuration, + additionalParams: [ + "intent_id": intentID, + ], + error: error + ) + ) + } +} + +// MARK: - Card Metadata +extension STPAnalyticsClient { + @_spi(STP) public func logUserEnteredCompletePANBeforeMetadataLoaded() { + log( + analytic: GenericPaymentAnalytic( + event: .cardMetadataLoadedTooSlow, + paymentConfiguration: nil, + additionalParams: [:] + ) + ) + } + + func logCardMetadataResponseFailure() { + log( + analytic: GenericPaymentAnalytic( + event: .cardMetadataResponseFailure, + paymentConfiguration: nil, + additionalParams: [:] + ) + ) + } + + func logCardMetadataMissingRange() { + log( + analytic: GenericPaymentAnalytic( + event: .cardMetadataMissingRange, + paymentConfiguration: nil, + additionalParams: [:] + ) + ) + } +} + +// MARK: - Card Scanning +extension STPAnalyticsClient { + @_spi(STP) public func logCardScanSucceeded(withDuration duration: TimeInterval) { + log( + analytic: GenericAnalytic( + event: .cardScanSucceeded, + params: [ + "duration": NSNumber(value: round(duration)), + ] + ) + ) + } + + @_spi(STP) public func logCardScanCancelled(withDuration duration: TimeInterval) { + log( + analytic: GenericAnalytic( + event: .cardScanCancelled, + params: [ + "duration": NSNumber(value: round(duration)), + ] + ) + ) + } +} + +// MARK: - Card Element Config +extension STPAnalyticsClient { + @_spi(STP) public func logCardElementConfigLoadFailed() { + log( + analytic: GenericPaymentAnalytic( + event: .cardElementConfigLoadFailure, + paymentConfiguration: nil, + additionalParams: [:] + ) + ) + } +} + +/// An analytic specific to payments that serializes payment-specific +/// information into its params. +@_spi(STP) public protocol PaymentAnalytic: Analytic { + var additionalParams: [String: Any] { get } +} + +@_spi(STP) extension PaymentAnalytic { + public var params: [String: Any] { + var params = additionalParams + + params["apple_pay_enabled"] = NSNumber(value: StripeAPI.deviceSupportsApplePay()) + params["ocr_type"] = PaymentsSDKVariant.ocrTypeString + params["pay_var"] = PaymentsSDKVariant.variant + return params + } +} diff --git a/StripePayments/StripePayments/Source/Internal/Categories/NSArray+Stripe.swift b/StripePayments/StripePayments/Source/Internal/Categories/NSArray+Stripe.swift new file mode 100644 index 00000000..7686f078 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Categories/NSArray+Stripe.swift @@ -0,0 +1,38 @@ +// +// NSArray+Stripe.swift +// StripePayments +// +// Created by Jack Flintermann on 1/19/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension Array { + func stp_arrayByRemovingNulls() -> [Any] { + var result: [Any] = [] + + for obj in self { + switch obj { + case let obj as Array: + // Save array after removing any null values + result.append(obj.stp_arrayByRemovingNulls()) + case let obj as [AnyHashable: Any]: + // Save dictionary after removing any null values + let dict = obj.stp_dictionaryByRemovingNulls() + result.append(dict) + case let obj as Any: + if obj is NSNull { + // Skip null value + continue + } + // Save other value + result.append(obj) + default: + continue + } + } + + return result + } +} diff --git a/StripePayments/StripePayments/Source/Internal/Categories/NSDecimalNumber+Stripe_Currency.swift b/StripePayments/StripePayments/Source/Internal/Categories/NSDecimalNumber+Stripe_Currency.swift new file mode 100644 index 00000000..4d9f7a7f --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Categories/NSDecimalNumber+Stripe_Currency.swift @@ -0,0 +1,58 @@ +// +// NSDecimalNumber+Stripe_Currency.swift +// StripePayments +// +// Created by Jack Flintermann on 4/20/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension NSDecimalNumber { + // The number of decimal places for some currencies varies between Stripe and NumberFormatter, + // This maps the currency code to the number of decimal digits. + static let decimalCountSpecialCases = [ + "COP": 2, + "PKR": 2, + "LAK": 2, + "RSD": 2, + "IDR": 2, + "ISK": 2, + ] + + @objc @_spi(STP) public class func stp_decimalNumber( + withAmount amount: Int, + currency: String? + ) -> NSDecimalNumber { + let number = self.init(mantissa: UInt64(amount), exponent: 0, isNegative: false) + let decimalCount = decimalCount(for: currency) + return number.multiplying(byPowerOf10: -Int16(decimalCount)) + } + + @objc @_spi(STP) public func stp_amount(withCurrency currency: String?) -> Int { + var ourNumber = self + let decimalCount = NSDecimalNumber.decimalCount(for: currency) + ourNumber = multiplying(byPowerOf10: Int16(decimalCount)) + return Int(ourNumber.doubleValue) + } + + private class func decimalCount(for currency: String?) -> Int { + if let currency = currency?.uppercased(), + let specialCase = Self.decimalCountSpecialCases[currency] + { + return specialCase + } + + let currencyLocaleIdentifier = Locale.availableIdentifiers.first(where: { + let locale = Locale(identifier: $0) + return locale.stp_currencyCode?.lowercased() == currency?.lowercased() + }) + + let currencyFormatter = NumberFormatter() + currencyFormatter.numberStyle = .currency + currencyFormatter.locale = Locale(identifier: currencyLocaleIdentifier ?? "") + + return currencyFormatter.maximumFractionDigits + } +} diff --git a/StripePayments/StripePayments/Source/Internal/Categories/NSDictionary+Stripe.swift b/StripePayments/StripePayments/Source/Internal/Categories/NSDictionary+Stripe.swift new file mode 100644 index 00000000..cc95598a --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Categories/NSDictionary+Stripe.swift @@ -0,0 +1,131 @@ +// +// NSDictionary+Stripe.swift +// StripePayments +// +// Created by Jack Flintermann on 10/15/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension Dictionary where Key == AnyHashable, Value: Any { + @_spi(STP) public func stp_dictionaryByRemovingNulls() -> [AnyHashable: Any] { + var result = [AnyHashable: Any]() + + (self as NSDictionary).enumerateKeysAndObjects({ key, obj, _ in + guard let key = key as? AnyHashable else { + assertionFailure() + return + } + if let obj = obj as? [Any] { + // Save array after removing any null values + let stp = obj.stp_arrayByRemovingNulls() + result[key] = stp + } else if let obj = obj as? [AnyHashable: Any] { + // Save dictionary after removing any null values + let stp = obj.stp_dictionaryByRemovingNulls() + result[key] = stp + } else if obj is NSNull { + // Skip null value + } else { + // Save other value + result[key] = obj + } + }) + + // Make immutable copy + return result + } + + func stp_dictionaryByRemovingNonStrings() -> [String: String] { + var result: [String: String] = [:] + + (self as NSDictionary).enumerateKeysAndObjects({ key, obj, _ in + if let key = key as? String, let obj = obj as? String { + // Save valid key/value pair + result[key] = obj + } + }) + + // Make immutable copy + return result + } + + // Getters + @_spi(STP) public func stp_array(forKey key: String) -> [Any]? { + let value = self[key] + if value != nil { + return value as? [Any] + } + return nil + } + + @_spi(STP) public func stp_bool(forKey key: String, or defaultValue: Bool) -> Bool { + let value = self[key] + if value != nil { + if let value = value as? NSNumber { + return value.boolValue + } + if value is NSString { + let string = (value as? String)?.lowercased() + // boolValue on NSString is true for "Y", "y", "T", "t", or 1-9 + if (string == "true") || (string as NSString?)?.boolValue ?? false { + return true + } else { + return false + } + } + } + return defaultValue + } + + @_spi(STP) public func stp_date(forKey key: String) -> Date? { + let value = self[key] + if let value = value as? NSNumber { + let timeInterval = value.doubleValue + return Date(timeIntervalSince1970: TimeInterval(timeInterval)) + } else if let value = value as? NSString { + let timeInterval = value.doubleValue + return Date(timeIntervalSince1970: TimeInterval(timeInterval)) + } + return nil + } + + @_spi(STP) public func stp_dictionary(forKey key: String) -> [AnyHashable: Any]? { + let value = self[key] + if value != nil && (value is [AnyHashable: Any]) { + return value as? [AnyHashable: Any] + } + return nil + } + + func stp_int(forKey key: String, or defaultValue: Int) -> Int { + let value = self[key] + if let value = value as? NSNumber { + return value.intValue + } else if let value = value as? NSString { + return Int(value.intValue) + } + return defaultValue + } + + func stp_number(forKey key: String) -> NSNumber? { + return self[key] as? NSNumber + } + + @_spi(STP) public func stp_string(forKey key: String) -> String? { + let value = self[key] + if value != nil && (value is NSString) { + return value as? String + } + return nil + } + + func stp_url(forKey key: String) -> URL? { + let value = self[key] + if value != nil && (value is NSString) && ((value as? String)?.count ?? 0) > 0 { + return URL(string: value as? String ?? "") + } + return nil + } +} diff --git a/StripePayments/StripePayments/Source/Internal/Categories/NSString+Stripe.swift b/StripePayments/StripePayments/Source/Internal/Categories/NSString+Stripe.swift new file mode 100644 index 00000000..581dc090 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Categories/NSString+Stripe.swift @@ -0,0 +1,73 @@ +// +// NSString+Stripe.swift +// StripePayments +// +// Created by Jack Flintermann on 10/16/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension String { + + /// A Boolean value indicating whether the string contains only whitespace. + @_spi(STP) public var isBlank: Bool { + return allSatisfy({ $0.isWhitespace }) + } + + /// Returns a substring up to the specified index. + /// + /// This method clamps out-of-bound indexes and always returns a valid (non-nil) string. + /// + /// - Parameter index: Index of last character to include in the substring. + /// - Returns: Substring. + @_spi(STP) public func stp_safeSubstring(to index: Int) -> String { + let maxLength = max(min(index, count), 0) + return String(prefix(maxLength)) + } + + /// Returns the substring starting from the specified index. + /// + /// This method clamps out-of-bound indexes and always returns a valid (non-nil) string. + /// + /// - Parameter index: Index of starting point of substring. + /// - Returns: Substring. + @_spi(STP) public func stp_safeSubstring(from index: Int) -> String { + let maxLength = max(min(count - index, count), 0) + return String(suffix(maxLength)) + } + + @_spi(STP) public func stp_string(byRemovingSuffix suffix: String?) -> String { + if let suffix = suffix, self.hasSuffix(suffix) { + return String(dropLast(suffix.count)) + } else { + return self + } + } + + // e.g. localizedAmountDisplayString(for: 1099, "USD") -> "$10.99" in en_US, "10,99 $" in fr_FR + @_spi(STP) public static func localizedAmountDisplayString( + for amount: Int, + currency: String, + locale: Locale = NSLocale.autoupdatingCurrent + ) -> String { + let decimalizedAmount = NSDecimalNumber.stp_decimalNumber( + withAmount: amount, + currency: currency + ) + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.usesGroupingSeparator = true + formatter.locale = locale + formatter.currencyCode = currency + let failsafeString = "\(formatter.currencySymbol ?? "")\(decimalizedAmount)" + return formatter.string(from: decimalizedAmount) ?? failsafeString + } + + /// Function to determine if this string is the country code of the United State + /// @param caseSensitive - Whether this string should only be considered the US country code if it matches the expected capitalization + @_spi(STP) public func isUSCountryCode(_ caseSensitive: Bool = false) -> Bool { + return caseSensitive ? self == "US" : self.caseInsensitiveCompare("US") == .orderedSame + } + +} diff --git a/StripePayments/StripePayments/Source/Internal/Categories/STPAPIClient+PaymentsCore.swift b/StripePayments/StripePayments/Source/Internal/Categories/STPAPIClient+PaymentsCore.swift new file mode 100644 index 00000000..540535ee --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Categories/STPAPIClient+PaymentsCore.swift @@ -0,0 +1,23 @@ +// +// STPAPIClient+PaymentsCore.swift +// StripePayments +// +// Created by David Estes on 1/25/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +extension STPAPIClient { + + /// - Parameter additionalValues: A list of values to append to the `payment_user_agent`. e.g. `["deferred-intent", "autopm"]` will append "; deferred-intent; autopm" to the `payment_user_agent`. + @_spi(STP) public class func paramsAddingPaymentUserAgent( + _ params: [String: Any], + additionalValues: [String] = [] + ) -> [String: Any] { + var newParams = params + newParams["payment_user_agent"] = ([PaymentsSDKVariant.paymentUserAgent] + additionalValues).joined(separator: "; ") + return newParams + } +} diff --git a/StripePayments/StripePayments/Source/Internal/Helpers/ConnectionsSDKAvailability.swift b/StripePayments/StripePayments/Source/Internal/Helpers/ConnectionsSDKAvailability.swift new file mode 100644 index 00000000..03eb1e0b --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/Helpers/ConnectionsSDKAvailability.swift @@ -0,0 +1,88 @@ +// +// ConnectionsSDKAvailability.swift +// StripePayments +// +// Created by Vardges Avetisyan on 2/24/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import SwiftUI +import UIKit + +@_spi(STP) public struct FinancialConnectionsSDKAvailability { + static let FinancialConnectionsSDKClass: FinancialConnectionsSDKInterface.Type? = + NSClassFromString("StripeFinancialConnections.FinancialConnectionsSDKImplementation") + as? FinancialConnectionsSDKInterface.Type + + static let isUnitTest: Bool = { + #if targetEnvironment(simulator) + return NSClassFromString("XCTest") != nil + #else + return false + #endif + }() + + static let isUITest: Bool = { + #if targetEnvironment(simulator) + return ProcessInfo.processInfo.environment["UITesting"] != nil + #else + return false + #endif + }() + + // Return true for unit tests, the value of `FinancialConnectionsSDKAvailable` for UI tests, + // and whether or not the Financial Connections SDK is available otherwise. + @_spi(STP) public static var isFinancialConnectionsSDKAvailable: Bool { + if isUnitTest { + return true + } else if isUITest { + let financialConnectionsSDKAvailable = ProcessInfo.processInfo.environment["FinancialConnectionsSDKAvailable"] == "true" + return financialConnectionsSDKAvailable + } else { + return FinancialConnectionsSDKClass != nil + } + } + + static func financialConnections() -> FinancialConnectionsSDKInterface? { + let financialConnectionsStubbedResult = ProcessInfo.processInfo.environment["FinancialConnectionsStubbedResult"] == "true" + if isUnitTest || (isUITest && financialConnectionsStubbedResult) { + return StubbedConnectionsSDKInterface() + } + + guard let klass = FinancialConnectionsSDKClass else { + return nil + } + + return klass.init() + } +} + +final class StubbedConnectionsSDKInterface: FinancialConnectionsSDKInterface { + func presentFinancialConnectionsSheet( + apiClient: STPAPIClient, + clientSecret: String, + returnURL: String?, + elementsSessionContext: ElementsSessionContext?, + onEvent: ((FinancialConnectionsEvent) -> Void)?, + from presentingViewController: UIViewController, + completion: @escaping (FinancialConnectionsSDKResult) -> Void + ) { + DispatchQueue.main.async { + let stubbedBank = FinancialConnectionsLinkedBank( + sessionId: "las_123", + accountId: "fca_123", + displayName: "Test Bank", + bankName: "Test Bank", + last4: "1234", + instantlyVerified: true + ) + completion( + FinancialConnectionsSDKResult.completed( + .financialConnections(stubbedBank) + ) + ) + } + } +} diff --git a/StripePayments/StripePayments/Source/Internal/STPPaymentHandlerActionParams.swift b/StripePayments/StripePayments/Source/Internal/STPPaymentHandlerActionParams.swift new file mode 100644 index 00000000..44e21576 --- /dev/null +++ b/StripePayments/StripePayments/Source/Internal/STPPaymentHandlerActionParams.swift @@ -0,0 +1,190 @@ +// +// STPPaymentHandlerActionParams.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/28/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import AuthenticationServices +import Foundation +@_spi(STP) import StripeCore + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +@_spi(STP) public protocol STPPaymentHandlerActionParams: NSObject, ASWebAuthenticationPresentationContextProviding { + var threeDS2Service: STDSThreeDS2Service? { get } + var threeDS2Transaction: STDSTransaction? { get set } + var authenticationContext: STPAuthenticationContext { get } + var apiClient: STPAPIClient { get } + var threeDSCustomizationSettings: STPThreeDSCustomizationSettings { get } + var returnURLString: String? { get } + var intentStripeID: String { get } + /// Returns the payment or setup intent's next action + func nextAction() -> STPIntentAction? + func complete(with status: STPPaymentHandlerActionStatus, error: NSError?) +} + +@_spi(STP) +public class STPPaymentHandlerPaymentIntentActionParams: NSObject, STPPaymentHandlerActionParams { + private var serviceInitialized = false + + @_spi(STP) public let authenticationContext: STPAuthenticationContext + @_spi(STP) public let apiClient: STPAPIClient + @_spi(STP) public let threeDSCustomizationSettings: STPThreeDSCustomizationSettings + @_spi(STP) public let paymentIntentCompletion: + STPPaymentHandlerActionPaymentIntentCompletionBlock + @_spi(STP) public let returnURLString: String? + @_spi(STP) public var paymentIntent: STPPaymentIntent + @_spi(STP) public var threeDS2Transaction: STDSTransaction? + + @_spi(STP) public var intentStripeID: String { + return paymentIntent.stripeId + } + + private var _threeDS2Service: STDSThreeDS2Service? + + @_spi(STP) public var threeDS2Service: STDSThreeDS2Service? { + if !serviceInitialized { + serviceInitialized = true + _threeDS2Service = STDSThreeDS2Service() + + STDSSwiftTryCatch.try( + { + let configParams = STDSConfigParameters() + if !self.paymentIntent.livemode { + configParams.addParameterNamed( + "kInternalStripeTestingConfigParam", + withValue: "Y" + ) + } + self._threeDS2Service?.initialize( + withConfig: configParams, + locale: Locale.autoupdatingCurrent, + uiSettings: self.threeDSCustomizationSettings.uiCustomization + .uiCustomization + ) + }, + catch: { _ in + self._threeDS2Service = nil + }, + finallyBlock: { + } + ) + } + + return _threeDS2Service + } + + init( + apiClient: STPAPIClient, + authenticationContext: STPAuthenticationContext, + threeDSCustomizationSettings: STPThreeDSCustomizationSettings, + paymentIntent: STPPaymentIntent, + returnURL returnURLString: String?, + completion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) { + self.apiClient = apiClient + self.authenticationContext = authenticationContext + self.threeDSCustomizationSettings = threeDSCustomizationSettings + self.returnURLString = returnURLString + self.paymentIntent = paymentIntent + self.paymentIntentCompletion = completion + super.init() + } + + @_spi(STP) public func nextAction() -> STPIntentAction? { + return paymentIntent.nextAction + } + + @_spi(STP) public func complete(with status: STPPaymentHandlerActionStatus, error: NSError?) { + paymentIntentCompletion(status, paymentIntent, error) + } + + // Translate the STPAuthenticationContext to an ASPresentationAnchor if possible + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return authenticationContext.authenticationPresentingViewController().view.window ?? ASPresentationAnchor() + } +} + +internal class STPPaymentHandlerSetupIntentActionParams: NSObject, STPPaymentHandlerActionParams { + private var serviceInitialized = false + + let authenticationContext: STPAuthenticationContext + let apiClient: STPAPIClient + let threeDSCustomizationSettings: STPThreeDSCustomizationSettings + let setupIntentCompletion: STPPaymentHandlerActionSetupIntentCompletionBlock + let returnURLString: String? + var setupIntent: STPSetupIntent + var threeDS2Transaction: STDSTransaction? + + var intentStripeID: String { + return setupIntent.stripeID + } + + private var _threeDS2Service: STDSThreeDS2Service? + + var threeDS2Service: STDSThreeDS2Service? { + if !serviceInitialized { + serviceInitialized = true + _threeDS2Service = STDSThreeDS2Service() + + STDSSwiftTryCatch.try( + { + let configParams = STDSConfigParameters() + if !self.setupIntent.livemode { + configParams.addParameterNamed( + "kInternalStripeTestingConfigParam", + withValue: "Y" + ) + } + self._threeDS2Service?.initialize( + withConfig: configParams, + locale: Locale.autoupdatingCurrent, + uiSettings: self.threeDSCustomizationSettings.uiCustomization + .uiCustomization + ) + }, + catch: { _ in + self._threeDS2Service = nil + }, + finallyBlock: { + } + ) + } + + return _threeDS2Service + } + + init( + apiClient: STPAPIClient, + authenticationContext: STPAuthenticationContext, + threeDSCustomizationSettings: STPThreeDSCustomizationSettings, + setupIntent: STPSetupIntent, + returnURL returnURLString: String?, + completion: @escaping STPPaymentHandlerActionSetupIntentCompletionBlock + ) { + self.apiClient = apiClient + self.authenticationContext = authenticationContext + self.threeDSCustomizationSettings = threeDSCustomizationSettings + self.returnURLString = returnURLString + self.setupIntent = setupIntent + self.setupIntentCompletion = completion + super.init() + } + + func nextAction() -> STPIntentAction? { + return setupIntent.nextAction + } + + func complete(with status: STPPaymentHandlerActionStatus, error: NSError?) { + setupIntentCompletion(status, setupIntent, error) + } + + // Translate the STPAuthenticationContext to an ASPresentationAnchor if possible + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return authenticationContext.authenticationPresentingViewController().view.window ?? ASPresentationAnchor() + } +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPAnalyticsClient+STPPaymentHandler.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPAnalyticsClient+STPPaymentHandler.swift new file mode 100644 index 00000000..71668d63 --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPAnalyticsClient+STPPaymentHandler.swift @@ -0,0 +1,119 @@ +// +// STPAnalyticsClient+STPPaymentHandler.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 4/3/24. +// + +@_spi(STP) import StripeCore + +extension STPPaymentHandlerActionStatus { + var stringValue: String { + switch self { + case .canceled: + return "canceled" + case .failed: + return "failed" + case .succeeded: + return "succeeded" + } + } +} + +extension STPPaymentHandler { + struct Analytic: StripeCore.Analytic { + let event: StripeCore.STPAnalyticEvent + let intentID: String? + let status: STPPaymentHandlerActionStatus? + let paymentMethodType: String? + let paymentMethodID: String? + let error: Error? + + var params: [String: Any] { + var params: [String: Any] = error?.serializeForV1Analytics() ?? [:] + params["intent_id"] = intentID + params["status"] = status?.stringValue + params["payment_method_type"] = paymentMethodType + params["payment_method_id"] = paymentMethodID + return params + } + } + + // MARK: - Confirm started + + func logConfirmSetupIntentStarted(setupIntentID: String?, confirmParams: STPSetupIntentConfirmParams) { + let analytic = Analytic( + event: .paymentHandlerConfirmStarted, + intentID: setupIntentID, + status: nil, + paymentMethodType: confirmParams.paymentMethodType?.identifier, + paymentMethodID: confirmParams.paymentMethodID, + error: nil + ) + analyticsClient.log(analytic: analytic, apiClient: apiClient) + } + + func logConfirmPaymentIntentStarted(paymentIntentID: String?, paymentParams: STPPaymentIntentParams) { + let analytic = Analytic( + event: .paymentHandlerConfirmStarted, + intentID: paymentIntentID, + status: nil, + paymentMethodType: paymentParams.paymentMethodType?.identifier, + paymentMethodID: paymentParams.paymentMethodId, + error: nil + ) + analyticsClient.log(analytic: analytic, apiClient: apiClient) + } + + // MARK: - Confirm completed + + func logConfirmPaymentIntentCompleted(paymentIntentID: String?, paymentParams: STPPaymentIntentParams, status: STPPaymentHandlerActionStatus, error: Error?) { + let analytic = Analytic( + event: .paymentHandlerConfirmFinished, + intentID: paymentIntentID, + status: status, + paymentMethodType: paymentParams.paymentMethodType?.identifier, + paymentMethodID: paymentParams.paymentMethodId, + error: error + ) + analyticsClient.log(analytic: analytic, apiClient: apiClient) + } + + func logConfirmSetupIntentCompleted(setupIntentID: String?, confirmParams: STPSetupIntentConfirmParams, status: STPPaymentHandlerActionStatus, error: Error?) { + let analytic = Analytic( + event: .paymentHandlerConfirmFinished, + intentID: setupIntentID, + status: status, + paymentMethodType: confirmParams.paymentMethodType?.identifier, + paymentMethodID: confirmParams.paymentMethodID, + error: error + ) + analyticsClient.log(analytic: analytic, apiClient: apiClient) + } + + // MARK: - Handle next action + + func logHandleNextActionStarted(intentID: String?, paymentMethod: STPPaymentMethod?) { + let analytic = Analytic( + event: .paymentHandlerHandleNextActionStarted, + intentID: intentID, + status: nil, + paymentMethodType: paymentMethod?.type.identifier, + paymentMethodID: paymentMethod?.stripeId, + error: nil + ) + analyticsClient.log(analytic: analytic, apiClient: apiClient) + } + + func logHandleNextActionFinished(intentID: String?, paymentMethod: STPPaymentMethod?, status: STPPaymentHandlerActionStatus, error: Error?) { + let analytic = Analytic( + event: .paymentHandlerHandleNextActionFinished, + intentID: intentID, + status: status, + paymentMethodType: paymentMethod?.type.identifier, + paymentMethodID: paymentMethod?.stripeId, + error: error + ) + analyticsClient.log(analytic: analytic, apiClient: apiClient) + } +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPAuthenticationContext.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPAuthenticationContext.swift new file mode 100644 index 00000000..0e5d1f1a --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPAuthenticationContext.swift @@ -0,0 +1,36 @@ +// +// STPAuthenticationContext.swift +// StripePayments +// +// Created by Cameron Sabol on 5/10/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import SafariServices +import UIKit + +/// `STPAuthenticationContext` provides information required to present authentication challenges +/// to a user. +@objc public protocol STPAuthenticationContext: NSObjectProtocol { + /// The Stripe SDK will modally present additional view controllers on top + /// of the `authenticationPresentingViewController` when required for user + /// authentication, like in the Challenge Flow for 3DS2 transactions. + func authenticationPresentingViewController() -> UIViewController + + /// This method is called before presenting a UIViewController for authentication. + /// @note `STPPaymentHandler` will not proceed until `completion` is called. + @objc(prepareAuthenticationContextForPresentation:) optional func prepare( + forPresentation completion: @escaping STPVoidBlock + ) + /// This method is called before presenting an SFSafariViewController for web-based authentication. + /// Implement this method to configure the `SFSafariViewController` instance, e.g. `viewController.preferredBarTintColor = MyBarTintColor` + /// @note Setting the `delegate` property has no effect. + @objc optional func configureSafariViewController(_ viewController: SFSafariViewController) + /// This method is called when an authentication UIViewController is about to be dismissed. + /// Implement this method to prepare your UI for the authentication view controller to be dismissed. For example, + /// if you requested authentication while displaying an STPBankSelectionViewController, you may want to hide + /// it to return the user to your desired view controller. + @objc(authenticationContextWillDismissViewController:) + optional func authenticationContextWillDismiss(_ viewController: UIViewController) +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPPaymentHandler.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPPaymentHandler.swift new file mode 100644 index 00000000..571d1d5f --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPPaymentHandler.swift @@ -0,0 +1,2504 @@ +// +// STPPaymentHandler.swift +// StripePayments +// +// Created by Cameron Sabol on 5/10/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import AuthenticationServices +import Foundation +import PassKit +import SafariServices +@_spi(STP) import StripeCore + +#if canImport(Stripe3DS2) +import Stripe3DS2 +#endif + +/// `STPPaymentHandlerActionStatus` represents the possible outcomes of requesting an action by `STPPaymentHandler`. An action could be confirming and/or handling the next action for a PaymentIntent. +@objc public enum STPPaymentHandlerActionStatus: Int { + /// The action succeeded. + case succeeded + /// The action was cancelled by the cardholder/user. + case canceled + /// The action failed. See the error code for more details. + case failed +} + +/// Error codes generated by `STPPaymentHandler` +@objc public enum STPPaymentHandlerErrorCode: Int { + /// Indicates that the action requires an authentication method not recognized or supported by the SDK. + @objc(STPPaymentHandlerUnsupportedAuthenticationErrorCode) + case unsupportedAuthenticationErrorCode + + /// Indicates that the action requires an authentication app, but either the app is not installed or the request to switch to the app was denied. + @objc(STPPaymentHandlerRequiredAppNotAvailableErrorCode) + case requiredAppNotAvailable + + /// Attach a payment method to the PaymentIntent or SetupIntent before using `STPPaymentHandler`. + @objc(STPPaymentHandlerRequiresPaymentMethodErrorCode) + case requiresPaymentMethodErrorCode + + /// The PaymentIntent or SetupIntent status cannot be resolved by `STPPaymentHandler`. + @objc(STPPaymentHandlerIntentStatusErrorCode) + case intentStatusErrorCode + + /// The action timed out. + @objc(STPPaymentHandlerTimedOutErrorCode) + case timedOutErrorCode + + /// There was an error in the Stripe3DS2 SDK. + @objc(STPPaymentHandlerStripe3DS2ErrorCode) + case stripe3DS2ErrorCode + + /// The transaction did not authenticate (e.g. user entered the wrong code). + @objc(STPPaymentHandlerNotAuthenticatedErrorCode) + case notAuthenticatedErrorCode + + /// `STPPaymentHandler` does not support concurrent actions. + @objc(STPPaymentHandlerNoConcurrentActionsErrorCode) + case noConcurrentActionsErrorCode + + /// Payment requires a valid `STPAuthenticationContext`. Make sure your presentingViewController isn't already presenting. + @objc(STPPaymentHandlerRequiresAuthenticationContextErrorCode) + case requiresAuthenticationContextErrorCode + + /// There was an error confirming the Intent. + /// Inspect the `paymentIntent.lastPaymentError` or `setupIntent.lastSetupError` property. + @objc(STPPaymentHandlerPaymentErrorCode) + case paymentErrorCode + + /// The provided PaymentIntent of SetupIntent client secret does not match the expected pattern for client secrets. + /// Make sure that your server is returning the correct value and that is being passed to `STPPaymentHandler`. + @objc(STPPaymentHandlerInvalidClientSecret) + case invalidClientSecret + + /// The payment method requires a return URL and one was not provided. Your integration should provide one in your `STPPaymentIntentParams`/`STPSetupIntentConfirmParams` object if you call `STPPaymentHandler.confirm...` or when you call `STPPaymentHandler.handleNextAction`. + @objc(STPPaymentHandlerMissingReturnURL) + case missingReturnURL + + /// The SDK encountered an unexpected error, indicating a problem with the SDK or the Stripe API. + @objc(STPPaymentHandlerUnexpectedErrorCode) + case unexpectedErrorCode +} + +/// Completion block typedef for use in `STPPaymentHandler` methods for Payment Intents. +public typealias STPPaymentHandlerActionPaymentIntentCompletionBlock = ( + STPPaymentHandlerActionStatus, STPPaymentIntent?, NSError? +) -> Void +/// Completion block typedef for use in `STPPaymentHandler` methods for Setup Intents. +public typealias STPPaymentHandlerActionSetupIntentCompletionBlock = ( + STPPaymentHandlerActionStatus, STPSetupIntent?, NSError? +) -> Void + +let missingReturnURLErrorMessage = "The payment method requires a return URL and one was not provided. Your integration should provide one in your `STPPaymentIntentParams`/`STPSetupIntentConfirmParams` object if you call `STPPaymentHandler.confirm...` or when you call `STPPaymentHandler.handleNextAction`." + +/// `STPPaymentHandler` is a utility class that confirms PaymentIntents/SetupIntents and handles any authentication required, such as 3DS1/3DS2 for Strong Customer Authentication. +/// It can present authentication UI on top of your app or redirect users out of your app (to e.g. their banking app). +/// - seealso: https://stripe.com/docs/payments/3d-secure +public class STPPaymentHandler: NSObject { + /// The error domain for errors in `STPPaymentHandler`. + @objc public static let errorDomain = "STPPaymentHandlerErrorDomain" + + /// These indicate a programming error in STPPaymentHandler. They are separate from the NSErrors vended to merchants; these errors are only reported to analytics and do not get vended to users of this class. + enum InternalError: Error { + case invalidState + } + + private var currentAction: STPPaymentHandlerActionParams? + /// YES from when a public method is first called until its associated completion handler is called. + /// This property guards against simultaneous usage of this class; only one "next action" can be handled at a time. + private static var inProgress = false + private var safariViewController: SFSafariViewController? + private var asWebAuthenticationSession: ASWebAuthenticationSession? + + /// Set this to true if you want a specific test to run the _canPresent code + /// it will automatically toggle back to false after running the code once + internal var checkCanPresentInTest: Bool = false + + /// The globally shared instance of `STPPaymentHandler`. + @objc public static let sharedHandler: STPPaymentHandler = STPPaymentHandler() + + /// The globally shared instance of `STPPaymentHandler`. + @objc + public class func shared() -> STPPaymentHandler { + return STPPaymentHandler.sharedHandler + } + + @_spi(STP) public init( + apiClient: STPAPIClient = .shared, + threeDSCustomizationSettings: STPThreeDSCustomizationSettings = + STPThreeDSCustomizationSettings() + ) { + self.apiClient = apiClient + self.threeDSCustomizationSettings = threeDSCustomizationSettings + super.init() + } + + /// By default `sharedHandler` initializes with STPAPIClient.shared. + @objc public var apiClient: STPAPIClient + + /// Customizable settings to use when performing 3DS2 authentication. + /// Note: Configure this before calling any methods. + /// Defaults to `STPThreeDSCustomizationSettings()`. + @objc public var threeDSCustomizationSettings: STPThreeDSCustomizationSettings + + internal var _simulateAppToAppRedirect: Bool = false + + /// When this flag is enabled, STPPaymentHandler will confirm certain PaymentMethods using + /// Safari instead of SFSafariViewController. If you'd like to use this in your own + /// testing or Continuous Integration platform, please see the IntegrationTester app + /// for usage examples. + /// + /// Note: This flag is only intended for development, and only impacts payments made with testmode keys. + /// Setting this to `true` with a livemode key will fail. + @objc public var simulateAppToAppRedirect: Bool + { + get { + _simulateAppToAppRedirect && STPAPIClient.shared.isTestmode + } + set { + _simulateAppToAppRedirect = newValue + } + } + + internal var _redirectShim: ((URL, URL?, Bool) -> Void)? + internal var isInProgress: Bool { + return STPPaymentHandler.inProgress + } + internal var analyticsClient: STPAnalyticsClient = .sharedClient + + /// Confirms the PaymentIntent with the provided parameters and handles any `nextAction` required + /// to authenticate the PaymentIntent. + /// Call this method if you are using automatic confirmation. - seealso:https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom + /// - Parameters: + /// - paymentParams: The params used to confirm the PaymentIntent. Note that this method overrides the value of `paymentParams.useStripeSDK` to `@YES`. + /// - authenticationContext: The authentication context used to authenticate the payment. + /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the PaymentIntent status is not necessarily STPPaymentIntentStatusSucceeded (e.g. some bank payment methods take days before the PaymentIntent succeeds). + @objc(confirmPayment:withAuthenticationContext:completion:) + public func confirmPayment( + _ paymentParams: STPPaymentIntentParams, + with authenticationContext: STPAuthenticationContext, + completion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) { + let paymentIntentID = paymentParams.stripeId + logConfirmPaymentIntentStarted(paymentIntentID: paymentIntentID, paymentParams: paymentParams) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: STPPaymentHandlerActionPaymentIntentCompletionBlock = { [weak self] status, paymentIntent, error in + self?.logConfirmPaymentIntentCompleted(paymentIntentID: paymentIntentID, paymentParams: paymentParams, status: status, error: error) + completion(status, paymentIntent, error) + } + if Self.inProgress { + assertionFailure("`STPPaymentHandler.confirmPayment` was called while a previous call is still in progress.") + completion(.failed, nil, _error(for: .noConcurrentActionsErrorCode)) + return + } else if !STPPaymentIntentParams.isClientSecretValid(paymentParams.clientSecret) { + assertionFailure("`STPPaymentHandler.confirmPayment` was called with an invalid client secret. See https://docs.stripe.com/api/payment_intents/object#payment_intent_object-client_secret") + completion(.failed, nil, _error(for: .invalidClientSecret)) + return + } + Self.inProgress = true + weak var weakSelf = self + // wrappedCompletion ensures we perform some final logic before calling the completion block. + let wrappedCompletion: STPPaymentHandlerActionPaymentIntentCompletionBlock = { + status, + paymentIntent, + error in + guard let strongSelf = weakSelf else { + return + } + // Reset our internal state + Self.inProgress = false + + // Ensure the .succeeded case returns a PaymentIntent in the expected state. + if let paymentIntent = paymentIntent, status == .succeeded { + let successIntentState = + paymentIntent.status == .succeeded || paymentIntent.status == .requiresCapture + || (paymentIntent.status == .processing + && STPPaymentHandler._isProcessingIntentSuccess( + for: paymentIntent.paymentMethod?.type ?? .unknown + ) + || (paymentIntent.status == .requiresAction + && strongSelf.isNextActionSuccessState( + nextAction: paymentIntent.nextAction + ))) + + if error == nil && successIntentState { + completion(.succeeded, paymentIntent, nil) + } else { + let errorMessage = "STPPaymentHandler status is succeeded, but the PI is not in a success state or there was an error." + stpAssertionFailure(errorMessage) + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: [ + "error_message": errorMessage, + "payment_intent": paymentIntent.stripeId, + "payment_intent_status": STPPaymentIntentStatus.string(from: paymentIntent.status), + "error_details": error?.serializeForV1Analytics() ?? [:], + ]) + strongSelf.analyticsClient.log(analytic: errorAnalytic, apiClient: strongSelf.apiClient) + completion( + .failed, + paymentIntent, + error ?? strongSelf._error(for: .intentStatusErrorCode) + ) + } + return + } + completion(status, paymentIntent, error) + } + + let confirmCompletionBlock: STPPaymentIntentCompletionBlock = { paymentIntent, error in + guard let strongSelf = weakSelf else { + return + } + if let paymentIntent = paymentIntent, + error == nil + { + strongSelf._handleNextAction( + forPayment: paymentIntent, + with: authenticationContext, + returnURL: paymentParams.returnURL + ) { status, completedPaymentIntent, completedError in + wrappedCompletion(status, completedPaymentIntent, completedError) + } + } else { + wrappedCompletion(.failed, paymentIntent, error as NSError?) + } + } + + var params = paymentParams + // We always set useStripeSDK = @YES in STPPaymentHandler + if !(params.useStripeSDK?.boolValue ?? false) { + params = paymentParams.copy() as! STPPaymentIntentParams + params.useStripeSDK = NSNumber(value: true) + } + apiClient.confirmPaymentIntent( + with: params, + expand: ["payment_method"], + completion: confirmCompletionBlock + ) + } + + /// :nodoc: + @available( + *, + deprecated, + message: "Use confirmPayment(_:with:completion:) instead", + renamed: "confirmPayment(_:with:completion:)" + ) + public func confirmPayment( + withParams: STPPaymentIntentParams, + authenticationContext: STPAuthenticationContext, + completion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) { + self.confirmPayment(withParams, with: authenticationContext, completion: completion) + } + + /// Handles any `nextAction` required to authenticate the PaymentIntent. + /// Call this method if you are using server-side confirmation. - seealso: https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom + /// - Parameters: + /// - paymentIntentClientSecret: The client secret of the PaymentIntent to handle next actions for. + /// - authenticationContext: The authentication context used to authenticate the payment. + /// - returnURL: An optional URL to redirect your customer back to after they authenticate or cancel in a webview. This should match the returnURL you specified during PaymentIntent confirmation. + /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the PaymentIntent status is not necessarily STPPaymentIntentStatusSucceeded (e.g. some bank payment methods take days before the PaymentIntent succeeds). + @objc(handleNextActionForPayment:withAuthenticationContext:returnURL:completion:) + public func handleNextAction( + forPayment paymentIntentClientSecret: String, + with authenticationContext: STPAuthenticationContext, + returnURL: String?, + completion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) { + let paymentIntentID = STPPaymentIntent.id(fromClientSecret: paymentIntentClientSecret) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: STPPaymentHandlerActionPaymentIntentCompletionBlock = { [weak self] status, paymentIntent, error in + self?.logHandleNextActionFinished(intentID: paymentIntentID, paymentMethod: paymentIntent?.paymentMethod, status: status, error: error) + completion(status, paymentIntent, error) + } + logHandleNextActionStarted(intentID: paymentIntentID, paymentMethod: nil) + if !STPPaymentIntentParams.isClientSecretValid(paymentIntentClientSecret) { + assertionFailure("`STPPaymentHandler.handleNextAction` was called with an invalid client secret. See https://docs.stripe.com/api/payment_intents/object#payment_intent_object-client_secret") + completion(.failed, nil, _error(for: .invalidClientSecret)) + return + } + apiClient.retrievePaymentIntent( + withClientSecret: paymentIntentClientSecret, + expand: ["payment_method"] + ) { [weak self] paymentIntent, error in + guard let self else { + return + } + if let paymentIntent = paymentIntent, error == nil { + self.handleNextAction(for: paymentIntent, with: authenticationContext, returnURL: returnURL, shouldSendAnalytic: false, completion: completion) + } else { + completion(.failed, paymentIntent, error as NSError?) + } + } + } + + /// (Internal) Handles any `nextAction` required to authenticate the PaymentIntent. + /// Call this method if you are using server-side confirmation. - seealso: https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom + /// - Parameters: + /// - paymentIntent: The PaymentIntent to handle next actions for. + /// - authenticationContext: The authentication context used to authenticate the payment. + /// - returnURL: An optional URL to redirect your customer back to after they authenticate or cancel in a webview. This should match the returnURL you specified during PaymentIntent confirmation. + /// - shouldSendStartAnalytic: Tracks whether STPPaymentHandler has already sent a start analytic for `handleNextAction` because this method was called from the other `handleNextAction` + /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the PaymentIntent status is not necessarily STPPaymentIntentStatusSucceeded (e.g. some bank payment methods take days before the PaymentIntent succeeds). + /// - Note: The PaymentIntent must have been fetched with an expanded paymentMethod object (see how `handleNextAction(forPayment:)` does this). + @_spi(STP) public func handleNextAction( + for paymentIntent: STPPaymentIntent, + with authenticationContext: STPAuthenticationContext, + returnURL: String?, + shouldSendAnalytic: Bool = true, + completion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) { + let paymentIntentID = paymentIntent.stripeId + let paymentMethod = paymentIntent.paymentMethod + if shouldSendAnalytic { + logHandleNextActionStarted(intentID: paymentIntentID, paymentMethod: paymentMethod) + } + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: STPPaymentHandlerActionPaymentIntentCompletionBlock = { [weak self] status, paymentIntent, error in + if shouldSendAnalytic { + self?.logHandleNextActionFinished(intentID: paymentIntentID, paymentMethod: paymentMethod, status: status, error: error) + } + completion(status, paymentIntent, error) + } + if Self.inProgress { + assertionFailure("`STPPaymentHandler.handleNextAction` was called while a previous call is still in progress.") + completion(.failed, nil, _error(for: .noConcurrentActionsErrorCode)) + return + } + if paymentIntent.paymentMethodId != nil { + assert(paymentIntent.paymentMethod != nil, "A PaymentIntent w/ attached paymentMethod must be retrieved w/ an expanded PaymentMethod") + } + Self.inProgress = true + + weak var weakSelf = self + // wrappedCompletion ensures we perform some final logic before calling the completion block. + let wrappedCompletion: STPPaymentHandlerActionPaymentIntentCompletionBlock = { + status, + paymentIntent, + error in + guard let strongSelf = weakSelf else { + return + } + // Reset our internal state + Self.inProgress = false + // Ensure the .succeeded case returns a PaymentIntent in the expected state. + if let paymentIntent = paymentIntent, + status == .succeeded + { + let successIntentState = + paymentIntent.status == .succeeded || paymentIntent.status == .requiresCapture || paymentIntent.status == .requiresConfirmation + || (paymentIntent.status == .processing && STPPaymentHandler._isProcessingIntentSuccess(for: paymentIntent.paymentMethod?.type ?? .unknown)) + || (paymentIntent.status == .requiresAction && strongSelf.isNextActionSuccessState(nextAction: paymentIntent.nextAction)) + + if error == nil && successIntentState { + completion(.succeeded, paymentIntent, nil) + } else { + let errorMessage = "STPPaymentHandler status is succeeded, but the PI is not in a success state or there was an error." + stpAssertionFailure(errorMessage) + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: [ + "error_message": errorMessage, + "payment_intent": paymentIntent.stripeId, + "payment_intent_status": STPPaymentIntentStatus.string(from: paymentIntent.status), + "error_details": error?.serializeForV1Analytics() ?? [:], + ]) + strongSelf.analyticsClient.log(analytic: errorAnalytic, apiClient: strongSelf.apiClient) + completion( + .failed, + paymentIntent, + error ?? strongSelf._error(for: .intentStatusErrorCode) + ) + } + return + } + completion(status, paymentIntent, error) + } + + if paymentIntent.status == .requiresConfirmation { + // The caller forgot to confirm the paymentIntent on the backend before calling this method + wrappedCompletion( + .failed, + paymentIntent, + _error( + for: .intentStatusErrorCode, + loggingSafeErrorMessage: "Confirm the PaymentIntent on the backend before calling handleNextActionForPayment:withAuthenticationContext:completion." + ) + ) + } else { + _handleNextAction( + forPayment: paymentIntent, + with: authenticationContext, + returnURL: returnURL + ) { status, completedPaymentIntent, completedError in + wrappedCompletion(status, completedPaymentIntent, completedError) + } + } + } + + /// Confirms the SetupIntent with the provided parameters and handles any `nextAction` required + /// to authenticate the SetupIntent. + /// - seealso: https://stripe.com/docs/payments/save-during-payment?platform=ios + /// - Parameters: + /// - setupIntentConfirmParams: The params used to confirm the SetupIntent. Note that this method overrides the value of `setupIntentConfirmParams.useStripeSDK` to `@YES`. + /// - authenticationContext: The authentication context used to authenticate the SetupIntent. + /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the SetupIntent status will always be STPSetupIntentStatusSucceeded. + @objc(confirmSetupIntent:withAuthenticationContext:completion:) + public func confirmSetupIntent( + _ setupIntentConfirmParams: STPSetupIntentConfirmParams, + with authenticationContext: STPAuthenticationContext, + completion: @escaping STPPaymentHandlerActionSetupIntentCompletionBlock + ) { + let setupIntentID = STPSetupIntent.id(fromClientSecret: setupIntentConfirmParams.clientSecret) + logConfirmSetupIntentStarted(setupIntentID: setupIntentID, confirmParams: setupIntentConfirmParams) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: STPPaymentHandlerActionSetupIntentCompletionBlock = { [weak self] status, setupIntent, error in + self?.logConfirmSetupIntentCompleted(setupIntentID: setupIntentID, confirmParams: setupIntentConfirmParams, status: status, error: error) + completion(status, setupIntent, error) + } + + if Self.inProgress { + assertionFailure("`STPPaymentHandler.confirmSetupIntent` was called while a previous call is still in progress.") + completion(.failed, nil, _error(for: .noConcurrentActionsErrorCode)) + return + } else if !STPSetupIntentConfirmParams.isClientSecretValid( + setupIntentConfirmParams.clientSecret + ) { + assertionFailure("`STPPaymentHandler.confirmSetupIntent` was called with an invalid client secret. See https://docs.stripe.com/api/payment_intents/object#setup_intent_object-client_secret") + completion(.failed, nil, _error(for: .invalidClientSecret)) + return + } + + Self.inProgress = true + weak var weakSelf = self + // wrappedCompletion ensures we perform some final logic before calling the completion block. + let wrappedCompletion: STPPaymentHandlerActionSetupIntentCompletionBlock = { + status, + setupIntent, + error in + guard let strongSelf = weakSelf else { + return + } + // Reset our internal state + Self.inProgress = false + + if status == .succeeded { + // Ensure the .succeeded case returns a SetupIntent in the expected state. + if let setupIntent = setupIntent, + error == nil, + setupIntent.status == .succeeded + || (setupIntent.status == .requiresAction + && self.isNextActionSuccessState(nextAction: setupIntent.nextAction)) + { + completion(.succeeded, setupIntent, nil) + } else { + let errorMessage = "STPPaymentHandler status is succeeded, but the SI is not in a success state or there was an error." + stpAssertionFailure(errorMessage) + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: [ + "error_message": errorMessage, + "setup_intent": setupIntent?.stripeID ?? "nil", + "setup_intent_status": setupIntent?.status.rawValue ?? "nil", + "error_details": error?.serializeForV1Analytics() ?? [:], + ]) + strongSelf.analyticsClient.log(analytic: errorAnalytic, apiClient: strongSelf.apiClient) + completion( + .failed, + setupIntent, + error ?? strongSelf._error(for: .intentStatusErrorCode) + ) + } + + } else { + completion(status, setupIntent, error) + } + } + + let confirmCompletionBlock: STPSetupIntentCompletionBlock = { setupIntent, error in + guard let strongSelf = weakSelf else { + return + } + + if let setupIntent = setupIntent, + error == nil + { + let action = STPPaymentHandlerSetupIntentActionParams( + apiClient: self.apiClient, + authenticationContext: authenticationContext, + threeDSCustomizationSettings: self.threeDSCustomizationSettings, + setupIntent: setupIntent, + returnURL: setupIntentConfirmParams.returnURL + ) { status, resultSetupIntent, resultError in + guard let strongSelf2 = weakSelf else { + return + } + strongSelf2.currentAction = nil + + wrappedCompletion(status, resultSetupIntent, resultError) + } + strongSelf.currentAction = action + let requiresAction = strongSelf._handleSetupIntentStatus(forAction: action) + if requiresAction { + strongSelf._handleAuthenticationForCurrentAction() + } + } else { + wrappedCompletion(.failed, setupIntent, error as NSError?) + } + } + var params = setupIntentConfirmParams + if !(params.useStripeSDK?.boolValue ?? false) { + params = setupIntentConfirmParams.copy() as! STPSetupIntentConfirmParams + params.useStripeSDK = NSNumber(value: true) + } + apiClient.confirmSetupIntent(with: params, expand: ["payment_method"], completion: confirmCompletionBlock) + } + + /// Handles any `nextAction` required to authenticate the SetupIntent. + /// Call this method if you are confirming the SetupIntent on your backend and get a status of requires_action. + /// - Parameters: + /// - setupIntentClientSecret: The client secret of the SetupIntent to handle next actions for. + /// - authenticationContext: The authentication context used to authenticate the SetupIntent. + /// - returnURL: An optional URL to redirect your customer back to after they authenticate or cancel in a webview. This should match the returnURL you specified during SetupIntent confirmation. + /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the SetupIntent status will always be STPSetupIntentStatusSucceeded. + @objc(handleNextActionForSetupIntent:withAuthenticationContext:returnURL:completion:) + public func handleNextAction( + forSetupIntent setupIntentClientSecret: String, + with authenticationContext: STPAuthenticationContext, + returnURL: String?, + completion: @escaping STPPaymentHandlerActionSetupIntentCompletionBlock + ) { + let setupIntentID = STPSetupIntent.id(fromClientSecret: setupIntentClientSecret) + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: STPPaymentHandlerActionSetupIntentCompletionBlock = { [weak self] status, setupIntent, error in + self?.logHandleNextActionFinished(intentID: setupIntentID, paymentMethod: setupIntent?.paymentMethod, status: status, error: error) + completion(status, setupIntent, error) + } + logHandleNextActionStarted(intentID: setupIntentID, paymentMethod: nil) + + if !STPSetupIntentConfirmParams.isClientSecretValid(setupIntentClientSecret) { + assertionFailure("`STPPaymentHandler.handleNextAction` was called with an invalid client secret. See https://docs.stripe.com/api/payment_intents/object#setup_intent_object-client_secret") + completion(.failed, nil, _error(for: .invalidClientSecret)) + return + } + + apiClient.retrieveSetupIntent(withClientSecret: setupIntentClientSecret, expand: ["payment_method"]) { [weak self] setupIntent, error in + guard let self else { + return + } + if let setupIntent, error == nil { + self.handleNextAction(for: setupIntent, with: authenticationContext, returnURL: returnURL, completion: completion) + } else { + completion(.failed, setupIntent, error as NSError?) + } + } + } + + /// Handles any `nextAction` required to authenticate the SetupIntent. + /// Call this method if you are confirming the SetupIntent on your backend and get a status of requires_action. + /// - Parameters: + /// - setupIntent: The SetupIntent to handle next actions for. + /// - authenticationContext: The authentication context used to authenticate the SetupIntent. + /// - returnURL: An optional URL to redirect your customer back to after they authenticate or cancel in a webview. This should match the returnURL you specified during SetupIntent confirmation. + /// - shouldSendStartAnalytic: Tracks whether STPPaymentHandler has already sent a start analytic for `handleNextAction` because this method was called from the other `handleNextAction` + /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the SetupIntent status will always be STPSetupIntentStatusSucceeded. + /// - Note: The SetupIntent must have been fetched with an expanded paymentMethod object (see how `handleNextAction(forPayment:)` does this). + @_spi(STP) public func handleNextAction( + for setupIntent: STPSetupIntent, + with authenticationContext: STPAuthenticationContext, + returnURL: String?, + shouldSendAnalytic: Bool = true, + completion: @escaping STPPaymentHandlerActionSetupIntentCompletionBlock + ) { + let setupIntentID = setupIntent.stripeID + let paymentMethod = setupIntent.paymentMethod + if shouldSendAnalytic { + logHandleNextActionStarted(intentID: setupIntentID, paymentMethod: paymentMethod) + } + // Overwrite completion to send an analytic before calling the caller-supplied completion + let completion: STPPaymentHandlerActionSetupIntentCompletionBlock = { [weak self] status, setupIntent, error in + if shouldSendAnalytic { + self?.logHandleNextActionFinished(intentID: setupIntentID, paymentMethod: paymentMethod, status: status, error: error) + } + completion(status, setupIntent, error) + } + if Self.inProgress { + assertionFailure("`STPPaymentHandler.confirmPayment` was called while a previous call is still in progress.") + completion(.failed, nil, _error(for: .noConcurrentActionsErrorCode)) + return + } + if setupIntent.paymentMethodID != nil { + assert(setupIntent.paymentMethod != nil, "A SetupIntent w/ attached paymentMethod must be retrieved w/ an expanded PaymentMethod") + } + + Self.inProgress = true + weak var weakSelf = self + // wrappedCompletion ensures we perform some final logic before calling the completion block. + let wrappedCompletion: STPPaymentHandlerActionSetupIntentCompletionBlock = { + status, + setupIntent, + error in + guard let strongSelf = weakSelf else { + return + } + // Reset our internal state + Self.inProgress = false + + if status == .succeeded { + // Ensure the .succeeded case returns a PaymentIntent in the expected state. + if let setupIntent = setupIntent, + error == nil, + setupIntent.status == .succeeded + { + completion(.succeeded, setupIntent, nil) + } else { + let errorMessage = "STPPaymentHandler status is succeeded, but the SI is not in a success state or there was an error." + stpAssertionFailure(errorMessage) + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: [ + "error_message": errorMessage, + "setup_intent": setupIntent?.stripeID ?? "nil", + "setup_intent_status": setupIntent?.status.rawValue ?? "nil", + "error_details": error?.serializeForV1Analytics() ?? [:], + ]) + strongSelf.analyticsClient.log(analytic: errorAnalytic, apiClient: strongSelf.apiClient) + completion( + .failed, + setupIntent, + error ?? strongSelf._error(for: .intentStatusErrorCode) + ) + } + + } else { + completion(status, setupIntent, error) + } + } + + if setupIntent.status == .requiresConfirmation { + // The caller forgot to confirm the setupIntent on the backend before calling this method + wrappedCompletion(.failed, setupIntent, _error(for: .intentStatusErrorCode, loggingSafeErrorMessage: "Confirm the SetupIntent on the backend before calling handleNextActionForSetupIntent:withAuthenticationContext:completion.") + ) + } else { + _handleNextAction( + for: setupIntent, + with: authenticationContext, + returnURL: returnURL + ) { status, completedSetupIntent, completedError in + wrappedCompletion(status, completedSetupIntent, completedError) + } + } + } + + // MARK: - Private Helpers + + /// Depending on the PaymentMethod Type, after handling next action and confirming, + /// we should either expect a success state on the PaymentIntent, or for certain asynchronous + /// PaymentMethods like SEPA Debit, processing is considered a completed PaymentIntent flow + /// because the funds can take up to 14 days to transfer from the customer's bank. + class func _isProcessingIntentSuccess(for type: STPPaymentMethodType) -> Bool { + switch type { + // Asynchronous payment methods whose intent.status is 'processing' after handling the next action + case .SEPADebit, + .bacsDebit, // Bacs Debit takes 2-3 business days + .AUBECSDebit, + .sofort, + .USBankAccount: + return true + + // Synchronous + case .alipay, + .card, + .UPI, + .iDEAL, + .FPX, + .cardPresent, + .giropay, + .EPS, + .payPal, + .przelewy24, + .bancontact, + .netBanking, + .OXXO, + .grabPay, + .afterpayClearpay, + .blik, + .weChatPay, + .boleto, + .link, + .klarna, + .affirm, + .cashApp, + .paynow, + .zip, + .revolutPay, + .mobilePay, + .amazonPay, + .alma, + .sunbit, + .billie, + .satispay, + .crypto, + .konbini, + .promptPay, + .swish, + .twint, + .multibanco: + return false + + case .unknown: + return false + + @unknown default: + return false + } + } + + func _handleNextAction( + forPayment paymentIntent: STPPaymentIntent, + with authenticationContext: STPAuthenticationContext, + returnURL returnURLString: String?, + completion: @escaping STPPaymentHandlerActionPaymentIntentCompletionBlock + ) { + guard paymentIntent.status != .requiresPaymentMethod else { + // The caller forgot to attach a paymentMethod. + completion( + .failed, + paymentIntent, + _error(for: .requiresPaymentMethodErrorCode) + ) + return + } + + weak var weakSelf = self + let action = STPPaymentHandlerPaymentIntentActionParams( + apiClient: apiClient, + authenticationContext: authenticationContext, + threeDSCustomizationSettings: threeDSCustomizationSettings, + paymentIntent: paymentIntent, + returnURL: returnURLString + ) { status, resultPaymentIntent, error in + guard let strongSelf = weakSelf else { + return + } + strongSelf.currentAction = nil + completion(status, resultPaymentIntent, error) + } + currentAction = action + let requiresAction = _handlePaymentIntentStatus(forAction: action) + if requiresAction { + _handleAuthenticationForCurrentAction() + } + } + + func _handleNextAction( + for setupIntent: STPSetupIntent, + with authenticationContext: STPAuthenticationContext, + returnURL returnURLString: String?, + completion: @escaping STPPaymentHandlerActionSetupIntentCompletionBlock + ) { + guard setupIntent.status != .requiresPaymentMethod else { + // The caller forgot to attach a paymentMethod. + completion( + .failed, + setupIntent, + _error(for: .requiresPaymentMethodErrorCode) + ) + return + } + + weak var weakSelf = self + let action = STPPaymentHandlerSetupIntentActionParams( + apiClient: apiClient, + authenticationContext: authenticationContext, + threeDSCustomizationSettings: threeDSCustomizationSettings, + setupIntent: setupIntent, + returnURL: returnURLString + ) { status, resultSetupIntent, resultError in + guard let strongSelf = weakSelf else { + return + } + strongSelf.currentAction = nil + completion(status, resultSetupIntent, resultError) + } + currentAction = action + let requiresAction = _handleSetupIntentStatus(forAction: action) + if requiresAction { + _handleAuthenticationForCurrentAction() + } + } + + /// Calls the current action's completion handler for the SetupIntent status, or returns YES if the status is ...RequiresAction. + func _handleSetupIntentStatus( + forAction action: STPPaymentHandlerSetupIntentActionParams + ) + -> Bool + { + let setupIntent = action.setupIntent + switch setupIntent.status { + case .unknown: + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error( + for: .unexpectedErrorCode, + loggingSafeErrorMessage: "Unknown SetupIntent status" + ) + ) + + case .requiresPaymentMethod: + // If the user forgot to attach a PaymentMethod, they get an error before this point. + // If confirmation fails (eg not authenticated, card declined) the SetupIntent transitions to this state. + if let lastSetupError = setupIntent.lastSetupError { + if lastSetupError.code == STPSetupIntentLastSetupError.CodeAuthenticationFailure { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .notAuthenticatedErrorCode) + ) + } else if lastSetupError.type == .card { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error( + for: .paymentErrorCode, + apiErrorCode: lastSetupError.code, + localizedDescription: lastSetupError.message + ) + ) + } else { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .paymentErrorCode, apiErrorCode: lastSetupError.code) + ) + } + } else { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .paymentErrorCode) + ) + } + + case .requiresConfirmation: + action.complete(with: STPPaymentHandlerActionStatus.succeeded, error: nil) + + case .requiresAction: + return true + + case .processing: + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .intentStatusErrorCode) + ) + + case .succeeded: + action.complete(with: STPPaymentHandlerActionStatus.succeeded, error: nil) + + case .canceled: + action.complete(with: STPPaymentHandlerActionStatus.canceled, error: nil) + } + return false + } + + /// Calls the current action's completion handler for the PaymentIntent status, or returns YES if the status is ...RequiresAction. + func _handlePaymentIntentStatus( + forAction action: STPPaymentHandlerPaymentIntentActionParams + ) + -> Bool + { + let paymentIntent = action.paymentIntent + switch paymentIntent.status { + case .unknown: + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error( + for: .unexpectedErrorCode, + loggingSafeErrorMessage: "Unknown PaymentIntent status" + ) + ) + + case .requiresPaymentMethod: + // If the user forgot to attach a PaymentMethod, they get an error before this point. + // If confirmation fails (eg not authenticated, card declined) the PaymentIntent transitions to this state. + if let lastPaymentError = paymentIntent.lastPaymentError { + if lastPaymentError.code + == STPPaymentIntentLastPaymentError.ErrorCodeAuthenticationFailure + { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .notAuthenticatedErrorCode) + ) + } else if lastPaymentError.type == .card { + + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error( + for: .paymentErrorCode, + apiErrorCode: lastPaymentError.code, + localizedDescription: lastPaymentError.message + ) + ) + } else { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .paymentErrorCode, apiErrorCode: lastPaymentError.code) + ) + } + } else { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .paymentErrorCode) + ) + } + + case .requiresConfirmation: + action.complete(with: STPPaymentHandlerActionStatus.succeeded, error: nil) + + case .requiresAction: + return true + + case .processing: + if let type = paymentIntent.paymentMethod?.type, + STPPaymentHandler._isProcessingIntentSuccess(for: type) + { + action.complete(with: STPPaymentHandlerActionStatus.succeeded, error: nil) + } else { + action.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error(for: .intentStatusErrorCode) + ) + } + + case .succeeded: + action.complete(with: STPPaymentHandlerActionStatus.succeeded, error: nil) + + case .requiresCapture: + action.complete(with: STPPaymentHandlerActionStatus.succeeded, error: nil) + + case .canceled: + action.complete(with: STPPaymentHandlerActionStatus.canceled, error: nil) + + case .requiresSource: + action.complete(with: .failed, error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "PaymentIntent status is requiresSource")) + case .requiresSourceAction: + action.complete(with: .failed, error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "PaymentIntent status is requiresSourceAction")) + } + return false + } + + func _handleAuthenticationForCurrentAction() { + guard let currentAction else { + stpAssertionFailure("Calling _handleAuthenticationForCurrentAction without a currentAction") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling _handleAuthenticationForCurrentAction without a currentAction"]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + guard let authenticationAction = currentAction.nextAction() else { + stpAssertionFailure("Calling _handleAuthenticationForCurrentAction without a next action!") + currentAction.complete( + with: .failed, + error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Calling _handleAuthenticationForCurrentAction without a next action!") + ) + return + } + + let failCurrentActionWithMissingNextActionDetails = { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error( + for: .unexpectedErrorCode, + loggingSafeErrorMessage: "Authentication action \(authenticationAction.type) is missing expected details." + ) + ) + } + + switch authenticationAction.type { + case .unknown: + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error( + for: .unsupportedAuthenticationErrorCode, + loggingSafeErrorMessage: "Unknown authentication action type" + ) + ) + case .redirectToURL: + if let redirectToURL = authenticationAction.redirectToURL { + let redirectURL = redirectToURL.followRedirects ? + // Pre-follow the redirect trampoline and pass the final URL to ASWebAuthenticationSession + // so that the consent dialog will show the correct domain (e.g. "klarna.com" instead of "stripe.com") + self.followRedirect(to: redirectToURL.url) : + redirectToURL.url + _handleRedirect(to: redirectURL, withReturn: redirectToURL.returnURL, useWebAuthSession: redirectToURL.useWebAuthSession) + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .alipayHandleRedirect: + if let alipayHandleRedirect = authenticationAction.alipayHandleRedirect { + _handleRedirect( + to: alipayHandleRedirect.nativeURL, + fallbackURL: alipayHandleRedirect.url, + return: alipayHandleRedirect.returnURL, + useWebAuthSession: false + ) + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .weChatPayRedirectToApp: + if let weChatPayRedirectToApp = authenticationAction.weChatPayRedirectToApp { + _handleRedirect( + to: weChatPayRedirectToApp.nativeURL, + fallbackURL: nil, + return: nil, + useWebAuthSession: false + ) + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .OXXODisplayDetails: + if let hostedVoucherURL = authenticationAction.oxxoDisplayDetails?.hostedVoucherURL { + self._handleRedirect(to: hostedVoucherURL, withReturn: nil, useWebAuthSession: false) + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .boletoDisplayDetails: + if let hostedVoucherURL = authenticationAction.boletoDisplayDetails?.hostedVoucherURL { + self._handleRedirect(to: hostedVoucherURL, withReturn: nil, useWebAuthSession: false) + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .multibancoDisplayDetails: + if let hostedVoucherURL = authenticationAction.multibancoDisplayDetails?.hostedVoucherURL { + self._handleRedirect(to: hostedVoucherURL, withReturn: nil, useWebAuthSession: false) + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .useStripeSDK: + if let useStripeSDK = authenticationAction.useStripeSDK { + switch useStripeSDK.type { + case .unknown: + currentAction.complete(with: STPPaymentHandlerActionStatus.failed, error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Unexpected useStripeSDK type")) + + case .threeDS2Fingerprint: + guard let threeDSService = currentAction.threeDS2Service else { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: _error( + for: .stripe3DS2ErrorCode, + loggingSafeErrorMessage: "Failed to initialize STDSThreeDS2Service." + ) + ) + return + } + var transaction: STDSTransaction? + var authRequestParams: STDSAuthenticationRequestParameters? + + STDSSwiftTryCatch.try( + { + transaction = threeDSService.createTransaction( + forDirectoryServer: useStripeSDK.directoryServerID ?? "", + serverKeyID: useStripeSDK.directoryServerKeyID, + certificateString: useStripeSDK.directoryServerCertificate ?? "", + rootCertificateStrings: useStripeSDK.rootCertificateStrings ?? [], + withProtocolVersion: "2.2.0" + ) + authRequestParams = transaction?.createAuthenticationRequestParameters() + }, + catch: { exception in + + self.analyticsClient + .log3DS2AuthenticationRequestParamsFailed( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + error: self._error( + for: .stripe3DS2ErrorCode, + loggingSafeErrorMessage: exception.description + ) + ) + + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error( + for: .stripe3DS2ErrorCode, + loggingSafeErrorMessage: exception.description + ) + ) + }, + finallyBlock: { + } + ) + + analyticsClient.log3DS2AuthenticateAttempt( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID + ) + + guard let authParams = authRequestParams, let transaction else { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error( + for: .stripe3DS2ErrorCode, + loggingSafeErrorMessage: transaction == nil ? "Missing transaction." : "Missing auth request params." + ) + ) + return + } + currentAction.threeDS2Transaction = transaction + currentAction.apiClient.authenticate3DS2( + authParams, + sourceIdentifier: useStripeSDK.threeDSSourceID ?? "", + returnURL: currentAction.returnURLString, + maxTimeout: currentAction.threeDSCustomizationSettings + .authenticationTimeout, + publishableKeyOverride: useStripeSDK.publishableKeyOverride + ) { (authenticateResponse, error) in + guard let authenticateResponse else { + let error = error ?? self._error(for: .stripe3DS2ErrorCode, loggingSafeErrorMessage: "Missing authenticate response") + currentAction.complete(with: .failed, error: error as NSError) + return + } + guard error == nil else { + currentAction.complete(with: .failed, error: error! as NSError) + return + } + if let aRes = authenticateResponse.authenticationResponse { + + if aRes.isChallengeRequired { + let challengeParameters = STDSChallengeParameters( + authenticationResponse: aRes + ) + + let doChallenge: STPVoidBlock = { + var presentationError: NSError? + + guard self._canPresent( + with: currentAction.authenticationContext, + error: &presentationError + ) else { + currentAction.complete( + with: .failed, + error: presentationError + ) + return + } + STDSSwiftTryCatch.try({ + let presentingViewController = currentAction.authenticationContext.authenticationPresentingViewController() + let timeout = TimeInterval(currentAction.threeDSCustomizationSettings.authenticationTimeout * 60) + if let paymentSheet = presentingViewController as? PaymentSheetAuthenticationContext { + transaction.doChallenge( + with: challengeParameters, + challengeStatusReceiver: self, + timeout: timeout + ) { threeDSChallengeViewController, completion in + paymentSheet.present( + threeDSChallengeViewController, + completion: completion + ) + } + } else { + transaction.doChallenge( + with: presentingViewController, + challengeParameters: challengeParameters, + challengeStatusReceiver: self, + timeout: timeout + ) + } + }, catch: { exception in + self.currentAction?.complete( + with: .failed, + error: self._error( + for: .stripe3DS2ErrorCode, + loggingSafeErrorMessage: exception.description + ) + ) + }, finallyBlock: {} + ) + } + + if currentAction.authenticationContext.responds( + to: #selector( + STPAuthenticationContext.prepare(forPresentation:)) + ) { + currentAction.authenticationContext.prepare?( + forPresentation: doChallenge + ) + } else { + doChallenge() + } + + } else { + // Challenge not required, finish the flow. + transaction.close() + currentAction.threeDS2Transaction = nil + self.analyticsClient.log3DS2FrictionlessFlow( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID + ) + + self._retrieveAndCheckIntentForCurrentAction() + } + + } else if let fallbackURL = authenticateResponse.fallbackURL { + self._handleRedirect( + to: fallbackURL, + withReturn: URL(string: currentAction.returnURLString ?? ""), useWebAuthSession: false + ) + } else { + currentAction.complete( + with: .failed, + error: self._error( + for: .unexpectedErrorCode, + loggingSafeErrorMessage: "3DS2 authenticate response missing both response and fallback URL." + ) + ) + } + } + + case .threeDS2Redirect: + guard let redirectURL = useStripeSDK.redirectURL else { + currentAction.complete(with: .failed, error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Next action type is threeDS2Redirect but missing redirect URL.")) + return + } + let returnURL: URL? + if let returnURLString = currentAction.returnURLString { + returnURL = URL(string: returnURLString) + } else { + returnURL = nil + } + _handleRedirect(to: redirectURL, withReturn: returnURL, useWebAuthSession: false) + } + } else { + failCurrentActionWithMissingNextActionDetails() + } + + case .BLIKAuthorize: + // The customer must authorize the transaction in their banking app within 1 minute + if let presentingVC = currentAction.authenticationContext as? PaymentSheetAuthenticationContext { + guard let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams else { + currentAction.complete(with: .failed, error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Handling BLIKAuthorize next action with SetupIntent is not supported")) + return + } + // If we are using PaymentSheet, PollingViewController will poll Stripe to determine success and complete the currentAction + presentingVC.presentPollingVCForAction(action: currentAction, type: .blik, safariViewController: nil) + } else { + // The merchant integration should spin and poll their backend or Stripe to determine success + currentAction.complete(with: .succeeded, error: nil) + } + case .verifyWithMicrodeposits: + // The customer must authorize after the microdeposits appear in their bank account + // which may take 1-2 business days + currentAction.complete(with: .succeeded, error: nil) + case .upiAwaitNotification: + // The customer must authorize the transaction in their banking app within 5 minutes + if let presentingVC = currentAction.authenticationContext as? PaymentSheetAuthenticationContext { + guard let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams else { + currentAction.complete(with: .failed, error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Handling upiAwaitNotification next action with SetupIntent is not supported")) + return + } + // If we are using PaymentSheet, PollingViewController will poll Stripe to determine success and complete the currentAction + presentingVC.presentPollingVCForAction(action: currentAction, type: .UPI, safariViewController: nil) + } else { + // The merchant integration should spin and poll their backend or Stripe to determine success + currentAction.complete(with: .succeeded, error: nil) + } + case .cashAppRedirectToApp: + guard let returnURL = URL(string: currentAction.returnURLString ?? "") else { + assertionFailure(missingReturnURLErrorMessage) + currentAction.complete(with: .failed, error: _error(for: .missingReturnURL)) + return + } + + if let mobileAuthURL = authenticationAction.cashAppRedirectToApp?.mobileAuthURL { + _handleRedirect(to: mobileAuthURL, fallbackURL: mobileAuthURL, return: returnURL, useWebAuthSession: false) + } else { + failCurrentActionWithMissingNextActionDetails() + } + case .payNowDisplayQrCode: + guard let returnURL = URL(string: currentAction.returnURLString ?? "") else { + assertionFailure(missingReturnURLErrorMessage) + currentAction.complete(with: .failed, error: _error(for: .missingReturnURL)) + return + } + guard let hostedInstructionsURL = authenticationAction.payNowDisplayQrCode?.hostedInstructionsURL else { + failCurrentActionWithMissingNextActionDetails() + return + } + guard let presentingVC = currentAction.authenticationContext as? PaymentSheetAuthenticationContext else { + assertionFailure("PayNow is not supported outside of PaymentSheet.") + currentAction.complete(with: .failed, error: _error(for: .unsupportedAuthenticationErrorCode, loggingSafeErrorMessage: "PayNow is not supported outside of PaymentSheet.")) + return + } + guard let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams else { + currentAction.complete(with: .failed, error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Handling payNowDisplayQrCode next action with SetupIntent is not supported")) + return + } + _handleRedirect(to: hostedInstructionsURL, fallbackURL: hostedInstructionsURL, return: returnURL, useWebAuthSession: false) { safariViewController in + // Present the polling view controller behind the web view so we can start polling right away + presentingVC.presentPollingVCForAction(action: currentAction, type: .paynow, safariViewController: safariViewController) + } + case .konbiniDisplayDetails: + if let hostedVoucherURL = authenticationAction.konbiniDisplayDetails?.hostedVoucherURL { + self._handleRedirect(to: hostedVoucherURL, withReturn: nil, useWebAuthSession: false) + } else { + failCurrentActionWithMissingNextActionDetails() + } + case .promptpayDisplayQrCode: + guard let returnURL = URL(string: currentAction.returnURLString ?? "") else { + assertionFailure(missingReturnURLErrorMessage) + currentAction.complete(with: .failed, error: _error(for: .missingReturnURL)) + return + } + guard let hostedInstructionsURL = authenticationAction.promptPayDisplayQrCode?.hostedInstructionsURL else { + failCurrentActionWithMissingNextActionDetails() + return + } + guard let presentingVC = currentAction.authenticationContext as? PaymentSheetAuthenticationContext else { + assertionFailure("PromptPay is not supported outside of PaymentSheet.") + currentAction.complete(with: .failed, error: _error(for: .unsupportedAuthenticationErrorCode, loggingSafeErrorMessage: "PromptPay is not supported outside of PaymentSheet.")) + return + } + guard let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams else { + currentAction.complete(with: .failed, error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Handling promptpayDisplayQrCode next action with SetupIntent is not supported")) + return + } + + _handleRedirect(to: hostedInstructionsURL, fallbackURL: hostedInstructionsURL, return: returnURL, useWebAuthSession: false) { safariViewController in + // Present the polling view controller behind the web view so we can start polling right away + presentingVC.presentPollingVCForAction(action: currentAction, type: .promptPay, safariViewController: safariViewController) + } + case .swishHandleRedirect: + guard let returnURL = URL(string: currentAction.returnURLString ?? "") else { + assertionFailure(missingReturnURLErrorMessage) + currentAction.complete(with: .failed, error: _error(for: .missingReturnURL)) + return + } + guard let mobileAuthURL = authenticationAction.swishHandleRedirect?.mobileAuthURL else { + failCurrentActionWithMissingNextActionDetails() + return + } + + _handleRedirect(to: mobileAuthURL, withReturn: returnURL, useWebAuthSession: false) + } + } + + // A URLSessionTaskDelegate that can not be redirected by HTTP redirect codes. It is very focused on its task, you see. + fileprivate class UnredirectableSessionDelegate: NSObject, URLSessionTaskDelegate { + public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { + // Don't get redirected, just call the completion handler + completionHandler(nil) + } + } + + // Follow the first redirect for a url, but not any subsequent redirects + @_spi(STP) public func followRedirect(to url: URL) -> URL { + let urlSession = URLSession(configuration: StripeAPIConfiguration.sharedUrlSessionConfiguration, delegate: UnredirectableSessionDelegate(), delegateQueue: nil) + let urlRequest = URLRequest(url: url) + let blockingDataTaskSemaphore = DispatchSemaphore(value: 0) + + var resultingUrl = url + let task = urlSession.dataTask(with: urlRequest) { _, response, error in + defer { + blockingDataTaskSemaphore.signal() + } + + guard error == nil, + let httpResponse = response as? HTTPURLResponse, + (200...308).contains(httpResponse.statusCode), + let responseURLString = httpResponse.allHeaderFields["Location"] as? String, + let responseURL = URL(string: responseURLString) + else { + return + } + resultingUrl = responseURL + } + task.resume() + blockingDataTaskSemaphore.wait() + return resultingUrl + } + + func _retryAfterDelay(retryCount: Int, delayTime: TimeInterval = 3, block: @escaping STPVoidBlock) { + DispatchQueue.main.asyncAfter(deadline: .now() + delayTime) { + block() + } + } + + func _retrieveAndCheckIntentForCurrentAction(currentAction: STPPaymentHandlerActionParams? = nil, retryCount: Int = maxChallengeRetries) { + // Alipay requires us to hit an endpoint before retrieving the PI, to ensure the status is up to date. + let pingMarlinIfNecessary: ((STPPaymentHandlerPaymentIntentActionParams, @escaping STPVoidBlock) -> Void) = { + currentAction, + completionBlock in + if let paymentMethod = currentAction.paymentIntent.paymentMethod, + paymentMethod.type == .alipay, + let alipayHandleRedirect = currentAction.nextAction()?.alipayHandleRedirect, + let alipayReturnURL = alipayHandleRedirect.marlinReturnURL + { + + // Make a request to the return URL + let request: URLRequest = URLRequest(url: alipayReturnURL) + let task: URLSessionDataTask = URLSession.shared.dataTask( + with: request, + completionHandler: { _, _, _ in + completionBlock() + } + ) + task.resume() + } else { + completionBlock() + } + } + guard let currentAction = currentAction ?? self.currentAction else { + stpAssertionFailure("Calling _retrieveAndCheckIntentForCurrentAction without a currentAction") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling _retrieveAndCheckIntentForCurrentAction without a currentAction"]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + if let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams { + pingMarlinIfNecessary( + currentAction, + { + self.retrieveOrRefreshPaymentIntent( + currentAction: currentAction + ) { [self] paymentIntent, error in + guard let paymentIntent, error == nil else { + let error = error ?? self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Missing PaymentIntent.") + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: error as NSError + ) + return + } + currentAction.paymentIntent = paymentIntent + // If the transaction is still unexpectedly processing, refresh the PaymentIntent + // This could happen if, for example, a payment is approved in an SFSafariViewController, the user closes the sheet, and the approval races with this fetch. + if + let paymentMethod = paymentIntent.paymentMethod, + !STPPaymentHandler._isProcessingIntentSuccess(for: paymentMethod.type), + paymentIntent.status == .processing && retryCount > 0 + { + self._retryAfterDelay(retryCount: retryCount) { + self._retrieveAndCheckIntentForCurrentAction( + retryCount: retryCount - 1 + ) + } + } else { + let requiresAction: Bool = self._handlePaymentIntentStatus( + forAction: currentAction + ) + if requiresAction { + guard let paymentMethod = paymentIntent.paymentMethod else { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "PaymentIntent requires action but missing PaymentMethod.") + ) + return + } + // If the status is still RequiresAction, the user exited from the redirect before the + // payment intent was updated. Consider it a cancel, unless it's a valid terminal next action + if self.isNextActionSuccessState( + nextAction: paymentIntent.nextAction + ) { + currentAction.complete(with: .succeeded, error: nil) + } else { + // If this is a web-based 3DS2 transaction that is still in requires_action, we may just need to refresh the PI a few more times. + // Also retry a few times for app redirects, the redirect flow is fast and sometimes the intent doesn't update quick enough + let shouldRetryForCard = paymentMethod.type == .card && paymentIntent.nextAction?.type == .useStripeSDK + if retryCount > 0, paymentMethod.type != .card || shouldRetryForCard, let pollingRequirement = paymentMethod.type.pollingRequirement { + self._retryAfterDelay(retryCount: retryCount, delayTime: pollingRequirement.timeBetweenPollingAttempts) { + self._retrieveAndCheckIntentForCurrentAction( + retryCount: retryCount - 1 + ) + } + } else if paymentMethod.type != .paynow && paymentMethod.type != .promptPay { + // For PayNow, we don't want to mark as canceled when the web view dismisses + // Instead we rely on the presented PollingViewController to complete the currentAction + self._markChallengeCanceled(currentAction: currentAction) { _, _ in + // We don't forward cancelation errors + currentAction.complete(with: .canceled, error: nil) + } + } + } + } + } + } + } + ) + } else if let currentAction = currentAction as? STPPaymentHandlerSetupIntentActionParams { + retrieveOrRefreshSetupIntent( + currentAction: currentAction + ) { setupIntent, error in + guard let setupIntent, error == nil else { + let error = error ?? self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Missing SetupIntent.") + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: error as NSError + ) + return + } + currentAction.setupIntent = setupIntent + if let type = setupIntent.paymentMethod?.type, + !STPPaymentHandler._isProcessingIntentSuccess(for: type), + setupIntent.status == .processing && retryCount > 0 + { + self._retryAfterDelay(retryCount: retryCount) { + self._retrieveAndCheckIntentForCurrentAction(retryCount: retryCount - 1) + } + } else { + let requiresAction: Bool = self._handleSetupIntentStatus( + forAction: currentAction + ) + + if requiresAction { + guard let paymentMethod = setupIntent.paymentMethod else { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "SetupIntent requires action but missing PaymentMethod.") + ) + return + } + // If the status is still RequiresAction, the user exited from the redirect before the + // payment intent was updated. Consider it a cancel, unless it's a valid terminal next action + if self.isNextActionSuccessState( + nextAction: setupIntent.nextAction + ) { + currentAction.complete(with: .succeeded, error: nil) + } else { + // If this is a web-based 3DS2 transaction that is still in requires_action, we may just need to refresh the SI a few more times. + // Also retry a few times for Cash App, the redirect flow is fast and sometimes the intent doesn't update quick enough + let shouldRetryForCard = paymentMethod.type == .card && setupIntent.nextAction?.type == .useStripeSDK + if retryCount > 0, paymentMethod.type != .card || shouldRetryForCard, let pollingRequirement = paymentMethod.type.pollingRequirement { + self._retryAfterDelay(retryCount: retryCount, delayTime: pollingRequirement.timeBetweenPollingAttempts) { + self._retrieveAndCheckIntentForCurrentAction( + retryCount: retryCount - 1 + ) + } + } else { + // If the status is still RequiresAction, the user exited from the redirect before the + // setup intent was updated. Consider it a cancel + self._markChallengeCanceled(currentAction: currentAction) { _, _ in + // We don't forward cancelation errors + currentAction.complete(with: .canceled, error: nil) + } + } + } + } + } + } + } else { + // TODO: Make currentAction an enum, stop optionally casting it + stpAssert(false, "currentAction is an unknown type or nil intent.") + currentAction.complete( + with: .failed, + error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "currentAction is an unknown type or nil intent.") + ) + } + } + + @objc func _handleWillForegroundNotification() { + NotificationCenter.default.removeObserver( + self, + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + STPURLCallbackHandler.shared().unregisterListener(self) + _retrieveAndCheckIntentForCurrentAction() + } + + @_spi(STP) public func _handleRedirect(to url: URL, withReturn returnURL: URL?, useWebAuthSession: Bool) { + _handleRedirect(to: url, fallbackURL: url, return: returnURL, useWebAuthSession: useWebAuthSession) + } + + @_spi(STP) public func _handleRedirectToExternalBrowser(to url: URL, withReturn returnURL: URL?) { + if let _redirectShim { + _redirectShim(url, returnURL, false) + } + guard let currentAction else { + stpAssert(false, "Calling _handleRedirect without a currentAction") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling _handleRedirect without a currentAction"]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + if let returnURL { + STPURLCallbackHandler.shared().register(self, for: returnURL) + } + analyticsClient.logURLRedirectNextAction( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + usesWebAuthSession: false + ) + + // Setting universalLinksOnly to false will allow iOS to open https:// urls in an external browser, hopefully Safari. + // The links are expected to be either universal links or Stripe owned URLs. + // In the case that it is a stripe owned URL, the URL is expected to redirect our financial partners, at which point Safari can + // redirect to a native app if the app has been installed. If Safari is not the default browser, then users not be + // automatically navigated to the native app. + let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [ + UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: false, + ] + UIApplication.shared.open( + url, + options: options, + completionHandler: { _ in + NotificationCenter.default.addObserver( + self, + selector: #selector(self._handleWillForegroundNotification), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + } + ) + } + + /// Handles redirection to URLs using a native URL or a fallback URL and updates the current action. + /// Redirects to an app if possible, if that fails opens the url in a web view + /// - Parameters: + /// - nativeURL: A URL to be opened natively. + /// - fallbackURL: A secondary URL to be attempted if the native URL is not available. + /// - returnURL: The URL to be registered with the `STPURLCallbackHandler`. + /// - useWebAuthSession: Use ASWebAuthenticationSession instead of SFSafariViewController. + /// - completion: A completion block invoked after the URL redirection is handled. The SFSafariViewController used is provided as an argument, if it was used for the redirect. + func _handleRedirect(to nativeURL: URL?, fallbackURL: URL?, return returnURL: URL?, useWebAuthSession: Bool, completion: ((SFSafariViewController?) -> Void)? = nil) { + if let _redirectShim, let url = nativeURL ?? fallbackURL { + _redirectShim(url, returnURL, true) + } + + // During testing, the completion block is not called since the `UIApplication.open` completion block is never invoked. + // As a workaround we invoke the completion in a defer block if the _redirectShim is not nil to simulate presenting a web view + defer { + if _redirectShim != nil { + completion?(nil) + } + } + + var url = nativeURL + guard let currentAction else { + stpAssertionFailure("Calling _handleRedirect without a currentAction") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling _handleRedirect without a currentAction"]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + if let returnURL { + STPURLCallbackHandler.shared().register(self, for: returnURL) + } + + analyticsClient.logURLRedirectNextAction( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + usesWebAuthSession: useWebAuthSession + ) + + // Open the link in SafariVC + let presentSFViewControllerBlock: (() -> Void) = { + let context = currentAction.authenticationContext + + let presentingViewController = context.authenticationPresentingViewController() + + let doChallenge: STPVoidBlock = { + var presentationError: NSError? + guard self._canPresent(with: context, error: &presentationError) else { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: presentationError + ) + return + } + + if let fallbackURL, + ["http", "https"].contains(fallbackURL.scheme) + { + if useWebAuthSession { + if self._redirectShim != nil { + // No-op if the redirect shim is active, as we don't want to open the consent dialog. We'll call the completion block automatically. + return + } + // Note that ASWebAuthenticationSession will also close based on the `redirectURL` defined in the app's Info.plist if called within the ASWAS, + // not only via this callbackURLScheme. + let asWebAuthenticationSession = ASWebAuthenticationSession(url: fallbackURL, callbackURLScheme: "stripesdk", completionHandler: { _, _ in + if context.responds( + to: #selector(STPAuthenticationContext.authenticationContextWillDismiss(_:)) + ) { + // This isn't great, but UIViewController is non-nil in the protocol. Maybe it's better to still call it, even if the VC isn't useful? + context.authenticationContextWillDismiss?(UIViewController()) + } + STPURLCallbackHandler.shared().unregisterListener(self) + self.analyticsClient.logURLRedirectNextActionCompleted( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + usesWebAuthSession: true + ) + self._retrieveAndCheckIntentForCurrentAction() + self.asWebAuthenticationSession = nil + }) + asWebAuthenticationSession.prefersEphemeralWebBrowserSession = false + asWebAuthenticationSession.presentationContextProvider = currentAction + self.asWebAuthenticationSession = asWebAuthenticationSession + if context.responds(to: #selector(STPAuthenticationContext.prepare(forPresentation:))) { + context.prepare?(forPresentation: { + asWebAuthenticationSession.start() + }) + } else { + asWebAuthenticationSession.start() + } + } else { + let safariViewController = SFSafariViewController(url: fallbackURL) + safariViewController.modalPresentationStyle = .overFullScreen +#if !canImport(CompositorServices) + safariViewController.dismissButtonStyle = .close + safariViewController.delegate = self +#endif + if context.responds( + to: #selector(STPAuthenticationContext.configureSafariViewController(_:)) + ) { + context.configureSafariViewController?(safariViewController) + } + self.safariViewController = safariViewController + presentingViewController.present(safariViewController, animated: true, completion: { + completion?(safariViewController) + }) + } + } else { + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error(for: .requiredAppNotAvailable) + ) + } + } + if context.responds(to: #selector(STPAuthenticationContext.prepare(forPresentation:))) { + context.prepare?(forPresentation: doChallenge) + } else { + doChallenge() + } + } + + // Redirect to an app + // We don't want universal links to open up Safari, but we do want to allow custom URL schemes + var options: [UIApplication.OpenExternalURLOptionsKey: Any] = [:] + #if !targetEnvironment(macCatalyst) + if let scheme = url?.scheme, scheme == "http" || scheme == "https" { + options[UIApplication.OpenExternalURLOptionsKey.universalLinksOnly] = true + } + #endif + + // If we're simulating app-to-app redirects, we always want to open the URL in Safari instead of an in-app web view. + // We'll tell Safari to open all URLs, not just universal links. + // If we don't have a nativeURL, we should open the fallbackURL in Safari instead. + if simulateAppToAppRedirect { + options[UIApplication.OpenExternalURLOptionsKey.universalLinksOnly] = false + url = nativeURL ?? fallbackURL + } + + // We don't check canOpenURL before opening the URL because that requires users to pre-register the custom URL schemes + if let url = url { + UIApplication.shared.open( + url, + options: options, + completionHandler: { success in + if !success { + // no app installed, launch safari view controller + presentSFViewControllerBlock() + } else { + completion?(nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(self._handleWillForegroundNotification), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + } + } + ) + } else { + presentSFViewControllerBlock() + } + } + + /// Checks if authenticationContext.authenticationPresentingViewController can be presented on. + /// @note Call this method after `prepareAuthenticationContextForPresentation:` + func _canPresent( + with authenticationContext: STPAuthenticationContext, + error: inout NSError? + ) + -> Bool + { + // Always allow in tests: + if NSClassFromString("XCTest") != nil { + if checkCanPresentInTest { + checkCanPresentInTest.toggle() + } else { + return true + } + } + + let presentingViewController = + authenticationContext.authenticationPresentingViewController() + var canPresent = true + var loggingSafeErrorMessage: String? + + // Is it in the window hierarchy? + if presentingViewController.viewIfLoaded?.window == nil { + canPresent = false + loggingSafeErrorMessage = + "authenticationPresentingViewController is not in the window hierarchy. You should probably return the top-most view controller instead." + } + + // Is it already presenting something? + if presentingViewController.presentedViewController != nil { + canPresent = false + loggingSafeErrorMessage = + "authenticationPresentingViewController is already presenting. You should probably dismiss the presented view controller in `prepareAuthenticationContextForPresentation`." + } + + if !canPresent { + error = _error( + for: .requiresAuthenticationContextErrorCode, + loggingSafeErrorMessage: loggingSafeErrorMessage + ) + } + return canPresent + } + + /// Check if the intent.nextAction is expected state after a successful on-session transaction + /// e.g. for voucher-based payment methods like OXXO that require out-of-band payment + func isNextActionSuccessState(nextAction: STPIntentAction?) -> Bool { + if let nextAction = nextAction { + switch nextAction.type { + case .unknown, + .redirectToURL, + .useStripeSDK, + .alipayHandleRedirect, + .weChatPayRedirectToApp, + .cashAppRedirectToApp, + .payNowDisplayQrCode, + .promptpayDisplayQrCode, + .swishHandleRedirect: + return false + case .OXXODisplayDetails, + .boletoDisplayDetails, + .konbiniDisplayDetails, + .verifyWithMicrodeposits, + .BLIKAuthorize, + .upiAwaitNotification, + .multibancoDisplayDetails: + return true + } + } + return false + } + + // This is only called after web-redirects because native 3DS2 cancels go directly + // to the ACS + func _markChallengeCanceled(currentAction: STPPaymentHandlerActionParams, completion: @escaping STPBooleanSuccessBlock) { + guard let nextAction = currentAction.nextAction() else { + stpAssert(false, "Calling _markChallengeCanceled without nextAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling _markChallengeCanceled without nextAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + var threeDSSourceID: String? + switch nextAction.type { + case .redirectToURL: + threeDSSourceID = nextAction.redirectToURL?.threeDSSourceID + case .useStripeSDK: + threeDSSourceID = nextAction.useStripeSDK?.threeDSSourceID + case .OXXODisplayDetails, .alipayHandleRedirect, .unknown, .BLIKAuthorize, + .weChatPayRedirectToApp, .boletoDisplayDetails, .verifyWithMicrodeposits, + .upiAwaitNotification, .cashAppRedirectToApp, .konbiniDisplayDetails, .payNowDisplayQrCode, + .promptpayDisplayQrCode, .swishHandleRedirect, .multibancoDisplayDetails: + break + } + + guard let cancelSourceID = threeDSSourceID else { + // If there's no threeDSSourceID, there's nothing for us to cancel + completion(true, nil) + return + } + + if let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams { + guard + currentAction.paymentIntent.paymentMethod?.card != nil || currentAction.paymentIntent.paymentMethod?.link != nil + else { + // Only cancel 3DS auth on payment method types that support 3DS. + completion(true, nil) + return + } + + analyticsClient.log3DS2RedirectUserCanceled( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID + ) + + let intentID = nextAction.useStripeSDK?.threeDS2IntentOverride ?? currentAction.paymentIntent.stripeId + + currentAction.apiClient.cancel3DSAuthentication( + forPaymentIntent: intentID, + withSource: cancelSourceID, + publishableKeyOverride: nextAction.useStripeSDK?.publishableKeyOverride + ) { paymentIntent, error in + if let paymentIntent { + currentAction.paymentIntent = paymentIntent + } + completion(paymentIntent != nil, error) + } + } else if let currentAction = currentAction as? STPPaymentHandlerSetupIntentActionParams { + let setupIntent = currentAction.setupIntent + guard setupIntent.paymentMethod?.card != nil || setupIntent.paymentMethod?.link != nil + else { + // Only cancel 3DS auth on payment method types that support 3DS. + completion(true, nil) + return + } + + analyticsClient.log3DS2RedirectUserCanceled( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID + ) + + let intentID = nextAction.useStripeSDK?.threeDS2IntentOverride ?? setupIntent.stripeID + + currentAction.apiClient.cancel3DSAuthentication( + forSetupIntent: intentID, + withSource: cancelSourceID, + publishableKeyOverride: nextAction.useStripeSDK?.publishableKeyOverride + ) { retrievedSetupIntent, error in + if let retrievedSetupIntent { + currentAction.setupIntent = retrievedSetupIntent + } + completion(retrievedSetupIntent != nil, error) + } + } else { + // TODO: Make currentAction an enum, stop optionally casting it + stpAssert(false, "currentAction is an unknown type or nil intent.") + currentAction.complete( + with: .failed, + error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "currentAction is an unknown type or nil intent.") + ) + } + } + + static let maxChallengeRetries = 5 + func _markChallengeCompleted( + withCompletion completion: @escaping STPBooleanSuccessBlock, + retryCount: Int = maxChallengeRetries + ) { + guard let currentAction, + let useStripeSDK = currentAction.nextAction()?.useStripeSDK, + let threeDSSourceID = useStripeSDK.threeDSSourceID + else { + let errorMessage: String = { + if currentAction == nil { + return "Attempted to mark challenge completed, but currentAction is nil" + } else if currentAction?.nextAction()?.useStripeSDK == nil { + return "Attempted to mark challenge completed, but useStripeSDK is nil" + } else { + return "Attempted to mark challenge completed, but threeDSSourceID is nil" + } + }() + stpAssertionFailure(errorMessage) + completion(false, self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: errorMessage)) + return + } + + func retrieveIntent(action: STPPaymentHandlerActionParams, completion: @escaping STPBooleanSuccessBlock) { + if let paymentIntentAction = action as? STPPaymentHandlerPaymentIntentActionParams { + currentAction.apiClient.retrievePaymentIntent( + withClientSecret: paymentIntentAction.paymentIntent.clientSecret, + expand: ["payment_method"] + ) { paymentIntent, retrieveError in + if let paymentIntent { + paymentIntentAction.paymentIntent = paymentIntent + } + completion(paymentIntent != nil, retrieveError) + } + } else if let setupIntentAction = action as? STPPaymentHandlerSetupIntentActionParams { + currentAction.apiClient.retrieveSetupIntent( + withClientSecret: setupIntentAction.setupIntent.clientSecret, + expand: ["payment_method"] + ) { retrievedSetupIntent, retrieveError in + if let retrievedSetupIntent { + setupIntentAction.setupIntent = retrievedSetupIntent + } + completion(retrievedSetupIntent != nil, retrieveError) + } + } else { + // TODO: Make currentAction an enum, stop optionally casting it + stpAssert(false, "currentAction is an unknown type or nil intent.") + currentAction.complete( + with: .failed, + error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "currentAction is an unknown type or nil intent.") + ) + } + } + + currentAction.apiClient.complete3DS2Authentication( + forSource: threeDSSourceID, + publishableKeyOverride: useStripeSDK.publishableKeyOverride + ) { success, error in + if success { + retrieveIntent(action: currentAction, completion: completion) + } else { + // This isn't guaranteed to succeed if the ACS isn't ready yet. + // Try it a few more times if it fails with a 400. (RUN_MOBILESDK-126) + if retryCount > 0 + && (error as NSError?)?.code == STPErrorCode.invalidRequestError.rawValue + { + self._retryAfterDelay( + retryCount: retryCount, + block: { + self._markChallengeCompleted( + withCompletion: completion, + retryCount: retryCount - 1 + ) + } + ) + } else { + // Completing the 3DS2 action failed, try to retrieve the intent anyways: + retrieveIntent(action: currentAction, completion: completion) + } + } + } + } + + func retrieveOrRefreshPaymentIntent(currentAction: STPPaymentHandlerPaymentIntentActionParams, + completion: @escaping STPPaymentIntentCompletionBlock) { + let paymentMethodType = currentAction.paymentIntent.paymentMethod?.type ?? .unknown + + if paymentMethodType.supportsRefreshing { + currentAction.apiClient.refreshPaymentIntent(withClientSecret: currentAction.paymentIntent.clientSecret, + completion: completion) + } else { + currentAction.apiClient.retrievePaymentIntent(withClientSecret: currentAction.paymentIntent.clientSecret, + expand: ["payment_method"], + completion: completion) + } + } + + func retrieveOrRefreshSetupIntent(currentAction: STPPaymentHandlerSetupIntentActionParams, + completion: @escaping STPSetupIntentCompletionBlock) { + let paymentMethodType = currentAction.setupIntent.paymentMethod?.type ?? .unknown + + if paymentMethodType.supportsRefreshing { + currentAction.apiClient.refreshSetupIntent(withClientSecret: currentAction.setupIntent.clientSecret, + completion: completion) + } else { + currentAction.apiClient.retrieveSetupIntent(withClientSecret: currentAction.setupIntent.clientSecret, + expand: ["payment_method"], + completion: completion) + } + } + + // MARK: - Errors + /// - Parameter loggingSafeErrorMessage: Error details that are safe to log i.e. don't contain PII/PDE or secrets. + @_spi(STP) public func _error( + for errorCode: STPPaymentHandlerErrorCode, + apiErrorCode: String? = nil, + loggingSafeErrorMessage: String? = nil, + localizedDescription: String? = nil + ) -> NSError { + var userInfo = [String: String]() + userInfo[STPError.errorMessageKey] = loggingSafeErrorMessage + userInfo[NSLocalizedDescriptionKey] = localizedDescription + switch errorCode { + // 3DS(2) flow expected user errors + case .notAuthenticatedErrorCode: + userInfo[NSLocalizedDescriptionKey] = STPLocalizedString( + "We are unable to authenticate your payment method. Please choose a different payment method and try again.", + "Error when 3DS2 authentication failed (e.g. customer entered the wrong code)" + ) + + case .timedOutErrorCode: + userInfo[NSLocalizedDescriptionKey] = STPLocalizedString( + "Timed out authenticating your payment method -- try again", + "Error when 3DS2 authentication timed out." + ) + + // PaymentIntent has an unexpected status + case .intentStatusErrorCode: + // The PI's status is processing or unknown + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] ?? "The PaymentIntent status cannot be handled." + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + case .unsupportedAuthenticationErrorCode: + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + case .requiredAppNotAvailable: + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] + ?? "This PaymentIntent action requires an app, but the app is not installed or the request to open the app was denied." + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + // Programming errors + case .requiresPaymentMethodErrorCode: + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] + ?? "The PaymentIntent requires a PaymentMethod or Source to be attached before using STPPaymentHandler." + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + case .noConcurrentActionsErrorCode: + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] + ?? "The current action is not yet completed. STPPaymentHandler does not support concurrent calls to its API." + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + case .requiresAuthenticationContextErrorCode: + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + case .missingReturnURL: + userInfo[STPError.errorMessageKey] = missingReturnURLErrorMessage + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + // Exceptions thrown from the Stripe3DS2 SDK. Other errors are reported via STPChallengeStatusReceiver. + case .stripe3DS2ErrorCode: + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] ?? "There was an error in the Stripe3DS2 SDK." + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + // Confirmation errors (eg card was declined) + case .paymentErrorCode: + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] + ?? "There was an error confirming the Intent. Inspect the `paymentIntent.lastPaymentError` or `setupIntent.lastSetupError` property." + + userInfo[NSLocalizedDescriptionKey] = + apiErrorCode.flatMap({ NSError.Utils.localizedMessage(fromAPIErrorCode: $0) }) + ?? userInfo[NSLocalizedDescriptionKey] + ?? NSError.stp_unexpectedErrorMessage() + + // Client secret format error + case .invalidClientSecret: + userInfo[STPError.errorMessageKey] = + userInfo[STPError.errorMessageKey] + ?? "The provided Intent client secret does not match the expected client secret format. Make sure your server is returning the correct value and that is passed to `STPPaymentHandler`." + userInfo[NSLocalizedDescriptionKey] = + userInfo[NSLocalizedDescriptionKey] ?? NSError.stp_unexpectedErrorMessage() + + case .unexpectedErrorCode: + break + } + return STPPaymentHandlerError(code: errorCode, loggingSafeUserInfo: userInfo) as NSError + } +} + +/// STPPaymentHandler errors (i.e. errors that are created by the STPPaymentHandler class and have a corresponding STPPaymentHandlerErrorCode) used to be NSErrors. +/// This struct exists so that these errors can be Swift errors to conform to AnalyticLoggableError, while still looking like the old NSErrors to users (i.e. same domain and code). +struct STPPaymentHandlerError: Error, CustomNSError, AnalyticLoggableError { + // AnalyticLoggableError properties + let analyticsErrorType: String = errorDomain + let analyticsErrorCode: String + let additionalNonPIIErrorDetails: [String: Any] + + // CustomNSError properties, to not break old behavior when this was an NSError + static let errorDomain: String = STPPaymentHandler.errorDomain + let errorUserInfo: [String: Any] + let errorCode: Int + + init(code: STPPaymentHandlerErrorCode, loggingSafeUserInfo: [String: String]) { + errorCode = code.rawValue + // Set analytics error code to the description (e.g. "invalidClientSecret") + analyticsErrorCode = code.description + errorUserInfo = loggingSafeUserInfo + additionalNonPIIErrorDetails = loggingSafeUserInfo + } +} + +#if !canImport(CompositorServices) +extension STPPaymentHandler: SFSafariViewControllerDelegate { + // MARK: - SFSafariViewControllerDelegate + /// :nodoc: + @objc + public func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + let context = currentAction?.authenticationContext + if context?.responds( + to: #selector(STPAuthenticationContext.authenticationContextWillDismiss(_:)) + ) ?? false { + context?.authenticationContextWillDismiss?(controller) + } + safariViewController = nil + STPURLCallbackHandler.shared().unregisterListener(self) + _retrieveAndCheckIntentForCurrentAction() + + self.analyticsClient.logURLRedirectNextActionCompleted( + with: currentAction?.apiClient._stored_configuration, + intentID: currentAction?.intentStripeID, + usesWebAuthSession: true + ) + } +} +#endif + +/// :nodoc: +@_spi(STP) extension STPPaymentHandler: STPURLCallbackListener { + /// :nodoc: + @_spi(STP) public func handleURLCallback(_ url: URL) -> Bool { + if currentAction?.nextAction()?.redirectToURL?.useWebAuthSession ?? false { + // Don't handle the URL — If a user clicks the URL in ASWebAuthenticationSession, ASWebAuthenticationSession will handle it internally. + // If we're returning from another app via a URL while ASWebAuthenticationSession is open, it's likely that the PM initiated a redirect to another app + // (such as a banking app) and is waiting for a response from that app. + return false + } + // Note: At least my iOS 15 device, willEnterForegroundNotification is triggered before this method when returning from another app, which means this method isn't called because it unregisters from STPURLCallbackHandler. + let context = currentAction?.authenticationContext + if context?.responds( + to: #selector(STPAuthenticationContext.authenticationContextWillDismiss(_:)) + ) ?? false, + let safariViewController = safariViewController + { + context?.authenticationContextWillDismiss?(safariViewController) + } + + NotificationCenter.default.removeObserver( + self, + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + STPURLCallbackHandler.shared().unregisterListener(self) + safariViewController?.dismiss(animated: true) { + self.safariViewController = nil + } + _retrieveAndCheckIntentForCurrentAction() + return true + } +} + +extension STPPaymentHandler { + // MARK: - STPChallengeStatusReceiver + /// :nodoc: + @objc(transaction:didCompleteChallengeWithCompletionEvent:) + dynamic func transaction( + _ transaction: STDSTransaction, + didCompleteChallengeWith completionEvent: STDSCompletionEvent + ) { + guard let currentAction else { + stpAssertionFailure("Calling didCompleteChallengeWith without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling didCompleteChallengeWith without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + let transactionStatus = completionEvent.transactionStatus + analyticsClient.log3DS2ChallengeFlowCompleted( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + uiType: transaction.presentedChallengeUIType + ) + if transactionStatus == "Y" { + _markChallengeCompleted(withCompletion: { _, _ in + if let currentAction = self.currentAction + as? STPPaymentHandlerPaymentIntentActionParams + { + let requiresAction = self._handlePaymentIntentStatus(forAction: currentAction) + if requiresAction { + stpAssertionFailure("3DS2 challenge completed, but the PaymentIntent is still requiresAction") + currentAction.complete( + with: .failed, + error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "3DS2 challenge completed, but the PaymentIntent is still requiresAction") + ) + } + } else if let currentAction = self.currentAction + as? STPPaymentHandlerSetupIntentActionParams + { + let requiresAction = self._handleSetupIntentStatus(forAction: currentAction) + if requiresAction { + stpAssertionFailure("3DS2 challenge completed, but the SetupIntent is still requiresAction") + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "3DS2 challenge completed, but the SetupIntent is still requiresAction") + ) + } + } + }) + } else { + // going to ignore the rest of the status types because they provide more detail than we require + _markChallengeCompleted(withCompletion: { _, _ in + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error( + for: .notAuthenticatedErrorCode, + loggingSafeErrorMessage: "Failed with transaction_status: \(transactionStatus)" + ) + ) + }) + } + } + + /// :nodoc: + @objc(transactionDidCancel:) + dynamic func transactionDidCancel(_ transaction: STDSTransaction) { + guard let currentAction else { + stpAssertionFailure("Calling transactionDidCancel without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling transactionDidCancel without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + analyticsClient.log3DS2ChallengeFlowUserCanceled( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + uiType: transaction.presentedChallengeUIType + ) + _markChallengeCompleted(withCompletion: { _, _ in + // we don't forward cancelation errors + currentAction.complete(with: STPPaymentHandlerActionStatus.canceled, error: nil) + }) + } + + /// :nodoc: + @objc(transactionDidTimeOut:) + dynamic func transactionDidTimeOut(_ transaction: STDSTransaction) { + guard let currentAction else { + stpAssertionFailure("Calling transactionDidTimeOut without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling transactionDidTimeOut without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + analyticsClient.log3DS2ChallengeFlowTimedOut( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + uiType: transaction.presentedChallengeUIType + ) + _markChallengeCompleted(withCompletion: { _, _ in + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: self._error(for: .timedOutErrorCode) + ) + }) + + } + + /// :nodoc: + @objc(transaction:didErrorWithProtocolErrorEvent:) + dynamic func transaction( + _ transaction: STDSTransaction, + didErrorWith protocolErrorEvent: STDSProtocolErrorEvent + ) { + guard let currentAction else { + stpAssertionFailure("Calling didErrorWithProtocolErrorEvent without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling didErrorWithProtocolErrorEvent without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + _markChallengeCompleted(withCompletion: { [weak self] _, _ in + // Add localizedError to the 3DS2 SDK error + let threeDSError = protocolErrorEvent.errorMessage.nsErrorValue() as NSError + var userInfo = threeDSError.userInfo + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + let localizedError = NSError( + domain: threeDSError.domain, + code: threeDSError.code, + userInfo: userInfo + ) + self?.analyticsClient.log3DS2ChallengeFlowErrored( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + error: localizedError + ) + currentAction.complete( + with: .failed, + error: localizedError + ) + }) + } + + /// :nodoc: + @objc(transaction:didErrorWithRuntimeErrorEvent:) + dynamic func transaction( + _ transaction: STDSTransaction, + didErrorWith runtimeErrorEvent: STDSRuntimeErrorEvent + ) { + guard let currentAction else { + stpAssertionFailure("Calling didErrorWithRuntimeErrorEvent without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling didErrorWithRuntimeErrorEvent without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + _markChallengeCompleted(withCompletion: { [weak self] _, _ in + // Add localizedError to the 3DS2 SDK error + let threeDSError = runtimeErrorEvent.nsErrorValue() as NSError + var userInfo = threeDSError.userInfo + userInfo[NSLocalizedDescriptionKey] = NSError.stp_unexpectedErrorMessage() + + let localizedError = NSError( + domain: threeDSError.domain, + code: threeDSError.code, + userInfo: userInfo + ) + + self?.analyticsClient.log3DS2ChallengeFlowErrored( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + error: localizedError + ) + currentAction.complete( + with: STPPaymentHandlerActionStatus.failed, + error: localizedError + ) + }) + } + + /// :nodoc: + @objc(transactionDidPresentChallengeScreen:) + dynamic func transactionDidPresentChallengeScreen(_ transaction: STDSTransaction) { + guard let currentAction else { + stpAssertionFailure("Calling transactionDidPresentChallengeScreen without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling transactionDidPresentChallengeScreen without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + analyticsClient.log3DS2ChallengeFlowPresented( + with: currentAction.apiClient._stored_configuration, + intentID: currentAction.intentStripeID, + uiType: transaction.presentedChallengeUIType + ) + } + + /// :nodoc: + @objc(dismissChallengeViewController:forTransaction:) + dynamic func dismiss( + _ challengeViewController: UIViewController, + for transaction: STDSTransaction + ) { + guard let currentAction else { + stpAssertionFailure("Calling dismiss(challengeViewController:) without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling dismiss(challengeViewController:) without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + + if let paymentSheet = currentAction.authenticationContext + .authenticationPresentingViewController() as? PaymentSheetAuthenticationContext + { + paymentSheet.dismiss(challengeViewController, completion: nil) + } else { + challengeViewController.dismiss(animated: true, completion: nil) + } + } + + @_spi(STP) public func cancel3DS2ChallengeFlow() { + guard let currentAction else { + stpAssertionFailure("Calling cancel3DS2ChallengeFlow without currentAction.") + let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentHandlerError, error: InternalError.invalidState, additionalNonPIIParams: ["error_message": "Calling cancel3DS2ChallengeFlow without currentAction."]) + analyticsClient.log(analytic: errorAnalytic, apiClient: apiClient) + return + } + guard let transaction = currentAction.threeDS2Transaction else { + stpAssertionFailure("Calling cancel3DS2ChallengeFlow without a threeDS2Transaction.") + currentAction.complete( + with: .failed, + error: _error(for: .unexpectedErrorCode, loggingSafeErrorMessage: "Calling cancel3DS2ChallengeFlow without a threeDS2Transaction.") + ) + return + } + transaction.cancelChallengeFlow() + } +} + +/// Internal authentication context for PaymentSheet magic +@_spi(STP) public protocol PaymentSheetAuthenticationContext: STPAuthenticationContext { + func present(_ authenticationViewController: UIViewController, completion: @escaping () -> Void) + func dismiss(_ authenticationViewController: UIViewController, completion: (() -> Void)?) + func presentPollingVCForAction(action: STPPaymentHandlerPaymentIntentActionParams, type: STPPaymentMethodType, safariViewController: SFSafariViewController?) +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSButtonCustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSButtonCustomization.swift new file mode 100644 index 00000000..a32d78ea --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSButtonCustomization.swift @@ -0,0 +1,126 @@ +// +// STPThreeDSButtonCustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// An enum that defines the different types of buttons that are able to be customized. +@objc public enum STPThreeDSCustomizationButtonType: Int { + /// The submit button type. + case submit = 0 + /// The continue button type. + case `continue` = 1 + /// The next button type. + case next = 2 + /// The cancel button type. + case cancel = 3 + /// The resend button type. + case resend = 4 +} + +/// An enumeration of the case transformations that can be applied to the button's title +@objc public enum STPThreeDSButtonTitleStyle: Int { + /// Default style, doesn't modify the title + case `default` + /// Applies localizedUppercaseString to the title + case uppercase + /// Applies localizedLowercaseString to the title + case lowercase + /// Applies localizedCapitalizedString to the title + case sentenceCapitalized +} + +/// A customization object to use to configure the UI of a button. +public class STPThreeDSButtonCustomization: NSObject { + /// The default settings for the provided button type. + @objc(defaultSettingsForButtonType:) public class func defaultSettings( + for type: STPThreeDSCustomizationButtonType + ) -> STPThreeDSButtonCustomization { + let stdsButtonCustomization = STDSButtonCustomization.defaultSettings( + for: STDSUICustomizationButtonType(rawValue: type.rawValue)! + ) + let buttonCustomization = STPThreeDSButtonCustomization.init( + backgroundColor: stdsButtonCustomization.backgroundColor, + cornerRadius: stdsButtonCustomization.cornerRadius + ) + buttonCustomization.buttonCustomization = stdsButtonCustomization + return buttonCustomization + } + + internal var buttonCustomization: STDSButtonCustomization + + /// Initializes an instance of STDSButtonCustomization with the given backgroundColor and colorRadius. + @objc + public init( + backgroundColor: UIColor, + cornerRadius: CGFloat + ) { + buttonCustomization = STDSButtonCustomization( + backgroundColor: backgroundColor, + cornerRadius: cornerRadius + ) + super.init() + } + + /// The background color of the button. + /// The default for .resend and .cancel is clear. + /// The default for .submit, .continue, and .next is blue. + + @objc public var backgroundColor: UIColor { + get { + return buttonCustomization.backgroundColor + } + set { + buttonCustomization.backgroundColor = newValue + } + } + /// The corner radius of the button. Defaults to 8. + + @objc public var cornerRadius: CGFloat { + get { + return buttonCustomization.cornerRadius + } + set { + buttonCustomization.cornerRadius = newValue + } + } + /// The capitalization style of the button title. + + @objc public var titleStyle: STPThreeDSButtonTitleStyle { + get { + return STPThreeDSButtonTitleStyle(rawValue: buttonCustomization.titleStyle.rawValue)! + } + set { + buttonCustomization.titleStyle = STDSButtonTitleStyle(rawValue: newValue.rawValue)! + } + } + /// The font of the title. + + @objc public var font: UIFont? { + get { + return buttonCustomization.font + } + set(font) { + buttonCustomization.font = font + } + } + /// The text color of the title. + + @objc public var textColor: UIColor? { + get { + return buttonCustomization.textColor + } + set { + buttonCustomization.textColor = newValue + } + } +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSCustomizationSettings.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSCustomizationSettings.swift new file mode 100644 index 00000000..bcea5f6a --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSCustomizationSettings.swift @@ -0,0 +1,44 @@ +// +// STPThreeDSCustomizationSettings.swift +// StripePayments +// +// Created by Cameron Sabol on 5/30/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// `STPThreeDSCustomizationSettings` provides customization options for 3DS2 authentication flows in your app. +public class STPThreeDSCustomizationSettings: NSObject { + /// Returns an `STPThreeDSCustomizationSettings` preconfigured with the default + /// Stripe UI settings and a 10 minute `authenticationTimeout`. + /// @deprecated Use STPThreeDSCustomizationSettings() instead. The default settings are the same. + @available( + *, + deprecated, + message: + "Use STPThreeDSCustomizationSettings() instead of STPThreeDSCustomizationSettings.defaultSettings()." + ) + @objc + public class func defaultSettings() -> STPThreeDSCustomizationSettings { + return STPThreeDSCustomizationSettings() + } + + /// `uiCustomization` can be used to provide custom UI settings for the authentication + /// challenge screens presented during a Three Domain Secure authentication. For more information see + /// our guide on supporting 3DS2 in your iOS application. + /// Note: It's important to configure this object appropriately before calling any `STPPaymentHandler` APIs. + /// The API makes a copy of the customization settings you provide; it ignores any subsequent changes you + /// make to your `STPThreeDSUICustomization` instance. + /// Defaults to `STPThreeDSUICustomization.defaultSettings()`. + @objc public var uiCustomization = STPThreeDSUICustomization.defaultSettings() + /// `authenticationTimeout` is the total time allowed for a user to complete a 3DS2 authentication + /// interaction, in minutes. This value *must* be at least 5 minutes. + /// Defaults to 10 minutes. + @objc public var authenticationTimeout = 10 +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSFooterCustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSFooterCustomization.swift new file mode 100644 index 00000000..3578b467 --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSFooterCustomization.swift @@ -0,0 +1,88 @@ +// +// STPThreeDSFooterCustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// The Challenge view displays a footer with additional details that +/// expand when tapped. This object configures the appearance of that view. +public class STPThreeDSFooterCustomization: NSObject { + /// The default settings. + @objc + public class func defaultSettings() -> STPThreeDSFooterCustomization { + return STPThreeDSFooterCustomization() + } + + internal var footerCustomization = STDSFooterCustomization.defaultSettings() + /// The background color of the footer. + /// Defaults to gray. + + @objc public var backgroundColor: UIColor { + get { + return footerCustomization.backgroundColor + } + set { + footerCustomization.backgroundColor = newValue + } + } + + /// The color of the chevron. Defaults to a dark gray. + @objc public var chevronColor: UIColor { + get { + return footerCustomization.chevronColor + } + set { + footerCustomization.chevronColor = newValue + } + } + + /// The color of the heading text. Defaults to black. + @objc public var headingTextColor: UIColor { + get { + return footerCustomization.headingTextColor + } + set { + footerCustomization.headingTextColor = newValue + } + } + /// The font to use for the heading text. + + @objc public var headingFont: UIFont { + get { + return footerCustomization.headingFont + } + set { + footerCustomization.headingFont = newValue + } + } + + /// The font of the text. + @objc public var font: UIFont? { + get { + return footerCustomization.font + } + set { + footerCustomization.font = newValue + } + } + + /// The color of the text. + @objc public var textColor: UIColor? { + get { + return footerCustomization.textColor + } + set { + footerCustomization.textColor = newValue + } + } + +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSLabelCustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSLabelCustomization.swift new file mode 100644 index 00000000..302013b5 --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSLabelCustomization.swift @@ -0,0 +1,67 @@ +// +// STPThreeDSLabelCustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// A customization object to use to configure the UI of a text label. +public class STPThreeDSLabelCustomization: NSObject { + /// The default settings. + @objc + public class func defaultSettings() -> STPThreeDSLabelCustomization { + return STPThreeDSLabelCustomization() + } + + internal var labelCustomization = STDSLabelCustomization.defaultSettings() + + /// The font to use for heading text. + + @objc public var headingFont: UIFont { + get { + return labelCustomization.headingFont + } + set(headingFont) { + labelCustomization.headingFont = headingFont + } + } + /// The color of heading text. Defaults to black. + + @objc public var headingTextColor: UIColor { + get { + return labelCustomization.headingTextColor + } + set(headingTextColor) { + labelCustomization.headingTextColor = headingTextColor + } + } + + /// The font to use for non-heading text. + @objc public var font: UIFont? { + get { + return labelCustomization.font + } + set(font) { + labelCustomization.font = font + } + } + + /// The color to use for non-heading text. Defaults to black. + @objc public var textColor: UIColor? { + get { + return labelCustomization.textColor + } + set(textColor) { + labelCustomization.textColor = textColor + } + } + +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSNavigationBarCustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSNavigationBarCustomization.swift new file mode 100644 index 00000000..8d897029 --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSNavigationBarCustomization.swift @@ -0,0 +1,101 @@ +// +// STPThreeDSNavigationBarCustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// A customization object to use to configure a UINavigationBar. +public class STPThreeDSNavigationBarCustomization: NSObject { + /// The default settings. + @objc + public class func defaultSettings() -> STPThreeDSNavigationBarCustomization { + return STPThreeDSNavigationBarCustomization() + } + + @_spi(STP) public var navigationBarCustomization = + STDSNavigationBarCustomization.defaultSettings() + + /// The tint color of the navigation bar background. + /// Defaults to nil. + + @objc public var barTintColor: UIColor? { + get { + return navigationBarCustomization.barTintColor + } + set(barTintColor) { + navigationBarCustomization.barTintColor = barTintColor + } + } + /// The navigation bar style. + /// Defaults to UIBarStyleDefault. + /// @note This property controls the `UIStatusBarStyle`. Set this to `UIBarStyleBlack` + /// to change the `statusBarStyle` to `UIStatusBarStyleLightContent` - even if you also set + /// `barTintColor` to change the actual color of the navigation bar. + + @objc public var barStyle: UIBarStyle { + get { + return navigationBarCustomization.barStyle + } + set(barStyle) { + navigationBarCustomization.barStyle = barStyle + } + } + /// A Boolean value indicating whether the navigation bar is translucent or not. + /// Defaults to YES. + @objc public var translucent: Bool { + get { + return navigationBarCustomization.translucent + } + set(translucent) { + navigationBarCustomization.translucent = translucent + } + } + /// The text to display in the title of the navigation bar. + /// Defaults to "Secure checkout". + + @objc public var headerText: String { + get { + return navigationBarCustomization.headerText + } + set(headerText) { + navigationBarCustomization.headerText = headerText + } + } + /// The text to display for the button in the navigation bar. + /// Defaults to "Cancel". + @objc public var buttonText: String { + get { + return navigationBarCustomization.buttonText + } + set(buttonText) { + navigationBarCustomization.buttonText = buttonText + } + } + /// The font to use for the title. Defaults to nil. + @objc public var font: UIFont? { + get { + return navigationBarCustomization.font + } + set(font) { + navigationBarCustomization.font = font + } + } + /// The color to use for the title. Defaults to nil. + @objc public var textColor: UIColor? { + get { + return navigationBarCustomization.textColor + } + set(textColor) { + navigationBarCustomization.textColor = textColor + } + } +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSSelectionCustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSSelectionCustomization.swift new file mode 100644 index 00000000..d791de48 --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSSelectionCustomization.swift @@ -0,0 +1,72 @@ +// +// STPThreeDSSelectionCustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// A customization object that configures the appearance of +/// radio buttons and checkboxes. +public class STPThreeDSSelectionCustomization: NSObject { + /// The default settings. + @objc + public class func defaultSettings() -> STPThreeDSSelectionCustomization { + return STPThreeDSSelectionCustomization() + } + + internal var selectionCustomization = STDSSelectionCustomization.defaultSettings() + + /// The primary color of the selected state. + /// Defaults to blue. + + @objc public var primarySelectedColor: UIColor { + get { + return selectionCustomization.primarySelectedColor + } + set(primarySelectedColor) { + selectionCustomization.primarySelectedColor = primarySelectedColor + } + } + /// The secondary color of the selected state (e.g. the checkmark color). + /// Defaults to white. + + @objc public var secondarySelectedColor: UIColor { + get { + return selectionCustomization.secondarySelectedColor + } + set(secondarySelectedColor) { + selectionCustomization.secondarySelectedColor = secondarySelectedColor + } + } + /// The background color displayed in the unselected state. + /// Defaults to light blue. + + @objc public var unselectedBackgroundColor: UIColor { + get { + return selectionCustomization.unselectedBackgroundColor + } + set(unselectedBackgroundColor) { + selectionCustomization.unselectedBackgroundColor = unselectedBackgroundColor + } + } + /// The color of the border drawn around the view in the unselected state. + /// Defaults to blue. + + @objc public var unselectedBorderColor: UIColor { + get { + return selectionCustomization.unselectedBorderColor + } + set(unselectedBorderColor) { + selectionCustomization.unselectedBorderColor = unselectedBorderColor + } + } + +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSTextFieldCustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSTextFieldCustomization.swift new file mode 100644 index 00000000..ebdc64c1 --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSTextFieldCustomization.swift @@ -0,0 +1,94 @@ +// +// STPThreeDSTextFieldCustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/18/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// A customization object to use to configure the UI of a text field. +public class STPThreeDSTextFieldCustomization: NSObject { + /// The default settings. + @objc + public class func defaultSettings() -> STPThreeDSTextFieldCustomization { + return STPThreeDSTextFieldCustomization() + } + + internal var textFieldCustomization = STDSTextFieldCustomization.defaultSettings() + + /// The border width of the text field. Defaults to 2. + @objc public var borderWidth: CGFloat { + get { + return textFieldCustomization.borderWidth + } + set(borderWidth) { + textFieldCustomization.borderWidth = borderWidth + } + } + + /// The color of the border of the text field. Defaults to clear. + @objc public var borderColor: UIColor { + get { + return textFieldCustomization.borderColor + } + set(borderColor) { + textFieldCustomization.borderColor = borderColor + } + } + + /// The corner radius of the edges of the text field. Defaults to 8. + @objc public var cornerRadius: CGFloat { + get { + return textFieldCustomization.cornerRadius + } + set(cornerRadius) { + textFieldCustomization.cornerRadius = cornerRadius + } + } + /// The appearance of the keyboard. Defaults to UIKeyboardAppearanceDefault. + + @objc public var keyboardAppearance: UIKeyboardAppearance { + get { + return textFieldCustomization.keyboardAppearance + } + set(keyboardAppearance) { + textFieldCustomization.keyboardAppearance = keyboardAppearance + } + } + /// The color of the placeholder text. Defaults to light gray. + + @objc public var placeholderTextColor: UIColor { + get { + return textFieldCustomization.placeholderTextColor + } + set(placeholderTextColor) { + textFieldCustomization.placeholderTextColor = placeholderTextColor + } + } + + /// The font to use for text. + @objc public var font: UIFont? { + get { + return textFieldCustomization.font + } + set(font) { + textFieldCustomization.font = font + } + } + /// The color to use for the text. Defaults to black. + @objc public var textColor: UIColor? { + get { + return textFieldCustomization.textColor + } + set(textColor) { + textFieldCustomization.textColor = textColor + } + } +} diff --git a/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSUICustomization.swift b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSUICustomization.swift new file mode 100644 index 00000000..148bcc3a --- /dev/null +++ b/StripePayments/StripePayments/Source/PaymentHandler/STPThreeDSUICustomization.swift @@ -0,0 +1,193 @@ +// +// STPThreeDSUICustomization.swift +// StripePayments +// +// Created by Yuki Tokuhiro on 6/17/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +#if canImport(Stripe3DS2) + import Stripe3DS2 +#endif + +/// The `STPThreeDSUICustomization` provides configuration for UI elements displayed during 3D Secure authentication. +/// Note: It's important to configure this object appropriately before calling any `STPPaymentHandler` APIs. +/// The API makes a copy of the customization settings you provide; it ignores any subsequent changes you +/// make to your `STPThreeDSUICustomization` instance. +/// - seealso: https://stripe.com/docs/payments/3d-secure +public class STPThreeDSUICustomization: NSObject { + /// The default settings. See individual properties for their default values. + @objc + public class func defaultSettings() -> STPThreeDSUICustomization { + return STPThreeDSUICustomization() + } + + internal var uiCustomization = STDSUICustomization.defaultSettings() + + private var _navigationBarCustomization = STPThreeDSNavigationBarCustomization.defaultSettings() + /// Provides custom settings for the UINavigationBar of all UIViewControllers displayed during 3D Secure authentication. + /// The default is `STPThreeDSNavigationBarCustomization.defaultSettings()`. + @objc public var navigationBarCustomization: STPThreeDSNavigationBarCustomization { + get { + _navigationBarCustomization + } + set(navigationBarCustomization) { + _navigationBarCustomization = navigationBarCustomization + uiCustomization.navigationBarCustomization = + navigationBarCustomization.navigationBarCustomization + } + } + + private var _labelCustomization = STPThreeDSLabelCustomization.defaultSettings() + /// Provides custom settings for labels. + /// The default is `STPThreeDSLabelCustomization.defaultSettings()`. + @objc public var labelCustomization: STPThreeDSLabelCustomization { + get { + _labelCustomization + } + set(labelCustomization) { + _labelCustomization = labelCustomization + uiCustomization.labelCustomization = labelCustomization.labelCustomization + } + } + + private var _textFieldCustomization = STPThreeDSTextFieldCustomization.defaultSettings() + /// Provides custom settings for text fields. + /// The default is `STPThreeDSTextFieldCustomization.defaultSettings()`. + @objc public var textFieldCustomization: STPThreeDSTextFieldCustomization { + get { + _textFieldCustomization + } + set(textFieldCustomization) { + _textFieldCustomization = textFieldCustomization + uiCustomization.textFieldCustomization = textFieldCustomization.textFieldCustomization + } + } + + /// The primary background color of all UIViewControllers displayed during 3D Secure authentication. + /// Defaults to white. + @objc public var backgroundColor: UIColor { + get { + return uiCustomization.backgroundColor + } + set(backgroundColor) { + uiCustomization.backgroundColor = backgroundColor + } + } + + private var _footerCustomization = STPThreeDSFooterCustomization.defaultSettings() + /// Provides custom settings for the footer the challenge view can display containing additional details. + /// The default is `STPThreeDSFooterCustomization.defaultSettings()`. + @objc public var footerCustomization: STPThreeDSFooterCustomization { + get { + _footerCustomization + } + set(footerCustomization) { + _footerCustomization = footerCustomization + uiCustomization.footerCustomization = footerCustomization.footerCustomization + } + } + + /// Sets a given button customization for the specified type. + /// - Parameters: + /// - buttonCustomization: The buttom customization to use. + /// - buttonType: The type of button to use the customization for. + @objc(setButtonCustomization:forType:) public func setButtonCustomization( + _ buttonCustomization: STPThreeDSButtonCustomization, + for buttonType: STPThreeDSCustomizationButtonType + ) { + buttonCustomizationDictionary[NSNumber(value: buttonType.rawValue)] = buttonCustomization + self.uiCustomization.setButton( + buttonCustomization.buttonCustomization, + for: STDSUICustomizationButtonType(rawValue: buttonType.rawValue)! + ) + } + + /// Retrieves a button customization object for the given button type. + /// - Parameter buttonType: The button type to retrieve a customization object for. + /// - Returns: A button customization object, or the default if none was set. + /// - seealso: STPThreeDSButtonCustomization + @objc(buttonCustomizationForButtonType:) public func buttonCustomization( + for buttonType: STPThreeDSCustomizationButtonType + ) -> STPThreeDSButtonCustomization { + return (buttonCustomizationDictionary[NSNumber(value: buttonType.rawValue)])! + } + + private var _selectionCustomization = STPThreeDSSelectionCustomization.defaultSettings() + /// Provides custom settings for radio buttons and checkboxes. + /// The default is `STPThreeDSSelectionCustomization.defaultSettings()`. + @objc public var selectionCustomization: STPThreeDSSelectionCustomization { + get { + _selectionCustomization + } + set(selectionCustomization) { + _selectionCustomization = selectionCustomization + uiCustomization.selectionCustomization = selectionCustomization.selectionCustomization + } + } + // MARK: - Progress View + + /// The style of `UIActivityIndicatorView`s displayed. + /// This should contrast with `backgroundColor`. Defaults to gray. + + @objc public var activityIndicatorViewStyle: UIActivityIndicatorView.Style { + get { + return uiCustomization.activityIndicatorViewStyle + } + set(activityIndicatorViewStyle) { + uiCustomization.activityIndicatorViewStyle = activityIndicatorViewStyle + } + } + + /// The style of the `UIBlurEffect` displayed underneath the `UIActivityIndicatorView`. + /// Defaults to `UIBlurEffectStyleLight`. + @objc public var blurStyle: UIBlurEffect.Style { + get { + return uiCustomization.blurStyle + } + set(blurStyle) { + uiCustomization.blurStyle = blurStyle + } + } + + private var buttonCustomizationDictionary: [NSNumber: STPThreeDSButtonCustomization] + + /// :nodoc: + @objc + public override init() { + // Initialize defaults for all properties + let nextButton = STPThreeDSButtonCustomization.defaultSettings(for: .next) + let cancelButton = STPThreeDSButtonCustomization.defaultSettings(for: .cancel) + let resendButton = STPThreeDSButtonCustomization.defaultSettings(for: .resend) + let submitButton = STPThreeDSButtonCustomization.defaultSettings(for: .submit) + let continueButton = STPThreeDSButtonCustomization.defaultSettings(for: .continue) + buttonCustomizationDictionary = [ + NSNumber(value: STPThreeDSCustomizationButtonType.next.rawValue): nextButton, + NSNumber(value: STPThreeDSCustomizationButtonType.cancel.rawValue): cancelButton, + NSNumber(value: STPThreeDSCustomizationButtonType.resend.rawValue): resendButton, + NSNumber(value: STPThreeDSCustomizationButtonType.submit.rawValue): submitButton, + NSNumber(value: STPThreeDSCustomizationButtonType.continue.rawValue): continueButton, + ] + + // Initialize the underlying STDS class we are wrapping + uiCustomization = STDSUICustomization() + uiCustomization.setButton(nextButton.buttonCustomization, for: .next) + uiCustomization.setButton(cancelButton.buttonCustomization, for: .cancel) + uiCustomization.setButton(resendButton.buttonCustomization, for: .resend) + uiCustomization.setButton(submitButton.buttonCustomization, for: .submit) + uiCustomization.setButton(continueButton.buttonCustomization, for: .continue) + + super.init() + + uiCustomization.footerCustomization = footerCustomization.footerCustomization + uiCustomization.labelCustomization = labelCustomization.labelCustomization + uiCustomization.navigationBarCustomization = + navigationBarCustomization.navigationBarCustomization + uiCustomization.selectionCustomization = selectionCustomization.selectionCustomization + uiCustomization.textFieldCustomization = textFieldCustomization.textFieldCustomization + + } +} diff --git a/StripePayments/StripePayments/StripePayments.h b/StripePayments/StripePayments/StripePayments.h new file mode 100644 index 00000000..92df4c4c --- /dev/null +++ b/StripePayments/StripePayments/StripePayments.h @@ -0,0 +1,18 @@ +// +// StripePayments.h +// StripePayments +// +// Created by David Estes on 6/30/22. +// + +#import + +//! Project version number for StripePayments. +FOUNDATION_EXPORT double StripePaymentsVersionNumber; + +//! Project version string for StripePayments. +FOUNDATION_EXPORT const unsigned char StripePaymentsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripePayments/StripePaymentsObjcTestUtils/Info.plist b/StripePayments/StripePaymentsObjcTestUtils/Info.plist new file mode 100644 index 00000000..9bcb2444 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/3DSSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/3DSSource.json new file mode 100644 index 00000000..bff15783 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/3DSSource.json @@ -0,0 +1,57 @@ +// Source: https://stripe.com/docs/sources/three-d-secure +{ + "id": "src_456", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_456", + "created": 1483663790, + "currency": "eur", + "flow": "redirect", + "livemode": false, + "metadata": {}, + "owner": { + "address": { + "city": "Pittsburgh", + "country": "US", + "line1": "123 Fake St", + "line2": "Apt 1", + "postal_code": "19219", + "state": "PA" + }, + "email": "jenny.rosen@example.com", + "name": "Jenny Rosen", + "phone": "555-867-5309", + "verified_address": { + "city": "Pittsburgh", + "country": "US", + "line1": "123 Fake St", + "line2": "Apt 1", + "postal_code": "19219", + "state": "PA" + }, + "verified_email": "jenny.rosen@example.com", + "verified_name": "Jenny Rosen", + "verified_phone": "555-867-5309" + }, + "receiver": { + "address": "test_1MBhWS3uv4ynCfQXF3xQjJkzFPukr4K56N", + "amount_charged": 300, + "amount_received": 200, + "amount_returned": 100, + "refund_attributes_method": "email", + "refund_attributes_status": "missing" + }, + "redirect": { + "return_url": "exampleappschema://stripe_callback", + "status": "pending", + "url": "https://hooks.stripe.com/redirect/authenticate/src_19YlvWAHEMiOZZp1QQlOD79v?client_secret=src_client_secret_kBwCSm6Xz5MQETiJ43hUH8qv" + }, + "status": "pending", + "type": "three_d_secure", + "usage": "single_use", + "three_d_secure": { + "card": "src_123", + "customer": null, + "authenticated": false + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/AlipaySource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/AlipaySource.json new file mode 100644 index 00000000..a38ae906 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/AlipaySource.json @@ -0,0 +1,34 @@ +// Source: https://stripe.com/docs/sources/alipay +{ + "id": "src_123", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_123", + "created": 1445277809, + "currency": "usd", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null, + }, + "redirect": { + "return_url": "https://shop.foo.com/crtABC", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_123?client_secret=src_client_secret_123" + }, + "statement_descriptor": null, + "status": "pending", + "type": "alipay", + "usage": "single_use", + "alipay": { + "statement_descriptor": null, + "native_url": null + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/ApplePayPaymentMethod.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/ApplePayPaymentMethod.json new file mode 100644 index 00000000..8831c871 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/ApplePayPaymentMethod.json @@ -0,0 +1,30 @@ +{ + "card": { + "brand": "visa", + "checks": { + }, + "country": "US", + "exp_month": 12, + "exp_year": 2020, + "funding": "credit", + "generated_from": null, + "last4": 4242, + "three_d_secure_usage": { + "supported": 1 + }, + "wallet": { + "apple_pay": { + }, + "dynamic_last4": 4242, + "type": "apple_pay" + } + }, + "created": 1558545782, + "id": "pm_123456789", + "livemode": false, + "metadata": { + }, + "object": "payment_method", + "type": "card" +} + diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BacsDebitPaymentMethod.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BacsDebitPaymentMethod.json new file mode 100644 index 00000000..f42e15cb --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BacsDebitPaymentMethod.json @@ -0,0 +1,28 @@ +{ + "id": "pm_1G4EmqKlwPmebFhpaMbye30L", + "object": "payment_method", + "bacs_debit": { + "fingerprint": "9eMbmctOrd8i7DYa", + "last4": "2345", + "sort_code": "108800" + }, + "billing_details": { + "address": { + "city": "f", + "country": "US", + "line1": "f", + "line2": "f", + "postal_code": "f", + "state": "FL" + }, + "email": "f@f.1", + "name": "f", + "phone": null + }, + "created": 1579820912, + "customer": "cus_GWUuPERgJF44Dm", + "livemode": false, + "metadata": { + }, + "type": "bacs_debit" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BancontactSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BancontactSource.json new file mode 100644 index 00000000..4a9cf384 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BancontactSource.json @@ -0,0 +1,38 @@ +// Source: https://stripe.com/docs/sources/bancontact +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "statement_descriptor": null, + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "status": "pending", + "type": "bancontact", + "usage": "single_use", + "bancontact": { + "bank_code": null, + "bic": null, + "bank_name": null, + "iban_last4": null, + "statement_descriptor": null, + "preferred_language": null + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BankAccount.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BankAccount.json new file mode 100644 index 00000000..0f7c2227 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/BankAccount.json @@ -0,0 +1,18 @@ +{ + "id": "ba_1AZmya2eZvKYlo2CQzt7Fwnz", + "object": "bank_account", + "account": "acct_1032D82eZvKYlo2C", + "account_holder_name": "Jane Austen", + "account_holder_type": "individual", + "bank_name": "STRIPE TEST BANK", + "country": "US", + "currency": "usd", + "default_for_currency": false, + "fingerprint": "1JWtPxqbdX5Gamtc", + "last4": "6789", + "metadata": { + "order_id": "6735" + }, + "routing_number": "110000000", + "status": "new" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/Card.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/Card.json new file mode 100644 index 00000000..14e3c133 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/Card.json @@ -0,0 +1,26 @@ +// Source: https://stripe.com/docs/api#card_object +{ + "id": "card_103kbR2eZvKYlo2CDczLmw4K", + "object": "card", + "address_city": "Pittsburgh", + "address_country": "US", + "address_line1": "123 Fake St", + "address_line1_check": "pass", + "address_line2": "Apt 1", + "address_state": "PA", + "address_zip": "19219", + "address_zip_check": "pass", + "brand": "Visa", + "country": "US", + "customer": "cus_3kbRl1kVJmQ4Ur", + "currency": "usd", + "cvc_check": "pass", + "dynamic_last4": "5678", + "exp_month": 5, + "exp_year": 2017, + "fingerprint": "Xt5EWLLDS7FJjR1c", + "funding": "credit", + "last4": "4242", + "name": "Jane Austen", + "tokenization_method": null +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CardPaymentMethod.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CardPaymentMethod.json new file mode 100644 index 00000000..c425fa07 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CardPaymentMethod.json @@ -0,0 +1,66 @@ +// Source: https://stripe.com/docs/api/payment_methods/object +{ + "id": "pm_123456789", + "object": "payment_method", + "billing_details": { + "address": { + "city": "München", + "country": "DE", + "postal_code": "80337", + "line1": "Marienplatz", + "line2": "8", + "state": "Bayern", + }, + "email": "jenny@example.com", + "phone": "+15555555555", + "name": "jenny", + }, + "card": { + "brand": "visa", + "checks": { + }, + "country": "US", + "display_brand": "cartes_bancaires", + "description": "Visa Classic", + "exp_month": 8, + "exp_year": 2020, + "fingerprint": "6gVyxfIhqc8Z0g0X", + "funding": "credit", + "iin": "424242", + "issuer": "Stripe Payments UK Limited", + "last4": "4242", + "three_d_secure_usage": { + "supported": true + }, + "networks": { + "available": ["visa", "mastercard"], + "preferred": "visa" + }, + "wallet": { + "type": "visa_checkout", + "visa_checkout": { + "name": "Jenny", + "email": "jenny@example.com", + "billing_address": { + "city": "München", + "country": "DE", + "postal_code": "80337", + "line1": "Marienplatz", + "line2": "8", + "state": "Bayern", + }, + "shipping_address": { + "city": "München", + "country": "DE", + "postal_code": "80337", + "line1": "Marienplatz", + "line2": "8", + "state": "Bayern", + }, + } + } + }, + "created": 123456789, + "livemode": false, + "type": "card" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CardSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CardSource.json new file mode 100644 index 00000000..415e4aaa --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CardSource.json @@ -0,0 +1,33 @@ +// Source: https://stripe.com/docs/sources/cards +{ + "id": "src_123", + "object": "source", + "amount": null, + "client_secret": "src_client_secret_123", + "created": 1483575790, + "currency": null, + "flow": "none", + "livemode": false, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "status": "chargeable", + "type": "card", + "usage": "reusable", + "card": { + "brand": "Visa", + "country": "US", + "exp_month": 12, + "exp_year": 2034, + "funding": "debit", + "last4": "5556", + "three_d_secure": "not_supported" + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CryptoPaymentMethod.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CryptoPaymentMethod.json new file mode 100644 index 00000000..45706f68 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/CryptoPaymentMethod.json @@ -0,0 +1,24 @@ +{ + "id": "pm_1QO42LFY0qyl6XeWuK9FjZq5", + "object": "payment_method", + "allow_redisplay": "unspecified", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "created": 1732309509, + "crypto": {}, + "customer": null, + "livemode": false, + "metadata": {}, + "type": "crypto" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/Customer.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/Customer.json new file mode 100644 index 00000000..79dcda8c --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/Customer.json @@ -0,0 +1,26 @@ +{ + "id": "cus_123", + "object": "customer", + "created": 1463413795, + "default_source": "card_123", + "livemode": false, + "shipping": { + "address": { + "city": "Baltimore", + "country": "AF", + "line1": "67 Ave C", + "line2": "3A", + "postal_code": "10002", + "state": "MD" + }, + "name": "Ben Guo", + "phone": "(555) 555-5555" + }, + "sources": { + "object": "list", + "data": [], + "has_more": false, + "total_count": 0, + "url": "/v1/customers/cus_8SjRZeRUyWjiBz/sources" + }, +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/EPSSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/EPSSource.json new file mode 100644 index 00000000..36e2da04 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/EPSSource.json @@ -0,0 +1,34 @@ +// Source: https://stripe.com/docs/sources/eps +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "eps", + "usage": "single_use", + "eps": { + "reference": null, + "statement_descriptor": null, + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/ElementsSession.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/ElementsSession.json new file mode 100644 index 00000000..75498d94 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/ElementsSession.json @@ -0,0 +1,196 @@ +{ + "account_id": null, + "apple_pay_preference": "enabled", + "business_name": "Mobile Example Account", + "experiments": { + "element_link_autofill_in_link_authentication_element": "control", + "element_link_autofill_in_payment_element": "control_test", + "elements_link_aa": "control", + "elements_link_in_payment_element_only": "treatment", + "elements_link_in_payment_element_only_holdback": "control", + "elements_link_longterm_holdback": "control", + "lpm_discoverability_upe_experiment_1": "control" + }, + "experiments_data": { + "arb_id": "35386406-1724-456b-ab1d-a4c4d8659098", + "experiment_assignments": { + "element_link_autofill_in_link_authentication_element": "control", + "element_link_autofill_in_payment_element": "control_test", + "elements_link_aa": "control", + "elements_link_in_payment_element_only": "treatment", + "elements_link_in_payment_element_only_holdback": "control", + "elements_link_longterm_holdback": "control", + "lpm_discoverability_upe_experiment_1": "control" + } + }, + "flags": { + "elements_disable_paypal_express": true, + "elements_enable_blik": true, + "elements_enable_br_card_installments": false, + "elements_enable_deferred_intent": false, + "elements_enable_demo_pay": false, + "elements_enable_express_checkout": false, + "elements_enable_external_payment_method_paypal": false, + "elements_enable_external_payment_method_venmo": false, + "elements_enable_mobilepay": false, + "elements_enable_mx_card_installments": false, + "elements_enable_revolut_pay": false, + "elements_link_enable_email_domain_correction": false, + "elements_lpm_discoverability_downward_arrow": false, + "elements_lpm_discoverability_rotating_cycle": false, + "elements_web_lpm_server_driven_ui": true, + "financial_connections_enable_deferred_intent_flow": false, + "merchant_success_log_element_is_visible": true + }, + "google_pay_preference": "enabled", + "link_consumer_info": null, + "link_settings": { + "link_authenticated_change_event_enabled": false, + "link_bank_incentives_enabled": false, + "link_bank_onboarding_enabled": false, + "link_crypto_onramp_bank_upsell": false, + "link_crypto_onramp_elements_logout_disabled": false, + "link_crypto_onramp_force_cvc_reverification": false, + "link_elements_is_crypto_onramp": false, + "link_elements_pageload_sign_up_disabled": false, + "link_email_verification_login_enabled": false, + "link_financial_incentives_experiment_enabled": false, + "link_funding_sources": ["CARD"], + "link_instant_debits_create_link_account_session_on_instantiation": false, + "link_local_storage_login_enabled": false, + "link_m2_default_integration_enabled": true, + "link_only_for_payment_method_types_enabled": false, + "link_passthrough_mode_enabled": false, + "link_pay_button_element_enabled": true, + "link_session_storage_login_enabled": true + }, + "merchant_country": "US", + "merchant_currency": "usd", + "merchant_id": "acct_1HvTI7Lu5o3P18Zp", + "meta_pay_signed_container_context": null, + "order": null, + "ordered_payment_method_types_and_wallets": [ + "card", + "link", + "apple_pay", + "google_pay", + "us_bank_account", + "afterpay_clearpay", + "klarna", + "cashapp", + "alipay", + "wechat_pay" + ], + "payment_method_preference": { + "object": "payment_method_preference", + "country_code": "US", + "ordered_payment_method_types": [ + "card", + "link", + "us_bank_account", + "afterpay_clearpay", + "klarna", + "cashapp", + "alipay", + "wechat_pay" + ], + "type": "deferred_intent" + }, + "payment_method_specs": [ + { + "async": false, + "fields": [ + { + "type": "afterpay_header" + }, + { + "api_path": { + "v1": "billing_details[name]" + }, + "type": "name" + }, + { + "api_path": { + "v1": "billing_details[email]" + }, + "type": "email" + }, + { + "allowed_country_codes": null, + "type": "billing_address" + } + ], + "selector_icon": { + "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-afterpay@3x-6776ded2b20306c85d02639aea1e7dc5.png", + "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-afterpay-abedc6b87e4e9f917e22bbe6648ba809.svg" + }, + "type": "afterpay_clearpay" + }, + { + "async": false, + "fields": [], + "selector_icon": { + "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-alipay.png", + "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/alipay-22c167d415e209c71b2ac68b7fbc9f43.svg" + }, + "type": "alipay" + }, + { + "async": false, + "fields": [], + "type": "card" + }, + { + "async": false, + "fields": [], + "selector_icon": { + "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + }, + "type": "cashapp" + }, + { + "async": false, + "fields": [ + { + "type": "klarna_header" + }, + { + "api_path": { + "v1": "billing_details[email]" + }, + "type": "email" + }, + { + "api_path": { + "v1": "billing_details[address][country]" + }, + "type": "klarna_country" + } + ], + "selector_icon": { + "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-klarna@3x-d8624aa9a5662d719a44d16b9fcca0be.png", + "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-klarna-bb91aa8f173a3c72931696b0f752ec73.svg" + }, + "type": "klarna" + }, + { + "async": false, + "fields": [], + "selector_icon": { + "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/wechat_pay.png", + "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-wechat-pay-f62a5a27f646cb5f596c610475d14444.svg" + }, + "type": "wechat_pay" + } + ], + "paypal_express_config": { + "client_id": null, + "paypal_merchant_id": null + }, + "session_id": "elements_session_1MFNQu2rLVE", + "shipping_address_settings": { + "autocomplete_allowed": true + }, + "unactivated_payment_method_types": [] +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/EphemeralKey.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/EphemeralKey.json new file mode 100644 index 00000000..1b3272ad --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/EphemeralKey.json @@ -0,0 +1,14 @@ +{ + "id": "ephkey_123", + "object": "ephemeral_key", + "secret": "ek_test_123", + "created": 1483575790, + "livemode": false, + "expires": 1483579790, + "associated_objects": [ + { + "type": "customer", + "id": "cus_123" + } + ] +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/FileUpload.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/FileUpload.json new file mode 100644 index 00000000..e03a733f --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/FileUpload.json @@ -0,0 +1,10 @@ +// Source: https://stripe.com/docs/api#file_upload_object +{ + "id": "file_1AZl0o2eZvKYlo2CoIkwLzfd", + "object": "file_upload", + "created": 1498674938, + "purpose": "dispute_evidence", + "size": 34478, + "type": "jpg", + "url": "https://stripe-upload-api.s3.amazonaws.com/uploads/file_1AXyapEOD54MuFwSnhlqqvsX?AWSAccessKeyId=KEY_ID&Expires=TIMESTAMP&Signature=SIGNATURE" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/GiropaySource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/GiropaySource.json new file mode 100644 index 00000000..a95b7df1 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/GiropaySource.json @@ -0,0 +1,36 @@ +// Source: https://stripe.com/docs/sources/giropay +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": null, + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "giropay", + "usage": "single_use", + "giropay": { + "bank_code": null, + "bic": null, + "bank_name": null, + "statement_descriptor": null + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/MultibancoSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/MultibancoSource.json new file mode 100644 index 00000000..05250c28 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/MultibancoSource.json @@ -0,0 +1,42 @@ +// Source: https://stripe.com/docs/sources/multibanco +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "receiver", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "receiver": { + "address": "12345-123456789", + "amount_charged": 0, + "amount_received": 0, + "amount_returned": 0, + "refund_attributes_method": "email", + "refund_attributes_status": "missing" + }, + "statement_descriptor": null, + "status": "pending", + "type": "multibanco", + "usage": "single_use", + "multibanco": { + "reference": "12345", + "entity": "123456789", + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/P24Source.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/P24Source.json new file mode 100644 index 00000000..5948340c --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/P24Source.json @@ -0,0 +1,33 @@ +// Source: https://stripe.com/docs/sources/p24 +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": "jenny.rosen@example.com", + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": "jenny.rosen@example.com", + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "p24", + "usage": "single_use", + "p24": { + "reference": "P24-000-111-222" + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/PaymentIntent.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/PaymentIntent.json new file mode 100644 index 00000000..c0e7bfad --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/PaymentIntent.json @@ -0,0 +1,82 @@ +{ + "id": "pi_1Cl15wIl4IdHmuTbCWrpJXN6", + "object": "payment_intent", + "payment_method_types": [ + "card" + ], + "amount": 2345, + "canceled_at": 1530911045, + "capture_method": "manual", + "client_secret": "pi_1Cl15wIl4IdHmuTbCWrpJXN6_secret_EkKtQ7Sg75hLDFKqFG8DtWcaK", + "confirmation_method": "automatic", + "created": 1530911040, + "currency": "usd", + "description": "My Sample PaymentIntent", + "last_payment_error": { + "code": "payment_intent_authentication_failure", + "doc_url": "https://stripe.com/docs/error-codes#payment-intent-authentication-failure", + "message": "The provided PaymentMethod has failed authentication. You can provide payment_method_data or a new PaymentMethod to attempt to fulfill this PaymentIntent again.", + "payment_method": { + "id": "pm_1F5KZ4KlwPmebFhph828lKsZ", + "object": "payment_method", + "billing_details": { + "address": { + }, + }, + "card": { + "brand": "visa", + "checks": { + }, + "country": "US", + "exp_month": 2, + "exp_year": 2042, + "funding": "credit", + "last4": "3063", + "three_d_secure_usage": { + "supported": true + }, + }, + "created": 1565305114, + "livemode": false, + "metadata": { + }, + "type": "card" + }, + "type": "invalid_request_error" + }, + "livemode": false, + "next_source_action": { + "type": "authorize_with_url", + "authorize_with_url": { + "return_url": "payments-example://stripe-redirect", + "url": "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk" + } + }, + "next_action": { + "type": "redirect_to_url", + "redirect_to_url": { + "return_url": "payments-example://stripe-redirect", + "url": "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk" + } + }, + "receipt_email": "danj@example.com", + "shipping": { + "address": { + "city": "San Francisco", + "country": "USA", + "line1": "123 Main St", + "line2": "Apt 456", + "postal_code": "94107", + "state": "CA" + }, + "carrier": "USPS", + "name": "Dan", + "phone": "1-415-555-1234", + "tracking_number": "xyz123abc" + }, + "source": "src_1Cl1AdIl4IdHmuTbseiDWq6m", + "status": "requires_action", + "payment_method_types" : [ + "card" + ], +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SEPADebitPaymentMethod.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SEPADebitPaymentMethod.json new file mode 100644 index 00000000..24b291c4 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SEPADebitPaymentMethod.json @@ -0,0 +1,30 @@ +{ + "id": "pm_123456789", + "object": "payment_method", + "sepa_debit": { + "bank_code": "DE86213522400189569728", + "country": "DE", + "fingerprint": "j9brhn8f41Dh3As", + "last4": "1234", + "mandate_reference": "MR123", + "mandate_url": "https://stripe.com/mandate" + }, + "billing_details": { + "address": { + "city": "Berlin", + "country": "DE", + "line1": "Straße des 17. Juni", + "line2": "135", + "postal_code": "10623", + "state": "Berlin" + }, + "email": "test@test.com", + "name": "John Doe", + "phone": "1231231234" + }, + "created": 1579820912, + "customer": "cus_123456789", + "livemode": true, + "metadata": {}, + "type": "sepa_debit" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SEPADebitSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SEPADebitSource.json new file mode 100644 index 00000000..18abb191 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SEPADebitSource.json @@ -0,0 +1,38 @@ +// Source: https://stripe.com/docs/sources/sepa-debit +{ + "id": "src_18HgGjHNCLa1Vra6Y9TIP6tU", + "object": "source", + "amount": null, + "client_secret": "src_client_secret_XcBmS94nTg5o0xc9MSliSlDW", + "created": 1464803577, + "currency": "eur", + "flow": "none", + "livemode": false, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": null, + "verified_phone": null + }, + "status": "chargeable", + "type": "sepa_debit", + "usage": "reusable", + "sepa_debit": { + "bank_code": "37040044", + "branch_code": "a_branch", + "country": "DE", + "fingerprint": "NxdSyRegc9PsMkWy", + "last4": "3001", + "mandate": "NXDSYREGC9PSMKWY", + "mandate_reference": "NXDSYREGC9PSMKWY", + "mandate_url": "https://hooks.stripe.com/adapter/sepa_debit/file/src_18HgGjHNCLa1Vra6Y9TIP6tU/src_client_secret_XcBmS94nTg5o0xc9MSliSlDW" + }, + "verification": { + "attempts_remaining": 5, + "status": "pending" + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SetupIntent.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SetupIntent.json new file mode 100644 index 00000000..1b293c3e --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SetupIntent.json @@ -0,0 +1,71 @@ +{ + "id": "seti_123456789", + "object": "setup_intent", + "application": "ca_123456789", + "client_secret": "seti_123456789_secret_123456789", + "created": 123456789, + "customer": "cus_123456", + "description": "My Sample SetupIntent", + "last_setup_error": { + "code": "setup_intent_authentication_failure", + "doc_url": "https://stripe.com/docs/error-codes#setup-intent-authentication-failure", + "message": "The latest attempt to set up the payment method has failed because authentication failed.", + "payment_method": { + "id": "pm_1F5fdSKlwPmebFhpPTD7Tg4l", + "object": "payment_method", + "billing_details": { + "address": { + "city": null, + "country": null, + "line1": null, + "line2": null, + "postal_code": null, + "state": null + }, + "email": null, + "name": null, + "phone": null + }, + "card": { + "brand": "visa", + "checks": { + "address_line1_check": null, + "address_postal_code_check": null, + "cvc_check": null + }, + "country": null, + "exp_month": 2, + "exp_year": 2042, + "funding": "credit", + "generated_from": null, + "last4": "3246", + "three_d_secure_usage": { + "supported": true + }, + "wallet": null + }, + "created": 1565386110, + "customer": null, + "livemode": false, + "metadata": { + }, + "type": "card" + }, + "type": "invalid_request_error" + }, + "livemode": false, + "next_action": { + "type": "redirect_to_url", + "redirect_to_url": { + "return_url": "payments-example://stripe-redirect", + "url": "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk" + } + }, + "on_behalf_of": null, + "payment_method": "pm_123456", + "payment_method_types": [ + "card" + ], + "status": "requires_action", + "usage": "off_session" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SofortSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SofortSource.json new file mode 100644 index 00000000..83349507 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/SofortSource.json @@ -0,0 +1,39 @@ +// Source: https://stripe.com/docs/sources/sofort +{ + "id": "src_16xhynE8WzK49JbAs9M21jaR", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null + }, + "redirect": { + "return_url": "https://shop.example.com/crtA6B28E1", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_16xhynE8WzK49JbAs9M21jaR?client_secret=src_client_secret_UfwvW2WHpZ0s3QEn9g5x7waU" + }, + "statement_descriptor": null, + "status": "pending", + "type": "sofort", + "usage": "single_use", + "sofort": { + "country": "DE", + "bank_code": null, + "bic": null, + "bank_name": null, + "iban_last4": null, + "preferred_language": null, + "statement_descriptor": null + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/USBankAccountPaymentMethod.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/USBankAccountPaymentMethod.json new file mode 100644 index 00000000..316ef53c --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/USBankAccountPaymentMethod.json @@ -0,0 +1,31 @@ +{ + "id": "pm_123456789", + "object": "payment_method", + "us_bank_account": { + "country": "US", + "bank_name": "TD BANK", + "fingerprint": "Ec5gh5t1Ord8i7DYa", + "last4": "6789", + "routing_number": "123456789", + "account_holder_name": "John Doe", + "account_holder_type": "individual", + "account_type": "checking" + }, + "billing_details": { + "address": { + "city": "New York", + "country": "US", + "line1": "123 Main St", + "line2": "", + "postal_code": "10001", + "state": "NY" + }, + "email": "test@test.com", + "name": "John Doe", + "phone": "1231231234" + }, + "created": 1599420912, + "customer": "cus_123456789", + "livemode": true, + "type": "us_bank_account" +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/WeChatPaySource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/WeChatPaySource.json new file mode 100644 index 00000000..97dc5738 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/WeChatPaySource.json @@ -0,0 +1,40 @@ +// Source: https://stripe.com/docs/sources/wechat-pay +{ + "id" : "src_1FCC54BNJ02ErVOjfA2jH45g", + "livemode" : true, + "amount" : 1010, + "metadata" : { + + }, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "statement_descriptor" : null, + "usage" : "single_use", + "type" : "wechat", + "wechat" : { + "android_sign" : "2709CDC079DC1BB820FDB69941BE85C8EB9363D0057A7A3A3F521E64F7F08D52", + "android_package" : "Sign=WXPay", + "android_appId" : "wxa0df51ec63e578ce", + "ios_native_url" : "weixin:\/\/app\/wxa0df51ec63e578ce\/pay\/?appId=wxa0df51ec63e578ce&nonceStr=NLc6rkkjVIswJ43S&package=Sign%3DWXPay&partnerId=268716457&prepayId=wx280519598839065fb52b27b81487775200&timeStamp=1566940800&sign=2709CDC079DC1BB820FDB69941BE85C8EB9363D0057A7A3A3F521E64F7F08D52", + "android_timeStamp" : "1566940800", + "qr_code_url" : null, + "android_partnerId" : "268716457", + "android_nonceStr" : "NLc6rkkjVIswJ43S", + "android_prepayId" : "wx280519598839065fb52b27b81487775200" + }, + "object" : "source", + "created" : 1566940798, + "client_secret" : "src_client_secret_FhbHtMh4dGYSAE7UBnhDhsNb", + "flow" : "none", + "currency" : "usd", + "status" : "pending" +} + diff --git a/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/iDEALSource.json b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/iDEALSource.json new file mode 100644 index 00000000..2ea86eea --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/Resources/Mock Files/iDEALSource.json @@ -0,0 +1,32 @@ +// Source: https://stripe.com/docs/sources/ideal +{ + "id": "src_123", + "object": "source", + "amount": 1099, + "client_secret": "src_client_secret_123", + "created": 1445277809, + "currency": "eur", + "flow": "redirect", + "livemode": true, + "owner": { + "address": null, + "email": null, + "name": "Jenny Rosen", + "phone": null, + "verified_address": null, + "verified_email": null, + "verified_name": "Jenny Rosen", + "verified_phone": null, + }, + "redirect": { + "return_url": "https://shop.foo.com/crtABC", + "status": "pending", + "url": "https://pay.stripe.com/redirect/src_123?client_secret=src_client_secret_123" + }, + "status": "pending", + "type": "ideal", + "usage": "single_use", + "ideal": { + "bank": "ing" + } +} diff --git a/StripePayments/StripePaymentsObjcTestUtils/STPFixtures.h b/StripePayments/StripePaymentsObjcTestUtils/STPFixtures.h new file mode 100644 index 00000000..d6f47faa --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/STPFixtures.h @@ -0,0 +1,215 @@ +// +// STPFixtures.h +// Stripe +// +// Created by Ben Guo on 3/28/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +#import +#import +@import StripeCore; +@import StripePayments; + +NS_ASSUME_NONNULL_BEGIN +extern NSString *const STPTestJSONCustomer; + +extern NSString *const STPTestJSONCard; + +extern NSString *const STPTestJSONPaymentIntent; +extern NSString *const STPTestJSONSetupIntent; +extern NSString *const STPTestJSONPaymentMethodCard; +extern NSString *const STPTestJSONPaymentMethodApplePay; +extern NSString *const STPTestJSONPaymentMethodBacsDebit; + +extern NSString *const STPTestJSONSource3DS; +extern NSString *const STPTestJSONSourceAlipay; +extern NSString *const STPTestJSONSourceBancontact; +extern NSString *const STPTestJSONSourceCard; +extern NSString *const STPTestJSONSourceEPS; +extern NSString *const STPTestJSONSourceGiropay; +extern NSString *const STPTestJSONSourceiDEAL; +extern NSString *const STPTestJSONSourceMultibanco; +extern NSString *const STPTestJSONSourceP24; +extern NSString *const STPTestJSONSourceSEPADebit; +extern NSString *const STPTestJSONSourceSofort; + +@interface STPFixtures : NSObject + +/** + An STPConnectAccountParams object with all of the fields filled in, and + ToS accepted. + */ ++ (STPConnectAccountParams *)accountParams; + +/** + An Address object with all fields filled. + */ ++ (STPAddress *)address; + +/** + A PKPaymentObject with test payment data. + */ ++ (PKPayment *)applePayPayment; + +/** + A PKPayment from the simulator that can be tokenized in testmode. + */ ++ (PKPayment *)simulatorApplePayPayment; + +/** + A valid PKPaymentRequest with dummy data. + */ ++ (PKPaymentRequest *)applePayRequest; + +/** + A BankAccountParams object with all fields filled. + */ ++ (STPBankAccountParams *)bankAccountParams; + +/** + A CardParams object with a valid number, expMonth, expYear, and cvc. + */ ++ (STPCardParams *)cardParams; + +/** + A valid card object + */ ++ (STPCard *)card; + +/** + A Source object with type card + */ ++ (STPSource *)cardSource; + +/** + A Token for a card + */ ++ (STPToken *)cardToken; + +/** + A Customer object with an empty sources array. + */ ++ (STPCustomer *)customerWithNoSources; + +/** + A Customer object with a single card token in its sources array, and + default_source set to that card token. + */ ++ (STPCustomer *)customerWithSingleCardTokenSource; + +/** + The JSON data for a Customer with a single card token in its sources array, and + default_source set to that card token. + */ ++ (NSDictionary *)customerWithSingleCardTokenSourceJSON; + +/** + A Customer object with a single card source in its sources array, and + default_source set to that card source. + */ ++ (STPCustomer *)customerWithSingleCardSourceSource; + +/** + A Customer object with two cards in its sources array, + one a token/card type and one a source object type. + default_source is set to the card token. + */ ++ (STPCustomer *)customerWithCardTokenAndSourceSources; + +/** + A Customer object with a card source, and apple pay card source, and + default_source set to the apple pay source. + */ ++ (STPCustomer *)customerWithCardAndApplePaySources; + +/** + A Customer JSON blob with a card source, and apple pay card source, and + default_source set to the apple pay source. + */ ++ (NSDictionary *)customerWithCardAndApplePaySourcesJSON; + +/** + A customer object with a sources array that includes the listed json sources + in the order they are listed in the array. + + Valid keys are any STPTestJSONSource constants and the STPTestJSONCard constant. + + Ids for the sources will be automatically generated and will be equal to a + string that is the index of the array of that source. + */ ++ (STPCustomer *)customerWithSourcesFromJSONKeys:(NSArray *)jsonSourceKeys + defaultSource:(NSString *)jsonKeyForDefaultSource; + +/** + A Source object with type iDEAL + */ ++ (STPSource *)iDEALSource; + +/** + A Source object with type Alipay + */ ++ (STPSource *)alipaySource; + +/** + A Source object with type WeChat Pay + */ ++ (STPSource *)weChatPaySource; + +/** + A Source object with type Alipay and a native redirect url + */ ++ (STPSource *)alipaySourceWithNativeURL; + +/** + A PaymentIntent object + */ ++ (STPPaymentIntent *)paymentIntent; + +/** + A SetupIntent object + */ ++ (STPSetupIntent *)setupIntent; + +/** + A PaymentMethod object + */ ++ (STPPaymentMethod *)paymentMethod; + +/** + A PaymentMethod JSON dictionary + */ ++ (NSDictionary *)paymentMethodJSON; + +/** + A STPPaymentMethodCardParams object with a valid number, expMonth, expYear, and cvc. + */ ++ (STPPaymentMethodCardParams *)paymentMethodCardParams; + +/** + An Apple Pay Payment Method object. + */ ++ (STPPaymentMethod *)applePayPaymentMethod; + +/** + An Apple Pay Payment Method JSON dictionary. + */ ++ (NSDictionary *)applePayPaymentMethodJSON; + +/** + Bank account payment method + */ ++ (STPPaymentMethod *)bankAccountPaymentMethod; + +/** + Bank account payment payment method JSON Dictionary + */ ++ (NSDictionary *)bankAccountPaymentMethodJSON; + +@end + +@interface STPJsonSources : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/StripePayments/StripePaymentsObjcTestUtils/STPFixtures.m b/StripePayments/StripePaymentsObjcTestUtils/STPFixtures.m new file mode 100644 index 00000000..50bbbdea --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/STPFixtures.m @@ -0,0 +1,350 @@ +// +// STPFixtures.m +// Stripe +// +// Created by Ben Guo on 3/28/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +#import "STPFixtures.h" +#import "STPTestUtils.h" + +NSString *const STPTestJSONCustomer = @"Customer"; + +NSString *const STPTestJSONCard = @"Card"; + +NSString *const STPTestJSONPaymentIntent = @"PaymentIntent"; +NSString *const STPTestJSONSetupIntent = @"SetupIntent"; + +NSString *const STPTestJSONPaymentMethodCard = @"CardPaymentMethod"; +NSString *const STPTestJSONPaymentMethodApplePay = @"ApplePayPaymentMethod"; +NSString *const STPTestJSONPaymentMethodBacsDebit = @"BacsDebitPaymentMethod"; +NSString *const STPTestJSONSourceBankAccount = @"BankAccount"; + +NSString *const STPTestJSONSource3DS = @"3DSSource"; +NSString *const STPTestJSONSourceAlipay = @"AlipaySource"; +NSString *const STPTestJSONSourceBancontact = @"BancontactSource"; +NSString *const STPTestJSONSourceCard = @"CardSource"; +NSString *const STPTestJSONSourceEPS = @"EPSSource"; +NSString *const STPTestJSONSourceGiropay = @"GiropaySource"; +NSString *const STPTestJSONSourceiDEAL = @"iDEALSource"; +NSString *const STPTestJSONSourceMultibanco = @"MultibancoSource"; +NSString *const STPTestJSONSourceP24 = @"P24Source"; +NSString *const STPTestJSONSourceSEPADebit = @"SEPADebitSource"; +NSString *const STPTestJSONSourceSofort = @"SofortSource"; +NSString *const STPTestJSONSourceWeChatPay = @"WeChatPaySource"; + +@import StripeCore; + +@implementation STPFixtures + ++ (STPConnectAccountParams *)accountParams { + STPConnectAccountIndividualParams *params = [STPConnectAccountIndividualParams new]; + return [[STPConnectAccountParams alloc] initWithTosShownAndAccepted:YES + individual:params]; +} + ++ (STPAddress *)address { + STPAddress *address = [STPAddress new]; + address.name = @"Jenny Rosen"; + address.phone = @"5555555555"; + address.email = @"jrosen@example.com"; + address.line1 = @"27 Smith St"; + address.line2 = @"Apt 2"; + address.postalCode = @"10001"; + address.city = @"New York"; + address.state = @"NY"; + address.country = @"US"; + return address; +} + ++ (STPBankAccountParams *)bankAccountParams { + STPBankAccountParams *bankParams = [STPBankAccountParams new]; + // https://stripe.com/docs/testing#account-numbers + bankParams.accountNumber = @"000123456789"; + bankParams.routingNumber = @"110000000"; + bankParams.country = @"US"; + bankParams.currency = @"usd"; + bankParams.accountNumber = @"Jenny Rosen"; + return bankParams; +} + ++ (STPCardParams *)cardParams { + STPCardParams *cardParams = [STPCardParams new]; + cardParams.number = @"4242424242424242"; + cardParams.expMonth = 10; + cardParams.expYear = 99; + cardParams.cvc = @"123"; + return cardParams; +} + ++ (STPPaymentMethodCardParams *)paymentMethodCardParams { + STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new]; + cardParams.number = @"4242424242424242"; + cardParams.expMonth = @(10); + cardParams.expYear = @(50); + cardParams.cvc = @"123"; + return cardParams; +} + ++ (STPCard *)card { + return [STPCard decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:STPTestJSONCard]]; +} + ++ (STPSource *)cardSource { + return [STPSource decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:STPTestJSONSourceCard]]; +} + ++ (STPToken *)cardToken { + NSDictionary *cardDict = [STPTestUtils jsonNamed:STPTestJSONCard]; + NSDictionary *tokenDict = @{ + @"id": @"id_for_token", + @"object": @"token", + @"livemode": @NO, + @"created": @1353025450.0, + @"type": @"card", + @"used": @NO, + @"card": cardDict + }; + return [STPToken decodedObjectFromAPIResponse:tokenDict]; +} + ++ (STPCustomer *)customerWithNoSources { + return [STPCustomer decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:STPTestJSONCustomer]]; +} + ++ (STPCustomer *)customerWithSingleCardTokenSource { + return [STPCustomer decodedObjectFromAPIResponse:[self customerWithSingleCardTokenSourceJSON]]; +} + ++ (NSDictionary *)customerWithSingleCardTokenSourceJSON { + NSMutableDictionary *card1 = [[STPTestUtils jsonNamed:STPTestJSONCard] mutableCopy]; + card1[@"id"] = @"card_123"; + + NSMutableDictionary *customer = [[STPTestUtils jsonNamed:STPTestJSONCustomer] mutableCopy]; + NSMutableDictionary *sources = [customer[@"sources"] mutableCopy]; + sources[@"data"] = @[card1]; + customer[@"default_source"] = card1[@"id"]; + customer[@"sources"] = sources; + + return customer; +} + ++ (STPCustomer *)customerWithSingleCardSourceSource { + NSMutableDictionary *card1 = [[STPTestUtils jsonNamed:STPTestJSONSourceCard] mutableCopy]; + card1[@"id"] = @"card_123"; + + NSMutableDictionary *customer = [[STPTestUtils jsonNamed:STPTestJSONCustomer] mutableCopy]; + NSMutableDictionary *sources = [customer[@"sources"] mutableCopy]; + sources[@"data"] = @[card1]; + customer[@"default_source"] = card1[@"id"]; + customer[@"sources"] = sources; + + return [STPCustomer decodedObjectFromAPIResponse:customer]; +} + ++ (STPCustomer *)customerWithCardTokenAndSourceSources { + NSMutableDictionary *card1 = [[STPTestUtils jsonNamed:STPTestJSONCard] mutableCopy]; + card1[@"id"] = @"card_123"; + + NSMutableDictionary *card2 = [[STPTestUtils jsonNamed:STPTestJSONSourceCard] mutableCopy]; + card2[@"id"] = @"src_456"; + + NSMutableDictionary *customer = [[STPTestUtils jsonNamed:STPTestJSONCustomer] mutableCopy]; + NSMutableDictionary *sources = [customer[@"sources"] mutableCopy]; + sources[@"data"] = @[card1, card2]; + customer[@"default_source"] = card1[@"id"]; + customer[@"sources"] = sources; + + return [STPCustomer decodedObjectFromAPIResponse:customer]; + +} + ++ (STPCustomer *)customerWithCardAndApplePaySources { + return [STPCustomer decodedObjectFromAPIResponse:[self customerWithCardAndApplePaySourcesJSON]]; +} + ++ (NSDictionary *)customerWithCardAndApplePaySourcesJSON { + NSMutableDictionary *card1 = [[STPTestUtils jsonNamed:STPTestJSONSourceCard] mutableCopy]; + card1[@"id"] = @"src_apple_pay_123"; + NSMutableDictionary *cardDict = [card1[@"card"] mutableCopy]; + cardDict[@"tokenization_method"] = @"apple_pay"; + card1[@"card"] = cardDict; + + NSMutableDictionary *card2 = [[STPTestUtils jsonNamed:STPTestJSONSourceCard] mutableCopy]; + card2[@"id"] = @"src_card_456"; + + NSMutableDictionary *customer = [[STPTestUtils jsonNamed:STPTestJSONCustomer] mutableCopy]; + NSMutableDictionary *sources = [customer[@"sources"] mutableCopy]; + sources[@"data"] = @[card1, card2]; + customer[@"default_source"] = card1[@"id"]; + customer[@"sources"] = sources; + + return customer; +} + ++ (STPCustomer *)customerWithSourcesFromJSONKeys:(NSArray *)jsonSourceKeys + defaultSource:(NSString *)jsonKeyForDefaultSource { + NSMutableArray *sourceJSONDicts = [NSMutableArray new]; + NSString *defaultSourceID = nil; + NSUInteger sourceCount = 0; + for (NSString *jsonKey in jsonSourceKeys) { + NSMutableDictionary *sourceDict = [[STPTestUtils jsonNamed:jsonKey] mutableCopy]; + sourceDict[@"id"] = [NSString stringWithFormat:@"%@", @(sourceCount)]; + if ([jsonKeyForDefaultSource isEqualToString:jsonKey]) { + defaultSourceID = sourceDict[@"id"]; + } + sourceCount += 1; + [sourceJSONDicts addObject:sourceDict.copy]; + } + + NSMutableDictionary *customer = [[STPTestUtils jsonNamed:STPTestJSONCustomer] mutableCopy]; + NSMutableDictionary *sources = [customer[@"sources"] mutableCopy]; + sources[@"data"] = sourceJSONDicts.copy; + customer[@"default_source"] = defaultSourceID ?: @""; + customer[@"sources"] = sources; + + return [STPCustomer decodedObjectFromAPIResponse:customer]; +} + ++ (STPSource *)iDEALSource { + return [STPSource decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:STPTestJSONSourceiDEAL]]; +} + ++ (STPSource *)alipaySource { + return [STPSource decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:STPTestJSONSourceAlipay]]; +} + ++ (STPSource *)alipaySourceWithNativeURL { + NSMutableDictionary *dictionary = [STPTestUtils jsonNamed:STPTestJSONSourceAlipay].mutableCopy; + NSMutableDictionary *detailsDictionary = ((NSDictionary *)dictionary[@"alipay"]).mutableCopy; + detailsDictionary[@"native_url"] = @"alipay://test"; + dictionary[@"alipay"] = detailsDictionary; + return [STPSource decodedObjectFromAPIResponse:dictionary]; +} + ++ (STPSource *)weChatPaySource { + return [STPSource decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:STPTestJSONSourceWeChatPay]]; +} + ++ (STPPaymentIntent *)paymentIntent { + return [STPPaymentIntent decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:@"PaymentIntent"]]; +} + ++ (STPSetupIntent *)setupIntent { + return [STPSetupIntent decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:@"SetupIntent"]]; +} + ++ (PKPaymentRequest *)applePayRequest { + PKPaymentRequest *paymentRequest = [StripeAPI paymentRequestWithMerchantIdentifier:@"foo" country:@"US" currency:@"USD"]; + paymentRequest.paymentSummaryItems = @[[PKPaymentSummaryItem summaryItemWithLabel:@"bar" amount:[NSDecimalNumber decimalNumberWithString:@"10.00"]]]; + return paymentRequest; +} + ++ (PKPayment *)simulatorApplePayPayment { + PKPayment *payment = [PKPayment new]; + PKPaymentToken *paymentToken = [PKPaymentToken new]; + PKPaymentMethod *paymentMethod = [PKPaymentMethod new]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + [paymentMethod performSelector:@selector(setDisplayName:) withObject:@"Simulated Instrument"]; + [paymentMethod performSelector:@selector(setNetwork:) withObject:@"AmEx"]; + [paymentToken performSelector:@selector(setTransactionIdentifier:) withObject:@"Simulated Identifier"]; + + [paymentToken performSelector:@selector(setPaymentMethod:) withObject:paymentMethod]; + + [payment performSelector:@selector(setToken:) withObject:paymentToken]; + + // Add shipping + PKContact *shipping = [PKContact new]; + shipping.name = [[NSPersonNameComponentsFormatter new] personNameComponentsFromString:@"Jane Doe"]; + CNMutablePostalAddress *address = [CNMutablePostalAddress new]; + address.street = @"510 Townsend St"; + shipping.postalAddress = address; + [payment performSelector:@selector(setShippingContact:) withObject:shipping]; +#pragma clang diagnostic pop + return payment; +} + ++ (PKPayment *)applePayPayment { + PKPayment *payment = [PKPayment new]; + PKPaymentToken *paymentToken = [PKPaymentToken new]; + NSString *tokenDataString = @"{\"version\":\"EC_v1\",\"data\":\"lF8RBjPvhc2GuhjEh7qFNijDJjxD/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6+zeBx8OocvalHZ5ba/WA/" + @"tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY+utmdORhxyH4nUw2wuEY4lAE7/GtEU/RNDhaKx/" + @"m93l0oLlk84qD1ynTA5JP3gjkdX+RK23iCAZDScXCcCU0OnYlJV8sDyf3+8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2+" + @"DNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3+yrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E+" + @"LlBdXiREmlz4u1EA=\",\"signature\":" + @"\"MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQX" + @"BwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MD" + @"kyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCz" + @"AJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O/" + @"0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22/" + @"VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUI/JJxE+T5O8n5sT2KGw/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB/" + @"jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZ" + @"CB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3d" + @"y5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB/" + @"wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw+Soyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6+" + @"W5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS+/" + @"OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQsw" + @"CQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENl" + @"cnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou/" + @"ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT/" + @"VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk+TvJ+bE9ihsP6K7/" + @"S5LMA8GA1UdEwEB/" + @"wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH/" + @"BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr/0F+3ZD3VNoo6+8ZyBXkK3ifiY95tZn5jVQQ2PnenC/gIwMi3VRCGwowV3bF3zODuQZ/" + @"0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcH" + @"BsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQ" + @"EHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf/" + @"iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR+L6OVBH6iTPIFMBNPcq6gAAAAAAAA=\"," + @"\"header\":{\"transactionId\":\"a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9\",\"ephemeralPublicKey\":" + @"\"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV/" + @"P7CQztw28IvYozvKvjBPQ==\",\"publicKeyHash\":\"yRcyn7njT6JL3AY9nmg0KD/xm/ch7gW1sGl2OuEucZY=\"}}"; + NSData *data = [tokenDataString dataUsingEncoding:NSUTF8StringEncoding]; + + NSPersonNameComponents *nameComponents = [[NSPersonNameComponents alloc] init]; + [nameComponents setGivenName:@"Test"]; + [nameComponents setFamilyName:@"Testerson"]; + PKContact *contact = [[PKContact alloc] init]; + contact.name = nameComponents; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + // Add a fake display name + PKPaymentMethod *paymentMethod = [[PKPaymentMethod alloc] init]; + [paymentMethod performSelector:@selector(setDisplayName:) withObject:@"Master Charge"]; + + [paymentToken performSelector:@selector(setPaymentMethod:) withObject:paymentMethod]; + + [paymentToken performSelector:@selector(setPaymentData:) withObject:data]; + [payment performSelector:@selector(setToken:) withObject:paymentToken]; + [payment performSelector:@selector(setBillingContact:) withObject:contact]; +#pragma clang diagnostic pop + return payment; +} + +#pragma mark - Payment Method + ++ (STPPaymentMethod *)paymentMethod { + return [STPPaymentMethod decodedObjectFromAPIResponse:[self paymentMethodJSON]]; +} + ++ (NSDictionary *)paymentMethodJSON { + return [STPTestUtils jsonNamed:STPTestJSONPaymentMethodCard]; +} + ++ (STPPaymentMethod *)applePayPaymentMethod { + return [STPPaymentMethod decodedObjectFromAPIResponse:[self applePayPaymentMethodJSON]]; +} + ++ (NSDictionary *)applePayPaymentMethodJSON { + return [STPTestUtils jsonNamed:STPTestJSONPaymentMethodApplePay]; +} ++ (STPPaymentMethod *)bankAccountPaymentMethod { + return [STPPaymentMethod decodedObjectFromAPIResponse:[self bankAccountPaymentMethodJSON]]; +} ++ (NSDictionary *)bankAccountPaymentMethodJSON { + return [STPTestUtils jsonNamed:STPTestJSONSourceBankAccount]; +} + +@end diff --git a/StripePayments/StripePaymentsObjcTestUtils/STPTestUtils.h b/StripePayments/StripePaymentsObjcTestUtils/STPTestUtils.h new file mode 100644 index 00000000..66440c27 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/STPTestUtils.h @@ -0,0 +1,49 @@ +// +// STPTestUtils.h +// Stripe +// +// Created by Ben Guo on 7/14/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +#import +#import + +@interface STPTestUtils : NSObject + ++ (NSDictionary *)jsonNamed:(NSString *)name; + +/** + Using runtime inspection, what are all the property names for this object? + + @param object the object to introspect + @return list of property names, usable with `valueForKey:` + */ ++ (NSArray *)propertyNamesOf:(NSObject *)object; + +@end + + +/** + Custom assertion function to compare to UIImage instances. + + On iOS 9, `XCTAssertEqualObjects` incorrectly fails when provided with identical images. + + This just calls `XCTAssertEqualObjects` with the `UIImagePNGRepresentation` of each + image. Can be removed when we drop support for iOS 9. + + @param image1 First UIImage to compare + @param image2 Second UIImage to compare + */ +NS_INLINE void STPAssertEqualImages(UIImage *image1, UIImage *image2) { + XCTAssertEqualObjects(UIImagePNGRepresentation(image1), UIImagePNGRepresentation(image2)); +}; + +/** + Calls FBSnapshotVerifyView with a default 2% per-pixel color differentiation, as M1 and Intel machines render shadows differently. + @param view The view to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + */ +#define STPSnapshotVerifyView(view__, identifier__) \ +FBSnapshotVerifyViewWithPixelOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0.02, 0) + diff --git a/StripePayments/StripePaymentsObjcTestUtils/STPTestUtils.m b/StripePayments/StripePaymentsObjcTestUtils/STPTestUtils.m new file mode 100644 index 00000000..103f30f6 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/STPTestUtils.m @@ -0,0 +1,73 @@ +// +// STPTestUtils.m +// Stripe +// +// Created by Ben Guo on 7/14/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +#import "STPTestUtils.h" + +@import ObjectiveC.runtime; + +@implementation STPTestUtils + ++ (NSDictionary *)jsonNamed:(NSString *)name { + NSData *data = [self dataFromJSONFile:name]; + if (data != nil) { + return [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)kNilOptions error:nil]; + } + return nil; +} + ++ (NSArray *)propertyNamesOf:(NSObject *)object { + uint propertyCount; + objc_property_t *propertyList = class_copyPropertyList([object class], &propertyCount); + NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity:propertyCount]; + + for (uint i = 0; i < propertyCount; i++) { + objc_property_t property = propertyList[i]; + NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)]; + [propertyNames addObject:propertyName]; + } + free(propertyList); + return propertyNames; +} + +#pragma mark - + ++ (NSBundle *)testBundle { + return [NSBundle bundleForClass:[STPTestUtils class]]; +} + ++ (NSData *)dataFromJSONFile:(NSString *)name { + NSBundle *bundle = [self testBundle]; + NSString *path = [bundle pathForResource:name ofType:@"json" inDirectory:@"Mock Files"]; + + if (!path) { + // Missing JSON file + NSLog(@"Missing file: %@ in bundle: %@", name, bundle); + return nil; + } + + NSError *error = nil; + NSString *jsonString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; + + if (!jsonString) { + // File read error + return nil; + } + + // Strip all lines that begin with `//` + NSMutableArray *jsonLines = [[NSMutableArray alloc] init]; + + for (NSString *line in [jsonString componentsSeparatedByString:@"\n"]) { + if (![line hasPrefix:@"//"]) { + [jsonLines addObject:line]; + } + } + + return [[jsonLines componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; +} + +@end diff --git a/StripePayments/StripePaymentsObjcTestUtils/STPTestingAPIClient.h b/StripePayments/StripePaymentsObjcTestUtils/STPTestingAPIClient.h new file mode 100644 index 00000000..9b6541cd --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/STPTestingAPIClient.h @@ -0,0 +1,92 @@ +// +// STPTestingAPIClient.h +// StripeiOS +// +// Created by Cameron Sabol on 2/20/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import + +@import StripeCore; +@class STPEphemeralKey; + +NS_ASSUME_NONNULL_BEGIN + +/** + Test account info: + Account: acct_1G6m1pFY0qyl6XeW + Dashboard login/pw: fetch mobile-payments-sdk-ci + */ +static NSString * const STPTestingDefaultPublishableKey = @"pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6"; +// Test account in Australia +static NSString * const STPTestingAUPublishableKey = @"pk_test_GNmlCJ6AFgWXm4mJYiyWSOWN00KIIiri7F"; +// Test account in Mexico +static NSString * const STPTestingMEXPublishableKey = @"pk_test_51GvAY5HNG4o8pO5lDEegY72rkF1TMiMyuTxSFJsmsH7U0KjTwmEf2VuXHVHecil64QA8za8Um2uSsFsfrG0BkzFo00sb1uhblF"; +// Test account in SG +static NSString * const STPTestingSGPublishableKey = @"pk_test_51H7oXMAOnZToJom1hqiSvNGsUVTrG1SaXRSBon9xcEp0yDFAxEh5biA4n0ty6paEsD5Mo5ps1b7Taj9WAHQzjup800m8A8Nc3u"; +// Test account in Belgium +static NSString * const STPTestingBEPublishableKey = @"pk_test_51HZi0VArGMi59tL4sIXUjwXbMiM5uSHVfsKjNXcepJ80C5niX4bCm5rJ3CeDI1vjZ5Mz55Phsmw9QqjoZTsBFoWh009RQaGx0R"; +static NSString * const STPTestingINPublishableKey = @"pk_test_51H7wmsBte6TMTRd4gph9Wm7gnQOKJwdVTCj30AhtB8MhWtlYj6v9xDn1vdCtKYGAE7cybr6fQdbQQtgvzBihE9cl00tOnrTpL9"; +// Test account in Brazil +static NSString * const STPTestingBRPublishableKey = @"pk_test_51JYFFjJQVROkWvqT6Hy9pW7uPb6UzxT3aACZ0W3olY8KunzDE9mm6OxE5W2EHcdZk7LxN6xk9zumFbZL8zvNwixR0056FVxQmt"; +// Test account in Great Britain +static NSString * const STPTestingGBPublishableKey = @"pk_test_51KmkHbGoesj9fw9QAZJlz1qY4dns8nFmLKc7rXiWKAIj8QU7NPFPwSY1h8mqRaFRKQ9njs9pVJoo2jhN6ZKSDA4h00mjcbGF7b"; +// Test account in Malaysia +static NSString * const STPTestingMYPublishableKey = + @"pk_test_vGCjSmT6Idy5zwfGBKnlq5rd00JT2vbrHb"; +static NSString * const STPTestingJPPublishableKey = + @"pk_test_51NpIYRIq2LmpyICoBLPaTxfWFW4I34pnWuBjKXf8CgOlVih7Ni6oDfPRHGTzBEnpsrHiPvqP2UyydilqY66BWp8N00mQCJ1PU5"; +// Test account in France +static NSString * const STPTestingFRPublishableKey = + @"pk_test_51JtgfQKG6vc7r7YCU0qQNOkDaaHrEgeHgGKrJMNfuWwaKgXMLzPUA1f8ZlCNPonIROLOnzpUnJK1C1xFH3M3Mz8X00Q6O4GfUt"; +// Test account in Thailand +static NSString * const STPTestingTHPublishableKey = + @"pk_test_51NpEAWBgCYKNuUnnoBpaJZQYWOO6UpLtcioKggla08zpvDDy0cjfGKZdl5BsU8Gm5ilJNCqT7laCsqvyc0LndskG00pnPnJSpD"; + +// Test account in Germany +// Account token: acct_1PSnNaAlz2yHYCNZ +// Scenario link: https://admin.corp.stripe.com/scenarios?runId=scnrun*AZAoKlcYbwAAAIDN +static NSString * const STPTestingDEPublishableKey = + @"pk_test_51PSnNaAlz2yHYCNZgjajit4L8Hl1rDDPPCj9XhHNZWRSi4vwHhrHIbTgstLJptPSzwQVl1HlyqhwWRs1rBJHag8W00sM0SOXIL"; + +// Test account in Italy +// Account token: acct_1PSnETIFbdis1OxT +// Scenario link: https://admin.corp.stripe.com/scenarios?runId=scnrun*AZAoIbaznQAAAJ96 +static NSString * const STPTestingITPublishableKey = + @"pk_test_51PSnETIFbdis1OxTALF4Z8ugUQpVS06UQDVahMSmwrbEYphjNYitXtOSqMPVKfzl3jukg6gLLrtZNnPlDrRbDpMd00U0tId6iv"; + +@interface STPTestingAPIClient : NSObject + ++ (instancetype)sharedClient; + +// Set this to the Stripe SDK session for SWHTTPRecorder recording to work correctly +@property (nonatomic, readwrite) NSURLSessionConfiguration *sessionConfig; + +- (void)createPaymentIntentWithParams:(nullable NSDictionary *)params + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion; + +- (void)createPaymentIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account // nil for default or "au" for Australia test account or "mex" for Mexico test account + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion; + +- (void)createPaymentIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account // nil for default or "au" for Australia test account or "mex" for Mexico test account + apiVersion:(nullable NSString *)apiVersion // nil for default or pass with beta headers + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion; + +- (void)createSetupIntentWithParams:(nullable NSDictionary *)params + completion:(void (^)(NSString *_Nullable, NSError * _Nullable))completion; + +- (void)createSetupIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account // nil for default or "au" for Australia test account or "mex" for Mexico test account + completion:(void (^)(NSString *_Nullable, NSError * _Nullable))completion; + +- (void)createSetupIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account // nil for default or "au" for Australia test account or "mex" for Mexico test account + apiVersion:(nullable NSString *)apiVersion // nil for default or pass with beta headers + completion:(void (^)(NSString *_Nullable, NSError * _Nullable))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/StripePayments/StripePaymentsObjcTestUtils/STPTestingAPIClient.m b/StripePayments/StripePaymentsObjcTestUtils/STPTestingAPIClient.m new file mode 100644 index 00000000..5ed41196 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/STPTestingAPIClient.m @@ -0,0 +1,178 @@ +// +// STPTestingAPIClient.m +// StripeiOS +// +// Created by Cameron Sabol on 2/20/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import "STPTestingAPIClient.h" +@import StripeCore; + +static NSString * const STPTestingBackendURL = @"https://stp-mobile-ci-test-backend-e1b3.stripedemos.com/"; + +NS_ASSUME_NONNULL_BEGIN + +@implementation STPTestingAPIClient + ++ (instancetype)sharedClient { + static dispatch_once_t onceToken; + static STPTestingAPIClient *sharedClient = nil; + dispatch_once(&onceToken, ^{ + sharedClient = [[STPTestingAPIClient alloc] init]; + }); + + return sharedClient; +} + +- (instancetype)init { + self = [super init]; + self.sessionConfig = [[NSURLSession sharedSession] configuration]; + return self; +} + +- (void)createPaymentIntentWithParams:(nullable NSDictionary *)params + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion { + [self createPaymentIntentWithParams:params + account:nil + completion:completion]; +} + +- (void)createPaymentIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion { + [self createPaymentIntentWithParams:params + account:account + apiVersion:nil + completion:completion]; +} + +- (void)createPaymentIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account + apiVersion:(nullable NSString *)apiVersion + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion { + NSURLSession *session = [NSURLSession sessionWithConfiguration:self.sessionConfig]; + NSURL *url = [NSURL URLWithString:[STPTestingBackendURL stringByAppendingString:@"create_payment_intent"]]; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSData *postData = [NSJSONSerialization dataWithJSONObject:@{@"account" : account ?: @"", + @"create_params": params ?: @{}, + @"version": apiVersion ?: STPAPIClient.apiVersion, + } options:0 error:NULL]; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:postData + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (error) { + completion(nil, error); + } else if (data == nil || httpResponse.statusCode != 200) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *errorStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSDictionary *userInfo = @{ + STPError.errorMessageKey: errorStr, + NSLocalizedDescriptionKey: errorStr + }; + NSError *apiError = [NSError errorWithDomain:STPError.stripeDomain code:STPAPIError userInfo:userInfo]; + NSLog(@"%@", apiError); + completion(nil, apiError); + }); + } else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && + [json isKindOfClass:[NSDictionary class]] && + [json[@"secret"] isKindOfClass:[NSString class]]) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(json[@"secret"], nil); + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, jsonError); + }); + } + } + }]; + + [uploadTask resume]; +} + +- (void)createSetupIntentWithParams:(nullable NSDictionary *)params + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion { + [self createSetupIntentWithParams:params + account:nil + completion:completion]; +} + +- (void)createSetupIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account + completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completion { + [self createSetupIntentWithParams:params + account:account + apiVersion:nil + completion:completion]; +} + +- (void)createSetupIntentWithParams:(nullable NSDictionary *)params + account:(nullable NSString *)account + apiVersion:(nullable NSString *)apiVersion + completion:(void (^)(NSString *_Nullable, NSError * _Nullable))completion { + NSURLSession *session = [NSURLSession sessionWithConfiguration:self.sessionConfig]; + NSURL *url = [NSURL URLWithString:[STPTestingBackendURL stringByAppendingString:@"create_setup_intent"]]; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + NSData *postData = [NSJSONSerialization dataWithJSONObject:@{@"account" : account ?: @"", + @"create_params": params ?: @{}, + @"version": apiVersion ?: STPAPIClient.apiVersion, + } options:0 error:NULL]; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:postData + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + if (error) { + completion(nil, error); + } else if (data == nil || httpResponse.statusCode != 200) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *errorStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSDictionary *userInfo = @{ + STPError.errorMessageKey: errorStr, + NSLocalizedDescriptionKey: errorStr + }; + NSError *apiError = [NSError errorWithDomain:STPError.stripeDomain code:STPAPIError userInfo:userInfo]; + NSLog(@"%@", apiError); + completion(nil, apiError); + }); + } else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && + [json isKindOfClass:[NSDictionary class]] && + [json[@"secret"] isKindOfClass:[NSString class]]) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(json[@"secret"], nil); + }); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, jsonError); + }); + } + } + }]; + + [uploadTask resume]; +} + +@end + + +NS_ASSUME_NONNULL_END diff --git a/StripePayments/StripePaymentsObjcTestUtils/SWHttpTrafficRecorder.h b/StripePayments/StripePaymentsObjcTestUtils/SWHttpTrafficRecorder.h new file mode 100755 index 00000000..ecd2fee9 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/SWHttpTrafficRecorder.h @@ -0,0 +1,211 @@ +/*********************************************************************************** + * Copyright 2015 Capital One Services, LLC + * SPDX-License-Identifier: Apache-2.0 + * SPDX-Copyright: Copyright (c) Capital One Services, LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***********************************************************************************/ + + +//////////////////////////////////////////////////////////////////////////////// + +// Created by Jinlian (Sunny) Wang on 8/23/15. + +#import + +//! Project version number for SWHttpTrafficRecorder. +FOUNDATION_EXPORT double SWHttpTrafficRecorderVersionNumber; + +//! Project version string for SWHttpTrafficRecorder. +FOUNDATION_EXPORT const unsigned char SWHttpTrafficRecorderVersionString[]; + +/** + * Recording formats that is supported by SWHttpTrafficRecorder. + */ +typedef NS_ENUM(NSInteger, SWHTTPTrafficRecordingFormat) { + /* Custom format when the recorder records a request through an optional createFileInCustomFormatBlock block. */ + SWHTTPTrafficRecordingFormatCustom = -1, + + /* For BodyOnly format, the recorder creates a recorded file for each request using only its response body. If it is a JSON response, the file uses .json extension. Otherwise, .txt extenion is used. */ + SWHTTPTrafficRecordingFormatBodyOnly = 1, + + /* For Mocktail format, the recorder creates a recorded file for each request and its response in format that is defined by Mocktail framework at https://github.com/puls/objc-mocktail. The file uses .tail extension. */ + SWHTTPTrafficRecordingFormatMocktail = 2, + + /* For HTTPMessage format, the recorder creates a recorded file for each request and its response in format can be deserialized into a CFHTTPMessageRef raw HTTP Message. 'curl -is' also outputs in this format. */ + SWHTTPTrafficRecordingFormatHTTPMessage = 3 +}; + +/** + * Error codes for SWHttpTrafficRecorder. + */ +typedef NS_ENUM(NSInteger, SWHttpTrafficRecorderError) { + /** The specified path does not exist and cannot be created */ + SWHttpTrafficRecorderErrorPathFailedToCreate = 1, + /** The specified path was not writable */ + SWHttpTrafficRecorderErrorPathNotWritable +}; + +/** + * Recording progress that is reported by SWHttpTrafficRecorder at each phase of recording a request and its response. + */ +typedef NS_ENUM(NSInteger, SWHTTPTrafficRecordingProgressKind) { + /* A HTTP Request is received by the recorder. */ + SWHTTPTrafficRecordingProgressReceived = 1, + + /* A HTTP Request is skipped by the recorder for recording. */ + SWHTTPTrafficRecordingProgressSkipped = 2, + + /* The recorder starts downloading the response for a request. */ + SWHTTPTrafficRecordingProgressStarted = 3, + + /* The recorder finishes downloading the response for a request. */ + SWHTTPTrafficRecordingProgressLoaded = 4, + + /* The recorder finishes recording the response for a request. */ + SWHTTPTrafficRecordingProgressRecorded = 5, + + /* The recorder fails to download the response for a request for whatever reason. */ + SWHTTPTrafficRecordingProgressFailedToLoad = 6, + + /* The recorder fails to record the response for a request for whatever reason. */ + SWHTTPTrafficRecordingProgressFailedToRecord = 7 +}; + +/* The key in a recording progress info dictionary whose value indicates the current NSURLRequest that is being recorded.*/ +FOUNDATION_EXPORT NSString * const SWHTTPTrafficRecordingProgressRequestKey; + +/* The key in a recording progress info dictionary whose value indicates the current NSHTTPURLResponse that is being recorded.*/ +FOUNDATION_EXPORT NSString * const SWHTTPTrafficRecordingProgressResponseKey; + +/* The key in a recording progress info dictionary whose value indicates the current NSData response body that is being recorded.*/ +FOUNDATION_EXPORT NSString * const SWHTTPTrafficRecordingProgressBodyDataKey; + +/* The key in a recording progress info dictionary whose value indicates the current file path that is used for the recorded file.*/ +FOUNDATION_EXPORT NSString * const SWHTTPTrafficRecordingProgressFilePathKey; + +/* The key in a recording progress info dictionary whose value indicates the current recording format.*/ +FOUNDATION_EXPORT NSString * const SWHTTPTrafficRecordingProgressFileFormatKey; + +/* The key in a recording progress info dictionary whose value indicates the NSErrror which fails the recording.*/ +FOUNDATION_EXPORT NSString * const SWHTTPTrafficRecordingProgressErrorKey; + +/* The error domain for SWHttpTrafficRecorder. */ +FOUNDATION_EXPORT NSString * const SWHttpTrafficRecorderErrorDomain; + +/** An optional delegate SWHttpTrafficRecorder uses to report its recording progress. + */ +@protocol SWHttpTrafficRecordingProgressDelegate + +/** + * Delegate method to be called by the recorder to update its current progress. + * @param currentProgress The current progress of the recording. + * @param info A recording progress info dictionary where its values (including a request object, its response, response body data, file path, recording format and NSError) can be retrieved through different keys. The available values depend on the current progress. + */ +- (void)updateRecordingProgress:(SWHTTPTrafficRecordingProgressKind)currentProgress userInfo:(NSDictionary *)info; + +@end + +/** + * An SWHttpTrafficRecorder lets you intercepts the http requests made by an application and records their responses in a specified format. There are three built-in formats supported: ResponseBodyOnly, Mocktail and HTTPMessage. These formats are widely used by various mocking/stubbing frameworks such as Mocktail(https://github.com/puls/objc-mocktail), OHHTTPStubs(https://github.com/AliSoftware/OHHTTPStubs/tree/master/OHHTTPStubs), Nocilla(https://github.com/luisobo/Nocilla), etc. You can also use it to monitor the traffic for debugging purpose. + */ +@interface SWHttpTrafficRecorder : NSObject + +/** + * Returns the shared recorder object. + */ ++ (instancetype)sharedRecorder; + +/** + * Method to start recording using default path. + */ +- (BOOL)startRecording; + +/** + * Method to start recording and saves recorded files at a specified location. + * @param path The path where recorded files are saved. + * @param error An out value that returns any error encountered while accessing the recordingPath. Returns an NSError object if any error; otherwise returns nil. + */ +- (BOOL)startRecordingAtPath:(NSString *)recordingPath error:(NSError **) error; + +/** + * Method to start recording and saves recorded files at a specified location using given session configuration. + * @param recordingPath The path where recorded files are saved. + * @param sessionConfig The NSURLSessionConfiguration which will be modified. + * @param error An out value that returns any error encountered while accessing the recordingPath. Returns an NSError object if any error; otherwise returns nil. + */ +- (BOOL)startRecordingAtPath:(NSString *)recordingPath forSessionConfiguration:(NSURLSessionConfiguration *)sessionConfig error:(NSError **) error; + +/** + * Method to stop recording. + */ +- (void)stopRecording; + +/** + * A Boolean value which indicates whether the recording is recording traffic. + */ +@property(nonatomic, readonly, assign) BOOL isRecording; + +/** + * A Boolean value indicating whether to follow HTTP redirects. + * If NO, the recorder will only record the body of the redirect instead of the redirect itself. + */ +@property(nonatomic, assign) BOOL followRedirects; + +/** + * A Enum value which indicates the format the recording is using to record traffic. + */ +@property(nonatomic, assign) SWHTTPTrafficRecordingFormat recordingFormat; + +/** + * A Dictionary containing Regex/Token pairs for replacement in response data + */ +@property(nonatomic, assign) NSMutableDictionary *replacementDict; + +/** + * The delegate where the recording progress are reported. + */ +@property(nonatomic, assign) id progressDelegate; + +/** + * The optional block (if provided) to be applied to every request to determine whether the request shall be recorded by the recorder. It takes a NSURLRequest as parameter and returns a Boolean value that indicates whether the request shall be recorded. + */ +@property(nonatomic, copy) BOOL(^recordingTestBlock)(NSURLRequest *request); + +/** + * The optional block (if provided) to be applied to every request to determine whether the response body shall be base64 encodes before recording. It takes a NSURLRequest as parameter and returns a Boolean value that indicates whether the response body shall be base64 encoded. + */ +@property(nonatomic, copy) BOOL(^base64TestBlock)(NSURLRequest *request, NSURLResponse *response); + +/** + * The optional block (if provided) to be applied to every request to determine what file name is to be used while creating the recorded file. It takes a NSURLRequest and a default name that is generated by the recorder as parameters and returns a NSString value which is used as filename while creating the recorded file. + */ +@property(nonatomic, copy) NSString*(^fileNamingBlock)(NSURLRequest *request, NSURLResponse *response, NSString *defaultName); + +/** + * The optional block (if provided) to be applied to every request to determine what regular expression is to be used while creating a recorded file of Mocktail format. It takes a NSURLRequest and a default regular expression pattern that is generated by the recorder as parameters and returns a NSString value which is used as the regular expression pattern while creating the recorded file. + */ +@property(nonatomic, copy) NSString*(^urlRegexPatternBlock)(NSURLRequest *request, NSString *defaultPattern); + +/** + * The optional block (if provided) to be applied to every request to create the recorded file when the recording format is custom. It takes a NSURLRequest, its response, a body data and a filePath as parameters and be expected to create the recorded file at the filePath. + */ +@property(nonatomic, copy) NSString*(^createFileInCustomFormatBlock)(NSURLRequest *request, NSURLResponse *response, NSData *bodyData, NSString *filePath); + +/** + * The optional block (if provided) to transform the HTTP request body, which will be stored in the `X-Stripe-Mock-Request` header of the Mocktail. + */ +@property(nonatomic, copy) NSString*(^postBodyTransformBlock)(NSURLRequest *request, NSString *postBody); + +@end diff --git a/StripePayments/StripePaymentsObjcTestUtils/SWHttpTrafficRecorder.m b/StripePayments/StripePaymentsObjcTestUtils/SWHttpTrafficRecorder.m new file mode 100755 index 00000000..adc6e101 --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/SWHttpTrafficRecorder.m @@ -0,0 +1,540 @@ +/*********************************************************************************** + * Copyright 2015 Capital One Services, LLC + * SPDX-License-Identifier: Apache-2.0 + * SPDX-Copyright: Copyright (c) Capital One Services, LLC + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***********************************************************************************/ + + +//////////////////////////////////////////////////////////////////////////////// + +// Created by Jinlian (Sunny) Wang on 8/23/15. + +#import "SWHttpTrafficRecorder.h" + +NSString * const SWHTTPTrafficRecordingProgressRequestKey = @"REQUEST_KEY"; +NSString * const SWHTTPTrafficRecordingProgressResponseKey = @"RESPONSE_KEY"; +NSString * const SWHTTPTrafficRecordingProgressBodyDataKey = @"BODY_DATA_KEY"; +NSString * const SWHTTPTrafficRecordingProgressFilePathKey = @"FILE_PATH_KEY"; +NSString * const SWHTTPTrafficRecordingProgressFileFormatKey= @"FILE_FORMAT_KEY"; +NSString * const SWHTTPTrafficRecordingProgressErrorKey = @"ERROR_KEY"; + +NSString * const SWHttpTrafficRecorderErrorDomain = @"RECORDER_ERROR_DOMAIN"; + +@interface SWHttpTrafficRecorder() +@property(nonatomic, assign, readwrite) BOOL isRecording; +@property(nonatomic, strong) NSString *recordingPath; +@property(nonatomic, assign) int fileNo; +@property(nonatomic, strong) NSOperationQueue *fileCreationQueue; +@property(nonatomic, strong) NSURLSessionConfiguration *sessionConfig; +@property(nonatomic, assign) NSUInteger runTimeStamp; +@property(nonatomic, strong) NSDictionary *fileExtensionMapping; +@end + +@interface SWRecordingProtocol : NSURLProtocol @end + +@implementation SWHttpTrafficRecorder + ++ (instancetype)sharedRecorder +{ + static SWHttpTrafficRecorder *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = self.new; + shared.isRecording = NO; + shared.followRedirects = YES; + shared.fileNo = 0; + shared.fileCreationQueue = [[NSOperationQueue alloc] init]; + shared.runTimeStamp = 0; + shared.recordingFormat = SWHTTPTrafficRecordingFormatMocktail; + }); + return shared; +} + +- (BOOL)startRecording{ + return [self startRecordingAtPath:nil forSessionConfiguration:nil error:nil]; +} + +- (BOOL)startRecordingAtPath:(NSString *)recordingPath error:(NSError **) error { + return [self startRecordingAtPath:recordingPath forSessionConfiguration:nil error:error]; +} + +- (BOOL)startRecordingAtPath:(NSString *)recordingPath forSessionConfiguration:(NSURLSessionConfiguration *)sessionConfig error:(NSError **) error { + if(!self.isRecording){ + if(recordingPath){ + self.recordingPath = recordingPath; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if(![fileManager fileExistsAtPath:recordingPath]){ + NSError *bError = nil; + if(![fileManager createDirectoryAtPath:recordingPath withIntermediateDirectories:YES attributes:nil error:&bError]){ + if(error){ + *error = [NSError errorWithDomain:SWHttpTrafficRecorderErrorDomain code:SWHttpTrafficRecorderErrorPathFailedToCreate userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Path '%@' does not exist and error while creating it.", recordingPath], NSUnderlyingErrorKey: bError}]; + } + return NO; + } + } else if(![fileManager isWritableFileAtPath:recordingPath]){ + if (error){ + *error = [NSError errorWithDomain:SWHttpTrafficRecorderErrorDomain code:SWHttpTrafficRecorderErrorPathNotWritable userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Path '%@' is not writable.", recordingPath]}]; + } + return NO; + } + } else { + self.recordingPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; + } + + self.fileNo = 0; + self.runTimeStamp = (NSUInteger)[NSDate timeIntervalSinceReferenceDate]; + } + if(sessionConfig){ + self.sessionConfig = sessionConfig; + NSMutableOrderedSet *mutableProtocols = [[NSMutableOrderedSet alloc] initWithArray:sessionConfig.protocolClasses]; + [mutableProtocols insertObject:[SWRecordingProtocol class] atIndex:0]; + sessionConfig.protocolClasses = [mutableProtocols array]; + } + else { + [NSURLProtocol registerClass:[SWRecordingProtocol class]]; + } + + self.isRecording = YES; + + return YES; +} + +- (void)stopRecording{ + if(self.isRecording){ + if(self.sessionConfig) { + NSMutableArray *mutableProtocols = [[NSMutableArray alloc] initWithArray:self.sessionConfig.protocolClasses]; + [mutableProtocols removeObject:[SWRecordingProtocol class]]; + self.sessionConfig.protocolClasses = mutableProtocols; + self.sessionConfig = nil; + } + else { + [NSURLProtocol unregisterClass:[SWRecordingProtocol class]]; + } + } + self.isRecording = NO; +} + +- (int)increaseFileNo{ + @synchronized(self) { + return self.fileNo++; + } +} + +- (NSDictionary *)fileExtensionMapping{ + if(!_fileExtensionMapping){ + _fileExtensionMapping = @{ + @"application/json": @"json", @"image/png": @"png", @"image/jpeg" : @"jpg", + @"image/gif": @"gif", @"image/bmp": @"bmp", @"text/plain": @"txt", + @"text/css": @"css", @"text/html": @"html", @"application/javascript": @"js", + @"text/javascript": @"js", @"application/xml": @"xml", @"text/xml": @"xml", + @"image/tiff": @"tiff", @"image/x-tiff": @"tiff" + }; + } + return _fileExtensionMapping; +} + +@end + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Private Protocol Class + + +static NSString * const SWRecordingLProtocolHandledKey = @"SWRecordingLProtocolHandledKey"; + +@interface SWRecordingProtocol () + +@property (nonatomic, strong) NSURLSessionDataTask *dataTask; +@property (nonatomic, strong) NSMutableData *mutableData; +@property (nonatomic, strong) NSURLResponse *response; +@property (nonatomic, strong) NSURLSession *session; + +@end + + +@implementation SWRecordingProtocol + +#pragma mark - NSURLProtocol overrides + ++ (BOOL)canInitWithRequest:(NSURLRequest *)request { + BOOL isHTTP = [request.URL.scheme isEqualToString:@"https"] || [request.URL.scheme isEqualToString:@"http"]; + if ([NSURLProtocol propertyForKey:SWRecordingLProtocolHandledKey inRequest:request] || !isHTTP) { + return NO; + } + + [self updateRecorderProgressDelegate:SWHTTPTrafficRecordingProgressReceived userInfo:@{SWHTTPTrafficRecordingProgressRequestKey: request}]; + + BOOL(^testBlock)(NSURLRequest *request) = [SWHttpTrafficRecorder sharedRecorder].recordingTestBlock; + BOOL canInit = YES; + if(testBlock){ + canInit = testBlock(request); + } + if(!canInit){ + [self updateRecorderProgressDelegate:SWHTTPTrafficRecordingProgressSkipped userInfo:@{SWHTTPTrafficRecordingProgressRequestKey: request}]; + } + return canInit; +} + ++ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request { + return request; +} + +- (void) startLoading { + NSMutableURLRequest *newRequest = [self.request mutableCopy]; + [NSURLProtocol setProperty:@YES forKey:SWRecordingLProtocolHandledKey inRequest:newRequest]; + + [self.class updateRecorderProgressDelegate:SWHTTPTrafficRecordingProgressStarted userInfo:@{SWHTTPTrafficRecordingProgressRequestKey: self.request}]; + + self.session = [NSURLSession sessionWithConfiguration:SWHttpTrafficRecorder.sharedRecorder.sessionConfig + delegate:self + delegateQueue:nil]; + self.dataTask = [self.session dataTaskWithRequest:newRequest]; + [self.dataTask resume]; +} + +- (void) stopLoading { + [self.dataTask cancel]; + self.mutableData = nil; +} + +#pragma mark - NSURLConnectionDelegate + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { + [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + + self.response = response; + self.mutableData = [[NSMutableData alloc] init]; + completionHandler(NSURLSessionResponseAllow); +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { + [self.client URLProtocol:self didLoadData:data]; + + [self.mutableData appendData:data]; +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + NSData *data = [self.mutableData copy]; + + if (error != nil) { + [self.client URLProtocol:self didFailWithError:error]; + + [self.class updateRecorderProgressDelegate:SWHTTPTrafficRecordingProgressFailedToLoad + userInfo:@{SWHTTPTrafficRecordingProgressRequestKey: self.request, + SWHTTPTrafficRecordingProgressErrorKey: error + }]; + + return; + } + + [self.client URLProtocolDidFinishLoading:self]; + + NSHTTPURLResponse *response = (NSHTTPURLResponse *)self.response; + NSURLRequest *request = (NSURLRequest*)task.currentRequest; + + [self.class updateRecorderProgressDelegate:SWHTTPTrafficRecordingProgressLoaded + userInfo:@{SWHTTPTrafficRecordingProgressRequestKey: request, + SWHTTPTrafficRecordingProgressResponseKey: response, + SWHTTPTrafficRecordingProgressBodyDataKey: data + }]; + + NSString *path = [self getFilePath:request response:response]; + NSLog(@"Recording request: %@", request.URL); + NSLog(@"Recording response code: %ld", (long)response.statusCode); + NSLog(@"To path: %@", path); + SWHTTPTrafficRecordingFormat format = [SWHttpTrafficRecorder sharedRecorder].recordingFormat; + if(format == SWHTTPTrafficRecordingFormatBodyOnly){ + [self createBodyOnlyFileWithRequest:request response:response data:data atFilePath:path]; + } else if(format == SWHTTPTrafficRecordingFormatMocktail){ + [self createMocktailFileWithRequest:request response:response data:data atFilePath:path]; + } else if(format == SWHTTPTrafficRecordingFormatHTTPMessage){ + [self createHTTPMessageFileWithRequest:request response:response data:data atFilePath:path]; + } else if(format == SWHTTPTrafficRecordingFormatCustom && [SWHttpTrafficRecorder sharedRecorder].createFileInCustomFormatBlock != nil){ + [SWHttpTrafficRecorder sharedRecorder].createFileInCustomFormatBlock(request, response, data, path); + } else { + NSLog(@"File format: %ld is not supported.", (long)format); + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { + if (response != nil) { + [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response]; + } + if ([SWHttpTrafficRecorder sharedRecorder].followRedirects) { + completionHandler(request); + } else { + completionHandler(nil); + } +} + + +#pragma mark - File Creation Utility Methods + +-(NSString *)getFileName:(NSURLRequest *)request response:(NSHTTPURLResponse *)response{ + NSString *fileName = [request.URL lastPathComponent]; + + if(!fileName || [self isNotValidFileName: fileName]){ + fileName = @"Mocktail"; + } + + fileName = [NSString stringWithFormat:@"%@_%lu_%d", fileName, (unsigned long)[SWHttpTrafficRecorder sharedRecorder].runTimeStamp, [[SWHttpTrafficRecorder sharedRecorder] increaseFileNo]]; + + fileName = [fileName stringByAppendingPathExtension:[self getFileExtension:request response:response]]; + + NSString *(^fileNamingBlock)(NSURLRequest *request, NSURLResponse *response, NSString *defaultName) = [SWHttpTrafficRecorder sharedRecorder].fileNamingBlock; + + if(fileNamingBlock){ + fileName = fileNamingBlock(request, response, fileName); + } + return fileName; +} + +-(BOOL)isNotValidFileName:(NSString*) fileName{ + return NO; +} + +-(NSString *)getFilePath:(NSURLRequest *)request response:(NSHTTPURLResponse *)response{ + NSString *recordingPath = [SWHttpTrafficRecorder sharedRecorder].recordingPath; + NSString *filePath = [recordingPath stringByAppendingPathComponent:[self getFileName:request response:response]]; + + return filePath; +} + +-(NSString *)getFileExtension:(NSURLRequest *)request response:(NSHTTPURLResponse *)response{ + SWHTTPTrafficRecordingFormat format = [SWHttpTrafficRecorder sharedRecorder].recordingFormat; + if(format == SWHTTPTrafficRecordingFormatBodyOnly){ + /* Based on http://blog.ablepear.com/2010/08/how-to-get-file-extension-for-mime-type.html, we may be able to get the file extension from mime type. Use a fixed mapping for simpilicity for now unless there is a need later on */ + return [SWHttpTrafficRecorder sharedRecorder].fileExtensionMapping[response.MIMEType] ?: @"unknown"; + } else if(format == SWHTTPTrafficRecordingFormatMocktail){ + return @"tail"; + } else if(format == SWHTTPTrafficRecordingFormatHTTPMessage){ + return @"response"; + } + + return @"unknown"; +} + +-(BOOL)toBase64Body:(NSURLRequest *)request andResponse:(NSHTTPURLResponse *)response{ + if([SWHttpTrafficRecorder sharedRecorder].base64TestBlock){ + return [SWHttpTrafficRecorder sharedRecorder].base64TestBlock(request, response); + } + return [response.MIMEType hasPrefix:@"image"]; +} + +-(NSData *)doBase64:(NSData *)bodyData request: (NSURLRequest*)request response:(NSHTTPURLResponse*)response{ + BOOL toBase64 = [self toBase64Body:request andResponse:response]; + if(toBase64 && bodyData){ + return [bodyData base64EncodedDataWithOptions:0]; + } else { + return bodyData; + } +} + +-(NSData *)doJSONPrettyPrint:(NSData *)bodyData request: (NSURLRequest*)request response:(NSHTTPURLResponse*)response{ + if([response.MIMEType isEqualToString:@"application/json"] && bodyData) + { + NSError *error; + id json = [NSJSONSerialization JSONObjectWithData:bodyData options:0 error:&error]; + if(json && !error){ + bodyData = [NSJSONSerialization dataWithJSONObject:json options:NSJSONWritingPrettyPrinted error:&error]; + if(error){ + NSLog(@"Somehow the content is not a json though the mime type is json: %@", error); + } + } else { + NSLog(@"Somehow the content is not a json though the mime type is json: %@", error); + } + } + return bodyData; +} + +-(void)createFileAt:(NSString *)filePath usingData:(NSData *)data completionHandler:(void(^)(BOOL created))completionHandler{ + __block BOOL created = NO; + NSBlockOperation* creationOp = [NSBlockOperation blockOperationWithBlock: ^{ + created = [NSFileManager.defaultManager createFileAtPath:filePath contents:data attributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey]]; + }]; + creationOp.completionBlock = ^{ + completionHandler(created); + }; + [[SWHttpTrafficRecorder sharedRecorder].fileCreationQueue addOperation:creationOp]; +} + +#pragma mark - BodyOnly File Creation + +-(void)createBodyOnlyFileWithRequest:(NSURLRequest*)request response:(NSHTTPURLResponse*)response data:(NSData*)data atFilePath:(NSString *)filePath +{ + data = [self doJSONPrettyPrint:data request:request response:response]; + + NSDictionary *userInfo = @{SWHTTPTrafficRecordingProgressRequestKey: request, + SWHTTPTrafficRecordingProgressResponseKey: response, + SWHTTPTrafficRecordingProgressBodyDataKey: data, + SWHTTPTrafficRecordingProgressFileFormatKey: @(SWHTTPTrafficRecordingFormatBodyOnly), + SWHTTPTrafficRecordingProgressFilePathKey: filePath + }; + [self createFileAt:filePath usingData:data completionHandler:^(BOOL created) { + [self.class updateRecorderProgressDelegate:(created ? SWHTTPTrafficRecordingProgressRecorded : SWHTTPTrafficRecordingProgressFailedToRecord) userInfo:userInfo]; + }]; +} + +#pragma mark - Mocktail File Creation + +-(void)createMocktailFileWithRequest:(NSURLRequest*)request response:(NSHTTPURLResponse*)response data:(NSData*)data atFilePath:(NSString *)filePath +{ + NSMutableString *tail = NSMutableString.new; + + [tail appendFormat:@"%@\n", request.HTTPMethod]; + [tail appendFormat:@"%@\n", [self getURLRegexPattern:request]]; + [tail appendFormat:@"%ld\n", (long)response.statusCode]; + [tail appendFormat:@"%@%@\n", response.MIMEType, [self toBase64Body:request andResponse:response] ? @";base64": @""]; + NSEnumerator *headerKeys = [response.allHeaderFields keyEnumerator]; + for (NSString *key in headerKeys) { + [tail appendFormat:@"%@: %@\n", key, (NSString*)[response.allHeaderFields objectForKey:key]]; + } + + NSString *postBodyRequest = [self postBodyRequestStringForRequest:request]; + if (postBodyRequest != nil) { + [tail appendFormat:@"X-Stripe-Mock-Request: %@\n", postBodyRequest]; + } + + [tail appendString:@"\n"]; + + data = [self doBase64:data request:request response:response]; + + data = [self doJSONPrettyPrint:data request:request response:response]; + + data = [self replaceRegexWithTokensInData:data]; + + [tail appendFormat:@"%@", data ? [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] : @""]; + + NSDictionary *userInfo = @{SWHTTPTrafficRecordingProgressRequestKey: request, + SWHTTPTrafficRecordingProgressResponseKey: response, + SWHTTPTrafficRecordingProgressBodyDataKey: data, + SWHTTPTrafficRecordingProgressFileFormatKey: @(SWHTTPTrafficRecordingFormatMocktail), + SWHTTPTrafficRecordingProgressFilePathKey: filePath + }; + [self createFileAt:filePath usingData:[tail dataUsingEncoding:NSUTF8StringEncoding] completionHandler:^(BOOL created) { + [self.class updateRecorderProgressDelegate:(created ? SWHTTPTrafficRecordingProgressRecorded : SWHTTPTrafficRecordingProgressFailedToRecord) userInfo: userInfo]; + }]; +} + +-(NSString *)postBodyRequestStringForRequest:(NSURLRequest *)request { + NSString *postBody = [request valueForHTTPHeaderField:@"X-Stripe-Mock-Request"]; + if (postBody != nil && [postBody length] > 0) { + NSString *(^postBodyTransformBlock)(NSURLRequest *request, NSString *postBody) = [SWHttpTrafficRecorder sharedRecorder].postBodyTransformBlock; + + if(postBodyTransformBlock){ + postBody = postBodyTransformBlock(request, postBody); + } + + return postBody; + } + return nil; +} + +-(NSData *)replaceRegexWithTokensInData: (NSData *) data { + SWHttpTrafficRecorder *recorder = [SWHttpTrafficRecorder sharedRecorder]; + if(![recorder replacementDict]) { + return data; + } + else { + NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + for(NSString *key in [recorder replacementDict]) { + if([[[recorder replacementDict] objectForKey: key] isKindOfClass:[NSRegularExpression class]]) { + dataString = [[[recorder replacementDict] objectForKey:key] stringByReplacingMatchesInString:dataString options:0 range:NSMakeRange(0, [dataString length]) withTemplate:key]; + } + } + data = [dataString dataUsingEncoding:NSUTF8StringEncoding]; + return data; + } +} + +-(NSString *)getURLRegexPattern:(NSURLRequest *)request{ + NSString *urlPattern = request.URL.path; + if(request.URL.query){ + NSArray *queryArray = [request.URL.query componentsSeparatedByString:@"&"]; + NSMutableArray *processedQueryArray = [[NSMutableArray alloc] initWithCapacity:queryArray.count]; + for (NSString *part in queryArray) { + NSRegularExpression *urlRegex = [NSRegularExpression regularExpressionWithPattern:@"(.*)=(.*)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSString *newPart = [urlRegex stringByReplacingMatchesInString:part options:0 range:NSMakeRange(0, part.length) withTemplate:@"$1=.*"]; + [processedQueryArray addObject:newPart]; + } + urlPattern = [NSString stringWithFormat:@"%@\\?%@", request.URL.path, [processedQueryArray componentsJoinedByString:@"&"]]; + } + + NSString *(^urlRegexPatternBlock)(NSURLRequest *request, NSString *defaultPattern) = [SWHttpTrafficRecorder sharedRecorder].urlRegexPatternBlock; + + if(urlRegexPatternBlock){ + urlPattern = urlRegexPatternBlock(request, urlPattern); + } + + urlPattern = [urlPattern stringByAppendingString:@"$"]; + + return urlPattern; +} + +#pragma mark - HTTP Message File Creation + +-(void)createHTTPMessageFileWithRequest:(NSURLRequest*)request response:(NSHTTPURLResponse*)response data:(NSData*)data atFilePath:(NSString *)filePath +{ + NSMutableString *dataString = NSMutableString.new; + + [dataString appendFormat:@"%@\n", [self statusLineFromResponse:response]]; + + NSDictionary *headers = response.allHeaderFields; + for(NSString *key in headers){ + [dataString appendFormat:@"%@: %@\n", key, headers[key]]; + } + + [dataString appendString:@"\n"]; + + NSMutableData *responseData = [NSMutableData dataWithData:[dataString dataUsingEncoding:NSUTF8StringEncoding]]; + [responseData appendData:data]; + + NSDictionary *userInfo = @{SWHTTPTrafficRecordingProgressRequestKey: request, + SWHTTPTrafficRecordingProgressResponseKey: response, + SWHTTPTrafficRecordingProgressBodyDataKey: data, + SWHTTPTrafficRecordingProgressFileFormatKey: @(SWHTTPTrafficRecordingFormatHTTPMessage), + SWHTTPTrafficRecordingProgressFilePathKey: filePath + }; + + [self createFileAt:filePath usingData:responseData completionHandler:^(BOOL created) { + [self.class updateRecorderProgressDelegate:(created ? SWHTTPTrafficRecordingProgressRecorded : SWHTTPTrafficRecordingProgressFailedToRecord) userInfo:userInfo]; + }]; +} + +- (NSString *)statusLineFromResponse:(NSHTTPURLResponse*)response{ + CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, [response statusCode], NULL, kCFHTTPVersion1_1); + NSString *statusLine = (__bridge_transfer NSString *)CFHTTPMessageCopyResponseStatusLine(message); + CFRelease(message); + return statusLine; +} + +#pragma mark - Recording Progress + ++ (void)updateRecorderProgressDelegate:(SWHTTPTrafficRecordingProgressKind)progress userInfo:(NSDictionary *)info{ + SWHttpTrafficRecorder *recorder = [SWHttpTrafficRecorder sharedRecorder]; + if(recorder.progressDelegate && [recorder.progressDelegate respondsToSelector:@selector(updateRecordingProgress:userInfo:)]){ + [recorder.progressDelegate updateRecordingProgress:progress userInfo:info]; + } +} + +@end diff --git a/StripePayments/StripePaymentsObjcTestUtils/StripePaymentsObjcTestUtils.h b/StripePayments/StripePaymentsObjcTestUtils/StripePaymentsObjcTestUtils.h new file mode 100644 index 00000000..8050d60c --- /dev/null +++ b/StripePayments/StripePaymentsObjcTestUtils/StripePaymentsObjcTestUtils.h @@ -0,0 +1,19 @@ +// +// StripePaymentsObjcTestUtils.h +// StripePaymentsObjcTestUtils +// + +#import + +//! Project version number for StripePaymentsObjcTestUtils. +FOUNDATION_EXPORT double StripePaymentsObjcTestUtilsVersionNumber; + +//! Project version string for StripePaymentsObjcTestUtils. +FOUNDATION_EXPORT const unsigned char StripePaymentsObjcTestUtilsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +#import +#import +#import +#import diff --git a/StripePayments/StripePaymentsTestHostApp/AppDelegate.swift b/StripePayments/StripePaymentsTestHostApp/AppDelegate.swift new file mode 100644 index 00000000..a9ae1301 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/AppDelegate.swift @@ -0,0 +1,32 @@ +// +// AppDelegate.swift +// StripePaymentsTestHostApp +// +// Created by David Estes on 5/20/24. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + +} diff --git a/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/AccentColor.colorset/Contents.json b/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..13613e3e --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/Contents.json b/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePayments/StripePaymentsTestHostApp/Base.lproj/LaunchScreen.storyboard b/StripePayments/StripePaymentsTestHostApp/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripePayments/StripePaymentsTestHostApp/Base.lproj/Main.storyboard b/StripePayments/StripePaymentsTestHostApp/Base.lproj/Main.storyboard new file mode 100644 index 00000000..25a76385 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripePayments/StripePaymentsTestHostApp/Info.plist b/StripePayments/StripePaymentsTestHostApp/Info.plist new file mode 100644 index 00000000..9db1a26e --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/Info.plist @@ -0,0 +1,29 @@ + + + + + HCaptchaDomain + http://localhost + HCaptchaKey + a5f74b19-9e45-40e0-b45d-47ff91b7a6c2 + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/StripePayments/StripePaymentsTestHostApp/SceneDelegate.swift b/StripePayments/StripePaymentsTestHostApp/SceneDelegate.swift new file mode 100644 index 00000000..e3dd5205 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/SceneDelegate.swift @@ -0,0 +1,49 @@ +// +// SceneDelegate.swift +// StripePaymentsTestHostApp +// +// Created by David Estes on 5/20/24. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + // guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + +} diff --git a/StripePayments/StripePaymentsTestHostApp/ViewController.swift b/StripePayments/StripePaymentsTestHostApp/ViewController.swift new file mode 100644 index 00000000..570f8327 --- /dev/null +++ b/StripePayments/StripePaymentsTestHostApp/ViewController.swift @@ -0,0 +1,12 @@ +// +// ViewController.swift +// StripePaymentsTestHostApp +// +// Created by David Estes on 5/20/24. +// + +import UIKit + +class ViewController: UIViewController { + +} diff --git a/StripePayments/StripePaymentsTestUtils/Info.plist b/StripePayments/StripePaymentsTestUtils/Info.plist new file mode 100644 index 00000000..9bcb2444 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0000_get_status_429.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0000_get_status_429.tail new file mode 100644 index 00000000..1034ee7a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0000_get_status_429.tail @@ -0,0 +1,11 @@ +GET +https:\/\/luxurious-alpine-devourer\.glitch\.me\/status\/429\?$ +429 +text/html +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:40 GMT +Content-Type: text/html; charset=utf-8 +Content-Length: 0 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0001_get_status_429.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0001_get_status_429.tail new file mode 100644 index 00000000..2bc8c6ae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0001_get_status_429.tail @@ -0,0 +1,11 @@ +GET +https:\/\/luxurious-alpine-devourer\.glitch\.me\/status\/429\?$ +429 +text/html +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:42 GMT +Content-Type: text/html; charset=utf-8 +Content-Length: 0 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0002_get_status_429.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0002_get_status_429.tail new file mode 100644 index 00000000..96d22178 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429Backoff/0002_get_status_429.tail @@ -0,0 +1,11 @@ +GET +https:\/\/luxurious-alpine-devourer\.glitch\.me\/status\/429\?$ +429 +text/html +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:46 GMT +Content-Type: text/html; charset=utf-8 +Content-Length: 0 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429NoBackoff/0000_get_status_429.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429NoBackoff/0000_get_status_429.tail new file mode 100644 index 00000000..54300bd9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/test429NoBackoff/0000_get_status_429.tail @@ -0,0 +1,11 @@ +GET +https:\/\/luxurious-alpine-devourer\.glitch\.me\/status\/429\?$ +429 +text/html +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:47 GMT +Content-Type: text/html; charset=utf-8 +Content-Length: 0 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testDelete/0000_delete_delete.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testDelete/0000_delete_delete.tail new file mode 100644 index 00000000..ecc92b2d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testDelete/0000_delete_delete.tail @@ -0,0 +1,40 @@ +DELETE +https:\/\/luxurious-alpine-devourer\.glitch\.me\/delete\?$ +200 +application/json +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:47 GMT +Content-Type: application/json +Content-Length: 959 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + +{ + "json" : null, + "origin" : "136.24.137.206,::ffff:10.10.10.154,::ffff:10.10.89.203", + "url" : "https,http,http:\/\/luxurious-alpine-devourer.glitch.me\/delete", + "data" : "", + "headers" : { + "X-Forwarded-Host" : "luxurious-alpine-devourer.glitch.me", + "Connection" : "close", + "Accept-Encoding" : "gzip, deflate, br", + "Stripe-Version" : "2020-08-27", + "User-Agent" : "xctest\/22719 CFNetwork\/1406.0.4 Darwin\/23.5.0", + "Authorization" : "Bearer", + "X-Stripe-User-Agent" : "{\"vendor_identifier\":\"6B9BD889-FE40-4D9D-BD6E-CA93E8B754A3\",\"model\":\"iPhone\",\"type\":\"arm64\",\"os_version\":\"16.4\",\"bindings_version\":\"23.28.1\",\"lang\":\"objective-c\"}", + "Host" : "luxurious-alpine-devourer.glitch.me", + "Traceparent" : "00-adea1112fa374e4ebe6007f30c66ab1e-54e24023044f40d1-01", + "Accept-Language" : "en-US,en;q=0.9", + "Accept" : "*\/*", + "Content-Length" : "0" + }, + "args" : { + + }, + "files" : { + + }, + "form" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testGet/0000_get_get.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testGet/0000_get_get.tail new file mode 100644 index 00000000..23b7ea43 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testGet/0000_get_get.tail @@ -0,0 +1,31 @@ +GET +https:\/\/luxurious-alpine-devourer\.glitch\.me\/get\?$ +200 +application/json +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:48 GMT +Content-Type: application/json +Content-Length: 865 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + +{ + "args" : { + + }, + "headers" : { + "Accept" : "*\/*", + "Accept-Encoding" : "gzip, deflate, br", + "Connection" : "close", + "Host" : "luxurious-alpine-devourer.glitch.me", + "Traceparent" : "00-6daeb5505e184955a8aa327b69653353-bfbd568bc2a63c5c-01", + "Authorization" : "Bearer", + "Accept-Language" : "en-US,en;q=0.9", + "User-Agent" : "xctest\/22719 CFNetwork\/1406.0.4 Darwin\/23.5.0", + "Stripe-Version" : "2020-08-27", + "X-Forwarded-Host" : "luxurious-alpine-devourer.glitch.me", + "X-Stripe-User-Agent" : "{\"bindings_version\":\"23.28.1\",\"vendor_identifier\":\"6B9BD889-FE40-4D9D-BD6E-CA93E8B754A3\",\"lang\":\"objective-c\",\"type\":\"arm64\",\"os_version\":\"16.4\",\"model\":\"iPhone\"}" + }, + "origin" : "136.24.137.206,::ffff:10.10.10.154,::ffff:10.10.94.204", + "url" : "https,http,http:\/\/luxurious-alpine-devourer.glitch.me\/get" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testPost/0000_post_post.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testPost/0000_post_post.tail new file mode 100644 index 00000000..71d1224f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testPost/0000_post_post.tail @@ -0,0 +1,43 @@ +POST +https:\/\/luxurious-alpine-devourer\.glitch\.me\/post$ +200 +application/json +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:48 GMT +Content-Type: application/json +Content-Length: 1075 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * +X-Stripe-Mock-Request: foo=bar + +{ + "json" : null, + "origin" : "136.24.137.206,::ffff:10.10.10.154,::ffff:10.10.87.49", + "url" : "https,http,http:\/\/luxurious-alpine-devourer.glitch.me\/post", + "data" : "", + "headers" : { + "X-Forwarded-Host" : "luxurious-alpine-devourer.glitch.me", + "Connection" : "close", + "Accept-Encoding" : "gzip, deflate, br", + "Content-Type" : "application\/x-www-form-urlencoded", + "Stripe-Version" : "2020-08-27", + "User-Agent" : "xctest\/22719 CFNetwork\/1406.0.4 Darwin\/23.5.0", + "X-Stripe-Mock-Request" : "foo=bar", + "Authorization" : "Bearer", + "X-Stripe-User-Agent" : "{\"model\":\"iPhone\",\"lang\":\"objective-c\",\"bindings_version\":\"23.28.1\",\"os_version\":\"16.4\",\"type\":\"arm64\",\"vendor_identifier\":\"6B9BD889-FE40-4D9D-BD6E-CA93E8B754A3\"}", + "Host" : "luxurious-alpine-devourer.glitch.me", + "Traceparent" : "00-f68f326d3adb4fe181deef15d8434571-e672d7d7c30203de-01", + "Accept-Language" : "en-US,en;q=0.9", + "Accept" : "*\/*", + "Content-Length" : "7" + }, + "args" : { + + }, + "files" : { + + }, + "form" : { + "foo" : "bar" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testPublishableKeyAuthorization/0000_get_bearer.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testPublishableKeyAuthorization/0000_get_bearer.tail new file mode 100644 index 00000000..7f858b3a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/APIRequestTest/testPublishableKeyAuthorization/0000_get_bearer.tail @@ -0,0 +1,15 @@ +GET +https:\/\/luxurious-alpine-devourer\.glitch\.me\/bearer\?foo=bar$ +200 +application/json +access-control-allow-credentials: true +Date: Wed, 31 Jul 2024 02:08:48 GMT +Content-Type: application/json +Content-Length: 50 +Server: gunicorn/19.2.0 +Access-Control-Allow-Origin: * + +{ + "token" : "pk_foo", + "authenticated" : true +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testLookupSessionexistingConsumer/0000_post_v1_consumers_sessions_lookup.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testLookupSessionexistingConsumer/0000_post_v1_consumers_sessions_lookup.tail new file mode 100644 index 00000000..fa3446dd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testLookupSessionexistingConsumer/0000_post_v1_consumers_sessions_lookup.tail @@ -0,0 +1,109 @@ +POST +/v1/consumers/sessions/lookup$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Fsessions%2Flookup; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_gOhgD3Y1ylLvkP +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 3754 +Vary: Origin +Date: Wed, 31 Jul 2024 02:08:49 GMT +original-request: req_gOhgD3Y1ylLvkP +stripe-version: 2020-08-27 +idempotency-key: db20b7d2-9a07-48b6-8f3a-88183cc45e55 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "exists" : true, + "experiments" : [ + { + "response_id" : "", + "experiment_name" : "link_payment_element_recollection_flow", + "event_id" : "fabc9a9a-b10d-4b16-851d-7958edb73c89", + "variant" : "control" + } + ], + "error_message" : null, + "publishable_key" : "pk_test_51KZhvGBYgp8ftBq2dMntfdpGRvSoOFbaFnXa9ByFi3Lk9EqqAcQVuJUGwaQub9zFPwRLMuHViAYlaGnBjOWQvp4T00oRtbLrHO", + "consumer_session" : { + "unredacted_phone_number" : null, + "redacted_phone_number" : "+1********34", + "available_verification_factors" : [ + { + "id" : "casvf_kB-9yHDtoi0xY6qz5pP_o5TIb-0EWH00ctY1sc0XZVyOtaO8NfFdn0-op6m5irQ0InYXBYEzuAHt9dh2BBw2w5r1DBghrtgrxjKYqLSUgeRKxQa7jKorHJ9D6Hn5-nPr01oTixi5tKOxKVcm44P3vv3_E1adcXrkD2UNlFbQ_tVOsl5YWL_WK9KzP4TTH504FsBSquv7tC_0jlSaHRmpMM_D", + "auto_prompt_login" : true, + "type" : "SMS" + }, + { + "id" : "casvf_G5jhMkSpPz9njeW2Fj1nrR7XLlnTLO7pC8sl6W6ven2IqkyXsgIdKj_XZyGGKtdvugSvCK0cMWSGHriG0qLJD7VIijLfiZN6SF3ucIRJcQzD3GzUom_roaaBckI0NgdLFYaQf5mkngaSPoe16QoFaiQmBT27MpAsUhXJeDNoSO66XSI4jeUKIL-WXiX0QDZL98QyIF9S7aShWojpzLUibGi_", + "auto_prompt_login" : true, + "type" : "EMAIL" + }, + { + "id" : "casvf_nrLLvKRFYIjwrs73fLr2GlDregxK7yNx1u5_QAMy8uhxzvz5OXgF1myj0ZPY5oGhX1OziJ8Iq6h3webTTz9Ob0411VHK5MmjHB_8ivvSss3AbuifLcwbl-rGLgNHvdWO9zWqgPdtXMGg1ihwxlojVgq_ataY9Ba_aiEev-PLv0i7jBGy4f2ZEx3LS_kyNYED4dm-uDvpja25r_CyS_w_pRL6", + "auto_prompt_login" : true, + "type" : "PHONE_MATCH" + }, + { + "id" : "casvf_kqwVR55MIrSuVs6zBedYhfzcRQYwRIzRUo1FWj8JTbyzuakhkgB_AovWYFhMtf51MGCC9QS5lzrTSJacT9iuXAFmYvpRNiDDZn-kdRrAQ1Ju8VmZ_HaKwsGgs5blSiYOiGWFJ_TOvFkT_DOabgU8Gfh-UVZwI6aWXVGgJoSWhaSjelfSmf1A_0UuRg-a1IBCp_b5VpI11xedFlQhNpmKOwr8", + "auto_prompt_login" : true, + "type" : "PHONE_BY_LINK" + }, + { + "id" : "casvf_8kdvYiTXs9_GKERLUKC6kYp_TVl1TADVDz3kLl8KW6YzvIER6T85Gr9Eg5SySWV9ifV3wHWfKtwOvScYgbnqM9kC1-hgmyLwdaf5WleQKn-j1OX8vFjMd6R9JjjeHI8oqOz6aJlCSjFjg25NPDP8O3mHKLWjFoI3enjb-4zBOEr3_drsKC290j_ztg3_FzVv9i_-dhiQVkLmKdiJjxERND2o", + "auto_prompt_login" : true, + "type" : "EMAIL_BY_LINK" + }, + { + "id" : "casvf_k6YDvycKxPtvAVDDyvqsRtLxWtSiDobDYk5bVaeH8PwGiA8KR2DREHJMsUmLJ1PpIT8ULgSE-cTslQbdW2pHylLANQtDz-BVluOwNdtNgEprbT1OzJ2eGNKrTHmZZfl8vm_W-knwg7HEUk3bvDY96CpIEPsiIzvmvyS2Cz4-vQDHkfOJIgiZk7b1c1NXcOr0NGyE-wmzhLE_nE65lCcZ7lax", + "auto_prompt_login" : true, + "type" : "SNA" + } + ], + "phone_number_country" : null, + "redacted_formatted_phone_number" : "(***) *** **34", + "verification_sessions" : [ + + ], + "client_secret" : "pscs_AVsCY5VBeuX6TihAjz85esPT1N11orLsyf_jIbEQ4WSye3Nn94Wj53zMJvjKSa-k1suZnk8ubd6xZB3IyKwj9fdP4icKvaooizQWitNCS23lmgR-rx8iRm8ybcSeEGdUKEJp-C4D0bqCa1wAADVsn_DfkgqoY3nNRONzkAOSXk-bsf1Zf4g6DuHDzWmtQoFERVp50sl6zAWDRHVab2VC8BDSHo1EGtGQKuyLKa_aIucAUzbLFWCpv5C9Hd8Tl3JNqcvUS1AprTamncSnhqTc4DwilUNEuDKh0VGgiOUs5og5dpm6GSDRPbBEnJylysleKfvtU95fLOfiZpvWW-EW7coa8W1BsuDahxmFGFNg4syotW9cpf1IOsp1_fYgGcn-UzVrZDdmsBQBU1D7-7bv162s4hEHIUk1koR5ss8XwOKkIb2FOyMq1ldvMPg", + "support_payment_details_types" : [ + "CARD", + "BANK_ACCOUNT" + ], + "email_address" : "mobile-payments-sdk-ci+a-consumer@stripe.com" + }, + "redacted_payment_details" : [ + + ], + "shipping_addresses" : [ + + ], + "settings" : { + "link_app_redirect_token" : null, + "email_otp_verify_phone_despite_sms_otp" : true, + "link_card_program_names_enabled" : false, + "found_using_partial_cookie" : false, + "full_name_collection_required" : false, + "has_previous_merchant_relationship" : false, + "purchase_protections_holdback" : null, + "has_passkey" : false, + "email_otp_requires_additional_info" : true + }, + "auth_session_client_secret" : null, + "account_id" : "acct_1KZhvGBYgp8ftBq2" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testLookupSessionnewConsumer/0000_post_v1_consumers_sessions_lookup.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testLookupSessionnewConsumer/0000_post_v1_consumers_sessions_lookup.tail new file mode 100644 index 00000000..7f55fd77 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testLookupSessionnewConsumer/0000_post_v1_consumers_sessions_lookup.tail @@ -0,0 +1,41 @@ +POST +/v1/consumers/sessions/lookup$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Fsessions%2Flookup; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_2WpBdlswDywN64 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 226 +Vary: Origin +Date: Wed, 31 Jul 2024 02:08:50 GMT +original-request: req_2WpBdlswDywN64 +stripe-version: 2020-08-27 +idempotency-key: 05f9e672-3e31-45cb-8248-0ccf5c785de8 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "exists" : false, + "experiments" : [ + + ], + "error_message" : "No consumer found for the given email address.", + "publishable_key" : null, + "consumer_session" : null, + "auth_session_client_secret" : null, + "account_id" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0000_post_v1_consumers_accounts_sign_up.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0000_post_v1_consumers_accounts_sign_up.tail new file mode 100644 index 00000000..4f6a94aa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0000_post_v1_consumers_accounts_sign_up.tail @@ -0,0 +1,56 @@ +POST +/v1/consumers/accounts/sign_up$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Faccounts%2Fsign_up; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_qhf5bq3gPyDF79 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1424 +Vary: Origin +Date: Wed, 31 Jul 2024 02:08:50 GMT +original-request: req_qhf5bq3gPyDF79 +stripe-version: 2020-08-27 +idempotency-key: 19bb486d-e368-4a5c-acc2-3be6a2d39f26 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "publishable_key" : "pk_test_51PNlGbC40Oy4de1NJgp6GSi80JEj4sO4nFgJu9Y6LIHpK4Ih6MEdF0gVYcJwutISCr3OAwsb96g85zfnPFXs3cZg00wj9e7J6w", + "consumer_session" : { + "unredacted_phone_number" : null, + "redacted_phone_number" : "+1********34", + "available_verification_factors" : null, + "phone_number_country" : null, + "redacted_formatted_phone_number" : "(***) *** **34", + "verification_sessions" : [ + { + "id" : null, + "state" : "STARTED", + "type" : "SIGNUP", + "verification_token" : null + } + ], + "client_secret" : "pscs_AVsCY5VSHWCnNEc1MHWqrdBOkZznPskf5nvedkE0qy6vVjthveBvQJLDPnuskghizsVwWzncoIwbMDJqHGU8Y1xllyI1MeXAGO2YT6WwX77MMYlJyvDFdEbpogbdOdIUSHR-ik_qypDkvLwoHO7AquEc7Vrf3mu9oL706S9bEnJDBEbj-gDmdToL6c8ojXMfODe7ush9pQSZA3eRZ0L6FAeMGfvhANRhSi8PUT6RaBTQxgbJvSTpWJr4b7VkxC24Pzv87W8E3Y-Y_PxAwRkf72-LmX_Gs3pvk5NPHKaSXbfPKw-DpJ4D9QVFK_CRX0tyHyLAynVqP1jHdywf5ct8G8Wfk4JGHXDmzcy4wdE4RKj6ll9vLq1mtM4v_fSuk9Wvcj4rgAWVpO7yrJG9VgxUzq9LDZM1ASQPyKF3ZrqEDFFlVSUmBDClHGArFmo", + "support_payment_details_types" : [ + "CARD", + "BANK_ACCOUNT" + ], + "email_address" : "mobile-payments-sdk-ci+874e9b29-df47-4a14-be39-be257a89dccb@stripe.com" + }, + "account_id" : "acct_1PNlGbC40Oy4de1N", + "auth_session_client_secret" : "ascs_COoBEjxjYXNhc18yTXFiMkJOY094WmZ0eXkhZlMzYjNXNnpQMFd3ZjAtYkxydGh6UEdNNmNFI0dnVUk2Z0VZQVEaJDZkNGIxYjU5LTQzOWQtNGU0Ny1iYmRlLWIzMDNjMzViNGNlOCIIQWJCWlFibXk" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0001_post_v1_consumers_payment_details.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0001_post_v1_consumers_payment_details.tail new file mode 100644 index 00000000..6580d37a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0001_post_v1_consumers_payment_details.tail @@ -0,0 +1,82 @@ +POST +/v1/consumers/payment_details$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Fpayment_details; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_kxRTyThgD34eSy +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1338 +Vary: Origin +Date: Wed, 31 Jul 2024 02:08:51 GMT +original-request: req_kxRTyThgD34eSy +stripe-version: 2020-08-27 +idempotency-key: 02eeb33b-bf46-419a-9784-9eebe6e2e143 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "redacted_payment_details" : { + "card_details" : { + "brand_enum" : "visa", + "checks" : { + "address_postal_code_check" : "STATE_INVALID", + "cvc_check" : "STATE_INVALID", + "address_line1_check" : "STATE_INVALID" + }, + "country" : "COUNTRY_US", + "exp_month" : 12, + "funding" : "CREDIT", + "preferred_network" : null, + "program_details" : { + "card_art_network_id" : "", + "height" : 0, + "program_name" : "", + "width" : 0, + "background_color" : "", + "foreground_color" : "", + "card_art_url" : "" + }, + "brand" : "VISA", + "last4" : "4242", + "networks" : [ + "VISA" + ], + "exp_year" : 2025 + }, + "is_default" : false, + "id" : "csmrpd_test_61QrpvXKaugSBvBsB41C40Oy4de1NQS8", + "backup_ids" : [ + + ], + "is_us_debit_prepaid_or_bank_payment" : false, + "billing_address" : { + "line_1" : null, + "line_2" : null, + "locality" : null, + "postal_code" : "55555", + "sorting_code" : null, + "country_code" : "US", + "dependent_locality" : null, + "administrative_area" : null, + "name" : "Payments SDK CI" + }, + "nickname" : "", + "bank_account_details" : null, + "type" : "CARD", + "billing_email_address" : "mobile-payments-sdk-ci+874e9b29-df47-4a14-be39-be257a89dccb@stripe.com" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0002_post_v1_consumers_sessions_log_out.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0002_post_v1_consumers_sessions_log_out.tail new file mode 100644 index 00000000..1fdd883c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0002_post_v1_consumers_sessions_log_out.tail @@ -0,0 +1,47 @@ +POST +/v1/consumers/sessions/log_out$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Fsessions%2Flog_out; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_3Qy6cehxTVPAHL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:08:51 GMT +original-request: req_3Qy6cehxTVPAHL +stripe-version: 2020-08-27 +idempotency-key: 68814297-8254-4b41-952b-a6f8907b3a50 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "consumer_session" : { + "unredacted_phone_number" : null, + "redacted_phone_number" : "+1********34", + "available_verification_factors" : null, + "phone_number_country" : null, + "redacted_formatted_phone_number" : "(***) *** **34", + "verification_sessions" : [ + + ], + "client_secret" : "pscs_AVsCY5VSHWCnNEc1MHWqrdBOkZznPskf5nvedkE0qy6vVjthveBvQJLDPnuskghizsVwWzncoIwbMDJqHGU8Y1xllyI1MeXAGO2YT6WwX77MMYlJyvDFdEbpogbdOdIUSHR-ik_qypDkvLwoHO7AquEc7Vrf3mu9oL706S9bEnJDBEbj-gDmdToL6c8ojXMfODe7ush9pQSZA3eRZ0L6FAeMGfvhANRhSi8PUT6RaBTQxgbJvSTpWJr4b7VkxC24Pzv87W8E3Y-Y_PxAwRkf72-LmX_Gs3pvk5NPHKaSXbfPKw-DpJ4D9QVFK_CRX0tyHyLAynVqP1jHdywf5ct8G8Wfk4JGHXDmzcy4wdE4RKj6ll9vLq1mtM4v_fSuk9Wvcj4rgAWVpO7yrJG9VgxUzq9LDZM1ASQPyKF3ZrqEDFFlVSUmBDClHGArFmo", + "support_payment_details_types" : [ + + ], + "email_address" : "mobile-payments-sdk-ci+874e9b29-df47-4a14-be39-be257a89dccb@stripe.com" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0003_post_v1_consumers_payment_details.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0003_post_v1_consumers_payment_details.tail new file mode 100644 index 00000000..1478a77b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsAndLogout/0003_post_v1_consumers_payment_details.tail @@ -0,0 +1,36 @@ +POST +/v1/consumers/payment_details$ +401 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Fpayment_details; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_uDccERR5h7sIp6 +Content-Length: 148 +Vary: Origin +Date: Wed, 31 Jul 2024 02:08:51 GMT +original-request: req_uDccERR5h7sIp6 +stripe-version: 2020-08-27 +idempotency-key: 071e2057-273a-41ec-b6cb-e7b43e7ec875 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "error" : { + "message" : "Invalid credentials.", + "type" : "invalid_request_error", + "code" : "consumer_session_credentials_invalid" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsConnectAccount/0000_post_v1_consumers_accounts_sign_up.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsConnectAccount/0000_post_v1_consumers_accounts_sign_up.tail new file mode 100644 index 00000000..d76b1990 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsConnectAccount/0000_post_v1_consumers_accounts_sign_up.tail @@ -0,0 +1,58 @@ +POST +/v1/consumers/accounts/sign_up$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Faccounts%2Fsign_up; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +stripe-account: acct_1QPtbqFZrlYv4BIL +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true} +request-id: req_n73E02efW0evF7 +x-stripe-priority-routing-enabled: true +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1424 +Vary: Origin +Date: Mon, 02 Dec 2024 19:35:01 GMT +original-request: req_n73E02efW0evF7 +stripe-version: 2020-08-27 +idempotency-key: 16d097c8-c094-407a-add1-21ea0f8bcdba +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "publishable_key" : "pk_test_51QLBAm02WC4aZo3qOXDgbgM2VG3cUgfC6cBhCD4b7ZWq4NRaxkYwujmuzq3SJyMkxVHOk3LfwDQVuJJ6mAS0c8FC00snhXELi5", + "consumer_session" : { + "unredacted_phone_number" : null, + "redacted_phone_number" : "+1********34", + "available_verification_factors" : null, + "phone_number_country" : null, + "redacted_formatted_phone_number" : "(***) *** **34", + "verification_sessions" : [ + { + "id" : null, + "state" : "STARTED", + "type" : "SIGNUP", + "verification_token" : null + } + ], + "client_secret" : "pscs_AVsCY5XRu5O-pPykNqy-glaD04-vrJ4ceQUtfq8t3Y245kS0y6tkLqUM0tiqjWK0T6X2fuZIXnEG-drlR6HaYDt8TMJ-ZE8yBt_GinkERVpxEYe8oWpdsbGHwgczyAE-8s_UxTGugytOKjeLj5TcyrWD8AD8HYmixZjEzrTTETyQVG4FHNqJb6JDvjcTZfrIGAz2t9sBmMheGnZEuZH2Cja8Zu7bH7O_9FQTn2XoKReih5wmLotgOI3KB5yMYFWGqg5t8vIpaR1o8uzxTc9v9pSYlqJTubQRqSl4S4rvGgg4i899V4GPhrVUg-56aq4tfj7I5vcWDgr5a0HzMligkl9AoCpuMi6rzQdhCLm9zTSEKVStRD0tjqcAHSYZ6dUjQmUIJ9HOhWjwGL6O8ofsJziWxgMQW8p1ovMIgWp_l0i_s6mDUqjPgyzYXAU", + "support_payment_details_types" : [ + "CARD", + "BANK_ACCOUNT" + ], + "email_address" : "mobile-payments-sdk-ci+c7597926-3b83-47b9-84cc-d853e357d65a@stripe.com" + }, + "account_id" : "acct_1QLBAm02WC4aZo3q", + "auth_session_client_secret" : "ascs_COoBEjxjYXNhc19vVHpIWGxweEVObklmQVU4Vm5nTWQ0QSF0c2RUIU5uM01kTlR3MzRuITRJI0dnVUk2Z0VZQVEaJDA4YTVhYjNhLTI3NDktNGEyZS05MWYwLWRlMmI3ZWE4MWY2MSIIQWJCWlFia3Q" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsConnectAccount/0001_post_v1_consumers_payment_details.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsConnectAccount/0001_post_v1_consumers_payment_details.tail new file mode 100644 index 00000000..8ffee724 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/ConsumerSessionTests/testSignUpAndCreateDetailsConnectAccount/0001_post_v1_consumers_payment_details.tail @@ -0,0 +1,84 @@ +POST +/v1/consumers/payment_details$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fconsumers%2Fpayment_details; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true} +request-id: req_o0VWE8esDCrAWG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1382 +Vary: Origin +Date: Mon, 02 Dec 2024 19:35:01 GMT +original-request: req_o0VWE8esDCrAWG +stripe-version: 2020-08-27 +idempotency-key: ecc6246e-6dd4-496e-a95c-5990303df4ff +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: + +{ + "redacted_payment_details" : { + "card_details" : { + "brand_enum" : "visa", + "checks" : { + "address_postal_code_check" : "STATE_INVALID", + "cvc_check" : "STATE_INVALID", + "address_line1_check" : "STATE_INVALID" + }, + "country" : "COUNTRY_US", + "exp_month" : 12, + "funding" : "CREDIT", + "preferred_network" : null, + "program_details" : { + "card_art_network_id" : null, + "height" : null, + "program_name" : null, + "width" : null, + "background_color" : null, + "foreground_color" : null, + "card_art_url" : null + }, + "brand" : "VISA", + "last4" : "4242", + "networks" : [ + "VISA" + ], + "exp_year" : 2025 + }, + "is_default" : false, + "id" : "csmrpd_test_61Rb3LxHAzjGxavMJ4102WC4aZo3q19s", + "backup_ids" : [ + + ], + "is_us_debit_prepaid_or_bank_payment" : false, + "billing_address" : { + "line_1" : null, + "line_2" : null, + "locality" : null, + "postal_code" : "55555", + "sorting_code" : null, + "country_code" : "US", + "dependent_locality" : null, + "administrative_area" : null, + "name" : "Payments SDK CI" + }, + "klarna_details" : null, + "nickname" : "", + "bank_account_details" : null, + "type" : "CARD", + "billing_email_address" : "mobile-payments-sdk-ci+c7597926-3b83-47b9-84cc-d853e357d65a@stripe.com" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..4452092b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YWuM9R2WNpBTCj7nslCFsvbMHONd3SdgbNgPXrk3Ix0%2BL3FVGpIqnG%2Fa52YJ4rngjOybDqu8Ur7W4nFKfFjxQtDy6qb9KLjkXi1%2BlZSvhvWP4X20eOMVoBm83chSkRT72%2Fkwv8sCbM6WVjvfO39WP4QBp3IhU%2BNOLDj1ik4vIqsMM5go0GCSAFklr%2FWQXJ7zNrdxKuD3EXtRw%2B0h%2F50%2FCAc1QtYXkXh9V9%2B9%2BhHF7iY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cdbd8ce3de6fe7a8723db15fd2809940;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:12:59 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFpmYmVTN2NZcFJUMUgwNHlxOTVZbnV1Qm4xcHpSb2Y_001JyXhTVO","customer":"cus_QZbE6MH3ztMOLx"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..ac884d62 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8YDdEevszzf4lS0bQ0fl%2BHoJlgurkpq0nGPk56OGxZ2jgGPgqo4F5BhpMjyoO%2BnRgfrB3twCHAGt88BDey5QmLwdGSdW9hkzS19en1pvcjEongyqnWyabcD848M3VWl%2B2BvtD283ueyC%2BMOHXmWrYc%2FO%2FqPnqMLdYjDODz%2F7SdOIFvDa7DurEid93gIvF5DsJR4tFrW2yLmSGWBajgjEkJdoCgg12YV7NHIIT82CKpA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d43035cd5fbf39155e14568ab30a341a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:12:59 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2BFY0qyl6XeWRzP5gQiw","secret":"seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0002_get_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0002_get_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw.tail new file mode 100644 index 00000000..fa563546 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0002_get_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2BFY0qyl6XeWRzP5gQiw\?client_secret=seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PVaz3Z7BWQZTCD +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2BFY0qyl6XeWRzP5gQiw", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391979, + "client_secret" : "seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0003_post_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0003_post_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw_confirm.tail new file mode 100644 index 00000000..18365060 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0003_post_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2BFY0qyl6XeWRzP5gQiw\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_arOpY72hKTwVsh +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1553 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:01 GMT +original-request: req_arOpY72hKTwVsh +stripe-version: 2020-08-27 +idempotency-key: 42b2423c-ae30-4759-be34-4bc7921d599d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=always&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS2BFY0qyl6XeWRzP5gQiw", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2CFY0qyl6XeWBk25PK7s", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391980, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbE6MH3ztMOLx" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391979, + "client_secret" : "seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0004_get_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0004_get_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw.tail new file mode 100644 index 00000000..e2980a48 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0004_get_v1_setup_intents_seti_1PiS2BFY0qyl6XeWRzP5gQiw.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2BFY0qyl6XeWRzP5gQiw\?client_secret=seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9fhwB37rnnqvnA +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2BFY0qyl6XeWRzP5gQiw", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS2CFY0qyl6XeWBk25PK7s", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391979, + "client_secret" : "seti_1PiS2BFY0qyl6XeWRzP5gQiw_secret_QZbEInhkAC1eh3kYBW3dcMuv0HRiGU4", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..1c4e1f7f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaycustomerSession/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbE6MH3ztMOLx&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yxUxWANTfbFkrO +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS2CFY0qyl6XeWBk25PK7s", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391980, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbE6MH3ztMOLx" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..693d388d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Zh3VuzSO5GwKnGyk2jkFtpPkMnGBB49M6QwCqc6D7AEYTL1ikez9UStZe1q57Y3ErjFDus8H5LqfdyT4wV3xoofOEtr5mpVasqR7mWg34uehlJKwpjPI1vby9gB%2FQjiTPV0ONPY%2B3rNrXBzNcgcE7uWfjqJEfL3XuXXdX7FJ4Sq%2F7FJdSscR1cAUAlNM8pBRzUL%2Fo0GASiZ1L%2Fx8wAfZDsWfY%2FayfV7wB%2BuZoPVjFt0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7ce06dc1ee9f768a71f7d67aaeef1ac6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:02 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLG5NZnlaaWNwMFl6Wm9JY0VpZ0l5MDBFR05CZG5FVFY_009GWChzRN","customer":"cus_QZbE7ON4z2cCJ9"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..4606e168 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=L0%2FaOegnp3FU%2B2LA%2BhX4aaWGoUEdVcHrToCvrbeCrzUy0w9%2BXZ9%2FGHHFexV5BFdwyuHzHBz8rZpTV6AYEhp%2BoX1WQWZVzLPD8Q5IbsHsXaWDCXJYnvNtuXo82L2jYozyh5Fi49YbAhNpfsJws84cGzt1QkED1St%2BnoHGhySjh4RIf0vKBoghEu0HTUFoRrA%2BGJPLug2eLEcCXTIhrMsimm2cMyP%2FBm3BWKhnG6EVlz8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 56d7cf323a624403866bc471d3a84bf9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2EFY0qyl6XeW9Fvr0HAM","secret":"seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0002_get_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0002_get_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM.tail new file mode 100644 index 00000000..6bbf01cb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0002_get_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2EFY0qyl6XeW9Fvr0HAM\?client_secret=seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4TpJgpqUbHxgrj +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2EFY0qyl6XeW9Fvr0HAM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391982, + "client_secret" : "seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0003_post_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0003_post_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM_confirm.tail new file mode 100644 index 00000000..996bf906 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0003_post_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2EFY0qyl6XeW9Fvr0HAM\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_oEeaH1bMl4M5kI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1558 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:04 GMT +original-request: req_oEeaH1bMl4M5kI +stripe-version: 2020-08-27 +idempotency-key: a2c85d29-9cd8-499a-aaa1-419a80aee85b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS2EFY0qyl6XeW9Fvr0HAM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2FFY0qyl6XeWXuDmQuTO", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391983, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbE7ON4z2cCJ9" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391982, + "client_secret" : "seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0004_get_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0004_get_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM.tail new file mode 100644 index 00000000..fa52bb35 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0004_get_v1_setup_intents_seti_1PiS2EFY0qyl6XeW9Fvr0HAM.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2EFY0qyl6XeW9Fvr0HAM\?client_secret=seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YaNnfGoRqyaPLU +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2EFY0qyl6XeW9Fvr0HAM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS2FFY0qyl6XeWXuDmQuTO", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391982, + "client_secret" : "seti_1PiS2EFY0qyl6XeW9Fvr0HAM_secret_QZbEiBJsbou5kH0wKS5WWAMpd7VqwVH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..bed11ae7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testAllowRedisplaylegacy/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbE7ON4z2cCJ9&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dd2F5TEYOuQvDH +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS2FFY0qyl6XeWXuDmQuTO", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391983, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbE7ON4z2cCJ9" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..25733468 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=0QfAZegkCle9az9%2BBSTWKu%2FCQ4%2FLOSz2KSTr5utRK5bPH1TxDgsT%2Bq3mPzFVtutIb391BkthDpnd3hviMPOHelgy6yJB7DITeYZLUhnzRmsGGOoFL5xG90UsLf6LRaKL%2Fv6vUi6xTV2KtCysUZI0Kmzg6PhbeJr%2Bo4nk4Qmi1p6IOzRFQ53WGAk%2FIVje8TFeEUXC5Wnd%2BcZqI6t3C5O5Lw%2BysLI0C%2B2D0NNs6ynZKh0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 63525fd53f925f6b338b0fe0d26d2c81 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGY4azlaaGtOMndjdVhmeThTQmM2M0ZRVGt6bHFSV0o_00Obn7TR4y","customer":"cus_QZbEu9acXoVXZh"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..817ebcaf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=aySOvvw239eNJVEgPiKSPTwEP5gDOiGfhIdmWLULrbCM42tAD7U0bgpc0zdPNFM133GsA8YaFhoayQKAKTbdEzcYMX7JqmQhbKm9Q%2FMviw%2Be8i7d74h%2FMWw%2FOq8HAHFplGW20rBF%2Fp20I%2BhDAfWN8hJ1mQ5eJO8e9BWpL6b661eLu%2FsfR%2FZPQ%2BMfPYJ3M%2BQCxAfFj7PrnSqhvB3d9ogtU8Qk25twjcaqtNLJ55xoy%2F8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0f08c6a309a6ba19022cdcfab544e34b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2KFY0qyl6XeWaSCEpeqZ","secret":"seti_1PiS2KFY0qyl6XeWaSCEpeqZ_secret_QZbEfg7BkjI5BWo3mswhTZbAjVBqlh1","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0002_get_v1_setup_intents_seti_1PiS2KFY0qyl6XeWaSCEpeqZ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0002_get_v1_setup_intents_seti_1PiS2KFY0qyl6XeWaSCEpeqZ.tail new file mode 100644 index 00000000..91b3e415 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0002_get_v1_setup_intents_seti_1PiS2KFY0qyl6XeWaSCEpeqZ.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2KFY0qyl6XeWaSCEpeqZ\?client_secret=seti_1PiS2KFY0qyl6XeWaSCEpeqZ_secret_QZbEfg7BkjI5BWo3mswhTZbAjVBqlh1$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uFC8h5RnjyBtCN +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2KFY0qyl6XeWaSCEpeqZ", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391988, + "client_secret" : "seti_1PiS2KFY0qyl6XeWaSCEpeqZ_secret_QZbEfg7BkjI5BWo3mswhTZbAjVBqlh1", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0003_post_v1_setup_intents_seti_1PiS2KFY0qyl6XeWaSCEpeqZ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0003_post_v1_setup_intents_seti_1PiS2KFY0qyl6XeWaSCEpeqZ_confirm.tail new file mode 100644 index 00000000..68bb299b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmation/0003_post_v1_setup_intents_seti_1PiS2KFY0qyl6XeWaSCEpeqZ_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2KFY0qyl6XeWaSCEpeqZ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WDxlBbBjvrf4rp +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1558 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:10 GMT +original-request: req_WDxlBbBjvrf4rp +stripe-version: 2020-08-27 +idempotency-key: af7f9c88-ac0e-4f51-acff-ef8074587365 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2KFY0qyl6XeWaSCEpeqZ_secret_QZbEfg7BkjI5BWo3mswhTZbAjVBqlh1&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS2KFY0qyl6XeWaSCEpeqZ", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2LFY0qyl6XeWLirP2Db0", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391989, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbEu9acXoVXZh" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391988, + "client_secret" : "seti_1PiS2KFY0qyl6XeWaSCEpeqZ_secret_QZbEfg7BkjI5BWo3mswhTZbAjVBqlh1", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..69fffcc1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=cpkLjPTReQo%2FkcRoRDld5%2ByKKdh0eTBO809crHLwjt32wg%2FZ9Wqqb7RDbXzTvtBAJF47VzLrrYJ6UKqSoWOtMsonqcLhKOVYFczexvbBXelHk8QNlTG34bcrJknMb6R29WEvCtGCXs%2Bkcy%2FcZGD%2B%2FROfVdNZQCdhj%2FFgl%2Ft6b4odB37vjFCCui3D%2BbHTRNfEauEb2CymDJqvJaMeTB%2F8btPVvRsBNzY%2BrJI4usK2MrE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c4d0d9be80b119e5cfbdee9a8cb3bb17 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLHNBNkd5MjJpWGxQdDBmUjhCcVV0Y21Cc0pkUHU3NDA_00Qtzpk01b","customer":"cus_QZbEsHRb9nRnSG"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..36352ef5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ihuRfFhYaOWFtFBiMhfNS9npNV6qmNmR6eyV5S4q76poKC5tIP6mxizYaeEHLuYALBNh4XGzIkZVNtXv%2B%2FYogFotL0FXagb5GE4fmTzfte%2BMHOxDKJNVcd2mEs4KGxCnIVKJvbDApQ%2FOn3YKOVMdBYsx4TGtJtgFpSs5uYZ6oFnv8f9r8U9Z4NEe1kViWMIs3LYJT1qc8lFJB8m7v91sHc6oGonS95MLSL%2FZbNxabXA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2d89ce0b05a36b0a28f082057e24b4f5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2IKG6vc7r7YCvXuia3LK","secret":"seti_1PiS2IKG6vc7r7YCvXuia3LK_secret_QZbEgN3TE2zVURAIuzKijyiMWOFtxVj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0002_get_v1_setup_intents_seti_1PiS2IKG6vc7r7YCvXuia3LK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0002_get_v1_setup_intents_seti_1PiS2IKG6vc7r7YCvXuia3LK.tail new file mode 100644 index 00000000..9a4826ed --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0002_get_v1_setup_intents_seti_1PiS2IKG6vc7r7YCvXuia3LK.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2IKG6vc7r7YCvXuia3LK\?client_secret=seti_1PiS2IKG6vc7r7YCvXuia3LK_secret_QZbEgN3TE2zVURAIuzKijyiMWOFtxVj$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AlEOM0upQH0cgi +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2IKG6vc7r7YCvXuia3LK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391986, + "client_secret" : "seti_1PiS2IKG6vc7r7YCvXuia3LK_secret_QZbEgN3TE2zVURAIuzKijyiMWOFtxVj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0003_post_v1_setup_intents_seti_1PiS2IKG6vc7r7YCvXuia3LK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0003_post_v1_setup_intents_seti_1PiS2IKG6vc7r7YCvXuia3LK_confirm.tail new file mode 100644 index 00000000..ed38205e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testCardConfirmationFR/0003_post_v1_setup_intents_seti_1PiS2IKG6vc7r7YCvXuia3LK_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2IKG6vc7r7YCvXuia3LK\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jDcNkv47ZYcb0N +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1558 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:07 GMT +original-request: req_jDcNkv47ZYcb0N +stripe-version: 2020-08-27 +idempotency-key: fa41abb7-f355-4a9a-9699-22aa37d51d6f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2IKG6vc7r7YCvXuia3LK_secret_QZbEgN3TE2zVURAIuzKijyiMWOFtxVj&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS2IKG6vc7r7YCvXuia3LK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2IKG6vc7r7YCnSv4c3IY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391986, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbEsHRb9nRnSG" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391986, + "client_secret" : "seti_1PiS2IKG6vc7r7YCvXuia3LK_secret_QZbEgN3TE2zVURAIuzKijyiMWOFtxVj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..660efcd0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=wxF%2FAbOMeVmlf4vU7csX7d5iWuCy21%2F0DLK8EmS72EwkWaLWndIZ2iY5w%2Bzd9SIbYHUwtbR1IJ%2Bi%2BMCUXwEcJfeotUZgcUx5uiPQIDzKFxrLT5b85v8WE983YMFZzWcsSzChVpb00smGEFnVGBGxQqkCyOWPL0eDaaZyOTfJJ3Jdar7qwbCxnhyGzlAmiquofQhv9LF7iwbTfWyWVzpYKgLSYQwgcylQWbEVVO5qdRU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3f0af93c90e236b76fb4e4e70112ae10;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFhnOWJYdnBmbWVDdk9kdDRlNGxzRXZtOGN2SUlBRzA_00zERAFiZy","customer":"cus_QZbEt9juPZUx8w"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..e76a9800 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=RlyUg9KxlEzgBwPuLBkkhO8N0zEtKLjJpTDhvh%2F3VvQMVct%2Bpgt8YFC0zUXvAgnZAQhRskHn5hpXJJlGHWaWD0gmR8pqcLaXpAaQ0PCLbPtrmp7nbxIBxKTzfRfL1dA6OhXGFa9xvx%2FdU%2B3kDf2%2BSgBvSksAGNhGmkfyKuHqEKMXrbvKOK7MKuIItjO0m8W7EVR8kyyk3CLahiJn%2FlKsyW%2BdsbYz3uzysw3jkfGNBqo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a210a533a7935a1ceb3ec415e16c3acc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2NFY0qyl6XeWBZGcxTE5","secret":"seti_1PiS2NFY0qyl6XeWBZGcxTE5_secret_QZbE3gPvB4d378gwXSZFpW9TfBlkWRv","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0002_get_v1_setup_intents_seti_1PiS2NFY0qyl6XeWBZGcxTE5.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0002_get_v1_setup_intents_seti_1PiS2NFY0qyl6XeWBZGcxTE5.tail new file mode 100644 index 00000000..25bf97f0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0002_get_v1_setup_intents_seti_1PiS2NFY0qyl6XeWBZGcxTE5.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2NFY0qyl6XeWBZGcxTE5\?client_secret=seti_1PiS2NFY0qyl6XeWBZGcxTE5_secret_QZbE3gPvB4d378gwXSZFpW9TfBlkWRv$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8qTQI77EpFRC4I +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2NFY0qyl6XeWBZGcxTE5", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391991, + "client_secret" : "seti_1PiS2NFY0qyl6XeWBZGcxTE5_secret_QZbE3gPvB4d378gwXSZFpW9TfBlkWRv", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0003_post_v1_setup_intents_seti_1PiS2NFY0qyl6XeWBZGcxTE5_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0003_post_v1_setup_intents_seti_1PiS2NFY0qyl6XeWBZGcxTE5_confirm.tail new file mode 100644 index 00000000..6430a4b2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testSepaConfirmation/0003_post_v1_setup_intents_seti_1PiS2NFY0qyl6XeWBZGcxTE5_confirm.tail @@ -0,0 +1,81 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2NFY0qyl6XeWBZGcxTE5\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bLy4K73k07D90a +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1311 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:12 GMT +original-request: req_bLy4K73k07D90a +stripe-version: 2020-08-27 +idempotency-key: bad6fa1a-24fb-4c95-9641-186a0743c96e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2NFY0qyl6XeWBZGcxTE5_secret_QZbE3gPvB4d378gwXSZFpW9TfBlkWRv&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=San%20Francisco&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=123%20Main&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[billing_details]\[address]\[state]=AL&payment_method_data\[billing_details%5Bemail%5D]=test%40example\.com&payment_method_data\[billing_details%5Bname%5D]=John%20Doe&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sepa_debit%5Biban%5D]=DE89370400440532013000&payment_method_data\[type]=sepa_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS2NFY0qyl6XeWBZGcxTE5", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiS2NFY0qyl6XeWiSokAThl", + "billing_details" : { + "email" : "test@example.com", + "phone" : null, + "name" : "John Doe", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "San Francisco", + "line1" : "123 Main", + "postal_code" : "65432" + } + }, + "livemode" : false, + "created" : 1722391991, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_QZbEt9juPZUx8w" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391991, + "client_secret" : "seti_1PiS2NFY0qyl6XeWBZGcxTE5_secret_QZbE3gPvB4d378gwXSZFpW9TfBlkWRv", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..06258010 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=kMbSP24EoX14A3Doj0J84duO0NhBAPSCvnf59dcbxW5oABW%2FG55pMUlPQe2fuao22NRY2eR8SPPLhFiHWDnNvJBTbWklobwQxMfau3kpZTKyQ48EK1PAb8%2BC%2FNpv6i6KXwCY9ggmv%2B83K2YkfDhMPph7qnd4On06cobCC83ueHQeKtpx1JER2Vw%2FFw30rPHOWkK9PVr7arILzssXmNh9eaYbIMWTpZNFt3FcCPas3Hc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 507e1e7a9ca39dea4c7aa56b43c8bd88;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 18:31:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1QAbztFY0qyl6XeWSwepK8jr","secret":"seti_1QAbztFY0qyl6XeWSwepK8jr_secret_R2hOSpH9lTW0FDhshSOCzPRo8rCBfkD","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0001_get_v1_setup_intents_seti_1QAbztFY0qyl6XeWSwepK8jr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0001_get_v1_setup_intents_seti_1QAbztFY0qyl6XeWSwepK8jr.tail new file mode 100644 index 00000000..1f635d1d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0001_get_v1_setup_intents_seti_1QAbztFY0qyl6XeWSwepK8jr.tail @@ -0,0 +1,51 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1QAbztFY0qyl6XeWSwepK8jr\?client_secret=seti_1QAbztFY0qyl6XeWSwepK8jr_secret_R2hOSpH9lTW0FDhshSOCzPRo8rCBfkD$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5nVBeSCSHTFwil +Content-Length: 651 +Vary: Origin +Date: Wed, 16 Oct 2024 18:31:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1QAbztFY0qyl6XeWSwepK8jr", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1729103461, + "client_secret" : "seti_1QAbztFY0qyl6XeWSwepK8jr_secret_R2hOSpH9lTW0FDhshSOCzPRo8rCBfkD", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0002_post_v1_setup_intents_seti_1QAbztFY0qyl6XeWSwepK8jr_link_account_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0002_post_v1_setup_intents_seti_1QAbztFY0qyl6XeWSwepK8jr_link_account_sessions.tail new file mode 100644 index 00000000..73fc0e7e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccount/0002_post_v1_setup_intents_seti_1QAbztFY0qyl6XeWSwepK8jr_link_account_sessions.tail @@ -0,0 +1,59 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1QAbztFY0qyl6XeWSwepK8jr\/link_account_sessions$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Flink_account_sessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=lpm-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=lpm-bapi-srv"}],"include_subdomains":true} +request-id: req_xBrz9UIomatKW7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 531 +Vary: Origin +Date: Wed, 16 Oct 2024 18:31:02 GMT +original-request: req_xBrz9UIomatKW7 +stripe-version: 2020-08-27 +idempotency-key: 1835b3a2-61bd-436c-8422-5f4282a6e1f3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1QAbztFY0qyl6XeWSwepK8jr_secret_R2hOSpH9lTW0FDhshSOCzPRo8rCBfkD&hosted_surface=payment_element&link_mode=LINK_DISABLED&payment_method_data%5Bbilling_details%5D%5Bemail%5D=test%40example\.com&payment_method_data%5Bbilling_details%5D%5Bname%5D=John%20Doe&payment_method_data%5Btype%5D=us_bank_account + +{ + "object" : "link_account_session", + "filters" : { + "countries" : null, + "account_subcategories" : [ + "checking", + "savings" + ] + }, + "id" : "fcsess_1QAbzuFY0qyl6XeWKijAh6q4", + "livemode" : false, + "prefetch" : [ + + ], + "linked_accounts" : { + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "total_count" : 0, + "url" : "\/v1\/linked_accounts?session=fcsess_1QAbzuFY0qyl6XeWKijAh6q4" + }, + "client_secret" : "fcsess_client_secret_tGXvM4Bg6tqC1FlOXPLOWGDA", + "permissions" : [ + "payment_method" + ] +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..b9100a99 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=BNPYA5kiUgXXtmiVbpAFGUNLInLkSzv7Is%2BqNj9z%2B52bfTi7LyvTiBf0J5T4PtR8jio73ScDsvAfRYDJCxWqeGNhcR5PqNxUHKgfvE%2FnfaVNvAW8PXkaF4R3bHoAPO8R%2FqkE0FU8mj3NvBpyNvLyb2mUdx%2FpFKuj7W1Kda2EkVskf7FxPS75dN5n6gWAs%2BOIAvX8cowom3qjj1c4bb1QUHyedmiLkwphAieMByWdIsY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4d82c81053bbcc136b4149fa3caad951;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:11:41 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1QAal7FY0qyl6XeWmpU3uZvt","secret":"seti_1QAal7FY0qyl6XeWmpU3uZvt_secret_R2g7P4UwG2D372G9nm6uUC2XVjlnZNI","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0001_get_v1_setup_intents_seti_1QAal7FY0qyl6XeWmpU3uZvt.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0001_get_v1_setup_intents_seti_1QAal7FY0qyl6XeWmpU3uZvt.tail new file mode 100644 index 00000000..07e62d1b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0001_get_v1_setup_intents_seti_1QAal7FY0qyl6XeWmpU3uZvt.tail @@ -0,0 +1,51 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1QAal7FY0qyl6XeWmpU3uZvt\?client_secret=seti_1QAal7FY0qyl6XeWmpU3uZvt_secret_R2g7P4UwG2D372G9nm6uUC2XVjlnZNI$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mBT1iOLCg8OeGy +Content-Length: 651 +Vary: Origin +Date: Wed, 16 Oct 2024 17:11:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1QAal7FY0qyl6XeWmpU3uZvt", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1729098701, + "client_secret" : "seti_1QAal7FY0qyl6XeWmpU3uZvt_secret_R2g7P4UwG2D372G9nm6uUC2XVjlnZNI", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0002_post_v1_setup_intents_seti_1QAal7FY0qyl6XeWmpU3uZvt_link_account_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0002_post_v1_setup_intents_seti_1QAal7FY0qyl6XeWmpU3uZvt_link_account_sessions.tail new file mode 100644 index 00000000..1349275d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/CustomerSheetConfirmFlowTests/testUSBankAccountAttachDefaults/0002_post_v1_setup_intents_seti_1QAal7FY0qyl6XeWmpU3uZvt_link_account_sessions.tail @@ -0,0 +1,59 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1QAal7FY0qyl6XeWmpU3uZvt\/link_account_sessions$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Flink_account_sessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=lpm-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=lpm-bapi-srv"}],"include_subdomains":true} +request-id: req_gDesS7f5C15PW8 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 531 +Vary: Origin +Date: Wed, 16 Oct 2024 17:11:42 GMT +original-request: req_gDesS7f5C15PW8 +stripe-version: 2020-08-27 +idempotency-key: 541871ac-a8ee-4edd-a8dd-87b1bb8e73df +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1QAal7FY0qyl6XeWmpU3uZvt_secret_R2g7P4UwG2D372G9nm6uUC2XVjlnZNI&hosted_surface=payment_element&link_mode=LINK_DISABLED&payment_method_data%5Bbilling_details%5D%5Bemail%5D=test%40example\.com&payment_method_data%5Bbilling_details%5D%5Bname%5D=John%20Doe&payment_method_data%5Btype%5D=us_bank_account + +{ + "object" : "link_account_session", + "filters" : { + "countries" : null, + "account_subcategories" : [ + "checking", + "savings" + ] + }, + "id" : "fcsess_1QAal8FY0qyl6XeWofUBOUTR", + "livemode" : false, + "prefetch" : [ + + ], + "linked_accounts" : { + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "total_count" : 0, + "url" : "\/v1\/linked_accounts?session=fcsess_1QAal8FY0qyl6XeWofUBOUTR" + }, + "client_secret" : "fcsess_client_secret_NvhYqIW6cdPN0mjYyZxBpx8C", + "permissions" : [ + "payment_method" + ] +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..a3c8bc21 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0000_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_xtJ4s1W7ILXynw +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1Xp9B7NqL0R", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "0489d4f5-ec43-4b89-bb2b-d037296b2056", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..65a560e0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0001_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bmode%5D=setup&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_iIyWfo9GJqqPbg +Content-Length: 13001 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1czDyOSUA61", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "fdd60652-1696-4040-92e1-862803878c54", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..d90e212d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdate/0002_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_2b2WFNXckaYLgo +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1rNGHJXb9mG", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "de1a7bfc-461a-4cd5-b111-ea0e67a9be8f", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..7dab3893 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0000_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_oX6hmzAONGMpiK +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1eTE6CgayeU", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "8379bd94-091e-4895-816f-bc650bf97eb0", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..f2ac91ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0001_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_PQXMbnT2C4H9S7 +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_12SVVavfAE8", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "adc30663-6b5b-4d8d-9beb-7a99da78133c", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..9de8bb98 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateCancelsInFlightUpdate/0002_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bmode%5D=setup&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_EQPVm5TaXJiaSJ +Content-Length: 13001 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_14MY9euryfQ", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "a77271fb-24ff-4a26-9bb8-cbe89267e56a", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..bda4b365 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0000_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_4lfcPj0cCHOlMT +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_18BNQzEdsgm", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "5f81cb30-97d5-4359-9cc2-87f9ff879e70", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..57bd8f8f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0001_get_v1_elements_sessions.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=Invalid%20currency&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +400 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_trv4HubqyMGMNC +Content-Length: 1044 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "error" : { + "param" : "deferred_intent[currency]", + "message" : "Invalid currency: invalid currency. Stripe currently supports these currencies: usd, aed, afn, all, amd, ang, aoa, ars, aud, awg, azn, bam, bbd, bdt, bgn, bhd, bif, bmd, bnd, bob, brl, bsd, bwp, byn, bzd, cad, cdf, chf, clp, cny, cop, crc, cve, czk, djf, dkk, dop, dzd, egp, etb, eur, fjd, fkp, gbp, gel, gip, gmd, gnf, gtq, gyd, hkd, hnl, hrk, htg, huf, idr, ils, inr, isk, jmd, jod, jpy, kes, kgs, khr, kmf, krw, kwd, kyd, kzt, lak, lbp, lkr, lrd, lsl, mad, mdl, mga, mkd, mmk, mnt, mop, mur, mvr, mwk, mxn, myr, mzn, nad, ngn, nio, nok, npr, nzd, omr, pab, pen, pgk, php, pkr, pln, pyg, qar, ron, rsd, rub, rwf, sar, sbd, scr, sek, sgd, shp, sle, sos, srd, std, szl, thb, tjs, tnd, top, try, ttd, twd, tzs, uah, ugx, uyu, uzs, vnd, vuv, wst, xaf, xcd, xof, xpf, yer, zar, zmw, usdc, btn, ghs, eek, lvl, svc, vef, ltl, sll, mro", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_trv4HubqyMGMNC?t=1729094920" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..66824630 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/EmbeddedPaymentElementTest/testUpdateFails/0002_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_pHUREqBlWmEkQg +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 16:08:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1fezwrXSzAw", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "90d10d9a-fa68-49a3-9915-045be137cdd5", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentMethodMessagingViewFunctionalTest/testCreatesViewFromServerResponse/0000_get_content.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentMethodMessagingViewFunctionalTest/testCreatesViewFromServerResponse/0000_get_content.tail new file mode 100644 index 00000000..4002ec77 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentMethodMessagingViewFunctionalTest/testCreatesViewFromServerResponse/0000_get_content.tail @@ -0,0 +1,23 @@ +GET +https:\/\/ppm\.stripe\.com\/content\?amount=1099&client=ios&country=US¤cy=USD&locale=en-US&logo_color=black&payment_methods%5B0%5D=klarna&payment_methods%5B1%5D=afterpay_clearpay$ +404 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Date: Fri, 19 Jul 2024 22:38:31 GMT +access-control-allow-credentials: true +Content-Length: 199 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +x-content-type-options: nosniff +Vary: Origin + +{ + "error" : { + "message" : "Unrecognized request URL (GET: \/content). Please see https:\/\/stripe.com\/docs or we can help at https:\/\/support.stripe.com\/.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentMethodMessagingViewFunctionalTest/testInitializingWithBadConfigurationReturnsError/0000_get_content.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentMethodMessagingViewFunctionalTest/testInitializingWithBadConfigurationReturnsError/0000_get_content.tail new file mode 100644 index 00000000..a5778b1c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentMethodMessagingViewFunctionalTest/testInitializingWithBadConfigurationReturnsError/0000_get_content.tail @@ -0,0 +1,23 @@ +GET +https:\/\/ppm\.stripe\.com\/content\?amount=-100&client=ios&country=US¤cy=FOO&locale=en-US&logo_color=black&payment_methods%5B0%5D=klarna$ +404 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Date: Fri, 19 Jul 2024 22:38:31 GMT +access-control-allow-credentials: true +Content-Length: 199 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +x-content-type-options: nosniff +Vary: Origin + +{ + "error" : { + "message" : "Unrecognized request URL (GET: \/content). Please see https:\/\/stripe.com\/docs or we can help at https:\/\/support.stripe.com\/.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..742feabb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9hBxywxko7LXf7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:15 GMT +original-request: req_9hBxywxko7LXf7 +stripe-version: 2020-08-27 +idempotency-key: a73ddc65-a9f5-423e-89f8-03ae5a95121e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4000000000009995&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2RFY0qyl6XeWJaCK8FKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391995, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..a7cbcb27 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=CN1AV09m5ArJKxW82hgD14BhkOAiqP9OPk9%2BldmB7zviR9b97LksisojRT4uOoVLb4b0OfJgKftZiqx2AczUBFHE19KgoI4cybeKwpI5DDQF9iFbzAv0bdehacrTOq%2FJRvIH5O8OpJgr0QqFe7O8FANB0%2BmnRFKvyzrICSWi%2B8cMvTfRLdgv62C4xjeBEN0pS3MlxVjaogRANPDY2%2FV2RA6xZB4cAge4nBg9L4KTe4w%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6f55ec211df5b5f8fcdf66ed631d8b8d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:15 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2RFY0qyl6XeW0Li38Ol8","secret":"pi_3PiS2RFY0qyl6XeW0Li38Ol8_secret_tELaQZJYJ2tOOK2D3tIZWY1HG","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0002_get_v1_payment_intents_pi_3PiS2RFY0qyl6XeW0Li38Ol8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0002_get_v1_payment_intents_pi_3PiS2RFY0qyl6XeW0Li38Ol8.tail new file mode 100644 index 00000000..4448591c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0002_get_v1_payment_intents_pi_3PiS2RFY0qyl6XeW0Li38Ol8.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2RFY0qyl6XeW0Li38Ol8\?client_secret=pi_3PiS2RFY0qyl6XeW0Li38Ol8_secret_tELaQZJYJ2tOOK2D3tIZWY1HG&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Clt5RWhyolEsWb +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2RFY0qyl6XeW0Li38Ol8_secret_tELaQZJYJ2tOOK2D3tIZWY1HG", + "id" : "pi_3PiS2RFY0qyl6XeW0Li38Ol8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391995, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0003_post_v1_payment_intents_pi_3PiS2RFY0qyl6XeW0Li38Ol8_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0003_post_v1_payment_intents_pi_3PiS2RFY0qyl6XeW0Li38Ol8_confirm.tail new file mode 100644 index 00000000..c0ad960e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0003_post_v1_payment_intents_pi_3PiS2RFY0qyl6XeW0Li38Ol8_confirm.tail @@ -0,0 +1,194 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2RFY0qyl6XeW0Li38Ol8\/confirm$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2SrpebysIdCWCG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 4510 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:17 GMT +original-request: req_2SrpebysIdCWCG +stripe-version: 2020-08-27 +idempotency-key: ecd5c31b-5844-4168-812f-522b27234899 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS2RFY0qyl6XeW0Li38Ol8_secret_tELaQZJYJ2tOOK2D3tIZWY1HG&expand\[0]=payment_method&payment_method=pm_1PiS2RFY0qyl6XeWJaCK8FKI&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "error" : { + "charge" : "ch_3PiS2RFY0qyl6XeW0zNk8ciS", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : { + "charge" : "ch_3PiS2RFY0qyl6XeW0zNk8ciS", + "decline_code" : "insufficient_funds", + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "type" : "card_error", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2RFY0qyl6XeWJaCK8FKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391995, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + } + }, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2RFY0qyl6XeW0Li38Ol8_secret_tELaQZJYJ2tOOK2D3tIZWY1HG", + "id" : "pi_3PiS2RFY0qyl6XeW0Li38Ol8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391995, + "description" : null + }, + "decline_code" : "insufficient_funds", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_2SrpebysIdCWCG?t=1722391996", + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "type" : "card_error", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2RFY0qyl6XeWJaCK8FKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391995, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..0bfe60eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0004_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Hf8huntxqMoRjc +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:17 GMT +original-request: req_Hf8huntxqMoRjc +stripe-version: 2020-08-27 +idempotency-key: 9b641f2b-ec1a-42ad-a7c2-d89225346edd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4000000000009995&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2TFY0qyl6XeW3aKOM3KN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391997, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..482d8786 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +402 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=WFGwfBSrR%2BsFIozlqHB4Gf7a4d%2FI%2BhOsI7q7GqdV5x4GcvfyzNn5%2BRyFJNv2v0UivQKVTlMRz6Z5PhVwq%2BYI8mu7SUHigN9vuFbummFrMZg6g0kppIK3wuRWq%2FWqdxoVyVnmLu3UkdzXq64tgRV7VWE0TmqHRvwhylM0AXuf3k7ZkQmjjyff3PbiOnv3fOCGpoGkb2WOXtC4l7kAhkRH%2BXtI%2BZ%2F1w%2BqbI0RXilf1Xuo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c27958ae3cbe23f316fd0c7fb960e87b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:18 GMT +x-robots-tag: noindex, nofollow +Content-Length: 63 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +Error creating PaymentIntent: Your card has insufficient funds. \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0006_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0006_post_v1_payment_methods.tail new file mode 100644 index 00000000..d20accb2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0006_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KicrNntCpm4a3s +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:19 GMT +original-request: req_KicrNntCpm4a3s +stripe-version: 2020-08-27 +idempotency-key: fa85d908-e359-4129-8a76-28adf8215891 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4000000000009995&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2UFY0qyl6XeWwkM6CEzg", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391998, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0007_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0007_post_create_setup_intent.tail new file mode 100644 index 00000000..1a3290ca --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0007_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=g7iqxFOyI%2BOGLJ1YmLbRmmfD0FJTov5vfB80oE8uhOZRgNL2Y9kRCLqAXVgq%2FQVt3dw%2FOLCeTFLSlM6xoAjdtyXY6HW7Pfm1RLKB2UlIua3P9f%2B29hvDc2fXczUny8yT6sU8o00PB2G4dYqZC9OWyMPgy9knI17GKJy7Bg2AY4HUot0%2BkOxuUBMbw64HDHjyRlSMMy92ztHb5TboK9mfD4tnw7ml%2Bueq%2FkwTPGAqz2g%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7ce7722bc36a5c5cf70e088090e7186f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2VFY0qyl6XeWcg242nPq","secret":"seti_1PiS2VFY0qyl6XeWcg242nPq_secret_QZbEM1rqfCMUrCj6rvggAYEJUTYQO5n","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0008_get_v1_setup_intents_seti_1PiS2VFY0qyl6XeWcg242nPq.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0008_get_v1_setup_intents_seti_1PiS2VFY0qyl6XeWcg242nPq.tail new file mode 100644 index 00000000..f41860f1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0008_get_v1_setup_intents_seti_1PiS2VFY0qyl6XeWcg242nPq.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2VFY0qyl6XeWcg242nPq\?client_secret=seti_1PiS2VFY0qyl6XeWcg242nPq_secret_QZbEM1rqfCMUrCj6rvggAYEJUTYQO5n&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fzKcmCJcSaGftv +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2VFY0qyl6XeWcg242nPq", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391999, + "client_secret" : "seti_1PiS2VFY0qyl6XeWcg242nPq_secret_QZbEM1rqfCMUrCj6rvggAYEJUTYQO5n", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0009_post_v1_setup_intents_seti_1PiS2VFY0qyl6XeWcg242nPq_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0009_post_v1_setup_intents_seti_1PiS2VFY0qyl6XeWcg242nPq_confirm.tail new file mode 100644 index 00000000..2253a13b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0009_post_v1_setup_intents_seti_1PiS2VFY0qyl6XeWcg242nPq_confirm.tail @@ -0,0 +1,160 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2VFY0qyl6XeWcg242nPq\/confirm$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NEyyOwpUn9yCA2 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 3668 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:20 GMT +original-request: req_NEyyOwpUn9yCA2 +stripe-version: 2020-08-27 +idempotency-key: f850f89a-c7c6-4d1a-b514-3499b3bd3654 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2VFY0qyl6XeWcg242nPq_secret_QZbEM1rqfCMUrCj6rvggAYEJUTYQO5n&expand\[0]=payment_method&payment_method=pm_1PiS2UFY0qyl6XeWwkM6CEzg&use_stripe_sdk=true + +{ + "error" : { + "decline_code" : "insufficient_funds", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_NEyyOwpUn9yCA2?t=1722391999", + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "type" : "card_error", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2UFY0qyl6XeWwkM6CEzg", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391998, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "setup_intent" : { + "id" : "seti_1PiS2VFY0qyl6XeWcg242nPq", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : { + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2UFY0qyl6XeWwkM6CEzg", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391998, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "type" : "card_error", + "decline_code" : "insufficient_funds" + }, + "created" : 1722391999, + "client_secret" : "seti_1PiS2VFY0qyl6XeWcg242nPq_secret_QZbEM1rqfCMUrCj6rvggAYEJUTYQO5n", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0010_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0010_post_v1_payment_methods.tail new file mode 100644 index 00000000..f540c213 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0010_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FWVWSP3puohOB0 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:21 GMT +original-request: req_FWVWSP3puohOB0 +stripe-version: 2020-08-27 +idempotency-key: e39284ea-872b-4217-b0f3-33bf4a30c880 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4000000000009995&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2WFY0qyl6XeWYVeryhK5", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392000, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0011_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0011_post_create_setup_intent.tail new file mode 100644 index 00000000..26f3bf8e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmnewinsufficientfundscard/0011_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +402 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=cFhOSfTHGadbzJlbP2UI2mLFX%2FE%2BOnDMKZpyVvaXIW0LdI6%2FODbPJJd%2BGw3%2BxFXWI%2BeOJPVJ%2B16Hc8AExnf2z%2BVp5i3Lie8qjiSTdHUYCctq%2FEGzHJ%2B78tS%2BlRpQ21F6mwdlvl5UZBx9ZuiNNvKqeRl5lL9MLidBcoplgJ3yf37k1qrkti9taWd%2Brn94DsOghhO%2Fdv9pODQr0W%2BpWeBfsJj%2FBy5Wv1Xsx%2FpGG21O8ak%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 236915c33d0a93791b67c79390807b90;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:21 GMT +x-robots-tag: noindex, nofollow +Content-Length: 61 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +Error creating SetupIntent: Your card has insufficient funds. \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..5bb60d75 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4uxQIpx291va4H +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:22 GMT +original-request: req_4uxQIpx291va4H +stripe-version: 2020-08-27 +idempotency-key: 067547b4-d53f-444b-a7b8-88fde00040e9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2YFY0qyl6XeWjCMzIsrc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392002, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..9bbd1006 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Rq4PWAJ0LzqPjzmYw6%2F9QvB1F1ky9CEQcdJYhh3yOOraM0rOytJX3DZg9HvfDa8zlhcJzky4XVwkoSdsE%2BanDWYVNevjEt43ldkNsOoWGLbmhXV2QDmjjL9IZCc8UmdU%2BuDTEKNIBpZ51PCgAOF%2BGLEE%2BCKEi69HK1ojZ7eUwIswuD1J5ILGDQlN3WZr3FI%2BZwZEQSzzT6NTGFIrvyOGzToi2ht6KcEDGYT3%2F%2FlEaqQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4bbe807db5e0e550c96dcccce9815073 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:22 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2YFY0qyl6XeW025NyBhO","secret":"pi_3PiS2YFY0qyl6XeW025NyBhO_secret_n7A3ANVQZWYoFOresRnmSo8FY","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0002_get_v1_payment_intents_pi_3PiS2YFY0qyl6XeW025NyBhO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0002_get_v1_payment_intents_pi_3PiS2YFY0qyl6XeW025NyBhO.tail new file mode 100644 index 00000000..012a8df2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentclientsideconfirmvalidates/0002_get_v1_payment_intents_pi_3PiS2YFY0qyl6XeW025NyBhO.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2YFY0qyl6XeW025NyBhO\?client_secret=pi_3PiS2YFY0qyl6XeW025NyBhO_secret_n7A3ANVQZWYoFOresRnmSo8FY&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uL0FRQMR26j8Wa +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2YFY0qyl6XeW025NyBhO_secret_n7A3ANVQZWYoFOresRnmSo8FY", + "id" : "pi_3PiS2YFY0qyl6XeW025NyBhO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392002, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..64a17052 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KHa4wGNbVb8O6G +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:23 GMT +original-request: req_KHa4wGNbVb8O6G +stripe-version: 2020-08-27 +idempotency-key: 06a056ea-3226-4774-8838-137453204f0f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2ZFY0qyl6XeWQOegqtWk", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392003, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..1df16d0c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ADAwFHCw7z21NuMhJjSVY3I9X1%2BVca2xUqBv1SfKXQh4R9fAYgewcBmVm7Y5i96AqDHEkdFBPHXU6LYp82HTr7XH7S%2Fz4oOUWyaDaJusTYF79AyyaU2AOWooZlraWNOBrycSsnTzZUWp2%2BaG2iclBPPXxHYTUH%2BexSY%2FFGn7NLcemdrJ4ifnGyNdmzn1pPK56JAnXJcWcPf7Tct7RX3duoMe2z4g4wcSLa6nzKuHnKQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e9aff633cde763a97d01fb4c7d8c1ed8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2ZFY0qyl6XeW0HOLru0s","secret":"pi_3PiS2ZFY0qyl6XeW0HOLru0s_secret_QiIr40FLYH6UaXU7iKSpSjcJy","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0002_get_v1_payment_intents_pi_3PiS2ZFY0qyl6XeW0HOLru0s.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0002_get_v1_payment_intents_pi_3PiS2ZFY0qyl6XeW0HOLru0s.tail new file mode 100644 index 00000000..e496f529 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmpaymentintentserversideconfirmdoesntvalidate/0002_get_v1_payment_intents_pi_3PiS2ZFY0qyl6XeW0HOLru0s.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2ZFY0qyl6XeW0HOLru0s\?client_secret=pi_3PiS2ZFY0qyl6XeW0HOLru0s_secret_QiIr40FLYH6UaXU7iKSpSjcJy&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_M1nkkny8veDm5v +Content-Length: 1894 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2ZFY0qyl6XeWQOegqtWk", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392003, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2ZFY0qyl6XeW0HOLru0s_secret_QiIr40FLYH6UaXU7iKSpSjcJy", + "id" : "pi_3PiS2ZFY0qyl6XeW0HOLru0s", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392003, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..6251051d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=A3f9e8ulHib7QXr0BGTHq414KeJIV83uKn%2FFdN8wtlnhT%2BmRYqkXMSjhkvu5YfsGPSSTUCT6Z7%2FX3FQKOx%2F6rWBMnVzZRF%2FYEA1KazYfE7W7OwzjMy11sgfweT4gzlb2RNbm3FD7%2BD4AkKvg%2Bxt2yn2iph6RR6a36RLgIp%2FZM2vILhMzQHVttcLCzgMHa%2FnM7%2B1gjLEwFz2nJUK5ITHR3bsuVj9xXNBkYDGMa%2FJP7s8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 15abe8aa91afbd283d44eb446869c40d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2bFY0qyl6XeW0QGoXuQ6","secret":"pi_3PiS2bFY0qyl6XeW0QGoXuQ6_secret_IOlPlLOYX7TzjCRnrgDg0g6MZ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0001_get_v1_payment_intents_pi_3PiS2bFY0qyl6XeW0QGoXuQ6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0001_get_v1_payment_intents_pi_3PiS2bFY0qyl6XeW0QGoXuQ6.tail new file mode 100644 index 00000000..930a6959 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0001_get_v1_payment_intents_pi_3PiS2bFY0qyl6XeW0QGoXuQ6.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2bFY0qyl6XeW0QGoXuQ6\?client_secret=pi_3PiS2bFY0qyl6XeW0QGoXuQ6_secret_IOlPlLOYX7TzjCRnrgDg0g6MZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_B1WNHs8dH9GCqa +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2bFY0qyl6XeW0QGoXuQ6_secret_IOlPlLOYX7TzjCRnrgDg0g6MZ", + "id" : "pi_3PiS2bFY0qyl6XeW0QGoXuQ6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392005, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0002_post_v1_payment_intents_pi_3PiS2bFY0qyl6XeW0QGoXuQ6_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0002_post_v1_payment_intents_pi_3PiS2bFY0qyl6XeW0QGoXuQ6_confirm.tail new file mode 100644 index 00000000..37887a2b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0002_post_v1_payment_intents_pi_3PiS2bFY0qyl6XeW0QGoXuQ6_confirm.tail @@ -0,0 +1,194 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2bFY0qyl6XeW0QGoXuQ6\/confirm$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rmhZfG9DTaU7Db +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 4510 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:26 GMT +original-request: req_rmhZfG9DTaU7Db +stripe-version: 2020-08-27 +idempotency-key: cf3b596a-2ef2-4dfd-9cd3-63ac398038cc +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS2bFY0qyl6XeW0QGoXuQ6_secret_IOlPlLOYX7TzjCRnrgDg0g6MZ&expand\[0]=payment_method&payment_method=pm_card_visa_chargeDeclinedInsufficientFunds&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "error" : { + "charge" : "ch_3PiS2bFY0qyl6XeW0gPYwXEB", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : { + "charge" : "ch_3PiS2bFY0qyl6XeW0gPYwXEB", + "decline_code" : "insufficient_funds", + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "type" : "card_error", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2cFY0qyl6XeWU0B3krZu", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 7, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392006, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + } + }, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2bFY0qyl6XeW0QGoXuQ6_secret_IOlPlLOYX7TzjCRnrgDg0g6MZ", + "id" : "pi_3PiS2bFY0qyl6XeW0QGoXuQ6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392005, + "description" : null + }, + "decline_code" : "insufficient_funds", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_rmhZfG9DTaU7Db?t=1722392006", + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "type" : "card_error", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2cFY0qyl6XeWU0B3krZu", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 7, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392006, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0003_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0003_post_create_payment_intent.tail new file mode 100644 index 00000000..f29f1873 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0003_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +402 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=d73SjTqEQR%2FwcdHwqXloyXhymbYIO%2BnveptkKXNgFtw8SKePqJsoEUkRvPZMX4z2dCULCzvKdpaVdE7qSycqSrbHCQL1aXiqPav%2B22hGMZnBldZAksaPax81X4br%2Bkor%2FD9HQP3n9sJLVZ9IjGrP16%2BsTkBfBARnO0BAB9h7QTXpYKEYKzJ274WGLggKBjXhj%2F307IdJHtTSTEnPRUd3HP0pngRR1XNZZKpkKhlvXD4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 29213cb16281305b66ddb8104240dbbf +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:27 GMT +x-robots-tag: noindex, nofollow +Content-Length: 63 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +Error creating PaymentIntent: Your card has insufficient funds. \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0004_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0004_post_create_setup_intent.tail new file mode 100644 index 00000000..b2c66292 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0004_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Hygv2S8W21Mh2z2PD3EcDXjYAMtsuZX4S3ByUKNJw20hn6Df83q5KBfIY5OWD4vMbSHKIAsWSTBg2zuICBFkSJ4I9%2F0LmujUs1%2FabTrq2OLFL6M97TTJfjiY4OgA%2BMgSnOnI5jN4YK1jY1%2Bd3kwvwTw347ngUiMB0ecSYNTUfTDrm8O%2FMI7czYERAw2U7AfdJ3ATLh4msYerslBANQTIJuQ74pddOcEZGl5WixgmcEw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 95641c7c9042080290ca070bef7eb5b9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2eFY0qyl6XeWjBlYwG8E","secret":"seti_1PiS2eFY0qyl6XeWjBlYwG8E_secret_QZbF2H7PZtUHTBfZXfgqtovl5L7xNE1","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0005_get_v1_setup_intents_seti_1PiS2eFY0qyl6XeWjBlYwG8E.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0005_get_v1_setup_intents_seti_1PiS2eFY0qyl6XeWjBlYwG8E.tail new file mode 100644 index 00000000..dbd5577c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0005_get_v1_setup_intents_seti_1PiS2eFY0qyl6XeWjBlYwG8E.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2eFY0qyl6XeWjBlYwG8E\?client_secret=seti_1PiS2eFY0qyl6XeWjBlYwG8E_secret_QZbF2H7PZtUHTBfZXfgqtovl5L7xNE1&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZE4xGwN0lIOqRe +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2eFY0qyl6XeWjBlYwG8E", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392008, + "client_secret" : "seti_1PiS2eFY0qyl6XeWjBlYwG8E_secret_QZbF2H7PZtUHTBfZXfgqtovl5L7xNE1", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0006_post_v1_setup_intents_seti_1PiS2eFY0qyl6XeWjBlYwG8E_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0006_post_v1_setup_intents_seti_1PiS2eFY0qyl6XeWjBlYwG8E_confirm.tail new file mode 100644 index 00000000..8425976d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0006_post_v1_setup_intents_seti_1PiS2eFY0qyl6XeWjBlYwG8E_confirm.tail @@ -0,0 +1,160 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2eFY0qyl6XeWjBlYwG8E\/confirm$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pGUoQkh49JeDsn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 3668 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:29 GMT +original-request: req_pGUoQkh49JeDsn +stripe-version: 2020-08-27 +idempotency-key: 67de3b94-a2d5-4558-8b89-17b13376a340 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2eFY0qyl6XeWjBlYwG8E_secret_QZbF2H7PZtUHTBfZXfgqtovl5L7xNE1&expand\[0]=payment_method&payment_method=pm_card_visa_chargeDeclinedInsufficientFunds&use_stripe_sdk=true + +{ + "error" : { + "decline_code" : "insufficient_funds", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_pGUoQkh49JeDsn?t=1722392008", + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "type" : "card_error", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2eFY0qyl6XeWmwuOAUlL", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 7, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392008, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "setup_intent" : { + "id" : "seti_1PiS2eFY0qyl6XeWjBlYwG8E", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : { + "code" : "card_declined", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/card-declined", + "message" : "Your card has insufficient funds.", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2eFY0qyl6XeWmwuOAUlL", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "9995", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 7, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392008, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "type" : "card_error", + "decline_code" : "insufficient_funds" + }, + "created" : 1722392008, + "client_secret" : "seti_1PiS2eFY0qyl6XeWjBlYwG8E_secret_QZbF2H7PZtUHTBfZXfgqtovl5L7xNE1", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0007_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0007_post_create_setup_intent.tail new file mode 100644 index 00000000..5be024b9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsavedinsufficientfundscard/0007_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +402 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=f1NA6zxMu5YwjrPJpgH47e%2BPjHA3UHoQgeNMvCLhM9TSn9CzLrBBJYzptuS%2BDik%2Fpn%2BXHM0eXMUVyQbMSrJrgUQ38GQdMbEjekolXYxMZBH8bLV8s8dWj%2B9X%2FJ9uZPNd5bE1vupCXgLKzcEXXDT0jejADyoQLBmV%2FsgdcXbzwHXU2GvWciv%2FQfuOltgJjyGESpCX1pAYw7yJ8ILKnlSmDqtr9mz8RQ50QHKdrZI%2BPm0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 364eb4b7b3b738779ee27268b37ad849 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 61 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +Error creating SetupIntent: Your card has insufficient funds. \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..d47139ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EiBmNQNa8AdfAQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:31 GMT +original-request: req_EiBmNQNa8AdfAQ +stripe-version: 2020-08-27 +idempotency-key: 5d4fee35-421f-4ea6-9c1a-973dfe2a9054 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2gFY0qyl6XeWtrNWuxlW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392011, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..ebb7bbbd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2BhWH4kCYAxOfnMJI5Z6lWL9N4Q7hq7kUycc0Md7OjKLJhkc%2Fs53UN9AMK2DlMxlC%2BSL9z5z%2BnlaHx1bkOIJ1svE29dlijZKy4LUUblMepugWskGBzdOZFUYoGRSUoUcHermecwOXPzaXBzuurccpceWQQBWt1n35HxolfwYQpqhg9%2FOPwrfno2Iwi0y7bTR%2FI4xJqWyPuYIuTOuFqDxF3%2FEErirR%2FJ2e9pfq3cTWwek%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 84e2809b103813c1a262598699980d4c;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:32 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2hFY0qyl6XeW59ADP6Ul","secret":"seti_1PiS2hFY0qyl6XeW59ADP6Ul_secret_QZbFRfaepOx7eg3vjtPv0FdtYMt1p6W","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0002_get_v1_setup_intents_seti_1PiS2hFY0qyl6XeW59ADP6Ul.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0002_get_v1_setup_intents_seti_1PiS2hFY0qyl6XeW59ADP6Ul.tail new file mode 100644 index 00000000..fd961b3e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentserversideconfirmdoesntvalidate/0002_get_v1_setup_intents_seti_1PiS2hFY0qyl6XeW59ADP6Ul.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2hFY0qyl6XeW59ADP6Ul\?client_secret=seti_1PiS2hFY0qyl6XeW59ADP6Ul_secret_QZbFRfaepOx7eg3vjtPv0FdtYMt1p6W&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uMXuvFRzRSRIRE +Content-Length: 1537 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2hFY0qyl6XeW59ADP6Ul", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2gFY0qyl6XeWtrNWuxlW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392011, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "on_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392011, + "client_secret" : "seti_1PiS2hFY0qyl6XeW59ADP6Ul_secret_QZbFRfaepOx7eg3vjtPv0FdtYMt1p6W", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..f7f1790e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HMoULJGZjSCY7X +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:32 GMT +original-request: req_HMoULJGZjSCY7X +stripe-version: 2020-08-27 +idempotency-key: 66c40e9f-4961-4a3d-b070-97a7d6a7a335 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2iFY0qyl6XeWeH3oYupm", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392012, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..bf673c19 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=5UsIljddSOpuckdj3ogJHmSi3aWNr3q5MTeEbmkg%2BHgIbKdtiOt1cVb6ajOjiLkd0I7dGsNFLTihqZNqRm37XAfxiGj%2BdpvUJ2kqTIoH6wP%2FyEyRbKC7GgsXeJb89Lv2BSkV5OvSGwM8Ky1D0i9mIoDEfe500doKEKH39cLS%2FYTW9HLAXUR5rkhlxy4mFU4cheAL%2FJcx%2BX0KHrB1mlRQ%2BG4Fl1FwuZleN5rVjBqh%2B6w%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5a9707f18ecf3490c8332f926a4db46c +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2iFY0qyl6XeWqVyArXRB","secret":"seti_1PiS2iFY0qyl6XeWqVyArXRB_secret_QZbFPJSxXYk0B1MXmeWLImTT16T1TZc","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0002_get_v1_setup_intents_seti_1PiS2iFY0qyl6XeWqVyArXRB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0002_get_v1_setup_intents_seti_1PiS2iFY0qyl6XeWqVyArXRB.tail new file mode 100644 index 00000000..2a299ad1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmsetupintentusagedoesntmatchintentconfig/0002_get_v1_setup_intents_seti_1PiS2iFY0qyl6XeWqVyArXRB.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2iFY0qyl6XeWqVyArXRB\?client_secret=seti_1PiS2iFY0qyl6XeWqVyArXRB_secret_QZbFPJSxXYk0B1MXmeWLImTT16T1TZc&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EhJtXzd1C0ccI6 +Content-Length: 532 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2iFY0qyl6XeWqVyArXRB", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "on_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392012, + "client_secret" : "seti_1PiS2iFY0qyl6XeWqVyArXRB_secret_QZbFPJSxXYk0B1MXmeWLImTT16T1TZc", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..e0dad44a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SSqFv4V8pJ7Fog +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:33 GMT +original-request: req_SSqFv4V8pJ7Fog +stripe-version: 2020-08-27 +idempotency-key: a4ce1259-eb13-4b70-aab1-fced0f1c604d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2jFY0qyl6XeWNkf3GuhX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392013, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..4cbfa4c8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=PoEjl4rtICt70ujylkWkpdsZiPSVMvDnJCvq9KyfN%2BYpPqrFAN6p0lDvUBDdJbQqv55nCIsHLEykmF9zU328yB%2F%2BF2EoDkSci1UwmWXIcg3bPysD4iEiTS1naAHqbY7dwRo85jHbniRs8ypJ1qakLDDRIXWNsWoLaiurVQmX%2B9ifCDFMbVHJDSmpC4811W9He2JtfXPggwl6PE8ZsYbDdWoRb3q%2F1KjTyRKxZQVOwlc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ba4989c34eae6638203821ffc858ac5d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:34 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2jFY0qyl6XeW0whXhj0e","secret":"pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0002_get_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0002_get_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e.tail new file mode 100644 index 00000000..7abb5aee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0002_get_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2jFY0qyl6XeW0whXhj0e\?client_secret=pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1lBfdotI2apM9Y +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH", + "id" : "pi_3PiS2jFY0qyl6XeW0whXhj0e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392013, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0003_post_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0003_post_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e_confirm.tail new file mode 100644 index 00000000..f636a58d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0003_post_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e_confirm.tail @@ -0,0 +1,127 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2jFY0qyl6XeW0whXhj0e\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8lKEKwMmEbnCZ2 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2155 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:35 GMT +original-request: req_8lKEKwMmEbnCZ2 +stripe-version: 2020-08-27 +idempotency-key: 4d45e7fb-85eb-4c69-a87b-0e789ec4ca2b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH&expand\[0]=payment_method&payment_method=pm_1PiS2jFY0qyl6XeWNkf3GuhX&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2jFY0qyl6XeWNkf3GuhX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392013, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH", + "id" : "pi_3PiS2jFY0qyl6XeW0whXhj0e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392013, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0004_get_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0004_get_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e.tail new file mode 100644 index 00000000..7973755f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0004_get_v1_payment_intents_pi_3PiS2jFY0qyl6XeW0whXhj0e.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2jFY0qyl6XeW0whXhj0e\?client_secret=pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Tjd1dCeTEVWtdT +Content-Length: 1161 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS2jFY0qyl6XeWNkf3GuhX", + "client_secret" : "pi_3PiS2jFY0qyl6XeW0whXhj0e_secret_VIPltKyND0CiVtasqrsWDvkCH", + "id" : "pi_3PiS2jFY0qyl6XeW0whXhj0e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392013, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0005_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0005_post_v1_payment_methods.tail new file mode 100644 index 00000000..bb4eacde --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0005_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kRrIL8hdqxC0hg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:36 GMT +original-request: req_kRrIL8hdqxC0hg +stripe-version: 2020-08-27 +idempotency-key: 3d896f3e-f59e-4890-834a-ac427106ae6c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2mFY0qyl6XeWT4MHq7Iz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392016, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..308662da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=E0l4IqYmoLKWTtrqLdI9nQcAdtRhe6d3ZavNkSxDakzG8CMLf0DkOg6PjtOcgIdf98%2BEoMLDtSzTp5ze%2Bhg9e%2BhKSekI19XgsXNVY0SUnkC2Sb5Lt06jrIwXKy%2Blyr5t5dzBPGkWnfTnygTQWvpoRMwqPtLrUou0wZXJ792etzkwVPsZ0lSCYk6ZoYR7QoNxQrYsk9ODTLOV8S0H48EaHAEUHp4%2Bs7MyfaOaxwd%2BTj8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fe7321300ccf9af193a58e418ec15421 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2mFY0qyl6XeW1b5TXiWF","secret":"pi_3PiS2mFY0qyl6XeW1b5TXiWF_secret_5QvEwMOzfA50lzD8UB82bdMQZ","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0007_get_v1_payment_intents_pi_3PiS2mFY0qyl6XeW1b5TXiWF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0007_get_v1_payment_intents_pi_3PiS2mFY0qyl6XeW1b5TXiWF.tail new file mode 100644 index 00000000..25f9aae4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0007_get_v1_payment_intents_pi_3PiS2mFY0qyl6XeW1b5TXiWF.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2mFY0qyl6XeW1b5TXiWF\?client_secret=pi_3PiS2mFY0qyl6XeW1b5TXiWF_secret_5QvEwMOzfA50lzD8UB82bdMQZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WYmyReDgCeu1bL +Content-Length: 1894 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2mFY0qyl6XeWT4MHq7Iz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392016, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2mFY0qyl6XeW1b5TXiWF_secret_5QvEwMOzfA50lzD8UB82bdMQZ", + "id" : "pi_3PiS2mFY0qyl6XeW1b5TXiWF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392016, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0008_get_v1_payment_intents_pi_3PiS2mFY0qyl6XeW1b5TXiWF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0008_get_v1_payment_intents_pi_3PiS2mFY0qyl6XeW1b5TXiWF.tail new file mode 100644 index 00000000..f2e94560 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxdeselected/0008_get_v1_payment_intents_pi_3PiS2mFY0qyl6XeW1b5TXiWF.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2mFY0qyl6XeW1b5TXiWF\?client_secret=pi_3PiS2mFY0qyl6XeW1b5TXiWF_secret_5QvEwMOzfA50lzD8UB82bdMQZ$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8yOb8czNKBUpir +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS2mFY0qyl6XeWT4MHq7Iz", + "client_secret" : "pi_3PiS2mFY0qyl6XeW1b5TXiWF_secret_5QvEwMOzfA50lzD8UB82bdMQZ", + "id" : "pi_3PiS2mFY0qyl6XeW1b5TXiWF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392016, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..49f2ab6c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fVKsDtdGcfXc0L +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:38 GMT +original-request: req_fVKsDtdGcfXc0L +stripe-version: 2020-08-27 +idempotency-key: eb863992-b47f-4883-9f95-3db7c8c93229 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2oFY0qyl6XeWSWzQgU3Z", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392018, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..a0865746 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=sHPxho75zRef9yjDc6KiyPvPNGho%2Bk8p0bTOvQfk8Y4LwKzHKPSRRgm65umpPFT22AUz910VHhyRmdQhLuE3BfKe%2BUtEI8fwpi2UJyHl0xU5rmu0Nr6riLFz605m2fHjR86oEWd8pJ4uad%2FlJOFbdUDg%2FFMKYA6lYjVqEwoPXPD19DIxYz%2FqbElj1GqfnV1yTU6KZ8DK1Z8bZI6O0ykPwqC%2FG9C%2F%2BS3OPTP1MfzIHiM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8475a1eeff503180c76b0e52b7a4dbf1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:38 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2oFY0qyl6XeW1oMcHfVh","secret":"pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0002_get_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0002_get_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh.tail new file mode 100644 index 00000000..360c3620 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0002_get_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2oFY0qyl6XeW1oMcHfVh\?client_secret=pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XraXS1zQiaxToC +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC", + "id" : "pi_3PiS2oFY0qyl6XeW1oMcHfVh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392018, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0003_post_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0003_post_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh_confirm.tail new file mode 100644 index 00000000..5efacd10 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0003_post_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh_confirm.tail @@ -0,0 +1,132 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2oFY0qyl6XeW1oMcHfVh\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ufGTeQKvox3IuB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2252 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:40 GMT +original-request: req_ufGTeQKvox3IuB +stripe-version: 2020-08-27 +idempotency-key: 658a8b4a-6313-417d-bb32-6039e1a91492 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC&expand\[0]=payment_method&payment_method=pm_1PiS2oFY0qyl6XeWSWzQgU3Z&payment_method_options\[card]\[setup_future_usage]=off_session&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2oFY0qyl6XeWSWzQgU3Z", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392018, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS2oFY0qyl6XeW1oMcHfVh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392018, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0004_get_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0004_get_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh.tail new file mode 100644 index 00000000..2a5a178b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0004_get_v1_payment_intents_pi_3PiS2oFY0qyl6XeW1oMcHfVh.tail @@ -0,0 +1,82 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2oFY0qyl6XeW1oMcHfVh\?client_secret=pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rTOorZ0RfSeEJN +Content-Length: 1258 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS2oFY0qyl6XeWSWzQgU3Z", + "client_secret" : "pi_3PiS2oFY0qyl6XeW1oMcHfVh_secret_WXxEYSr9dArFA442pEn0yd6wC", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS2oFY0qyl6XeW1oMcHfVh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392018, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0005_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0005_post_v1_payment_methods.tail new file mode 100644 index 00000000..3f6dddd4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0005_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0aVwwj4LI5UAtJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:41 GMT +original-request: req_0aVwwj4LI5UAtJ +stripe-version: 2020-08-27 +idempotency-key: decf912d-cd84-4f5f-b1a5-780d8c06df4d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2qFY0qyl6XeW3DGJI7Qn", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392021, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..1e0877bf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=e%2FNqZHr23QRKQqG2rupAziHBE3vuVimSFUPLkVrzdWmYJ01QNkgOt%2BjR8FCjHOEoTNXDXDVBV7FQjD5Pjn7gEZzgW69%2BDY1uM55LuYzmzXdiSkRf1XfjHX0F5nFx9GTOgcYiEa1EsxfNTDzSdKf%2BnlTQ0bOJsLX3eqbZoJPcpfOx1YMDJResgzU9Rscnpzph6tu89piwtGoWiK9sZhICKTmKhw9gNxpg6z4ckqoiLDo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e23ebd16578539cd15b7d51a54f114ef +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2rFY0qyl6XeW05Fedut8","secret":"pi_3PiS2rFY0qyl6XeW05Fedut8_secret_7hG6jHiWZiyu3kDFxTgAqWNTb","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0007_get_v1_payment_intents_pi_3PiS2rFY0qyl6XeW05Fedut8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0007_get_v1_payment_intents_pi_3PiS2rFY0qyl6XeW05Fedut8.tail new file mode 100644 index 00000000..7eafe493 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0007_get_v1_payment_intents_pi_3PiS2rFY0qyl6XeW05Fedut8.tail @@ -0,0 +1,115 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2rFY0qyl6XeW05Fedut8\?client_secret=pi_3PiS2rFY0qyl6XeW05Fedut8_secret_7hG6jHiWZiyu3kDFxTgAqWNTb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZFUTNARfOBPRD0 +Content-Length: 1991 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2qFY0qyl6XeW3DGJI7Qn", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392021, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2rFY0qyl6XeW05Fedut8_secret_7hG6jHiWZiyu3kDFxTgAqWNTb", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS2rFY0qyl6XeW05Fedut8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392021, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0008_get_v1_payment_intents_pi_3PiS2rFY0qyl6XeW05Fedut8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0008_get_v1_payment_intents_pi_3PiS2rFY0qyl6XeW05Fedut8.tail new file mode 100644 index 00000000..341df5f7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidnewcardandsavecheckboxselected/0008_get_v1_payment_intents_pi_3PiS2rFY0qyl6XeW05Fedut8.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2rFY0qyl6XeW05Fedut8\?client_secret=pi_3PiS2rFY0qyl6XeW05Fedut8_secret_7hG6jHiWZiyu3kDFxTgAqWNTb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qZRzJuoE3BpHj3 +Content-Length: 997 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS2qFY0qyl6XeW3DGJI7Qn", + "client_secret" : "pi_3PiS2rFY0qyl6XeW05Fedut8_secret_7hG6jHiWZiyu3kDFxTgAqWNTb", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS2rFY0qyl6XeW05Fedut8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392021, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..ab671179 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_g5HHzNfQlIgqC0 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:43 GMT +original-request: req_g5HHzNfQlIgqC0 +stripe-version: 2020-08-27 +idempotency-key: 388cfbe7-b026-4787-a748-3f6d8a92da9a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2tFY0qyl6XeWiz7eD4e9", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392023, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..7fda2572 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ESXkNjLOJ55UgxIwi%2B9j3wrkdrpmrT9GtmR6CJvxyrCHQmuPjjbPhXsLiOzlaEo2gSJPPbYBclQllyMqnWZIuFtXUSvaxT3fEYUz9%2FcfXXLrG61savArEDZ1OYMD6Y3FyzYoHAIr%2F6f4D3D%2FDd8KAK%2BA4ccXckC0V904q20iOYzS%2FZyMbsZ4%2FQ0jpmkSGvZgtnm4VpRO8%2F48iqaP6%2BV5mZBav2g8J7EJ8vy2x6lBkxc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 65db1734cb8396b05406746565dde449;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2tFY0qyl6XeW05MpbWg1","secret":"pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0002_get_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0002_get_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1.tail new file mode 100644 index 00000000..7d0c7f52 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0002_get_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2tFY0qyl6XeW05MpbWg1\?client_secret=pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kc2qoPaigw7d5L +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4", + "id" : "pi_3PiS2tFY0qyl6XeW05MpbWg1", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392023, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0003_post_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0003_post_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1_confirm.tail new file mode 100644 index 00000000..d2754e5f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0003_post_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1_confirm.tail @@ -0,0 +1,127 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2tFY0qyl6XeW05MpbWg1\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5ZtvRKIrkTno7V +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2155 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:44 GMT +original-request: req_5ZtvRKIrkTno7V +stripe-version: 2020-08-27 +idempotency-key: 970efc1c-70dc-4410-b9e9-2f342ce8667b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4&expand\[0]=payment_method&payment_method=pm_1PiS2tFY0qyl6XeWiz7eD4e9&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2tFY0qyl6XeWiz7eD4e9", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392023, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4", + "id" : "pi_3PiS2tFY0qyl6XeW05MpbWg1", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392023, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0004_get_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0004_get_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1.tail new file mode 100644 index 00000000..f712cd89 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0004_get_v1_payment_intents_pi_3PiS2tFY0qyl6XeW05MpbWg1.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2tFY0qyl6XeW05MpbWg1\?client_secret=pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DCI746fG5kDW12 +Content-Length: 1161 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS2tFY0qyl6XeWiz7eD4e9", + "client_secret" : "pi_3PiS2tFY0qyl6XeW05MpbWg1_secret_OnwuQdAgA28q3pbypOhZiC0c4", + "id" : "pi_3PiS2tFY0qyl6XeW05MpbWg1", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392023, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0005_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0005_post_v1_payment_methods.tail new file mode 100644 index 00000000..36da0974 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0005_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VI2p50V3j7faNE +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:45 GMT +original-request: req_VI2p50V3j7faNE +stripe-version: 2020-08-27 +idempotency-key: a54ca5ce-552c-4fee-8ec4-5bf3189b1f58 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2vFY0qyl6XeWudtXwgmz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392025, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..07805269 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Bte8utnIuF4v%2FO29iH2W2al2LLT8ZGa25vHeLD%2BOmJaCWqBgpYDPG8vbqAPeUVN1INLGlM%2Fb5CApSd5LMA%2F%2F0JiKx1ZLrtYVfXV%2FyEpVIRLp1cn%2BzMuYKH85bo0baxhVPey4u0tNsKJpnMQJuCnrdXNLYVJKXNTH5AypN6Y%2F%2F3Cv2kwvbw5b%2B%2BGm2l0dFYwab8e5VdtekhVJDeW%2FeL6lfgKT%2FMmVNReOUo8u3T7tSeY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7cd759c6b115087c65bac33f0c7e2722 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:46 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS2vFY0qyl6XeW0HK2Qn1f","secret":"pi_3PiS2vFY0qyl6XeW0HK2Qn1f_secret_bHeOqGGzOsB9tFaHkQFRsofWV","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0007_get_v1_payment_intents_pi_3PiS2vFY0qyl6XeW0HK2Qn1f.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0007_get_v1_payment_intents_pi_3PiS2vFY0qyl6XeW0HK2Qn1f.tail new file mode 100644 index 00000000..9ef76cbf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0007_get_v1_payment_intents_pi_3PiS2vFY0qyl6XeW0HK2Qn1f.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2vFY0qyl6XeW0HK2Qn1f\?client_secret=pi_3PiS2vFY0qyl6XeW0HK2Qn1f_secret_bHeOqGGzOsB9tFaHkQFRsofWV&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pOhidff0ADiOL7 +Content-Length: 1894 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2vFY0qyl6XeWudtXwgmz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392025, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS2vFY0qyl6XeW0HK2Qn1f_secret_bHeOqGGzOsB9tFaHkQFRsofWV", + "id" : "pi_3PiS2vFY0qyl6XeW0HK2Qn1f", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392025, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0008_get_v1_payment_intents_pi_3PiS2vFY0qyl6XeW0HK2Qn1f.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0008_get_v1_payment_intents_pi_3PiS2vFY0qyl6XeW0HK2Qn1f.tail new file mode 100644 index 00000000..b3f74344 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0008_get_v1_payment_intents_pi_3PiS2vFY0qyl6XeW0HK2Qn1f.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS2vFY0qyl6XeW0HK2Qn1f\?client_secret=pi_3PiS2vFY0qyl6XeW0HK2Qn1f_secret_bHeOqGGzOsB9tFaHkQFRsofWV$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_32WTBKTiN1n78Y +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS2vFY0qyl6XeWudtXwgmz", + "client_secret" : "pi_3PiS2vFY0qyl6XeW0HK2Qn1f_secret_bHeOqGGzOsB9tFaHkQFRsofWV", + "id" : "pi_3PiS2vFY0qyl6XeW0HK2Qn1f", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392025, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..9ac11b6f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0009_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JXX9saPxoq6dYx +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:47 GMT +original-request: req_JXX9saPxoq6dYx +stripe-version: 2020-08-27 +idempotency-key: 74117f50-173c-4ec6-af9d-5d79ee46cde5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2xFY0qyl6XeWjHIrioys", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392027, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0010_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0010_post_create_setup_intent.tail new file mode 100644 index 00000000..83e0eb2c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0010_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=0vAC95CSEiaABugJitQjNNqMS3zTQ4oaIm6FWYIOryxECWwC5VAvPm31wSw1XfEWDSGJLteHOfSbrOA%2BgtfANiRwwDs3X4wx%2F3Zvg0tF8jAtdaZzFZrhfooTzwLkVtWrFnhprr2MsVOtIEAVE1qlxgQMMhN9qXPDNFBQLHdn4Utn8g2bgs0JJ9Qrw3jCJ8aagsn29S9B4h%2BLTMgRb0kErkHqQSBJ9aDgsduNPdDgKGY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fce72bd4fc1b83b8e109b64298231757 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:48 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS2xFY0qyl6XeW0RJBh9ro","secret":"seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0011_get_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0011_get_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro.tail new file mode 100644 index 00000000..7de49666 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0011_get_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2xFY0qyl6XeW0RJBh9ro\?client_secret=seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sHvmWuJoEMNjVa +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2xFY0qyl6XeW0RJBh9ro", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392027, + "client_secret" : "seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0012_post_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0012_post_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro_confirm.tail new file mode 100644 index 00000000..8951efe7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0012_post_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2xFY0qyl6XeW0RJBh9ro\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BI8RYvIeXVv44I +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1538 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:49 GMT +original-request: req_BI8RYvIeXVv44I +stripe-version: 2020-08-27 +idempotency-key: e575ec40-0705-4e20-8d0a-10d1d625e1db +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ&expand\[0]=payment_method&payment_method=pm_1PiS2xFY0qyl6XeWjHIrioys&use_stripe_sdk=true + +{ + "id" : "seti_1PiS2xFY0qyl6XeW0RJBh9ro", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2xFY0qyl6XeWjHIrioys", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392027, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392027, + "client_secret" : "seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0013_get_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0013_get_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro.tail new file mode 100644 index 00000000..a913d636 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0013_get_v1_setup_intents_seti_1PiS2xFY0qyl6XeW0RJBh9ro.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS2xFY0qyl6XeW0RJBh9ro\?client_secret=seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_s3xiyrNTa1LaLt +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS2xFY0qyl6XeW0RJBh9ro", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS2xFY0qyl6XeWjHIrioys", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392027, + "client_secret" : "seti_1PiS2xFY0qyl6XeW0RJBh9ro_secret_QZbFPzilfesGDWCore1S9bZtPwEelaJ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0014_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0014_post_v1_payment_methods.tail new file mode 100644 index 00000000..2959c1d8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0014_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xsjaAhCb0kWwqe +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:50 GMT +original-request: req_xsjaAhCb0kWwqe +stripe-version: 2020-08-27 +idempotency-key: 302bc7fb-44c5-46e3-9c36-7fc27472d6c2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS2zFY0qyl6XeWlMKBbYVV", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392029, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0015_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0015_post_create_setup_intent.tail new file mode 100644 index 00000000..0a1eed6d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0015_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9G0Mv%2BrcNSnSmRp3t4ftwNyeKXNfxDGp0V%2BJ1qrZGzehsSP4EFHWJPf3V0pUxfXnJEKkSK%2FO0XBBOsJvoBw%2BWr4ewPtiZQLsCOPJpFhqbuSbRDVUvLJdsrnb25Bh1OOPH822o7lhFk1Vh7%2BxX4JHq1w7q0QMeL%2FbLR7LdBJl0J9eVqMoKEWt7HMrN5fnKnQHZaU%2BPhm%2F%2BSO2snDkUHENbWcJtT9og%2Fv2WZ7EDmB7GdM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f228479876c17247b96a48c19e76ea44 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:13:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS30FY0qyl6XeW4HMbAKPF","secret":"seti_1PiS30FY0qyl6XeW4HMbAKPF_secret_QZbF0iR3yyeh7KU0MAm6kohTeCYYpPq","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0016_get_v1_setup_intents_seti_1PiS30FY0qyl6XeW4HMbAKPF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0016_get_v1_setup_intents_seti_1PiS30FY0qyl6XeW4HMbAKPF.tail new file mode 100644 index 00000000..6000efbd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0016_get_v1_setup_intents_seti_1PiS30FY0qyl6XeW4HMbAKPF.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS30FY0qyl6XeW4HMbAKPF\?client_secret=seti_1PiS30FY0qyl6XeW4HMbAKPF_secret_QZbF0iR3yyeh7KU0MAm6kohTeCYYpPq&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_N4zhUqUdHD5D48 +Content-Length: 1538 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS30FY0qyl6XeW4HMbAKPF", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS2zFY0qyl6XeWlMKBbYVV", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392029, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392030, + "client_secret" : "seti_1PiS30FY0qyl6XeW4HMbAKPF_secret_QZbF0iR3yyeh7KU0MAm6kohTeCYYpPq", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0017_get_v1_setup_intents_seti_1PiS30FY0qyl6XeW4HMbAKPF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0017_get_v1_setup_intents_seti_1PiS30FY0qyl6XeW4HMbAKPF.tail new file mode 100644 index 00000000..7488865a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testDeferredConfirmvalidsavedcard/0017_get_v1_setup_intents_seti_1PiS30FY0qyl6XeW4HMbAKPF.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS30FY0qyl6XeW4HMbAKPF\?client_secret=seti_1PiS30FY0qyl6XeW4HMbAKPF_secret_QZbF0iR3yyeh7KU0MAm6kohTeCYYpPq$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RcCFbcViVY5Try +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:13:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS30FY0qyl6XeW4HMbAKPF", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS2zFY0qyl6XeWlMKBbYVV", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392030, + "client_secret" : "seti_1PiS30FY0qyl6XeW4HMbAKPF_secret_QZbF0iR3yyeh7KU0MAm6kohTeCYYpPq", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..23e34bfe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0000_get_v1_elements_sessions.tail @@ -0,0 +1,335 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1050&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bpayment_method_types%5D%5B1%5D=cashapp&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_4LyQka6U6YrELL +Content-Length: 13505 +Vary: Origin +Date: Wed, 16 Oct 2024 16:31:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1JViE168sJb", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "c6a67653-fcb0-46ec-acf3-39a6b39f50d9", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..29d28b96 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0001_post_v1_payment_methods.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3IScEmPgwG6ahN +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 16 Oct 2024 16:31:11 GMT +original-request: req_3IScEmPgwG6ahN +stripe-version: 2020-08-27 +idempotency-key: ceb66f24-52cd-4f40-9363-cc36f7223239 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1QAa7uFY0qyl6XeW2hHXRnsz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1729096271, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..260aef6a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=BcOiMHz81eEHjZTYsvuGt%2FcsMTPrDCV7VcghI9pWPPXGYhbeR5U9NN8joKfaoyrM1KWKa0Dgk9OvS%2Bc8amj2skyAnw0HlEr3XRUSCvh3pKs6eGtp8mq6KUhe1wA9rvPXjQiZ%2BoVFWB7v0QfMBECbU29rVqUkOpHh4G3aSGoFEQjuBdICLahKz5DvjQJ9wU1EyGNtQr7qXA5L3qF80T0a83kLw91PnFMfGpQ%2BjQa0cw4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 125fcd218978e3a834b531cd9f35a8cb;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 16:31:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAa7vFY0qyl6XeW0hfZTaBO","secret":"pi_3QAa7vFY0qyl6XeW0hfZTaBO_secret_a1geLuhKK7lkovq5mK3CMSekH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0003_get_v1_payment_intents_pi_3QAa7vFY0qyl6XeW0hfZTaBO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0003_get_v1_payment_intents_pi_3QAa7vFY0qyl6XeW0hfZTaBO.tail new file mode 100644 index 00000000..9231657b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0003_get_v1_payment_intents_pi_3QAa7vFY0qyl6XeW0hfZTaBO.tail @@ -0,0 +1,66 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAa7vFY0qyl6XeW0hfZTaBO\?client_secret=pi_3QAa7vFY0qyl6XeW0hfZTaBO_secret_a1geLuhKK7lkovq5mK3CMSekH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DoRCt8EhXcbZ0K +Content-Length: 904 +Vary: Origin +Date: Wed, 16 Oct 2024 16:31:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3QAa7vFY0qyl6XeW0hfZTaBO_secret_a1geLuhKK7lkovq5mK3CMSekH", + "id" : "pi_3QAa7vFY0qyl6XeW0hfZTaBO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1729096271, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0004_post_v1_payment_intents_pi_3QAa7vFY0qyl6XeW0hfZTaBO_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0004_post_v1_payment_intents_pi_3QAa7vFY0qyl6XeW0hfZTaBO_confirm.tail new file mode 100644 index 00000000..7ec381cc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntent/0004_post_v1_payment_intents_pi_3QAa7vFY0qyl6XeW0hfZTaBO_confirm.tail @@ -0,0 +1,129 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAa7vFY0qyl6XeW0hfZTaBO\/confirm$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_is4N6u9DRLaL6x +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2171 +Vary: Origin +Date: Wed, 16 Oct 2024 16:31:13 GMT +original-request: req_is4N6u9DRLaL6x +stripe-version: 2020-08-27 +idempotency-key: f4b0f3aa-fe08-42fb-a533-b9d33a293f3f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3QAa7vFY0qyl6XeW0hfZTaBO_secret_a1geLuhKK7lkovq5mK3CMSekH&expand\[0]=payment_method&payment_method=pm_1QAa7uFY0qyl6XeW2hHXRnsz&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1QAa7uFY0qyl6XeW2hHXRnsz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1729096271, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3QAa7vFY0qyl6XeW0hfZTaBO_secret_a1geLuhKK7lkovq5mK3CMSekH", + "id" : "pi_3QAa7vFY0qyl6XeW0hfZTaBO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1729096271, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..6335e8be --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0000_get_v1_elements_sessions.tail @@ -0,0 +1,335 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1050&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bpayment_method_types%5D%5B1%5D=cashapp&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_2HxN1KEPXY0edm +Content-Length: 13505 +Vary: Origin +Date: Wed, 16 Oct 2024 16:34:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1meUTkzPDkE", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "8596e044-500b-4503-9509-8f45f53ae613", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..c34c61cf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0001_post_v1_payment_methods.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SPUWXMlue4Eo4m +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 16 Oct 2024 16:34:03 GMT +original-request: req_SPUWXMlue4Eo4m +stripe-version: 2020-08-27 +idempotency-key: 1fd2b66f-cd7c-4230-8b3e-50401607ad99 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1QAaAhFY0qyl6XeWOAAbBTKO", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1729096443, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..88fd08dc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=u3RPw7wRiMTZSjcjWWK4D%2B17X9%2FBpvfpWWc%2BugSLqc89xCfwSKfi6wZqRpzgQYrqHH6JvYDQm0SsDrsjwpGe0fXHLCji5n4S%2BOsBP4bAuWw0Essil%2FHwxjzqOv4fmGXYRe1XatA2sI6YeBXQpt6XqTQljdsO5JNY28WZ8%2Boisq6FpPJEYtbuweyZ5R7%2F2If0u%2Fa%2BVl0xFcuwzlsKqASeXBljkhin43%2B%2FccfpV9eidhA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0422429dc60a3e0e0cc0fcab476a6eb1;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 16:34:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAaAiFY0qyl6XeW1qaotQDB","secret":"pi_3QAaAiFY0qyl6XeW1qaotQDB_secret_QPpR1jHt3QNhDKDq3CExsTztK","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0003_get_v1_payment_intents_pi_3QAaAiFY0qyl6XeW1qaotQDB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0003_get_v1_payment_intents_pi_3QAaAiFY0qyl6XeW1qaotQDB.tail new file mode 100644 index 00000000..7aeb6ecf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithDeferredIntentserverSideConfirmation/0003_get_v1_payment_intents_pi_3QAaAiFY0qyl6XeW1qaotQDB.tail @@ -0,0 +1,112 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAaAiFY0qyl6XeW1qaotQDB\?client_secret=pi_3QAaAiFY0qyl6XeW1qaotQDB_secret_QPpR1jHt3QNhDKDq3CExsTztK&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KYaywmn7b2QSCj +Content-Length: 1910 +Vary: Origin +Date: Wed, 16 Oct 2024 16:34:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1QAaAhFY0qyl6XeWOAAbBTKO", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1729096443, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3QAaAiFY0qyl6XeW1qaotQDB_secret_QPpR1jHt3QNhDKDq3CExsTztK", + "id" : "pi_3QAaAiFY0qyl6XeW1qaotQDB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1729096444, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..4f6375bb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=5i4i%2FMhs5CvrTHf%2BCxmNL3SkoCfy2wOkNZGSiyQV3Ty4APMCd1Bx4r9yWukeitAayxqzJtt5mg8trF49xfWi8l6Z281ap7wE%2FaEg25TEOMnQjDRWe5Dxiqw9%2BtKilKp65fLOqxuV6rc4JZwCHdmF6m8w%2BU1e7f5RSJ03paL7hiDO7z1md6U3BlUD7E1GVNhOGAXS7kAhAh5b1soCwaZxL1ZguakpyqRHS96J5wZqUMw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 07d3d6fc6719ee3c9b281f64d5c6a934;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 16:35:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAaCFFY0qyl6XeW0dHHsWb6","secret":"pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..ce5dc315 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0001_get_v1_elements_sessions.tail @@ -0,0 +1,593 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs&expand%5B0%5D=payment_method_preference\.payment_intent\.payment_method&locale=en-US&type=payment_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_jGR458EQ1eRwn0 +Content-Length: 20345 +Vary: Origin +Date: Wed, 16 Oct 2024 16:35:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs", + "id" : "pi_3QAaCFFY0qyl6XeW0dHHsWb6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "setup_future_usage" : null, + "created" : 1729096539, + "description" : null + }, + "ordered_payment_method_types" : [ + "card", + "sofort", + "bancontact", + "ideal" + ], + "type" : "payment_intent" + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_01yIoQuCLq1", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "f7b7b969-b996-4e5a-8ccb-3cc82f2b6d63", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact@3x-5b31be92d86c437286200810aeaa49ce.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact-c6d62da104212dacefee6ea12a070237.svg" + }, + "type" : "bancontact", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "items" : [ + { + "display_text" : "ABN Amro", + "api_value" : "abn_amro" + }, + { + "display_text" : "ASN Bank", + "api_value" : "asn_bank" + }, + { + "display_text" : "bunq B.V.", + "api_value" : "bunq" + }, + { + "display_text" : "ING Bank", + "api_value" : "ing" + }, + { + "display_text" : "Knab", + "api_value" : "knab" + }, + { + "display_text" : "N26", + "api_value" : "n26" + }, + { + "display_text" : "Nationale-Nederlanden", + "api_value" : "nn" + }, + { + "display_text" : "Rabobank", + "api_value" : "rabobank" + }, + { + "display_text" : "RegioBank", + "api_value" : "regiobank" + }, + { + "display_text" : "Revolut", + "api_value" : "revolut" + }, + { + "display_text" : "SNS Bank", + "api_value" : "sns_bank" + }, + { + "display_text" : "Triodos Bank", + "api_value" : "triodos_bank" + }, + { + "display_text" : "Van Lanschot", + "api_value" : "van_lanschot" + }, + { + "display_text" : "Yoursafe", + "api_value" : "yoursafe" + } + ], + "type" : "selector", + "translation_id" : "upe.labels.ideal.bank", + "api_path" : { + "v1" : "ideal[bank]" + } + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal@3x-fc5387c2139f55b84777c646208df7ee.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal-608d5ba5730f82c25f122960ccaa9836.svg" + }, + "type" : "ideal", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : true, + "fields" : [ + { + "for" : "name", + "type" : "placeholder" + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "type" : "country", + "api_path" : { + "v1" : "sofort[country]" + }, + "allowed_country_codes" : [ + "AT", + "BE", + "DE", + "ES", + "NL" + ] + }, + { + "for" : "billing_address_without_country", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "dark_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark@3x-1b34ec3a7503b3e20be0a069f8046f29.png", + "dark_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark-ded76aa1c92357f1b78dab85ed5b6347.svg", + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort@3x-8842e382bdf77386d547ff0ef56880c1.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort-c12d29b37f06a2d05dfb22ea9736104b.svg" + }, + "type" : "sofort", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + }, + "processing" : { + "type" : "finished" + } + } + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "sofort", + "bancontact", + "ideal" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay", + "sofort", + "bancontact", + "ideal" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0002_post_v1_payment_intents_pi_3QAaCFFY0qyl6XeW0dHHsWb6_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0002_post_v1_payment_intents_pi_3QAaCFFY0qyl6XeW0dHHsWb6_confirm.tail new file mode 100644 index 00000000..213508f3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0002_post_v1_payment_intents_pi_3QAaCFFY0qyl6XeW0dHHsWb6_confirm.tail @@ -0,0 +1,131 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAaCFFY0qyl6XeW0dHHsWb6\/confirm$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GVeHIzzf8COiOQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2201 +Vary: Origin +Date: Wed, 16 Oct 2024 16:35:42 GMT +original-request: req_GVeHIzzf8COiOQ +stripe-version: 2020-08-27 +idempotency-key: 44979450-1abc-4c2f-b713-71f2c0534db1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1QAaCHFY0qyl6XeWV3ZJcvI8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1729096541, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs", + "id" : "pi_3QAaCFFY0qyl6XeW0dHHsWb6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "setup_future_usage" : null, + "created" : 1729096539, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0003_get_v1_payment_intents_pi_3QAaCFFY0qyl6XeW0dHHsWb6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0003_get_v1_payment_intents_pi_3QAaCFFY0qyl6XeW0dHHsWb6.tail new file mode 100644 index 00000000..fed78e28 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntent/0003_get_v1_payment_intents_pi_3QAaCFFY0qyl6XeW0dHHsWb6.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAaCFFY0qyl6XeW0dHHsWb6\?client_secret=pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mkRuakR2cJJlSh +Content-Length: 1206 +Vary: Origin +Date: Wed, 16 Oct 2024 16:35:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1QAaCHFY0qyl6XeWV3ZJcvI8", + "client_secret" : "pi_3QAaCFFY0qyl6XeW0dHHsWb6_secret_PtMZHaF5Acnn0lzbFS3wowJYs", + "id" : "pi_3QAaCFFY0qyl6XeW0dHHsWb6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "setup_future_usage" : null, + "created" : 1729096539, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..13037684 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=rzYw0vSU2lxKl7qfWG0dJrsllhaItWN8hI9M3ag6IRzpvOZuCxvU1IyitVXqXVXueZLQqbiQvcsC1wpBhswfItCN5XNUGLBtk47SYTRvokP0Gt0FYWqG%2FUzSGskVgW3VdrBtTzpGiAMZujr5s4wc175Nr9CBXoLqPnqfEZIkFC387bd%2B2FrIl%2FZPBhkgxC%2BJQ6m5bO84xog37uRWPhO1Wo%2F395MNJiz7tEuLGUGKjiQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e946f05ebd704846eaf2efc64fdca6a4;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:03:41 GMT +x-robots-tag: noindex, nofollow +Content-Length: 145 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAadNFY0qyl6XeW1RowDu79","secret":"pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC","status":"requires_confirmation"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..f1374836 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0001_get_v1_elements_sessions.tail @@ -0,0 +1,405 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC&expand%5B0%5D=payment_method_preference\.payment_intent\.payment_method&locale=en-US&type=payment_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_HzKEtqNe4R6ITk +Content-Length: 15256 +Vary: Origin +Date: Wed, 16 Oct 2024 17:03:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_confirmation", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1QAadMFY0qyl6XeWgjEKL3wI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 10, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1729098220, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC", + "id" : "pi_3QAadNFY0qyl6XeW1RowDu79", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1729098221, + "description" : null + }, + "ordered_payment_method_types" : [ + "card" + ], + "type" : "payment_intent" + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_0OdBCBaAKSN", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "8b175a9b-1da6-487b-9e15-60328341c5e3", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0002_post_v1_payment_intents_pi_3QAadNFY0qyl6XeW1RowDu79_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0002_post_v1_payment_intents_pi_3QAadNFY0qyl6XeW1RowDu79_confirm.tail new file mode 100644 index 00000000..f6b37fc8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0002_post_v1_payment_intents_pi_3QAadNFY0qyl6XeW1RowDu79_confirm.tail @@ -0,0 +1,128 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAadNFY0qyl6XeW1RowDu79\/confirm$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RwvSNHmhZ016Lz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2156 +Vary: Origin +Date: Wed, 16 Oct 2024 17:03:43 GMT +original-request: req_RwvSNHmhZ016Lz +stripe-version: 2020-08-27 +idempotency-key: 0bbb6075-a495-49b9-a590-3faf164e36a1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC&expand\[0]=payment_method&payment_method=pm_card_visa&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1QAadOFY0qyl6XeW86euJSTT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 10, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1729098222, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC", + "id" : "pi_3QAadNFY0qyl6XeW1RowDu79", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1729098221, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0003_get_v1_payment_intents_pi_3QAadNFY0qyl6XeW1RowDu79.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0003_get_v1_payment_intents_pi_3QAadNFY0qyl6XeW1RowDu79.tail new file mode 100644 index 00000000..2b5accd0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testPaymentSheetLoadAndConfirmWithPaymentIntentAttachedPaymentMethod/0003_get_v1_payment_intents_pi_3QAadNFY0qyl6XeW1RowDu79.tail @@ -0,0 +1,78 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QAadNFY0qyl6XeW1RowDu79\?client_secret=pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zpSTzmtz2s8AoD +Content-Length: 1161 +Vary: Origin +Date: Wed, 16 Oct 2024 17:03:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1QAadOFY0qyl6XeW86euJSTT", + "client_secret" : "pi_3QAadNFY0qyl6XeW1RowDu79_secret_kBt6vYI8HUDYJ1JjvDuEsqmkC", + "id" : "pi_3QAadNFY0qyl6XeW1RowDu79", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1729098221, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..6acf3dd6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=eLt%2B1URU%2F84r2GQXacHq2jLhDdXna0ZO756z%2FezbbP4%2B%2FW8usaADDIgai5AoMf6ViMQEkaLZaYWABZgCY9aBjquzGMIEEIKVis00BxhQ%2BNpLIsvGcl0cY0LjEyKvvXb46EARt7AORVrsJ0fkylyB1ARZOt5VcsDMIuJ8jh2m7yGRJKQFQgdNSLxQTDZo5OMkVEAtpHFT%2FfgnC8Pj7Udhmse9yF1fVNrKTP4%2FNUYVIN0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e0ea5f742a81b77d741e06cdd0938ace;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS3FFY0qyl6XeW0zpYxa1Z","secret":"pi_3PiS3FFY0qyl6XeW0zpYxa1Z_secret_BynNy8vEwhZ5SxwacWoIXhBYI","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0001_get_v1_payment_intents_pi_3PiS3FFY0qyl6XeW0zpYxa1Z.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0001_get_v1_payment_intents_pi_3PiS3FFY0qyl6XeW0zpYxa1Z.tail new file mode 100644 index 00000000..ea54fb0e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0001_get_v1_payment_intents_pi_3PiS3FFY0qyl6XeW0zpYxa1Z.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3FFY0qyl6XeW0zpYxa1Z\?client_secret=pi_3PiS3FFY0qyl6XeW0zpYxa1Z_secret_BynNy8vEwhZ5SxwacWoIXhBYI$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QSytzKKV4wLfTJ +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS3FFY0qyl6XeW0zpYxa1Z_secret_BynNy8vEwhZ5SxwacWoIXhBYI", + "id" : "pi_3PiS3FFY0qyl6XeW0zpYxa1Z", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392045, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0002_post_v1_payment_intents_pi_3PiS3FFY0qyl6XeW0zpYxa1Z_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0002_post_v1_payment_intents_pi_3PiS3FFY0qyl6XeW0zpYxa1Z_confirm.tail new file mode 100644 index 00000000..c9f3079a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0002_post_v1_payment_intents_pi_3PiS3FFY0qyl6XeW0zpYxa1Z_confirm.tail @@ -0,0 +1,127 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3FFY0qyl6XeW0zpYxa1Z\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fYuyEtFG19VD6C +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2153 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:07 GMT +original-request: req_fYuyEtFG19VD6C +stripe-version: 2020-08-27 +idempotency-key: d2e9bd22-d4f2-40dc-829e-dccd3210361e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS3FFY0qyl6XeW0zpYxa1Z_secret_BynNy8vEwhZ5SxwacWoIXhBYI&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=1&payment_method_data\[card]\[exp_year]=2025&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3GFY0qyl6XeWkhBFAu5k", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392046, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS3FFY0qyl6XeW0zpYxa1Z_secret_BynNy8vEwhZ5SxwacWoIXhBYI", + "id" : "pi_3PiS3FFY0qyl6XeW0zpYxa1Z", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392045, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..9ebb0d85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0003_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CfgsOuhZbnDta7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:08 GMT +original-request: req_CfgsOuhZbnDta7 +stripe-version: 2020-08-27 +idempotency-key: 62d8ec5d-7d16-49dd-9060-d048b4409b0f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS3HFY0qyl6XeWBuHURBcB", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392047, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..d7e551f9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=eOt7WgeZVBQtdE0RPZO2cDNCuClsU1iJvUspQiiAeN%2B09tk0Y%2BWgwd2Dm5o6gkdRLmd2YF29C%2FTVhCWJJlO63lYNegCFHXNrLiXFyFZqmmxxosJvxa27%2FhipSK5bCGI0oF8k7XTUxiTmdkJUah301jz%2BZxD%2BGplscFeeu%2BMlb5khnBbK4%2Btae20nEsNfvfn6rLrqsV0nAwQUHD5tQJPq9uLgdzl1jIF8ObMxFVptSzw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3c427a8694d679d6e085d779d61a2841 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS3IFY0qyl6XeW1R8mJEdf","secret":"pi_3PiS3IFY0qyl6XeW1R8mJEdf_secret_bxRERDZeI3usvPBq0UpEaX2LR","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0005_get_v1_payment_intents_pi_3PiS3IFY0qyl6XeW1R8mJEdf.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0005_get_v1_payment_intents_pi_3PiS3IFY0qyl6XeW1R8mJEdf.tail new file mode 100644 index 00000000..bb01a963 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0005_get_v1_payment_intents_pi_3PiS3IFY0qyl6XeW1R8mJEdf.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3IFY0qyl6XeW1R8mJEdf\?client_secret=pi_3PiS3IFY0qyl6XeW1R8mJEdf_secret_bxRERDZeI3usvPBq0UpEaX2LR&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jyjx22Q4Sd3c86 +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS3IFY0qyl6XeW1R8mJEdf_secret_bxRERDZeI3usvPBq0UpEaX2LR", + "id" : "pi_3PiS3IFY0qyl6XeW1R8mJEdf", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392048, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0006_post_v1_payment_intents_pi_3PiS3IFY0qyl6XeW1R8mJEdf_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0006_post_v1_payment_intents_pi_3PiS3IFY0qyl6XeW1R8mJEdf_confirm.tail new file mode 100644 index 00000000..603b6410 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0006_post_v1_payment_intents_pi_3PiS3IFY0qyl6XeW1R8mJEdf_confirm.tail @@ -0,0 +1,127 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3IFY0qyl6XeW1R8mJEdf\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WVSYsPhCUxpfOK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2153 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:09 GMT +original-request: req_WVSYsPhCUxpfOK +stripe-version: 2020-08-27 +idempotency-key: f57d2ba9-5ef2-4050-b9e7-5740c2fd1e4b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS3IFY0qyl6XeW1R8mJEdf_secret_bxRERDZeI3usvPBq0UpEaX2LR&expand\[0]=payment_method&payment_method=pm_1PiS3HFY0qyl6XeWBuHURBcB&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3HFY0qyl6XeWBuHURBcB", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392047, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS3IFY0qyl6XeW1R8mJEdf_secret_bxRERDZeI3usvPBq0UpEaX2LR", + "id" : "pi_3PiS3IFY0qyl6XeW1R8mJEdf", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392048, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0007_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0007_post_create_payment_intent.tail new file mode 100644 index 00000000..77a6727e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0007_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6wnljpePabAtWqA%2FxHdOw3Fs3wp9MA0TgrpWQijO4TG2tsiUzbjKV7FnaG%2ByFVrtYLLBBHmi0RUvsKnUbnaG%2BaF9AYvLCauY6bhBQbv1t5UJfF2JhxTX7VrH25nFi66C0pk6fJBneNgPQ3xlMxueIlY4ZnWTDQJ9%2Fa4Pr8XnRUyyK6Qb%2FGrkDj2w56QDEarNqP9%2F8fqaylzDfr7%2F4zinJACUrGWGIp8VjBhm0OBz5XA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 43170bd3664bb5faec0ff75eba60ff73 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS3KFY0qyl6XeW0m1XmnUK","secret":"pi_3PiS3KFY0qyl6XeW0m1XmnUK_secret_YTCOQwHTNL09cQVccCVXOlUrE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0008_get_v1_payment_intents_pi_3PiS3KFY0qyl6XeW0m1XmnUK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0008_get_v1_payment_intents_pi_3PiS3KFY0qyl6XeW0m1XmnUK.tail new file mode 100644 index 00000000..8a8e232c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0008_get_v1_payment_intents_pi_3PiS3KFY0qyl6XeW0m1XmnUK.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3KFY0qyl6XeW0m1XmnUK\?client_secret=pi_3PiS3KFY0qyl6XeW0m1XmnUK_secret_YTCOQwHTNL09cQVccCVXOlUrE$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JSu6Z0OtDOhxmc +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS3KFY0qyl6XeW0m1XmnUK_secret_YTCOQwHTNL09cQVccCVXOlUrE", + "id" : "pi_3PiS3KFY0qyl6XeW0m1XmnUK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392050, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0009_post_v1_payment_intents_pi_3PiS3KFY0qyl6XeW0m1XmnUK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0009_post_v1_payment_intents_pi_3PiS3KFY0qyl6XeW0m1XmnUK_confirm.tail new file mode 100644 index 00000000..73603963 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0009_post_v1_payment_intents_pi_3PiS3KFY0qyl6XeW0m1XmnUK_confirm.tail @@ -0,0 +1,132 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3KFY0qyl6XeW0m1XmnUK\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mCHJyadJtkaptb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2250 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:11 GMT +original-request: req_mCHJyadJtkaptb +stripe-version: 2020-08-27 +idempotency-key: 07fc9b81-2341-4fbe-b67d-60b0741a6531 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS3KFY0qyl6XeW0m1XmnUK_secret_YTCOQwHTNL09cQVccCVXOlUrE&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=1&payment_method_data\[card]\[exp_year]=2025&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=off_session&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3KFY0qyl6XeW2a6ekQMB", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392050, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS3KFY0qyl6XeW0m1XmnUK_secret_YTCOQwHTNL09cQVccCVXOlUrE", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS3KFY0qyl6XeW0m1XmnUK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392050, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0010_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0010_post_v1_payment_methods.tail new file mode 100644 index 00000000..6e5b3375 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0010_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4gBs0JLzwpXUy4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:12 GMT +original-request: req_4gBs0JLzwpXUy4 +stripe-version: 2020-08-27 +idempotency-key: f912bf57-8718-4d69-a658-69cbb7641b3a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS3MFY0qyl6XeWhyIUJZng", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392052, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0011_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0011_post_create_payment_intent.tail new file mode 100644 index 00000000..4b660d23 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0011_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=q9iaTybDKzBZD9ujvvT6hm%2BpW%2FWrmyKOguwz0fY7WHVTUQ0YJQtSGn8dXouAeGSWYru7ImG8iq1AC4cBUChrJp3j4a9R2JJfyYe%2Bkak%2BNFcP8BHtOSlwCbautQkf6Rkad%2FqSNrgtFvhFAKqci4XMtE7H0NWtUNpK5rsx32k5Sfuv6MM8uIEykGMCY1x%2BH3hJ4An9ZJDpPfIGvHRA2ii%2B9avZpMlyVYmpjdKCiWKeLmk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: eb17cd0b4143747ea92531046d61ac3e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS3MFY0qyl6XeW011ApMz7","secret":"pi_3PiS3MFY0qyl6XeW011ApMz7_secret_1cWDUCMptCfDCOZsx4QFc0CKE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0012_get_v1_payment_intents_pi_3PiS3MFY0qyl6XeW011ApMz7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0012_get_v1_payment_intents_pi_3PiS3MFY0qyl6XeW011ApMz7.tail new file mode 100644 index 00000000..35ae6015 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0012_get_v1_payment_intents_pi_3PiS3MFY0qyl6XeW011ApMz7.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3MFY0qyl6XeW011ApMz7\?client_secret=pi_3PiS3MFY0qyl6XeW011ApMz7_secret_1cWDUCMptCfDCOZsx4QFc0CKE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rQTR2dJiy64dD3 +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS3MFY0qyl6XeW011ApMz7_secret_1cWDUCMptCfDCOZsx4QFc0CKE", + "id" : "pi_3PiS3MFY0qyl6XeW011ApMz7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392052, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0013_post_v1_payment_intents_pi_3PiS3MFY0qyl6XeW011ApMz7_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0013_post_v1_payment_intents_pi_3PiS3MFY0qyl6XeW011ApMz7_confirm.tail new file mode 100644 index 00000000..5bf71360 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPI/0013_post_v1_payment_intents_pi_3PiS3MFY0qyl6XeW011ApMz7_confirm.tail @@ -0,0 +1,132 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3MFY0qyl6XeW011ApMz7\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xI1hH98ZeTw71Z +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2250 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:14 GMT +original-request: req_xI1hH98ZeTw71Z +stripe-version: 2020-08-27 +idempotency-key: e389b4dc-059a-48bf-87d6-4d1a7695c71f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS3MFY0qyl6XeW011ApMz7_secret_1cWDUCMptCfDCOZsx4QFc0CKE&expand\[0]=payment_method&payment_method=pm_1PiS3MFY0qyl6XeWhyIUJZng&payment_method_options\[card]\[setup_future_usage]=off_session&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3MFY0qyl6XeWhyIUJZng", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392052, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS3MFY0qyl6XeW011ApMz7_secret_1cWDUCMptCfDCOZsx4QFc0CKE", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS3MFY0qyl6XeW011ApMz7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392052, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..71b52c9c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=WZyTN90oyisxhn0lz8im0MXSONQC5a2MvNKTXLfLt7IkkCqmQSVibmdl6N4mUdMy6PDVO5AzG5uY84GAP2K0G5Bqxd8Fy3%2FLmfNMGGcNJI2KEX42MOO3DyiVQxnnpO%2By0Yt8H4u7Gt0VIFqO5lDQ9PyXUPxan5Z%2F9gRZ5As6qxBRO1jQgb9HtgSs9CspnaWdVHUHDa1qB28eTyupkaXNFGSRSrfT1wd0AMNXRgRdgfU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 85ea0b094c03a3a8da6b9eba7dd56cea +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS3BFY0qyl6XeW06c19ynF","secret":"pi_3PiS3BFY0qyl6XeW06c19ynF_secret_MAP10C5mKb3zkfvuYW2sCkXBj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0001_get_v1_payment_intents_pi_3PiS3BFY0qyl6XeW06c19ynF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0001_get_v1_payment_intents_pi_3PiS3BFY0qyl6XeW06c19ynF.tail new file mode 100644 index 00000000..1a0570bf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0001_get_v1_payment_intents_pi_3PiS3BFY0qyl6XeW06c19ynF.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3BFY0qyl6XeW06c19ynF\?client_secret=pi_3PiS3BFY0qyl6XeW06c19ynF_secret_MAP10C5mKb3zkfvuYW2sCkXBj$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4JaMeS8j0YIdGY +Content-Length: 896 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS3BFY0qyl6XeW06c19ynF_secret_MAP10C5mKb3zkfvuYW2sCkXBj", + "id" : "pi_3PiS3BFY0qyl6XeW06c19ynF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392041, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0002_post_v1_payment_intents_pi_3PiS3BFY0qyl6XeW06c19ynF_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0002_post_v1_payment_intents_pi_3PiS3BFY0qyl6XeW06c19ynF_confirm.tail new file mode 100644 index 00000000..1b84e00d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0002_post_v1_payment_intents_pi_3PiS3BFY0qyl6XeW06c19ynF_confirm.tail @@ -0,0 +1,127 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3BFY0qyl6XeW06c19ynF\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UWkR8waSiPOYrS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2162 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:03 GMT +original-request: req_UWkR8waSiPOYrS +stripe-version: 2020-08-27 +idempotency-key: 9d80d4ff-5710-48ea-9440-3f84d8acd16f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS3BFY0qyl6XeW06c19ynF_secret_MAP10C5mKb3zkfvuYW2sCkXBj&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=1&payment_method_data\[card]\[exp_year]=2025&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3CFY0qyl6XeW3DFycEJ3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392042, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS3BFY0qyl6XeW06c19ynF_secret_MAP10C5mKb3zkfvuYW2sCkXBj", + "id" : "pi_3PiS3BFY0qyl6XeW06c19ynF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392041, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..beedccee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0003_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IRAEocet0ovNYs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:03 GMT +original-request: req_IRAEocet0ovNYs +stripe-version: 2020-08-27 +idempotency-key: 298c5212-b5d5-452d-89fd-a45c9c937069 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS3DFY0qyl6XeWvsl571mW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392043, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..cc125011 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=0oN%2F4uCNH8LRBzEhwwEmigp3HJJRY9X8z6Zmhwu3p4peG6O1NKAKl80BzWFT2kvQUDSgacsKN9LeVWrgDLEDa9vQPdj0qrBq07z4aP%2FsUUnUBEVvQJr0mPvveW59fdvkThU27or2Cn5DjscrQ4J6g9kpfeKmyrUNdG3hH%2BgYqiRxINvjWStwzSmMCSE86QRrlHXPovUDkZsNLa7JQp3ggDoyDZmj30377bwlT%2BbcmXo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 472daaaa7bec82dd513bb2e5fa5fa5fc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS3DFY0qyl6XeW0CVkYoHP","secret":"pi_3PiS3DFY0qyl6XeW0CVkYoHP_secret_RkBUie3OOMCPVt6sA9yKiWDhD","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0005_get_v1_payment_intents_pi_3PiS3DFY0qyl6XeW0CVkYoHP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0005_get_v1_payment_intents_pi_3PiS3DFY0qyl6XeW0CVkYoHP.tail new file mode 100644 index 00000000..fda684d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0005_get_v1_payment_intents_pi_3PiS3DFY0qyl6XeW0CVkYoHP.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3DFY0qyl6XeW0CVkYoHP\?client_secret=pi_3PiS3DFY0qyl6XeW0CVkYoHP_secret_RkBUie3OOMCPVt6sA9yKiWDhD&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lU3I4wS8yqKc2a +Content-Length: 896 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS3DFY0qyl6XeW0CVkYoHP_secret_RkBUie3OOMCPVt6sA9yKiWDhD", + "id" : "pi_3PiS3DFY0qyl6XeW0CVkYoHP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392043, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0006_post_v1_payment_intents_pi_3PiS3DFY0qyl6XeW0CVkYoHP_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0006_post_v1_payment_intents_pi_3PiS3DFY0qyl6XeW0CVkYoHP_confirm.tail new file mode 100644 index 00000000..4de6de1f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultPaymentIntentSFU/0006_post_v1_payment_intents_pi_3PiS3DFY0qyl6XeW0CVkYoHP_confirm.tail @@ -0,0 +1,127 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS3DFY0qyl6XeW0CVkYoHP\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BAPBdrjCKB3WWo +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2162 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:05 GMT +original-request: req_BAPBdrjCKB3WWo +stripe-version: 2020-08-27 +idempotency-key: 01ed37e0-f8bf-4a59-80b3-f7b383f041d9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS3DFY0qyl6XeW0CVkYoHP_secret_RkBUie3OOMCPVt6sA9yKiWDhD&expand\[0]=payment_method&payment_method=pm_1PiS3DFY0qyl6XeWvsl571mW&payment_method_options\[card]\[setup_future_usage]=&shipping\[address]\[country]=US&shipping\[address]\[line1]=Line%201&shipping\[name]=Jane%20Doe&shipping\[phone]=5551234567&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : "5551234567", + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "Line 1", + "postal_code" : null + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3DFY0qyl6XeWvsl571mW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392043, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS3DFY0qyl6XeW0CVkYoHP_secret_RkBUie3OOMCPVt6sA9yKiWDhD", + "id" : "pi_3PiS3DFY0qyl6XeW0CVkYoHP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392043, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..7c05e959 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FtRBxR2Tl4KAuezuejKPr8EqzWEik2VW4LW6mXMQ5sUOuLYvq3Gp46jrtgIs%2F4mMdpKJjCUmBjfzw3DclwZv2jUz4kKn7Ee9jRgeGbRT0IJNll7f4J9Dz%2BBs4QD8Wbq5F3j4PZeoffumpYUn5pCqlmAKuflsbzAijUG76mCvElNJTgA00dGHhKpojwnwlk%2BGjDH8FZSlNwd5pM1ygvo2z9suAPSjH%2Bx46cgiIaBR2eM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0fcadd426aa85fdbbd96b0970c5376cb +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS3OFY0qyl6XeWoJKyoMjz","secret":"seti_1PiS3OFY0qyl6XeWoJKyoMjz_secret_QZbFBXQDdTcRoSNgxN9iubu6Qt0IxzR","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0001_get_v1_setup_intents_seti_1PiS3OFY0qyl6XeWoJKyoMjz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0001_get_v1_setup_intents_seti_1PiS3OFY0qyl6XeWoJKyoMjz.tail new file mode 100644 index 00000000..19e6b7bb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0001_get_v1_setup_intents_seti_1PiS3OFY0qyl6XeWoJKyoMjz.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS3OFY0qyl6XeWoJKyoMjz\?client_secret=seti_1PiS3OFY0qyl6XeWoJKyoMjz_secret_QZbFBXQDdTcRoSNgxN9iubu6Qt0IxzR$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2YfXiQYmvFX6h4 +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS3OFY0qyl6XeWoJKyoMjz", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392054, + "client_secret" : "seti_1PiS3OFY0qyl6XeWoJKyoMjz_secret_QZbFBXQDdTcRoSNgxN9iubu6Qt0IxzR", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0002_post_v1_setup_intents_seti_1PiS3OFY0qyl6XeWoJKyoMjz_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0002_post_v1_setup_intents_seti_1PiS3OFY0qyl6XeWoJKyoMjz_confirm.tail new file mode 100644 index 00000000..b3c3aac7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0002_post_v1_setup_intents_seti_1PiS3OFY0qyl6XeWoJKyoMjz_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS3OFY0qyl6XeWoJKyoMjz\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6Lw5t1Y4V4FZfD +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1538 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:16 GMT +original-request: req_6Lw5t1Y4V4FZfD +stripe-version: 2020-08-27 +idempotency-key: 18dac4e2-8340-463f-bcf9-160c999ba77d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS3OFY0qyl6XeWoJKyoMjz_secret_QZbFBXQDdTcRoSNgxN9iubu6Qt0IxzR&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=1&payment_method_data\[card]\[exp_year]=2025&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "id" : "seti_1PiS3OFY0qyl6XeWoJKyoMjz", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3PFY0qyl6XeWQiooVA6V", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392055, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392054, + "client_secret" : "seti_1PiS3OFY0qyl6XeWoJKyoMjz_secret_QZbFBXQDdTcRoSNgxN9iubu6Qt0IxzR", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..e98345b5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0003_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XzTEE6jX8nXzAL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:16 GMT +original-request: req_XzTEE6jX8nXzAL +stripe-version: 2020-08-27 +idempotency-key: 9d125b09-dc26-4e61-81d4-8e88f6c6fb6e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS3QFY0qyl6XeWI3ULyqyh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392056, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0004_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0004_post_create_setup_intent.tail new file mode 100644 index 00000000..68c3ac75 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0004_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=rtDTW75TCBQwVtpz5mY7MnKfv0XVcKpqKdw7RcMZ6b120qpYBM0EpZPJkWs4UqHRyRBv0JoTD1AWxTUUTPyx3EXsLzMxXkCPpJdLzaj3LheG5Zie5AkSyPIS6nHgqkr3q2bchR826ke0Td65j7K%2FpS7TwZLSjZmYeyIKfDb6Ayt9QoIVDWFRLD0%2BR2XEunCKSlZf9cner%2BKdYK2WwZMNMNp3ELE18lEcx2w9x8eVJ%2FU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 725d54f9bc41465441d3dccfd9019550;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:14:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS3QFY0qyl6XeWA06BiwA7","secret":"seti_1PiS3QFY0qyl6XeWA06BiwA7_secret_QZbF9Jp6q1IbFHI0IxC8iWI7X3Yf7Fs","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0005_get_v1_setup_intents_seti_1PiS3QFY0qyl6XeWA06BiwA7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0005_get_v1_setup_intents_seti_1PiS3QFY0qyl6XeWA06BiwA7.tail new file mode 100644 index 00000000..861361a9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0005_get_v1_setup_intents_seti_1PiS3QFY0qyl6XeWA06BiwA7.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS3QFY0qyl6XeWA06BiwA7\?client_secret=seti_1PiS3QFY0qyl6XeWA06BiwA7_secret_QZbF9Jp6q1IbFHI0IxC8iWI7X3Yf7Fs&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jqd0VITWClnJMg +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS3QFY0qyl6XeWA06BiwA7", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392056, + "client_secret" : "seti_1PiS3QFY0qyl6XeWA06BiwA7_secret_QZbF9Jp6q1IbFHI0IxC8iWI7X3Yf7Fs", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0006_post_v1_setup_intents_seti_1PiS3QFY0qyl6XeWA06BiwA7_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0006_post_v1_setup_intents_seti_1PiS3QFY0qyl6XeWA06BiwA7_confirm.tail new file mode 100644 index 00000000..1b524b05 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testSetsNewlySavedPMAsDefaultSetupIntent/0006_post_v1_setup_intents_seti_1PiS3QFY0qyl6XeWA06BiwA7_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS3QFY0qyl6XeWA06BiwA7\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VTMnrphGtQFe2d +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1538 +Vary: Origin +Date: Wed, 31 Jul 2024 02:14:17 GMT +original-request: req_VTMnrphGtQFe2d +stripe-version: 2020-08-27 +idempotency-key: 7963c50b-7417-455d-b9ef-8904c9867f49 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS3QFY0qyl6XeWA06BiwA7_secret_QZbF9Jp6q1IbFHI0IxC8iWI7X3Yf7Fs&expand\[0]=payment_method&payment_method=pm_1PiS3QFY0qyl6XeWI3ULyqyh&use_stripe_sdk=true + +{ + "id" : "seti_1PiS3QFY0qyl6XeWA06BiwA7", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS3QFY0qyl6XeWI3ULyqyh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392056, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392056, + "client_secret" : "seti_1PiS3QFY0qyl6XeWA06BiwA7_secret_QZbF9Jp6q1IbFHI0IxC8iWI7X3Yf7Fs", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..79ca6a68 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0000_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_ogyLGEWDhM8cby +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:40:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1fFLrZzRFuY", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "0fc033e3-1700-4d0a-a1f7-a60e8e166b3d", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay", + "amazon_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..a969a774 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0001_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bmode%5D=setup&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_539onVCNyBAwD7 +Content-Length: 14470 +Vary: Origin +Date: Wed, 16 Oct 2024 16:40:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1KwpS4WUYZx", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "7e9e0fb2-1554-4583-9b92-6d5e7e454ab6", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay", + "amazon_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..cf69728b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdate/0002_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=100&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_1dPlhUZvzlu7vJ +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:40:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1IkBGN9ZAzh", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "1e5eef68-9d35-4694-8541-3aa466cb2f72", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay", + "amazon_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..878491da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0000_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_UAqlnpYB3gL8Db +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:36:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1ciY3LWzXck", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "6ba473e5-1fe2-4693-a575-62f8e59eae9d", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "amazon_pay", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..28707c31 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0001_get_v1_elements_sessions.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=Invalid%20currency&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +400 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_eAhfRsR0jTSf59 +Content-Length: 1044 +Vary: Origin +Date: Wed, 16 Oct 2024 16:36:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "error" : { + "param" : "deferred_intent[currency]", + "message" : "Invalid currency: invalid currency. Stripe currently supports these currencies: usd, aed, afn, all, amd, ang, aoa, ars, aud, awg, azn, bam, bbd, bdt, bgn, bhd, bif, bmd, bnd, bob, brl, bsd, bwp, byn, bzd, cad, cdf, chf, clp, cny, cop, crc, cve, czk, djf, dkk, dop, dzd, egp, etb, eur, fjd, fkp, gbp, gel, gip, gmd, gnf, gtq, gyd, hkd, hnl, hrk, htg, huf, idr, ils, inr, isk, jmd, jod, jpy, kes, kgs, khr, kmf, krw, kwd, kyd, kzt, lak, lbp, lkr, lrd, lsl, mad, mdl, mga, mkd, mmk, mnt, mop, mur, mvr, mwk, mxn, myr, mzn, nad, ngn, nio, nok, npr, nzd, omr, pab, pen, pgk, php, pkr, pln, pyg, qar, ron, rsd, rub, rwf, sar, sbd, scr, sek, sgd, shp, sle, sos, srd, std, szl, thb, tjs, tnd, top, try, ttd, twd, tzs, uah, ugx, uyu, uzs, vnd, vuv, wst, xaf, xcd, xof, xpf, yer, zar, zmw, usdc, btn, ghs, eek, lvl, svc, vef, ltl, sll, mro", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_eAhfRsR0jTSf59?t=1729096619" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..478f664c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateFails/0002_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_pHd4r7zA7bfwZp +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:37:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1KBzJLLCLJz", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "951dea70-1dd0-41bb-b572-3e74332b6657", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay", + "amazon_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..8b98ddae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0000_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_oSIUT1jfofGTDB +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:41:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1TiX6Cxn31P", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "c7c428bb-b032-443e-a314-bd2132cc9d6b", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "amazon_pay", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..1f6cb9fe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0001_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_LqTgcrSga78YHJ +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:41:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1vVSsEfQ7dA", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "f2179bdf-786d-4c2a-9ce9-85c85fbfe039", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay", + "amazon_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..bbad4d46 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetAPITest/testUpdateIgnoresInFlightUpdate/0002_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_m0ocGNaimS8lYB +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 16:41:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "amazon_pay", + "cashapp", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1MeSeC6Puz8", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "9cdddbbe-f3d8-4eb4-9e4d-f07ed8a42d70", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "amazon_pay", + "cashapp", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "amazon_pay", + "cashapp", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..ffa1f3c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=j%2B2N5v5FdjtUDXAnq%2BrPedIaopHlti8FG1i%2Ba5q7eZJjt6rOkJAb88AsKTrMF%2Buv1VsY3o5ptzMstWvwwbj8S9VOMmWxhnpgF5ubLIa4umZRGZdiuzIthHfL5rRl3UYDV7MZ1LrO5jFN1yBaGkRGEFay5ffBg1xEeWfmI3YsRtsP4SIvK7GgMohn2MOMnyZQWQcF8nU6WqgeT3t5tkVwHvXmQjKno%2Fv9FZ%2BRIidPPJw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6d1dbbaef3db152f01adb3f30a38a03f;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFZvQXBPdVV2NGxoR2dvZlkwcEMxdW5vSTBTNU1XT1M_00mzVTYzuF","customer":"cus_QahlP6CnaQkF5l"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..c877f540 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pMEGT3AWxwu46P +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:33 GMT +original-request: req_pMEGT3AWxwu46P +stripe-version: 2020-08-27 +idempotency-key: 0c0e8f3c-9913-4257-86b5-a5f3e99f7aad +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PjWLhFY0qyl6XeWed3pjOPH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646893, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..ba1c7692 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=emmYExHw%2BZqRupdbuwQBGxpB5rJKXmf63ij%2F2Ov3cZeLwbj%2BFw8tmP1M8KNLYBGQzS5EteUiGfbU5oD9gZQeb6bo5p6laGv%2FYMe8yNJVELq7unLy79fQ3FV2vpbk52ZvJ9qOXgmpFqMNF%2FXWWNs%2FsonHTZH2FkijggVFXiGC0TkImru55PdjHEo6ZvnTPjmQP3DWFmJUUU9IcXQl9soqILmvA%2FPQdFvRj1DZEo0kHRA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b81fe3b7a5f1ac0060cee7e0fcc35a11 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:34 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWLiFY0qyl6XeW1WQQJxTb","secret":"pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0003_get_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0003_get_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb.tail new file mode 100644 index 00000000..b6ae2f3b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0003_get_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLiFY0qyl6XeW1WQQJxTb\?client_secret=pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9WRj8VhLReyEYB +Content-Length: 889 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL", + "id" : "pi_3PjWLiFY0qyl6XeW1WQQJxTb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646894, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0004_post_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0004_post_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb_confirm.tail new file mode 100644 index 00000000..e29211da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0004_post_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb_confirm.tail @@ -0,0 +1,119 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLiFY0qyl6XeW1WQQJxTb\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ckx4yVCEQ0sdkD +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2006 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:35 GMT +original-request: req_Ckx4yVCEQ0sdkD +stripe-version: 2020-08-27 +idempotency-key: 2c500d0c-2ee6-46d7-a478-db8c910d41cd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL&expand\[0]=payment_method&payment_method=pm_1PjWLhFY0qyl6XeWed3pjOPH&payment_method_options\[card]\[setup_future_usage]=off_session&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWLhFY0qyl6XeWed3pjOPH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646893, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QahlP6CnaQkF5l" + }, + "client_secret" : "pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PjWLiFY0qyl6XeW1WQQJxTb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646894, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0005_get_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0005_get_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb.tail new file mode 100644 index 00000000..b5f9786d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0005_get_v1_payment_intents_pi_3PjWLiFY0qyl6XeW1WQQJxTb.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLiFY0qyl6XeW1WQQJxTb\?client_secret=pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fHi3thdDhMApyq +Content-Length: 997 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PjWLhFY0qyl6XeWed3pjOPH", + "client_secret" : "pi_3PjWLiFY0qyl6XeW1WQQJxTb_secret_gYOqrbQGeW20lA5if9SuziuCL", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PjWLiFY0qyl6XeW1WQQJxTb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646894, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0006_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0006_get_v1_payment_methods.tail new file mode 100644 index 00000000..5c9c47bb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0006_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahlP6CnaQkF5l&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HOPe3qNSrBRTJ0 +Content-Length: 1270 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PjWLhFY0qyl6XeWed3pjOPH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646893, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QahlP6CnaQkF5l" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0007_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0007_post_create_ephemeral_key.tail new file mode 100644 index 00000000..36f44d05 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0007_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fHyhPczPulvNC9rLGaIEaJROpUiqQ6nVkb01GfcT3ulFzxXayRFvBvX0gwZca7iR6LBkU4MRQ9LLRTt%2FP3pupxqZqiesZAgcJraPwLGxORz3Es1Yq3qX2MNpXYv6W2GVC8U03DaAA4lK8yAKx%2FGUVLJ22YPHvKJPh178qWtGYbWJJs%2FsW53ky8Paez1MFukZn8bcLLVCtuUQlUL%2FK3qBR1g9pTuj585WVubgjOtCpj4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 660dc2589eb4d5e17a172ad5135f92e4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLENBVGFISmZZYmhwU3lpWEVPUm94Q3hacHBsTGtLNkE_00rdVgoqC2","customer":"cus_QahlCjWo4YZc5H"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0008_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0008_post_v1_payment_methods.tail new file mode 100644 index 00000000..992ede72 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0008_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qAkoEwwDP3gVtF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:37 GMT +original-request: req_qAkoEwwDP3gVtF +stripe-version: 2020-08-27 +idempotency-key: 77bf8d61-ed7a-48b4-8a3c-a6274ee2bd07 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PjWLlFY0qyl6XeW4PsD7mqo", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646897, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0009_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0009_post_create_payment_intent.tail new file mode 100644 index 00000000..0a4e5cb5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0009_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=CT6KaM7tT3oXaR4OKsQ6j%2BIUtW37Q%2Fh5h6Yl%2FjRoYcUosJkmj2y4DUzxRFFugRSRqvg3gXjOJoaYnQjVBZTQ1JOUkEre%2Bm8GK9p3o6bOlpinCERlhNATZxIHByqgP1KTLBB0CPIzYorJVTbJ%2FvYxKUamXigJogrZMGzpCvUj6CaxtKkijldcmjaxN%2BeCr7crDzNpt3yu3n%2BBjWC4Q%2BPil5a4ObRU2GrKeBI6o2dF2AE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1f4a045adb0b4e4b3f4297c0ffe44cd2 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWLlFY0qyl6XeW0LAeRYcp","secret":"pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0010_get_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0010_get_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp.tail new file mode 100644 index 00000000..55938af9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0010_get_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLlFY0qyl6XeW0LAeRYcp\?client_secret=pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lbav6a16Jsqk4N +Content-Length: 889 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs", + "id" : "pi_3PjWLlFY0qyl6XeW0LAeRYcp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646897, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0011_post_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0011_post_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp_confirm.tail new file mode 100644 index 00000000..7d54d952 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0011_post_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLlFY0qyl6XeW0LAeRYcp\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fAixbsyjOmmCo9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1898 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:38 GMT +original-request: req_fAixbsyjOmmCo9 +stripe-version: 2020-08-27 +idempotency-key: 196e73ce-a03c-4615-b6bb-ab6d3c64250f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs&expand\[0]=payment_method&payment_method=pm_1PjWLlFY0qyl6XeW4PsD7mqo&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWLlFY0qyl6XeW4PsD7mqo", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646897, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs", + "id" : "pi_3PjWLlFY0qyl6XeW0LAeRYcp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646897, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0012_get_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0012_get_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp.tail new file mode 100644 index 00000000..95eed690 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0012_get_v1_payment_intents_pi_3PjWLlFY0qyl6XeW0LAeRYcp.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLlFY0qyl6XeW0LAeRYcp\?client_secret=pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RypgPGJ5yZ0C8T +Content-Length: 900 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PjWLlFY0qyl6XeW4PsD7mqo", + "client_secret" : "pi_3PjWLlFY0qyl6XeW0LAeRYcp_secret_hLqtp8ezfw2tvwAohU00LwSvs", + "id" : "pi_3PjWLlFY0qyl6XeW0LAeRYcp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646897, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0013_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0013_get_v1_payment_methods.tail new file mode 100644 index 00000000..937d23a7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0013_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahlCjWo4YZc5H&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DDHH94WaryoytU +Content-Length: 89 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0014_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0014_post_create_ephemeral_key.tail new file mode 100644 index 00000000..f0ef3ee5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0014_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=1TeAny4NmEn5BZFS%2FENxTysRlE7%2BLmUx8pj9jG3eUiz%2BTMkQEPw2yVaLtrA%2BCqMlFG1WgC5aunq87aG9dgHvUuO8vTRO4evPS7u5TWTV0%2Fpea%2BLcyscxbIOefUSOYbTPt5d7U%2B%2Bes33r%2FWUIKfpRfhRNPVE4kikUkqQ0RFRwmo73Vl1hrvMkaUiWHmBEj57anAbkGaEhE3nlN%2B%2FRmFiqCEV2atvYtN79pEcgGl8Apj8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b77688808ba6778f8393fa450ca61f88 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDNVbG02VnVaT0RBV3ljZ0dtODk3aTR5cUVqTWJOb1Y_00SDCKesWH","customer":"cus_QahlXJvNSoM6G2"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0015_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0015_post_v1_payment_methods.tail new file mode 100644 index 00000000..eedf7835 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0015_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FaIRRJs2zQWNVX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:40 GMT +original-request: req_FaIRRJs2zQWNVX +stripe-version: 2020-08-27 +idempotency-key: 645c5f00-f47c-4d9b-a175-50004b4cd2fe +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PjWLoFY0qyl6XeWBAc80han", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646900, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0016_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0016_post_create_payment_intent.tail new file mode 100644 index 00000000..af6a815f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0016_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=AnU2wc5kjVHS8VeVJjBAgMaL69m69zdnnj1EamJABy7TTEW613BG8Z0KaTbNUjVBxx%2BrNRk0C1GSpu%2BFDMbjjH4U6iv%2BdmaatYxuAr9pGbTI%2BklynmXcR38t%2BuZC4ZznYRZyNC9Sif0fAThc%2BEdklwR0qKXsEYgCVEahuR1meWDwZqNPMso3J70Bl9koZHH7PcYl6P6mg9MuKQWCJxiyYtoFDayA2f%2B2Tjyd8X0XaQA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4acb66fa31ee3981c0fea3d4fd686749 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWLoFY0qyl6XeW0iKpD6M5","secret":"pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0017_get_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0017_get_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5.tail new file mode 100644 index 00000000..120949e3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0017_get_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLoFY0qyl6XeW0iKpD6M5\?client_secret=pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SkGyaTjLA1xHQg +Content-Length: 889 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0", + "id" : "pi_3PjWLoFY0qyl6XeW0iKpD6M5", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0018_post_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0018_post_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5_confirm.tail new file mode 100644 index 00000000..4647078b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0018_post_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLoFY0qyl6XeW0iKpD6M5\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Fnw0Zof4U5jVNJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1898 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:42 GMT +original-request: req_Fnw0Zof4U5jVNJ +stripe-version: 2020-08-27 +idempotency-key: 91380560-a5fb-4b8a-8c44-5c8dd55c9298 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0&expand\[0]=payment_method&payment_method=pm_1PjWLoFY0qyl6XeWBAc80han&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWLoFY0qyl6XeWBAc80han", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646900, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0", + "id" : "pi_3PjWLoFY0qyl6XeW0iKpD6M5", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0019_get_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0019_get_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5.tail new file mode 100644 index 00000000..b988264a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0019_get_v1_payment_intents_pi_3PjWLoFY0qyl6XeW0iKpD6M5.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLoFY0qyl6XeW0iKpD6M5\?client_secret=pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aQ4r3lE8Zlc3UH +Content-Length: 900 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PjWLoFY0qyl6XeWBAc80han", + "client_secret" : "pi_3PjWLoFY0qyl6XeW0iKpD6M5_secret_BRv3FSLtNde4ckaJiTA8MEbJ0", + "id" : "pi_3PjWLoFY0qyl6XeW0iKpD6M5", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0020_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0020_get_v1_payment_methods.tail new file mode 100644 index 00000000..1b2a3bd2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIDeferredCSC/0020_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahlXJvNSoM6G2&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Y0y258JxZ1pA7l +Content-Length: 89 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..32eef180 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=piS%2Bvm38k03P65aeuqQBlH37aIqmaeEhilRAK3ps9H8Tl%2BMCiL7VuwXj4iAm14htew108o2N3ivjo4bh7aZxA07Im5sC4kO%2FqDV2IIdte7R4PpMH00rwiyWie4IBolx%2FD4dQP1R%2BhDfDCrAdEgKP5T0Zp2u2udoTg7I1rJwXE7WbRCgu0l8aBtCAvFucfldmbrT7GCXHClg5JXEVgroXhhP2tBLQdY098SErd88czUQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 289761d30212e612c89456add9dbad28;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDNNaDhMZDZ6dG4zUU43d01scHlqT2VmYTZ4aTVrVXQ_008kcvCBRw","customer":"cus_QahkrI5jSFut4E"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..54fa81b1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=gmpihC4KFu1wDqOENrEUI3sUvyhACQA2gYKDRTxj4Q1pRY9bfN6P6Y9wk8gwsEXseiIsfBMxluO8pX2zPGMa8hyRGO7n7WaECg3ZRaHGOnmsM6AokAg0soNZPK%2B1H0hd4j6vhdc4GspvgIG%2F1O%2F6M7m0pebNTLkdOgNEJQMlLCYv9Z8EocawvgOpxIFoFrbMBGYTLnyt3Ep4x%2BR4K966F%2BLfh13W%2FoAh3QiIvf6aIVk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 86a8b4d04e1e7f9c531597536e78c841 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWLHFY0qyl6XeW0b92TYtH","secret":"pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0002_get_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0002_get_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH.tail new file mode 100644 index 00000000..a841292d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0002_get_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLHFY0qyl6XeW0b92TYtH\?client_secret=pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_w3ZsO79ymoA9zh +Content-Length: 889 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw", + "id" : "pi_3PjWLHFY0qyl6XeW0b92TYtH", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646867, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0003_post_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0003_post_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH_confirm.tail new file mode 100644 index 00000000..cf3f0493 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0003_post_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH_confirm.tail @@ -0,0 +1,119 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLHFY0qyl6XeW0b92TYtH\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_G1jjTKnmH5TULo +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2006 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:09 GMT +original-request: req_G1jjTKnmH5TULo +stripe-version: 2020-08-27 +idempotency-key: 27711de0-06f5-42be-bd7f-2a1426c2ad86 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=always&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=off_session&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWLIFY0qyl6XeWCi5Zeaqx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646868, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QahkrI5jSFut4E" + }, + "client_secret" : "pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PjWLHFY0qyl6XeW0b92TYtH", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646867, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0004_get_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0004_get_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH.tail new file mode 100644 index 00000000..a2fed1b2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0004_get_v1_payment_intents_pi_3PjWLHFY0qyl6XeW0b92TYtH.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLHFY0qyl6XeW0b92TYtH\?client_secret=pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3G2W6YMjVQjseo +Content-Length: 997 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PjWLIFY0qyl6XeWCi5Zeaqx", + "client_secret" : "pi_3PjWLHFY0qyl6XeW0b92TYtH_secret_XmITHRusSgNxUioJ2HvU3KkNw", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PjWLHFY0qyl6XeW0b92TYtH", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646867, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..699f4ea1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahkrI5jSFut4E&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CLzfXtgFZpASYK +Content-Length: 1270 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PjWLIFY0qyl6XeWCi5Zeaqx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646868, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QahkrI5jSFut4E" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..d4093ef5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6fdG5ZkitwuFL38sxXOMKfVUAMioJl5dSnM3U%2FJml3qwJ3%2B6n3S7PDcb9lT%2Fyq3eSkshRNBCvd4sFnCQ%2Bdib%2FLF5uFK7R83fTDf0DSa%2B%2B2eeUJM%2BnQXy1YVneo6Z4a6pbJV480g62%2F%2FCZSK3nFXoMf%2F0N42CngTkRLOJ9l6QSiZvDUU6BgOUJVdtZ%2BXSPnBw%2BU8CfyqsiCDB%2BEI4ZK%2FAmPM8JFoaPBP7qfUSwu7eIx4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 156ffbeafa0aef5f284d935984538230 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHBKQ0JUWTNWMWRwUUtOR3VCWFdsY0dEYlZZTkZkMVc_00e0wKFBPr","customer":"cus_QahkoKIlFIeJHY"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0007_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0007_post_create_payment_intent.tail new file mode 100644 index 00000000..ae1d8de8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0007_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=BDHvt%2F5EoyCGcHc%2Fd6Uw%2FT83UJp3%2FF47jDJt6NkUQFGp7UfZbt%2FZXBwaWEt8XQ5tqt%2BT7Q%2BeQAsbn5qeL3LhvKpPpzcw2HABpCwuOupUSSaF3u7KCr6krEq3J9811%2Bkt5ROPQEwdX9HYi212QuawhAjIRoaB1LgGtiWTfsTEAeYTbwqBSCcxcb2HMTBgWup%2FAKyu7syLCYkJQh1%2BZqu7%2BACYEZykyAcIftRMg6qj3tY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d047ab78f3be049d64eca69accbd1f41 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWLKFY0qyl6XeW1gjmiaLT","secret":"pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0008_get_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0008_get_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT.tail new file mode 100644 index 00000000..396a6fb7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0008_get_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLKFY0qyl6XeW1gjmiaLT\?client_secret=pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3FthstfJv6Gw6V +Content-Length: 889 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh", + "id" : "pi_3PjWLKFY0qyl6XeW1gjmiaLT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646870, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0009_post_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0009_post_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT_confirm.tail new file mode 100644 index 00000000..f08b7ab0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0009_post_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLKFY0qyl6XeW1gjmiaLT\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_V27gypgufuefx6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1898 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:12 GMT +original-request: req_V27gypgufuefx6 +stripe-version: 2020-08-27 +idempotency-key: eaf80a97-861b-44c9-bdcd-9c4c73e1526a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWLLFY0qyl6XeWs3fAF1SO", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646871, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh", + "id" : "pi_3PjWLKFY0qyl6XeW1gjmiaLT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646870, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0010_get_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0010_get_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT.tail new file mode 100644 index 00000000..4c16298b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0010_get_v1_payment_intents_pi_3PjWLKFY0qyl6XeW1gjmiaLT.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLKFY0qyl6XeW1gjmiaLT\?client_secret=pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7UOVXCIKNEPQlz +Content-Length: 900 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PjWLLFY0qyl6XeWs3fAF1SO", + "client_secret" : "pi_3PjWLKFY0qyl6XeW1gjmiaLT_secret_VU1GnFe8FLfNz6Mt9bwsTxRIh", + "id" : "pi_3PjWLKFY0qyl6XeW1gjmiaLT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646870, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..d491f9fe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0011_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahkoKIlFIeJHY&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Hgnnxzumai0Uvt +Content-Length: 89 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0012_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0012_post_create_ephemeral_key.tail new file mode 100644 index 00000000..90362580 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0012_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=f96HED9xJMm0FC5R0vnA0lXOH%2BNgnfvRIQj5dOuOGKu0uIVk1h%2BxUI%2F0rr2AswRHcwHKkj97hSQ0849K2N00tm4pKzPRb5xBGoXhkMPtuaeDqKa2uDzWj%2BrAZ%2B6RDO1d2D1oqokpX7XsErPpS8iDSjhSqBNCmybmDLiSvSk1QipKWuACxCUkEP3yueZ1ynK6%2FFriu%2BIwOxnZ2NLQCv17fugLguLWYAjaRtr3g3gV9%2BA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b0645cd63cd1f6cba259e6b7fbcf0dc7 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLEpNSUFwaGFjOEN2eXdvNExaRTRNZEZkWVFORjJuQ04_00NwYcdSD7","customer":"cus_QahkceMQV9XOtN"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..204410f0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OgwbUxAtvJEQN23tyM%2BN%2FZM%2FNDVMcMqhVZcWx7gfIxitzy5wfhb7VzueqM1U0pLykng4bo81%2BfNbyayO9L6FVxteS%2BQBOoe3%2Bww%2BzWh3BMAp7rkJEEKh4nQDl3EWXav3TfZED9yMdjNxuiOwY4qNfk4Pygp9UnAuNiJp4LOuhgUaA9Bj%2B3kvzIHlzMiot8NHpihy%2FT9r3Sm1u1ZRHWjD5RTlByvu6KNbC9qPO%2BlDTwo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 39943927de44e1d668a65dcf9f01196f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWLNFY0qyl6XeW0WKDQbyd","secret":"pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0014_get_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0014_get_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd.tail new file mode 100644 index 00000000..aa063578 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0014_get_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLNFY0qyl6XeW0WKDQbyd\?client_secret=pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_48QekL7rvsg21M +Content-Length: 889 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU", + "id" : "pi_3PjWLNFY0qyl6XeW0WKDQbyd", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646873, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0015_post_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0015_post_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd_confirm.tail new file mode 100644 index 00000000..1bebcd76 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0015_post_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLNFY0qyl6XeW0WKDQbyd\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6cWNW1FdBMa6SU +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1898 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:15 GMT +original-request: req_6cWNW1FdBMa6SU +stripe-version: 2020-08-27 +idempotency-key: 96bf2289-6313-4cd3-bb1a-f9d71b12746a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWLOFY0qyl6XeWb6Eqe3SH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646874, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU", + "id" : "pi_3PjWLNFY0qyl6XeW0WKDQbyd", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646873, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0016_get_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0016_get_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd.tail new file mode 100644 index 00000000..88eaa810 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0016_get_v1_payment_intents_pi_3PjWLNFY0qyl6XeW0WKDQbyd.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWLNFY0qyl6XeW0WKDQbyd\?client_secret=pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KVrhqt5v2hLDiO +Content-Length: 900 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PjWLOFY0qyl6XeWb6Eqe3SH", + "client_secret" : "pi_3PjWLNFY0qyl6XeW0WKDQbyd_secret_8gaHgbHEs3XrYrFGlBMkWcYAU", + "id" : "pi_3PjWLNFY0qyl6XeW0WKDQbyd", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646873, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0017_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0017_get_v1_payment_methods.tail new file mode 100644 index 00000000..893d8389 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIIntentFirst/0017_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahkceMQV9XOtN&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LFfiCjdIo0nbDp +Content-Length: 89 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..7b9d3eae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8wRcQNT%2B%2B9HM3de0rqqMW9t0nEyxXZNi2LxAwEy13KRT20VetCN5K%2Fqo%2FJGyxblRKVfQ36eOMspp4krUkU2S%2Bc3WV3oX70tG4Bx4AtaHMIOBHnqQOxg2xA1aBNStJQ%2B%2FpdYFIXwy09T%2BtZL8V%2BZB9L7mk2SgQLBxTXMbL%2B7pujF07dNSako6QT3yHc5XYBlOz1L6jwL%2Bx6w%2FFbbrPkVn9%2FUJZle10yEA2JTMfMnygsY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 795bac842218526083d86a4d1e6605f0 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLEltYmJSZEZ4QUFRUXpzSEE1dHZ1M3Z1YXVOdVNWZlY_00jPaYRIAQ","customer":"cus_QZbIGCgWtqqfNU"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..ae605f30 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8mhdLSDlP0ERHn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:25 GMT +original-request: req_8mhdLSDlP0ERHn +stripe-version: 2020-08-27 +idempotency-key: ef3c7625-4f01-4537-9afd-644c951f1252 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5VFY0qyl6XeWNEeD7JgP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392185, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..44f2d17a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=FJfFLMsFVXkbUxXxNOZY18a0CN%2FNi3sRfEW57LTAS2abhaiuKq7u9umo1d4EJ5at8XjrRfZ4BSzOP37Tc6j5p4webQxa9M6iJuAInMQC92Y1dFzkDu1qEBh5CM%2BF1eDoUAh77REZ%2B6mMnegqugTgmPA8lSCQPvC0D3YxbQZy%2BdGx%2BlaLUQrSDrhrQQgsWLu94MDVbpwSkJvpBLr89KsMZG53hUh9%2FmJfZKjeETwLMW0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 795248f99571360332bb7249b6414458 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5WFY0qyl6XeW0TEoZrzq","secret":"pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0003_get_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0003_get_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq.tail new file mode 100644 index 00000000..4b6a97fe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0003_get_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5WFY0qyl6XeW0TEoZrzq\?client_secret=pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8nNKRPxwDV3nhE +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh", + "id" : "pi_3PiS5WFY0qyl6XeW0TEoZrzq", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392186, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0004_post_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0004_post_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq_confirm.tail new file mode 100644 index 00000000..ce7108ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0004_post_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq_confirm.tail @@ -0,0 +1,119 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5WFY0qyl6XeW0TEoZrzq\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MhRA6Yk2k4uwGu +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2015 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:27 GMT +original-request: req_MhRA6Yk2k4uwGu +stripe-version: 2020-08-27 +idempotency-key: 440fbec5-1717-40ab-9b6f-fb15e4441ac3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh&expand\[0]=payment_method&payment_method=pm_1PiS5VFY0qyl6XeWNEeD7JgP&payment_method_options\[card]\[setup_future_usage]=off_session&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5VFY0qyl6XeWNEeD7JgP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392185, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIGCgWtqqfNU" + }, + "client_secret" : "pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS5WFY0qyl6XeW0TEoZrzq", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392186, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0005_get_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0005_get_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq.tail new file mode 100644 index 00000000..5f0c7869 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0005_get_v1_payment_intents_pi_3PiS5WFY0qyl6XeW0TEoZrzq.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5WFY0qyl6XeW0TEoZrzq\?client_secret=pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_E6XRgGv89tulbi +Content-Length: 1006 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS5VFY0qyl6XeWNEeD7JgP", + "client_secret" : "pi_3PiS5WFY0qyl6XeW0TEoZrzq_secret_Qdewj1JkN6Nl7UmKyDNi8TfMh", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS5WFY0qyl6XeW0TEoZrzq", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392186, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0006_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0006_get_v1_payment_methods.tail new file mode 100644 index 00000000..9d7105b7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0006_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIGCgWtqqfNU&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dj13hfCo3GNoL4 +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5VFY0qyl6XeWNEeD7JgP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392185, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIGCgWtqqfNU" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0007_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0007_post_create_ephemeral_key.tail new file mode 100644 index 00000000..506094ec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0007_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=hInmYfvhJvxmXzol%2FQ8L0dCY1c%2B7rDD%2FTOZNGpIANUGH4ldXlamrwKwQz5d3KADmS7EG0VIOXXoJ4mfMmpJF3Pv553joEE18YqUaBAI7QaVNK6xbL1f1qKt4rc27Mks1pkcjmN%2BKDwMXivHfgVHz5hkxMyVAQLv6vMeoqOXHYsid1JmnUNy%2BMbQswoPzSJ7FrnJx0ClucxXnsnrFCObcU45XmaewT788oHks5e7vXlA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b28cd9e4e0cb57dd0d15746e5a528083 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDhtZWR0TEJ5YzFJamNYWWFWOExiOFRGMUFwQWZSUGQ_00gILU3gks","customer":"cus_QZbIDz7zyeAwA9"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0008_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0008_post_v1_payment_methods.tail new file mode 100644 index 00000000..00c8ea4e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0008_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1UIFGI0wRzlsfg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:29 GMT +original-request: req_1UIFGI0wRzlsfg +stripe-version: 2020-08-27 +idempotency-key: 0a3e2200-c259-4b0a-a6ee-564eeff3e2eb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5ZFY0qyl6XeWGUEbbDg1", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392189, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0009_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0009_post_create_payment_intent.tail new file mode 100644 index 00000000..d6b615b5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0009_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=k39hSEKVmmJuq9Yy7qRn7lsLxhM0xHTvklGGLp1YrgGiEt1G%2BVjZUaU3O29y7EQMmI7DJbY8LQmZAwTjaZtaDjTlR4z%2B01wxS%2B74wDI5UPzMiSa%2BrBi4IEuhbIEcftVa6aXa%2FpJGLJMXQ7wuhaf%2FH8Fvps8debb8yE5Y5pxYz6r1jV1SdDcZF4iclW1wzQ0KcvIcs%2BzrNwJuDf1OpPqoAQaTKx6EzzwgpMzzBPVxKYA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 275f7e8b521b890b2bb0fd0e9f8e9931 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5ZFY0qyl6XeW0IRPongV","secret":"pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0010_get_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0010_get_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV.tail new file mode 100644 index 00000000..83c71e60 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0010_get_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5ZFY0qyl6XeW0IRPongV\?client_secret=pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ttSDenpbJ47H73 +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga", + "id" : "pi_3PiS5ZFY0qyl6XeW0IRPongV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392189, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0011_post_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0011_post_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV_confirm.tail new file mode 100644 index 00000000..cbd6ee06 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0011_post_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5ZFY0qyl6XeW0IRPongV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TEilFcGdZWKy7w +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1919 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:31 GMT +original-request: req_TEilFcGdZWKy7w +stripe-version: 2020-08-27 +idempotency-key: 9381850f-845d-45e9-b88f-4268b6d33ac9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga&expand\[0]=payment_method&payment_method=pm_1PiS5ZFY0qyl6XeWGUEbbDg1&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5ZFY0qyl6XeWGUEbbDg1", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392189, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIDz7zyeAwA9" + }, + "client_secret" : "pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga", + "id" : "pi_3PiS5ZFY0qyl6XeW0IRPongV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392189, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0012_get_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0012_get_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV.tail new file mode 100644 index 00000000..4a58449a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0012_get_v1_payment_intents_pi_3PiS5ZFY0qyl6XeW0IRPongV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5ZFY0qyl6XeW0IRPongV\?client_secret=pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CGgVodwRIx3CiK +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS5ZFY0qyl6XeWGUEbbDg1", + "client_secret" : "pi_3PiS5ZFY0qyl6XeW0IRPongV_secret_MMwzRZHnKm55PZnVWuwhtqgga", + "id" : "pi_3PiS5ZFY0qyl6XeW0IRPongV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392189, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0013_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0013_get_v1_payment_methods.tail new file mode 100644 index 00000000..d34a453d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0013_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIDz7zyeAwA9&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2QCVu8gCQi1HDF +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5ZFY0qyl6XeWGUEbbDg1", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392189, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIDz7zyeAwA9" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0014_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0014_post_create_ephemeral_key.tail new file mode 100644 index 00000000..79c84c53 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0014_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=kR0BK3LY8NpxYS%2FXZ8AkFtyEJrGWhjkoaddOwpSQbeSBOEJCmDoE8BY53YNvyRBXoVJN%2Bi35FsE6IrzIq%2FPSpjxueadpDUIZnPF7R4tjxzAxmEC26DNw2fJPFXzEEXfWSZSofV0dKU%2FWRnPGEvPdSnKYFMN5126GqjGcR1luSDyG3gw4t2KsS8XUpkbJ3uxUJP0qx35IAeqAUhgPMbwFaoDU3js28x24FAXPwDxtyTA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 667eb6882f3de269cee15d74ed7dc5a7;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:32 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFZaWTV6MDc4eEU1MFZ3cU8xd2VxZ25UWXRmVm5tVFA_00A7GJzXQ7","customer":"cus_QZbIrXIjtLb7uD"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0015_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0015_post_v1_payment_methods.tail new file mode 100644 index 00000000..5e223e1d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0015_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KW1gTwQDjZmXDk +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:32 GMT +original-request: req_KW1gTwQDjZmXDk +stripe-version: 2020-08-27 +idempotency-key: 335fed62-18e8-4623-8f36-483dd49e5669 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5cFY0qyl6XeW8QNUYYHx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392192, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0016_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0016_post_create_payment_intent.tail new file mode 100644 index 00000000..220c3be6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0016_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=34xHSY8w48LEzZJR2gZuS%2F%2Fhm9WgBAxvSi1f8HrsL0SFjJVTBKCCiNS%2BFqaA5wdssHJ29%2BMjIP1MqY45%2BeCtgxVwLAUmPk1vy70%2FOlONFxPkNiMoodaIvwrl6ZeTMKTz5Sc%2FJ9CMT9xbcyZpko19WK4Ndl3GRlldPwKjpz8G%2BlDUzcJcuC2PT7KrPyGWrmaz0Yh32xtmGDyXkxAtDqvuLj6zoYjB1Z91s5GVtLKJ%2FAA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e6431f7226ddfd390e7dc1779590ebcb +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5dFY0qyl6XeW13nb2Yi8","secret":"pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0017_get_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0017_get_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8.tail new file mode 100644 index 00000000..f53aa9fb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0017_get_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5dFY0qyl6XeW13nb2Yi8\?client_secret=pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mcTCCrmQmamz51 +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU", + "id" : "pi_3PiS5dFY0qyl6XeW13nb2Yi8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392193, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0018_post_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0018_post_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8_confirm.tail new file mode 100644 index 00000000..ba115511 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0018_post_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5dFY0qyl6XeW13nb2Yi8\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wUfUksqt7hVRqC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1919 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:34 GMT +original-request: req_wUfUksqt7hVRqC +stripe-version: 2020-08-27 +idempotency-key: 0ab12935-a20a-4f9c-8299-417faa43c86d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU&expand\[0]=payment_method&payment_method=pm_1PiS5cFY0qyl6XeW8QNUYYHx&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5cFY0qyl6XeW8QNUYYHx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392192, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIrXIjtLb7uD" + }, + "client_secret" : "pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU", + "id" : "pi_3PiS5dFY0qyl6XeW13nb2Yi8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392193, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0019_get_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0019_get_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8.tail new file mode 100644 index 00000000..174ebbfd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0019_get_v1_payment_intents_pi_3PiS5dFY0qyl6XeW13nb2Yi8.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5dFY0qyl6XeW13nb2Yi8\?client_secret=pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nQSA4I5XhPqtTm +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS5cFY0qyl6XeW8QNUYYHx", + "client_secret" : "pi_3PiS5dFY0qyl6XeW13nb2Yi8_secret_XWZCQdNCzJSrOFD4z2yHwyVIU", + "id" : "pi_3PiS5dFY0qyl6XeW13nb2Yi8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392193, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0020_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0020_get_v1_payment_methods.tail new file mode 100644 index 00000000..d3a57acb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentcsc/0020_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIrXIjtLb7uD&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PS0XyOdGdHXyge +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5cFY0qyl6XeW8QNUYYHx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392192, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIrXIjtLb7uD" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..a06a72c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Q9i1CpBrNC3Z1xTOa5JFPFo2SPJvaRDkb4L8b4s9Mx43NNFD8TSFAdSjBetZEMHOzYYYxAvcMJVgWb53Lp8H0bJHxSSTjcdelVYkLHtk0ZXFw%2FFhNW3E%2BgiPkkCS7CTMVMOvnLcYLiUK5tzy67XPiD722JXMgrKOAFDoy3UAn8XfhPNMl%2BeUtodDzLf7jKks0LE3xHzhmgJzpHmUaneRqKdUBDm%2BvzLAbefLKp9Q85A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ca1c6f5c8fdc9df85352dee3766cc08c +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDd6eWQ3M2J1dnY5S2ZqU29uZ21nYjV0U29yMnVoSm4_00R79rBaQv","customer":"cus_QZbIbzqiq25TxV"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..c27a3fb1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3ScXQL4oSsR6Tr +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:36 GMT +original-request: req_3ScXQL4oSsR6Tr +stripe-version: 2020-08-27 +idempotency-key: e328c548-e6c0-499e-b122-b685fc0ef1a7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5gFY0qyl6XeW5EJh3yRP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392196, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..65049e3a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=aSfPCQ5LpXdUhSur6L%2BrtThVMxOtQ7sp20uHEtwvtLXjMJAiEBVHaK%2F4txDdL6Y%2F0bcPvWMYkq9Oxjcnneaas9WVR48iAJ0wizQCLtVOXoEveQ6GcMwk8heJkDSAmh9%2B42YVcieTD4lKzBNpGiVwXMcGms1esilVqQXssX%2Fo1p32UhENlES7yuszeImSnLRSQllksaROQqmn91OBiH7LVy479cM9nXMjP6E0Um0TcJQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 711046e9010688c9eb1ce7144c764227 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5gFY0qyl6XeW1RMB0Ifn","secret":"pi_3PiS5gFY0qyl6XeW1RMB0Ifn_secret_Bf8ShpCgdgiEvyL0IaaqDSBVH","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0003_get_v1_payment_intents_pi_3PiS5gFY0qyl6XeW1RMB0Ifn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0003_get_v1_payment_intents_pi_3PiS5gFY0qyl6XeW1RMB0Ifn.tail new file mode 100644 index 00000000..a4b682ea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0003_get_v1_payment_intents_pi_3PiS5gFY0qyl6XeW1RMB0Ifn.tail @@ -0,0 +1,115 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5gFY0qyl6XeW1RMB0Ifn\?client_secret=pi_3PiS5gFY0qyl6XeW1RMB0Ifn_secret_Bf8ShpCgdgiEvyL0IaaqDSBVH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_E2BWErb8iD5LQH +Content-Length: 2015 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5gFY0qyl6XeW5EJh3yRP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392196, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIbzqiq25TxV" + }, + "client_secret" : "pi_3PiS5gFY0qyl6XeW1RMB0Ifn_secret_Bf8ShpCgdgiEvyL0IaaqDSBVH", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS5gFY0qyl6XeW1RMB0Ifn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392196, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0004_get_v1_payment_intents_pi_3PiS5gFY0qyl6XeW1RMB0Ifn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0004_get_v1_payment_intents_pi_3PiS5gFY0qyl6XeW1RMB0Ifn.tail new file mode 100644 index 00000000..39daeafe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0004_get_v1_payment_intents_pi_3PiS5gFY0qyl6XeW1RMB0Ifn.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5gFY0qyl6XeW1RMB0Ifn\?client_secret=pi_3PiS5gFY0qyl6XeW1RMB0Ifn_secret_Bf8ShpCgdgiEvyL0IaaqDSBVH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7VFB44dUJtQgwV +Content-Length: 1006 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS5gFY0qyl6XeW5EJh3yRP", + "client_secret" : "pi_3PiS5gFY0qyl6XeW1RMB0Ifn_secret_Bf8ShpCgdgiEvyL0IaaqDSBVH", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS5gFY0qyl6XeW1RMB0Ifn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392196, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..af312983 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIbzqiq25TxV&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0Zi1gxMIEjqPTn +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5gFY0qyl6XeW5EJh3yRP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392196, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIbzqiq25TxV" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..432e53fd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8gNBOPksbh9bedUdlR9opKuCU9TC1TayCGI79u1noQselYxXFzBe0IhDiFR3aYEZElVRYpTTw%2FoPB6Ukq9OokLfqxpxASGNI3DbAyrJFYZvm0ghBgq664%2BjaJD11G%2BBFGKNsKJDu53ei8IU2XSWxr9qLc0jaOPF3qEDh3m%2FewpNI6reYqdg0CvpW7s%2BVX87Jy1MR%2FOOMGUp8UQfOsIbxJEi%2Fa09z3GcwSDeOL3%2BdxAI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 417494ace45584899614d58407aa110a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:39 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFNyQzdMU3Y3cjhEOFlGTzE3Vjh3ZnhHSzMweHd5bHI_00YX02HLv6","customer":"cus_QZbIMTQNunlvot"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..010af351 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hVFSgd0gljM4OT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:39 GMT +original-request: req_hVFSgd0gljM4OT +stripe-version: 2020-08-27 +idempotency-key: 3b2ff6f2-acec-4dec-b867-9076dc75d261 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5jFY0qyl6XeW7JOaTkP9", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392199, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..6264e844 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Q4iaIMLsmSsEKUOJMkTiPa%2FMytwHSwKlnzHGOaLVtmuZjUGg%2FVUcL%2Br96w1oxVUOq4y1q8xhMBW%2BXdl4qLN9eqhuFYhvkrC%2BFqrBqUza0RsrAdhfZd7Ntx0dPwmFN5IX%2BArjOq8QZY9tNNHhVJG%2FNT%2BQKvjrpq0igio9mYpuTCTBEtbszbd3sy9%2BBpqoH6%2BZSMwIP64AFXYwmzWa5R%2FM3e6IAG5kN26vUqlHhJ%2FO3D4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: daf3e46cc7ff986a186b447c6f6c196b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5jFY0qyl6XeW1anpvcKh","secret":"pi_3PiS5jFY0qyl6XeW1anpvcKh_secret_WjJKsJk4cm9kPh33VX8HqznTU","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0009_get_v1_payment_intents_pi_3PiS5jFY0qyl6XeW1anpvcKh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0009_get_v1_payment_intents_pi_3PiS5jFY0qyl6XeW1anpvcKh.tail new file mode 100644 index 00000000..1b843152 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0009_get_v1_payment_intents_pi_3PiS5jFY0qyl6XeW1anpvcKh.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5jFY0qyl6XeW1anpvcKh\?client_secret=pi_3PiS5jFY0qyl6XeW1anpvcKh_secret_WjJKsJk4cm9kPh33VX8HqznTU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uFr8ZcBUdycEqd +Content-Length: 1919 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5jFY0qyl6XeW7JOaTkP9", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392199, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIMTQNunlvot" + }, + "client_secret" : "pi_3PiS5jFY0qyl6XeW1anpvcKh_secret_WjJKsJk4cm9kPh33VX8HqznTU", + "id" : "pi_3PiS5jFY0qyl6XeW1anpvcKh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392199, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0010_get_v1_payment_intents_pi_3PiS5jFY0qyl6XeW1anpvcKh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0010_get_v1_payment_intents_pi_3PiS5jFY0qyl6XeW1anpvcKh.tail new file mode 100644 index 00000000..8022930f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0010_get_v1_payment_intents_pi_3PiS5jFY0qyl6XeW1anpvcKh.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5jFY0qyl6XeW1anpvcKh\?client_secret=pi_3PiS5jFY0qyl6XeW1anpvcKh_secret_WjJKsJk4cm9kPh33VX8HqznTU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_r8K4lYMOrZLaBw +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS5jFY0qyl6XeW7JOaTkP9", + "client_secret" : "pi_3PiS5jFY0qyl6XeW1anpvcKh_secret_WjJKsJk4cm9kPh33VX8HqznTU", + "id" : "pi_3PiS5jFY0qyl6XeW1anpvcKh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392199, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..dcddd4b2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0011_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIMTQNunlvot&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jmicDJ8NTedIa6 +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5jFY0qyl6XeW7JOaTkP9", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392199, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIMTQNunlvot" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0012_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0012_post_create_ephemeral_key.tail new file mode 100644 index 00000000..0d00c2cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0012_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=nq78hB41PGT3AZ0DepSPByYCRyP%2BDxMyW9En%2BUGtnoCdX2t4zwIFYPvmrJExpJ5gwj%2FdbzOnSPtG8Ry51sLM6ZRpYG4exyuuxVONFqCjGD1KB%2FhxWlJdl4gmeJhCIoi8ASLuuogafAM7peUjB57%2FeWsCR5DJRuTOuVGeJjmFk6savIoSOV%2FcM4sSh%2FPK5DT0oCZdS6Bv1q9enO8m1%2F0%2FQSODk6OTiIui4cKMGmkgf0M%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: dd3dcf12cc2269a0a4161f8af7335157 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHR3MFMwREpBZ2tjRXU0UmhRNXB0aTlzMEx3N2RlSWg_00UcrwGNWy","customer":"cus_QZbI5nzkONHfVq"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..e18692a6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0013_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tTYGVrVrVLUQUI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:42 GMT +original-request: req_tTYGVrVrVLUQUI +stripe-version: 2020-08-27 +idempotency-key: 404024e4-7eed-497e-93e6-f2a170ee0401 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5mFY0qyl6XeWvHTPBniN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392202, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..df627251 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fv%2BKdY8aief4x76ULkeTRGGNBqzYRiEGFV86y3YQlfARIj2VvNSWPNPTHkmz82WOJ80MU%2Fuy%2B9OurOaNAs4UAis6ijdfqqQnS%2F0qkPNZmVIt%2BpdpJ8cTrFVnAgHQpx5ahc2nyoROVPH0Mufn%2FDiNNk8FKVUrJJXqCGO3lL57cnimRVBJ4ZOol5ZsX4fA3H7O1MiSx%2FbI1MlfYeevFtXu2kSHhqQKyLZP%2BNtLX7d4%2Bmo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e9c476fd891d24191ea4ed47d1f8ed9b;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:44 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5mFY0qyl6XeW05mcfsYr","secret":"pi_3PiS5mFY0qyl6XeW05mcfsYr_secret_ZuQWWMjv23oZOoUeT7rOcsqB6","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0015_get_v1_payment_intents_pi_3PiS5mFY0qyl6XeW05mcfsYr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0015_get_v1_payment_intents_pi_3PiS5mFY0qyl6XeW05mcfsYr.tail new file mode 100644 index 00000000..b2c99ded --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0015_get_v1_payment_intents_pi_3PiS5mFY0qyl6XeW05mcfsYr.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5mFY0qyl6XeW05mcfsYr\?client_secret=pi_3PiS5mFY0qyl6XeW05mcfsYr_secret_ZuQWWMjv23oZOoUeT7rOcsqB6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_udBHFgg2pRsbqU +Content-Length: 1919 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5mFY0qyl6XeWvHTPBniN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392202, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbI5nzkONHfVq" + }, + "client_secret" : "pi_3PiS5mFY0qyl6XeW05mcfsYr_secret_ZuQWWMjv23oZOoUeT7rOcsqB6", + "id" : "pi_3PiS5mFY0qyl6XeW05mcfsYr", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392202, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0016_get_v1_payment_intents_pi_3PiS5mFY0qyl6XeW05mcfsYr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0016_get_v1_payment_intents_pi_3PiS5mFY0qyl6XeW05mcfsYr.tail new file mode 100644 index 00000000..71a932ff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0016_get_v1_payment_intents_pi_3PiS5mFY0qyl6XeW05mcfsYr.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5mFY0qyl6XeW05mcfsYr\?client_secret=pi_3PiS5mFY0qyl6XeW05mcfsYr_secret_ZuQWWMjv23oZOoUeT7rOcsqB6$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Sl2S2ix537izNt +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS5mFY0qyl6XeWvHTPBniN", + "client_secret" : "pi_3PiS5mFY0qyl6XeW05mcfsYr_secret_ZuQWWMjv23oZOoUeT7rOcsqB6", + "id" : "pi_3PiS5mFY0qyl6XeW05mcfsYr", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392202, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0017_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0017_get_v1_payment_methods.tail new file mode 100644 index 00000000..65ccd457 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUdeferredIntentssc/0017_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbI5nzkONHfVq&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rGsCaLfuwHpGgo +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5mFY0qyl6XeWvHTPBniN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392202, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbI5nzkONHfVq" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..6061c422 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=iYGmrjdZ%2FmtRv4u479rlFe2oNd9Za6a%2BJMbsW0YSDbLhrU%2BIaqHe%2BCGsdD7yH9Mh6ZIW4RlOsVfpRLGTgnaKFjhUmawfMgW5%2F%2FD5NP%2Fi%2FQ1KHHH4Xr8aSakM2%2F5rEnrLWLH5cZaGexaiOezwqYuLlYjrOfYJ4U9nSoAgDAaSMYh5r4aL4zot%2Ff4R%2FnVH8rWfCvAMC8Ru7euZhLI6G5dGsciX8IgHu9wo%2BWE5c1MpIXc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fbba492cc3b29ffaf55d294c0d7ce5b8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:45 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFRkekhybFV4SVJaRUR1ZFAyZ0dxSzd4VnhGaGxvbGU_00lIhmPn7G","customer":"cus_QZbILPvEtqrtyG"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..1474ca2f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=K%2BQ%2By2%2FGyypZvOtTvEmI5ES1KRH39Fet3g7oy86OXHJ1fbBCktxMSr8IsyJRkSlfV2rhAcF%2BTCRdZlX9hrJ2EMNeQf9Jk6FQFZXIVT5duAXq9V%2FyFx4C5aMHJzUjSdcCR79532DHtK6hflbtyAERvuBf00s0C4qXWTdgqSnVNVlS9oJRP4LS3OiGFK0txQ6b%2BqmPBFl4lLuYkq6X8%2FoReQSUVTn3JUhTzOC%2BXbCFlBs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c9a328a3350b8d6567a6135e233e17f3 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:45 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5pFY0qyl6XeW0JjoWPFh","secret":"pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0002_get_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0002_get_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh.tail new file mode 100644 index 00000000..bf32948e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0002_get_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5pFY0qyl6XeW0JjoWPFh\?client_secret=pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_a96FMXmGWYvEZt +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn", + "id" : "pi_3PiS5pFY0qyl6XeW0JjoWPFh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392205, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0003_post_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0003_post_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh_confirm.tail new file mode 100644 index 00000000..a2a49a33 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0003_post_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh_confirm.tail @@ -0,0 +1,119 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5pFY0qyl6XeW0JjoWPFh\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yCJ4P8QSPa2NP6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2015 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:47 GMT +original-request: req_yCJ4P8QSPa2NP6 +stripe-version: 2020-08-27 +idempotency-key: ef36ebac-dca5-437f-a2cd-3c65afcf7962 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=always&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=off_session&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5qFY0qyl6XeWv7dCTRbc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392206, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbILPvEtqrtyG" + }, + "client_secret" : "pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS5pFY0qyl6XeW0JjoWPFh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392205, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0004_get_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0004_get_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh.tail new file mode 100644 index 00000000..fbc4f819 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0004_get_v1_payment_intents_pi_3PiS5pFY0qyl6XeW0JjoWPFh.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5pFY0qyl6XeW0JjoWPFh\?client_secret=pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VlOBVZ5kZ3c7iY +Content-Length: 1006 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS5qFY0qyl6XeWv7dCTRbc", + "client_secret" : "pi_3PiS5pFY0qyl6XeW0JjoWPFh_secret_KvLDWyKPIHkzuRGJdrMao4Csn", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS5pFY0qyl6XeW0JjoWPFh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392205, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..7b060a77 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbILPvEtqrtyG&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8jATAAyKRWBv5p +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5qFY0qyl6XeWv7dCTRbc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392206, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbILPvEtqrtyG" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..fa0a7b3e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NwsYavM8sIioOjiLks1z3zNS4dBmiuomrCifvUqqB8gocOdt0ZIwXUZz00yMhEwkgBbIRD6sPIHwdq201Ye%2FwAZS3vs5mpxcV8uqtWCFmva%2BnxzgVbKY6JQyJPgBLbFQZUiAhNfYjg7jQOgIjyXkbiuJEentZtg1xEK8%2FNU3DIY3VPs7MvIJQ0TblDNAJsGg%2FU7uSKKI11NYaIeOxWNs2hwTG68jpcPhrmHkK9qzhlQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: df02fe21dd65f44a09b57736c3f157cf +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:48 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLERTdmxBUm4waFZNQzlGRmJhaW1Ib1JoeVcxRXpzemI_00LZrBkrko","customer":"cus_QZbIteyk5Oa1So"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0007_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0007_post_create_payment_intent.tail new file mode 100644 index 00000000..d6df7b52 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0007_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fVEpqU1rXRcDrHsj700v8Jy0Fi3kr4HComoli6TqwAtyA6drHGzMFCzXyPiLYHsj75VQ7DGmfxYUJs%2FmbfGva5BwZvwlNiOoGal3qcN3aMtuKYSyqrh2QJ3ptv%2FxOsRrdVeRHe0ufoqwKD2YOe%2FriwhkYEHiNw6l24cARvIcoKLqUywZf4a1wcCb97OUOzZKfNQBSO0TX%2BokJgYkkffpo8WUCNMoVhi2iOm7Kx7N4v0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c0584657b30d6fbacf0c3a0e5bea62e1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:49 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5tFY0qyl6XeW1GiWT7BV","secret":"pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0008_get_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0008_get_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV.tail new file mode 100644 index 00000000..56783cd5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0008_get_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5tFY0qyl6XeW1GiWT7BV\?client_secret=pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tkig1NAPB8R6sM +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH", + "id" : "pi_3PiS5tFY0qyl6XeW1GiWT7BV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392209, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0009_post_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0009_post_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV_confirm.tail new file mode 100644 index 00000000..54dde23e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0009_post_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5tFY0qyl6XeW1GiWT7BV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jK8BCRuElM7iXf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1919 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:50 GMT +original-request: req_jK8BCRuElM7iXf +stripe-version: 2020-08-27 +idempotency-key: b88751be-aecf-4088-a5e6-7aa00e95f3a0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=limited&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5tFY0qyl6XeWAjaJgApc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392209, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIteyk5Oa1So" + }, + "client_secret" : "pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH", + "id" : "pi_3PiS5tFY0qyl6XeW1GiWT7BV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392209, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0010_get_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0010_get_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV.tail new file mode 100644 index 00000000..e656fce3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0010_get_v1_payment_intents_pi_3PiS5tFY0qyl6XeW1GiWT7BV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5tFY0qyl6XeW1GiWT7BV\?client_secret=pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zG34iFkz5lQSFi +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS5tFY0qyl6XeWAjaJgApc", + "client_secret" : "pi_3PiS5tFY0qyl6XeW1GiWT7BV_secret_aEf6n2JoWcJgu1SB9yfRNOopH", + "id" : "pi_3PiS5tFY0qyl6XeW1GiWT7BV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392209, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..13e11abb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0011_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIteyk5Oa1So&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_A3X7rMJYEpFrsc +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5tFY0qyl6XeWAjaJgApc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392209, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIteyk5Oa1So" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0012_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0012_post_create_ephemeral_key.tail new file mode 100644 index 00000000..c56ccede --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0012_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=31vciZoQpxMUJROrc4QKcPgiYYEDe9H5Jup5R0EYKcOHm%2FfOkch52ymLneB8pW4da8NowMjdW868wkecsy85odYbHG89uJk3UbtI5isiQ049P%2B%2FqvW7Ce41f5qxz%2BUQ0BCn%2BkOATMzBVjMgPpE5p65uaXTZ2CwSGDmkbcUoLghlMER%2B2%2BswXooukDG7vdtgGx%2BZ0TVpNpYn01lVsCinS2X8hdPn3ysgbu0tuqi%2FvLKY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2251828a26f4dca31ae230beb60e7e96 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:52 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFQ3ZXFkSW05dHJ1VWhjajlDdDRQcXRuV09BSE01Ym0_00Q96VT8PK","customer":"cus_QZbIHRYAhKrX7B"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..12b6b362 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=naQSMun41J%2FcjhzZaffRWkJbzWriL8OlcppCros%2Fabl8EN4JYCV3qlTy%2FPko0DXyFcEluJ3d2%2FQIgpAONaaPMQZFIEqK%2F2wOIxcbDu1CMLihBKiJ2paQviC0HX62aLg38Ml8aSzMNHudTMA6Fp1BDT1JgLascaMn8RMcW8%2BDKEALFB%2F8OCIC2uoWLiwMsop%2FdbkotDkyhYwqLkPkNvOq77fLmA0ntsgwklH6%2BQuOThU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7bda3a43ac46657931e56a3c32aa9c36 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:52 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS5wFY0qyl6XeW1iavV4Xv","secret":"pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0014_get_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0014_get_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv.tail new file mode 100644 index 00000000..b82635eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0014_get_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5wFY0qyl6XeW1iavV4Xv\?client_secret=pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UurcpLH5mORSSJ +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC", + "id" : "pi_3PiS5wFY0qyl6XeW1iavV4Xv", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392212, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0015_post_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0015_post_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv_confirm.tail new file mode 100644 index 00000000..04885f8a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0015_post_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5wFY0qyl6XeW1iavV4Xv\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HSdJbJovhtsk62 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1919 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:54 GMT +original-request: req_HSdJbJovhtsk62 +stripe-version: 2020-08-27 +idempotency-key: 2619f4f1-eb91-4b66-bb6b-0f450a9364e0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=limited&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5xFY0qyl6XeWeBQFNAPi", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392213, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIHRYAhKrX7B" + }, + "client_secret" : "pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC", + "id" : "pi_3PiS5wFY0qyl6XeW1iavV4Xv", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392212, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0016_get_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0016_get_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv.tail new file mode 100644 index 00000000..ea4c0a67 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0016_get_v1_payment_intents_pi_3PiS5wFY0qyl6XeW1iavV4Xv.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS5wFY0qyl6XeW1iavV4Xv\?client_secret=pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DJ2QDLst01eSUK +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS5xFY0qyl6XeWeBQFNAPi", + "client_secret" : "pi_3PiS5wFY0qyl6XeW1iavV4Xv_secret_lULwk72sep0DWHHK5gtDNmvIC", + "id" : "pi_3PiS5wFY0qyl6XeW1iavV4Xv", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392212, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0017_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0017_get_v1_payment_methods.tail new file mode 100644 index 00000000..a7cf6b7c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPISFUintentFirstcsc/0017_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIHRYAhKrX7B&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wkNDfmzjp4O5WT +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5xFY0qyl6XeWeBQFNAPi", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392213, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIHRYAhKrX7B" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..9feed6fb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=RDgDMzDJ0U9nfF6D9l%2FZFQQPOElCs1VwJ%2BlboXymfVekZw8X4nyfv9b9sibP648J04aSQ86SWkpK9scqJgPrnNcf7gO5j0bpMFLKH7Lo1JENko4591slLXyo0zBiQYPi%2FjuNvBaKncUBEUtoOUyj2WJ%2FRSC%2Fb%2F1xh9ETh1vHImRTPoNAKCaV%2F2syzdWWOgjSysxScgTkjAXOx9Ct6S%2FWU8Y22CA2ftzMmDkkaeEhURM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 42878682d28b5e6698c1a1705908b431;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:55 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDJxNmVwNzVKY0tWdVNuRlhja1JPN0VHbkFvdFpNaGw_002zmA7qN4","customer":"cus_QahlmmC6XBKx41"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..44cce167 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_iXnbZd7FON2GGh +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:56 GMT +original-request: req_iXnbZd7FON2GGh +stripe-version: 2020-08-27 +idempotency-key: b7fb5674-94b0-4e8b-a0bb-d931d2da8c0f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PjWM3FY0qyl6XeWttHo0kTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646916, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..52a31246 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=HX8OeQfEfBmBTv2Y2MB4lfkIBDv5adyDoLqoglTzKkdLhwqNYgd5HYQfNSXeztfuZrmUK4RA6yrGPU5kxyMn5jXV8qjok%2Br1izsapNwvSF6Yt%2F4Ephr7fIOxdndDeNigOJKDEAr%2BJ0ailyleXDmuUu%2F5kYPPdtwQ2dc1BSQt6tqAW7SFJrP1KAwhyJNtI6p%2BTMdlRmaqfZULY72ieEnKfMwWpNk1Y8uIIGEkOG8uh9g%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cc473e26163153216ec686577ec02979 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:57 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWM4FY0qyl6XeW1p4g32Lz","secret":"pi_3PjWM4FY0qyl6XeW1p4g32Lz_secret_ZqaRhgm5Gr35Iso8kGkfPnekn","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0003_get_v1_payment_intents_pi_3PjWM4FY0qyl6XeW1p4g32Lz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0003_get_v1_payment_intents_pi_3PjWM4FY0qyl6XeW1p4g32Lz.tail new file mode 100644 index 00000000..4fe32b7f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0003_get_v1_payment_intents_pi_3PjWM4FY0qyl6XeW1p4g32Lz.tail @@ -0,0 +1,115 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWM4FY0qyl6XeW1p4g32Lz\?client_secret=pi_3PjWM4FY0qyl6XeW1p4g32Lz_secret_ZqaRhgm5Gr35Iso8kGkfPnekn&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IR8bRbcZXedewJ +Content-Length: 2006 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWM3FY0qyl6XeWttHo0kTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646916, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QahlmmC6XBKx41" + }, + "client_secret" : "pi_3PjWM4FY0qyl6XeW1p4g32Lz_secret_ZqaRhgm5Gr35Iso8kGkfPnekn", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PjWM4FY0qyl6XeW1p4g32Lz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646916, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0004_get_v1_payment_intents_pi_3PjWM4FY0qyl6XeW1p4g32Lz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0004_get_v1_payment_intents_pi_3PjWM4FY0qyl6XeW1p4g32Lz.tail new file mode 100644 index 00000000..a9bb372c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0004_get_v1_payment_intents_pi_3PjWM4FY0qyl6XeW1p4g32Lz.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWM4FY0qyl6XeW1p4g32Lz\?client_secret=pi_3PjWM4FY0qyl6XeW1p4g32Lz_secret_ZqaRhgm5Gr35Iso8kGkfPnekn$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_41nIDIkmrdsczZ +Content-Length: 997 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PjWM3FY0qyl6XeWttHo0kTR", + "client_secret" : "pi_3PjWM4FY0qyl6XeW1p4g32Lz_secret_ZqaRhgm5Gr35Iso8kGkfPnekn", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PjWM4FY0qyl6XeW1p4g32Lz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646916, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..004f51b6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahlmmC6XBKx41&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jKLZwy9x36SnzY +Content-Length: 1270 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PjWM3FY0qyl6XeWttHo0kTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646916, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QahlmmC6XBKx41" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..c711ba82 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=jDWAOrhHQg6IHwZd0G4c%2BphMrZ%2F6NrI0ZwY93BMGc5k8cQOCuqYIgkN%2FUQrjJr3hdmMXTwYMQZd%2BL%2BI4AX7iX21%2B5QjGXvV%2Ff1kaF86Tw7pE6uDOjE%2Fi0ajRnS2Q%2BWkabB2T161%2BFLGITRfevPGd9pm6ivjCtziahlbE2%2B9dvodYRYpCkrGZ8jqjEezVR2GqI3WoG%2F42wsdcF4vyvNVfsN23tGWvBw8YZ118%2Br3wdu0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0094b56b01fb1256f3b7e78019d64fec +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:01:58 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFdvdDlBTE9zVWE0Ujl0bnF0ZUs4QUZlZG5Xa0R3bXg_00BKlEwu6C","customer":"cus_QahlZC6RSmOHRO"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..7529f061 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Jk7NEov58VIidW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Sat, 03 Aug 2024 01:01:59 GMT +original-request: req_Jk7NEov58VIidW +stripe-version: 2020-08-27 +idempotency-key: 89590b69-bcac-4d4c-a4e2-8c1f08e4c90c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PjWM7FY0qyl6XeW4InVCeue", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646919, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..e3627be9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=bXEgfsCceJ7Zs2fDzbYBlsJ%2FALZ8fAKoPOn7rWSrwYdhSFr72OKOcyHNfQpgbWv4xQ8FYp8YCTaXiazOwVsILz8LiZN7hs1PGPFw2w6qPwh0YkoR2m5snNYc2zzaEXIR78r5h6gcwwuRwjOS%2BxnUV03sznIk1F2W5WFTkECTI8NtYob%2FrvxLzYNFfnfN2nGPWiDYKNg6AWtVLwJKYj%2BiNETGixw1lXM6QAnA6ijI2b8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e7ed787e7725ed8de1aa9b7050cb1e4e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:02:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWM7FY0qyl6XeW0bfoI51q","secret":"pi_3PjWM7FY0qyl6XeW0bfoI51q_secret_R4qD9UGx5iB4co2NEmlJPFAxb","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0009_get_v1_payment_intents_pi_3PjWM7FY0qyl6XeW0bfoI51q.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0009_get_v1_payment_intents_pi_3PjWM7FY0qyl6XeW0bfoI51q.tail new file mode 100644 index 00000000..018628f4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0009_get_v1_payment_intents_pi_3PjWM7FY0qyl6XeW0bfoI51q.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWM7FY0qyl6XeW0bfoI51q\?client_secret=pi_3PjWM7FY0qyl6XeW0bfoI51q_secret_R4qD9UGx5iB4co2NEmlJPFAxb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qneindBDIwuQWg +Content-Length: 1898 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWM7FY0qyl6XeW4InVCeue", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646919, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PjWM7FY0qyl6XeW0bfoI51q_secret_R4qD9UGx5iB4co2NEmlJPFAxb", + "id" : "pi_3PjWM7FY0qyl6XeW0bfoI51q", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646919, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0010_get_v1_payment_intents_pi_3PjWM7FY0qyl6XeW0bfoI51q.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0010_get_v1_payment_intents_pi_3PjWM7FY0qyl6XeW0bfoI51q.tail new file mode 100644 index 00000000..3017cd2e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0010_get_v1_payment_intents_pi_3PjWM7FY0qyl6XeW0bfoI51q.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWM7FY0qyl6XeW0bfoI51q\?client_secret=pi_3PjWM7FY0qyl6XeW0bfoI51q_secret_R4qD9UGx5iB4co2NEmlJPFAxb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PCseyIpSY9ykck +Content-Length: 900 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PjWM7FY0qyl6XeW4InVCeue", + "client_secret" : "pi_3PjWM7FY0qyl6XeW0bfoI51q_secret_R4qD9UGx5iB4co2NEmlJPFAxb", + "id" : "pi_3PjWM7FY0qyl6XeW0bfoI51q", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646919, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..3a4faeaf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0011_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QahlZC6RSmOHRO&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qmfReOBThldLvb +Content-Length: 89 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0012_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0012_post_create_ephemeral_key.tail new file mode 100644 index 00000000..7e9cbd13 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0012_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=U7OiMJU1GK2oqeghHtfzLgEqTPO5VrIogEhZDDunArND5cYmLnxrbHvkV0sjngegleTMjbcoTWwm1%2BA6ovSTAHwiM%2BBOTOlClbNJtqIuCp1id9UdK8PEw%2BpH2Jn2378uusk1e4iXlDdGn7Iza1wljlqp%2FYukh5iQdIE7uBBTau8HEEh22gfaMkM2h8nXwZge%2FFEYfIiqF4x5%2FvWcuWvygiXhAZ9qNn0HvuIxOJBD1Ac%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6a772ee64a442655da89ab68c37cbbe6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:02:02 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFF6emo5eGFTV0ZKTWJRTE9YemFaSnp0bEtRdVhjdHE_00KW6XRy8b","customer":"cus_Qahl1QJP8dXKNh"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..2d538b08 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0013_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PUYtN4CgNLo48A +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:02 GMT +original-request: req_PUYtN4CgNLo48A +stripe-version: 2020-08-27 +idempotency-key: e794a695-77d8-4be6-834f-0a3ae728d802 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PjWMAFY0qyl6XeW6U9UsvgA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646922, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..1421c8f4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ssnJXQkfBgDbAK0VpptvuZZtwGfriP2EVDo08wQFjl1jZsv4jZPo%2BeO7hwiDHwsDVMJMapoF8T8J5cP%2FzZdp6uDt20XWjqQ%2BopdwYf6uPilGfnF6fpWovTeL4HsLT6tFXPrNdaACXSCpKxd69Q%2BQMKyuOpfwR7o9P%2FBAWMhk1RjIlUPFel%2FeRxCYoSrWlP9Bp5dtNa7kGkoUaLwu7ijAizvUlQnw26CAMg6mhf90VO8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a33ce882cd9177b144d576c7b1dcbf9b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Sat, 03 Aug 2024 01:02:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PjWMAFY0qyl6XeW1hqdkyKh","secret":"pi_3PjWMAFY0qyl6XeW1hqdkyKh_secret_chGNYGoYtTjm1UrzwkS8kdr1B","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0015_get_v1_payment_intents_pi_3PjWMAFY0qyl6XeW1hqdkyKh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0015_get_v1_payment_intents_pi_3PjWMAFY0qyl6XeW1hqdkyKh.tail new file mode 100644 index 00000000..924359b8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0015_get_v1_payment_intents_pi_3PjWMAFY0qyl6XeW1hqdkyKh.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWMAFY0qyl6XeW1hqdkyKh\?client_secret=pi_3PjWMAFY0qyl6XeW1hqdkyKh_secret_chGNYGoYtTjm1UrzwkS8kdr1B&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kwOIjtQUFvioq1 +Content-Length: 1898 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PjWMAFY0qyl6XeW6U9UsvgA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722646922, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PjWMAFY0qyl6XeW1hqdkyKh_secret_chGNYGoYtTjm1UrzwkS8kdr1B", + "id" : "pi_3PjWMAFY0qyl6XeW1hqdkyKh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646922, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0016_get_v1_payment_intents_pi_3PjWMAFY0qyl6XeW1hqdkyKh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0016_get_v1_payment_intents_pi_3PjWMAFY0qyl6XeW1hqdkyKh.tail new file mode 100644 index 00000000..af1bf941 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0016_get_v1_payment_intents_pi_3PjWMAFY0qyl6XeW1hqdkyKh.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PjWMAFY0qyl6XeW1hqdkyKh\?client_secret=pi_3PjWMAFY0qyl6XeW1hqdkyKh_secret_chGNYGoYtTjm1UrzwkS8kdr1B$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0CkcyIMzNbJGPg +Content-Length: 900 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PjWMAFY0qyl6XeW6U9UsvgA", + "client_secret" : "pi_3PjWMAFY0qyl6XeW1hqdkyKh_secret_chGNYGoYtTjm1UrzwkS8kdr1B", + "id" : "pi_3PjWMAFY0qyl6XeW1hqdkyKh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722646922, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0017_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0017_get_v1_payment_methods.tail new file mode 100644 index 00000000..5491b72a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayPIdeferredIntentssc/0017_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_Qahl1QJP8dXKNh&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_P2JffCtjdO7QCL +Content-Length: 89 +Vary: Origin +Date: Sat, 03 Aug 2024 01:02:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..5d2ef362 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8bF%2FX3SHGSdgVl%2BWn0H%2Bc3ZwekRxRt7hkq7H1eij%2BtrNi0VZ0QhCSpzV5%2BV1pa9Oncav7za3n5cRr2DwocqoMeiicdrqgCmIDVT6Zob8VRJ%2BXyOLtEwNDRdr6qOeNKVNFMTZIVLdCridipuj5nQaqREAab30QvSx2XFXszNwfOUpe79Sk8qkFb4ByrjU91daDVpEFIP014L9QhNBMPjojHT%2FG14Uit044yzlzHQFkdE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 41072b12d40c679e4994826056985bff;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:55 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFNLRUloWURMclprOExqUTlvVmlrTDh6ODYzY0xTUGw_00kqW1ENFI","customer":"cus_QZbIqvVPiT5ngQ"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..29970926 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hbmrSZ8Ym778Ej +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:55 GMT +original-request: req_hbmrSZ8Ym778Ej +stripe-version: 2020-08-27 +idempotency-key: a5c9626b-32be-43fa-af6c-d6fba8086ba7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS5zFY0qyl6XeWOFAWc2Of", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392215, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0002_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0002_post_create_setup_intent.tail new file mode 100644 index 00000000..b3ca0110 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0002_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=abyWWWJ3tzjEZ1TOt9WV7747FASRPoUT1lLsskgJgKmpEsiipwzahLqE62bdS9NyNRkHftx%2BVzx1mMrAe3SbUujChJ0LdUE0xe2QfXrNzA6DFXr3GKU91MjuX0163fU%2BA%2BTJd5X2BpXkgobZpNOubmklrySYs3lpgnxdwlCIv4v1uTMzRNc9L65CpjJWPUvgUGFNVQqxSHms%2FdBrvRIC75QejDDeu4VRrh4f5bkhEqE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2ffec75f17513db98bbb6ef44ed10f84 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:56 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS5zFY0qyl6XeW7VSdzoRF","secret":"seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0003_get_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0003_get_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF.tail new file mode 100644 index 00000000..c2436d83 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0003_get_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS5zFY0qyl6XeW7VSdzoRF\?client_secret=seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2W62ElD8xOEdZx +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS5zFY0qyl6XeW7VSdzoRF", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392215, + "client_secret" : "seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0004_post_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0004_post_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF_confirm.tail new file mode 100644 index 00000000..c3c36226 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0004_post_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS5zFY0qyl6XeW7VSdzoRF\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jDQIuXxtFsdj4l +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1553 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:57 GMT +original-request: req_jDQIuXxtFsdj4l +stripe-version: 2020-08-27 +idempotency-key: 69907c1a-8dc3-49ff-84f5-1a204175abc7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH&expand\[0]=payment_method&payment_method=pm_1PiS5zFY0qyl6XeWOFAWc2Of&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS5zFY0qyl6XeW7VSdzoRF", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS5zFY0qyl6XeWOFAWc2Of", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392215, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIqvVPiT5ngQ" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392215, + "client_secret" : "seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0005_get_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0005_get_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF.tail new file mode 100644 index 00000000..2768b613 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0005_get_v1_setup_intents_seti_1PiS5zFY0qyl6XeW7VSdzoRF.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS5zFY0qyl6XeW7VSdzoRF\?client_secret=seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UCnUNwBXagVLcT +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS5zFY0qyl6XeW7VSdzoRF", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS5zFY0qyl6XeWOFAWc2Of", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392215, + "client_secret" : "seti_1PiS5zFY0qyl6XeW7VSdzoRF_secret_QZbIfiXVzHsFTJBn2NGDy9RcHagbxnH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0006_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0006_get_v1_payment_methods.tail new file mode 100644 index 00000000..16b316d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0006_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIqvVPiT5ngQ&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VOooskNxbjf5RE +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS5zFY0qyl6XeWOFAWc2Of", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392215, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIqvVPiT5ngQ" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0007_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0007_post_create_ephemeral_key.tail new file mode 100644 index 00000000..c68e3929 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0007_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9h9r8iBwVaZfUpGXwk2FSraZPJsxa2h%2BbGlxFvqrqCglna2pmtBQUgKklfSorNT5r1S4Chh%2B3pO6N32yNjRpVVF7n3ViIhYD%2FZfSgP4SltMIWEn9zD%2Fgdxa2Y%2BygYKJ7LX1Y5gNEj%2BnAegKRjKjZz20PTqBvW3ON2h1ZKM%2BVulFTw9Sh02%2FFr%2Bc4ePfS4BpGkiNl8x9cJGsM5%2F7J%2FBUG5L7%2Fb6X9w%2FkcWQzgIyj2PRQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 955ea99c57c873eca00bf909fe98d9be +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:58 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGVhUzBuMjlOSmd3TU5QaG5TazZwd1h2ZVprTUZWVXE_00RXRmgFcc","customer":"cus_QZbIj58OUsyGBB"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0008_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0008_post_v1_payment_methods.tail new file mode 100644 index 00000000..04f46263 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0008_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zws5cUjpcOcFE9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:58 GMT +original-request: req_zws5cUjpcOcFE9 +stripe-version: 2020-08-27 +idempotency-key: c87edc5d-4ab3-4622-9b68-012069810c16 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS62FY0qyl6XeWBcEeTW9U", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392218, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0009_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0009_post_create_setup_intent.tail new file mode 100644 index 00000000..a7ccf252 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0009_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=SXPjgi%2BLbPeUla2DlVM65uopaKoxJm8UqyL%2B2xV5SMU3IFwuSM84PnAhzbz8N%2BtpPY6hcwoNZIYuG%2Fbh4Yt3XkD5Lxr2NJJlSooD7In9lCPoFf1D3VLBIlU%2BVwDWdT6KQF906eHfTKpYl65D60GK9gj1%2BKoIB1dip%2BGayZQoy42VJI34S8TAG0CrLqfePlmYRZ5KFPVTjJiiBsGCQFXVPZaazMcLP4KlVyHdTn5eZu8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1a170aaa211d38bdbe706858126ccbe9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:16:59 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS63FY0qyl6XeW1r1MusJ5","secret":"seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0010_get_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0010_get_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5.tail new file mode 100644 index 00000000..03387b90 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0010_get_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS63FY0qyl6XeW1r1MusJ5\?client_secret=seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vZVtcNsj9USRZJ +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:16:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS63FY0qyl6XeW1r1MusJ5", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392219, + "client_secret" : "seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0011_post_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0011_post_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5_confirm.tail new file mode 100644 index 00000000..18c49576 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0011_post_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS63FY0qyl6XeW1r1MusJ5\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gKwpr2rDI8VA75 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1554 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:00 GMT +original-request: req_gKwpr2rDI8VA75 +stripe-version: 2020-08-27 +idempotency-key: 0a2d8fdf-0075-4b61-b4da-ae527ed0899f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj&expand\[0]=payment_method&payment_method=pm_1PiS62FY0qyl6XeWBcEeTW9U&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS63FY0qyl6XeW1r1MusJ5", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS62FY0qyl6XeWBcEeTW9U", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392218, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIj58OUsyGBB" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392219, + "client_secret" : "seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0012_get_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0012_get_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5.tail new file mode 100644 index 00000000..537b2cc3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0012_get_v1_setup_intents_seti_1PiS63FY0qyl6XeW1r1MusJ5.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS63FY0qyl6XeW1r1MusJ5\?client_secret=seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RR4vpOOPiXK00z +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS63FY0qyl6XeW1r1MusJ5", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS62FY0qyl6XeWBcEeTW9U", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392219, + "client_secret" : "seti_1PiS63FY0qyl6XeW1r1MusJ5_secret_QZbI351apHACn1H5sn2R82kWPyer6zj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0013_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0013_get_v1_payment_methods.tail new file mode 100644 index 00000000..71f8795e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0013_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIj58OUsyGBB&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_w9iOTWtdCZRLGW +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS62FY0qyl6XeWBcEeTW9U", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392218, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIj58OUsyGBB" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0014_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0014_post_create_ephemeral_key.tail new file mode 100644 index 00000000..2969d507 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0014_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=cTQC3XcM490M43gElt9wmCykazXKyC0rmwhowXbf4cbgFANoJXCsEL44SZ%2BtnF%2BsnqxFcdawKIhKOY7yhzUZW4YpZWF1NK3KIjyM%2FXy5viVr7Vf2JYRVuMzHaja%2BSv56q6IXx0OMbgMRVtWecg06OeKCMWszup8EB5wDegxCfNvde%2BsTdx6QRPb1dbDnPWAncnHYVPGpl6GySdyDSTPMLJ9kf%2BvyX3FnK2%2FEDXMDSN0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8e0ae71e3bb211b4631dd7d935527f10 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGhmenhCZHdZTXBSWE9IUHhGaGZLcjJvTVlaU2lHUDA_00SXpp8yGF","customer":"cus_QZbIEKfdN1L1yp"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0015_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0015_post_v1_payment_methods.tail new file mode 100644 index 00000000..b43dcdf1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0015_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Tz5QRts4belQCX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:02 GMT +original-request: req_Tz5QRts4belQCX +stripe-version: 2020-08-27 +idempotency-key: 4552fb58-c8ac-47df-83ab-d16a1cd45329 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS66FY0qyl6XeWrxu5wNP3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392222, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0016_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0016_post_create_setup_intent.tail new file mode 100644 index 00000000..f99f0f08 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0016_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=y%2FftW8Io4tv5lNhhgdNDZd%2BURGsq3c8mCEL%2Fc2oNuAQShNWqSMXzlo0CzccXB%2FS%2F%2BmwXMMcn89%2FcGYhIASfkWffiE%2FiR0V0esHX017bsOlsA2NeLdH6rWM044%2FNNM%2Fx835hiXp8rVjgLBr8Mv8cwPm0XC07Z620Fj1qlht%2FzXhRUSMaEq8JRmKqPXjk4ecppuU9uEmG5xn7xjXqm1yH31%2BocX9xcVbFzdOASmri%2Fy2g%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0cfa6a0a7333c87d52688223a6595508 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:02 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS66FY0qyl6XeW9NpSVdmL","secret":"seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0017_get_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0017_get_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL.tail new file mode 100644 index 00000000..d3f4be2e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0017_get_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS66FY0qyl6XeW9NpSVdmL\?client_secret=seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aqzvTEu6kVLkUR +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS66FY0qyl6XeW9NpSVdmL", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392222, + "client_secret" : "seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0018_post_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0018_post_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL_confirm.tail new file mode 100644 index 00000000..6ac8fe62 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0018_post_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS66FY0qyl6XeW9NpSVdmL\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ji0R1CFEd8zy1r +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1554 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:03 GMT +original-request: req_Ji0R1CFEd8zy1r +stripe-version: 2020-08-27 +idempotency-key: ee5d34f7-527d-4e5d-bae7-9fe116851b62 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN&expand\[0]=payment_method&payment_method=pm_1PiS66FY0qyl6XeWrxu5wNP3&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS66FY0qyl6XeW9NpSVdmL", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS66FY0qyl6XeWrxu5wNP3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392222, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIEKfdN1L1yp" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392222, + "client_secret" : "seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0019_get_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0019_get_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL.tail new file mode 100644 index 00000000..e963cdea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0019_get_v1_setup_intents_seti_1PiS66FY0qyl6XeW9NpSVdmL.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS66FY0qyl6XeW9NpSVdmL\?client_secret=seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PIC7zMZ79St1p1 +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS66FY0qyl6XeW9NpSVdmL", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS66FY0qyl6XeWrxu5wNP3", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392222, + "client_secret" : "seti_1PiS66FY0qyl6XeW9NpSVdmL_secret_QZbIehdVDZcF10UqUbsPYJECRmvBEJN", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0020_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0020_get_v1_payment_methods.tail new file mode 100644 index 00000000..90cd5f77 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentcsc/0020_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIEKfdN1L1yp&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hJeQwa3vmWWl96 +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS66FY0qyl6XeWrxu5wNP3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392222, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIEKfdN1L1yp" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..aee19608 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Q%2B9GTVRYi2XUZSGepK8a9i%2Bgs%2FvtekP8vgVx98af5DDWzOHl2F4oPL2G4HixtOHFMtDKtVarFMv29uKPq7I%2BQm%2Bpvrd7p%2FyOjSnT7jPdYAvR281MIDHTj4eQFimJv%2BctR0qqzoNqw5umdp%2Fzh4SV3JMLwwJtqLxTTmAMjWGivCNB7GYNtbhzJgnFQAky8Phh4cury9NU4qH225fMBxvOvrwvO%2FBL5c5VDkrz2gAeQNs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a1725a5c12e377c9c951cfb344f239e2 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:04 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHpQNlIwMmJVdzhYRGRiMk4xOHZoNXpVWHBIMUxqNzU_00JV8y3GWN","customer":"cus_QZbIdkKI3LL35n"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..c2e9f6eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zdXEIJNOHCqz9N +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:05 GMT +original-request: req_zdXEIJNOHCqz9N +stripe-version: 2020-08-27 +idempotency-key: 4842d2b0-fef5-4325-a22f-e0e73789402b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS69FY0qyl6XeWo3hvVbIu", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392225, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0002_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0002_post_create_setup_intent.tail new file mode 100644 index 00000000..56789dfd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0002_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=yufIT8%2BQbqBN0W8A4mzofEM2fIRkGajEA6VcuDrlYBYmVKno7QAsH6%2BDgF01I2D0f2BPWepjPQI3sDhoszwi5%2Bhk7clXJonao0RFcDPoWtmOFH3siExUDLLbBT8vQEhO8hSjSla21GdfQ52gBPZ1DJLcJjaTTjrCrZze8zG1HXvn7fMTvHNNHpQaTC9c17Jidn9SnNrbJrqF6%2Bjy8t%2FsYxMeD6cneUvDACNXuxgftdw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8c04bbaec4916a7798d6adc8a88499f5;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS69FY0qyl6XeWf9hJqA5J","secret":"seti_1PiS69FY0qyl6XeWf9hJqA5J_secret_QZbIPLfM7vIARYlrSZUthrw0Dq0BpgZ","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0003_get_v1_setup_intents_seti_1PiS69FY0qyl6XeWf9hJqA5J.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0003_get_v1_setup_intents_seti_1PiS69FY0qyl6XeWf9hJqA5J.tail new file mode 100644 index 00000000..822c8958 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0003_get_v1_setup_intents_seti_1PiS69FY0qyl6XeWf9hJqA5J.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS69FY0qyl6XeWf9hJqA5J\?client_secret=seti_1PiS69FY0qyl6XeWf9hJqA5J_secret_QZbIPLfM7vIARYlrSZUthrw0Dq0BpgZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EcUvhfPNsSi8JC +Content-Length: 1553 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS69FY0qyl6XeWf9hJqA5J", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS69FY0qyl6XeWo3hvVbIu", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392225, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIdkKI3LL35n" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392225, + "client_secret" : "seti_1PiS69FY0qyl6XeWf9hJqA5J_secret_QZbIPLfM7vIARYlrSZUthrw0Dq0BpgZ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0004_get_v1_setup_intents_seti_1PiS69FY0qyl6XeWf9hJqA5J.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0004_get_v1_setup_intents_seti_1PiS69FY0qyl6XeWf9hJqA5J.tail new file mode 100644 index 00000000..dd864033 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0004_get_v1_setup_intents_seti_1PiS69FY0qyl6XeWf9hJqA5J.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS69FY0qyl6XeWf9hJqA5J\?client_secret=seti_1PiS69FY0qyl6XeWf9hJqA5J_secret_QZbIPLfM7vIARYlrSZUthrw0Dq0BpgZ$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XMWtPinwlA6DtR +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS69FY0qyl6XeWf9hJqA5J", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS69FY0qyl6XeWo3hvVbIu", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392225, + "client_secret" : "seti_1PiS69FY0qyl6XeWf9hJqA5J_secret_QZbIPLfM7vIARYlrSZUthrw0Dq0BpgZ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..6059b5fc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIdkKI3LL35n&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HrtCtBi9uwvlrb +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS69FY0qyl6XeWo3hvVbIu", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392225, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIdkKI3LL35n" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..7f8fc486 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8i9KJzYlt%2FERnaYKgtRsi0HC3Tsfg6RXSdaR4%2BqE5Ic9EihdGFqRB2AGULDzdaUHbSVOiFY0G3PSyhH13MKNjIVC3ZeIxacwlR4HTTDDMqWkGAjIhlOSTyHItA4ZS%2F4mynXZaS%2FkeD%2BSdBJtmUuI0leYsNiOncu%2BlTNFVQBehiCvdStfsKx%2FfHVV%2BAWKeN5Xz%2BayPZUeEGo3UykTVuEYoDGPndQYgnSILU1yPsfEofU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 438aca7205383c288d25c69950647ebd +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFV5WGJEWkZTUVJtY0t6cExBZkM0bm1UOUVhdk14RDY_00I8AVf7qH","customer":"cus_QZbIp0JQBHG5Y0"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..748e70a6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_D0qCRk3RRua96J +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:08 GMT +original-request: req_D0qCRk3RRua96J +stripe-version: 2020-08-27 +idempotency-key: d2526e87-4ff0-4473-8266-f37661259752 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS6CFY0qyl6XeW8CW5tqeA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392228, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0008_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0008_post_create_setup_intent.tail new file mode 100644 index 00000000..81c328f9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0008_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=TWSIp5ufrVLnniRtKWbNAc0fJC6WGPjReeOUXDvZh8KGLjAk0CimbYT7riZ0Sz3B8wonFgAERnnaGJYG%2FTgM8WgoO3zgbzl3oWW1EGQUCcbYybvq6bJk4enI4xPloq1%2FxfpjaJnn4LBJ%2Fvd50nMJf%2B4Olh9oU0W8drtnuWY6%2Fk7o8Kz8%2FRI19TkcY5zQ7Kc5lI1qvx%2BfN2emnq7EWHA3yKKHNoP9j7a%2FUVuzQnrMJPg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cb88cfa0f22b7aa8246289a933f7fa8b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS6CFY0qyl6XeWwl1fntB3","secret":"seti_1PiS6CFY0qyl6XeWwl1fntB3_secret_QZbIYHZcMriVMUxmrRVuablfufLdUtb","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0009_get_v1_setup_intents_seti_1PiS6CFY0qyl6XeWwl1fntB3.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0009_get_v1_setup_intents_seti_1PiS6CFY0qyl6XeWwl1fntB3.tail new file mode 100644 index 00000000..7adc6a20 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0009_get_v1_setup_intents_seti_1PiS6CFY0qyl6XeWwl1fntB3.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6CFY0qyl6XeWwl1fntB3\?client_secret=seti_1PiS6CFY0qyl6XeWwl1fntB3_secret_QZbIYHZcMriVMUxmrRVuablfufLdUtb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_O33yg6c2zWW2Tg +Content-Length: 1554 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6CFY0qyl6XeWwl1fntB3", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS6CFY0qyl6XeW8CW5tqeA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392228, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIp0JQBHG5Y0" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392228, + "client_secret" : "seti_1PiS6CFY0qyl6XeWwl1fntB3_secret_QZbIYHZcMriVMUxmrRVuablfufLdUtb", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0010_get_v1_setup_intents_seti_1PiS6CFY0qyl6XeWwl1fntB3.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0010_get_v1_setup_intents_seti_1PiS6CFY0qyl6XeWwl1fntB3.tail new file mode 100644 index 00000000..bb22af9d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0010_get_v1_setup_intents_seti_1PiS6CFY0qyl6XeWwl1fntB3.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6CFY0qyl6XeWwl1fntB3\?client_secret=seti_1PiS6CFY0qyl6XeWwl1fntB3_secret_QZbIYHZcMriVMUxmrRVuablfufLdUtb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CXvlzBgYqsu40L +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6CFY0qyl6XeWwl1fntB3", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS6CFY0qyl6XeW8CW5tqeA", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392228, + "client_secret" : "seti_1PiS6CFY0qyl6XeWwl1fntB3_secret_QZbIYHZcMriVMUxmrRVuablfufLdUtb", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..884c45cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0011_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIp0JQBHG5Y0&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_U8fifAm5TkfqBP +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS6CFY0qyl6XeW8CW5tqeA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392228, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIp0JQBHG5Y0" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0012_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0012_post_create_ephemeral_key.tail new file mode 100644 index 00000000..bea3e798 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0012_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NoAsIUTS0vlkOgufXXPUByB5xjwRBGeC2QciT4M5tM3U3BUb1%2Bc8Z0so954O%2FMdusH1l0%2Fy1OUeNPQ%2FK2ssFvexTeqZY2eHtUNGxDJhN3Mpovc6f1Tp3eF3ZAXlVLdmqzVwzNRP7l9X9oA%2B1ZZ%2BYkUZOxSVBhKbsLlocs67HFaFmt4ASva5rI36WgpEbm07B3h6qfMlHocLig8nuSZTxxxtek%2F%2F%2FC2WPkI%2BlJYGfx4o%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 16eacd45b172800f610587018e27e5f5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDBPUENXemVRZzlDUmd3eHc3N29EZERGWWRVdllvVjU_00cPAFVHdH","customer":"cus_QZbIymnfYzEO61"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..8107a9ea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0013_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0St8zS8lJCJhHW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:11 GMT +original-request: req_0St8zS8lJCJhHW +stripe-version: 2020-08-27 +idempotency-key: 140a77f7-c236-4f42-8f58-a06ae36b344d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=limited&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS6FFY0qyl6XeWmBhLjPbS", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392231, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0014_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0014_post_create_setup_intent.tail new file mode 100644 index 00000000..7fef2d87 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0014_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=iJbJ0A0nANNyI1cUymJK%2FWta6yTAVjtp8r8qieyyzJMPzTbuvIkfmAYNCDqvUnnd2WSp9reoOF%2BQhSoTCSf5G26uLAkSwDSrpWF0ljI08JfDPz8olwqdL%2F%2BMDSKw2D7owJj1IhWtDHVjp456vI1AgN%2F4d%2FBNN9E587a9sE4eAYGmI806Oxy2FlWdWE%2F1uELGjtfAkuz3CEgPgFDr0mArrYH%2FBmCvY7aHaQVL1%2BqekxU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 030afe8adf6f84d02caffca141b21f9b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS6FFY0qyl6XeWeiWitQvy","secret":"seti_1PiS6FFY0qyl6XeWeiWitQvy_secret_QZbI2L6N6MfGECsQ1jzJHQnMyF2Zk8W","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0015_get_v1_setup_intents_seti_1PiS6FFY0qyl6XeWeiWitQvy.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0015_get_v1_setup_intents_seti_1PiS6FFY0qyl6XeWeiWitQvy.tail new file mode 100644 index 00000000..8fad4032 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0015_get_v1_setup_intents_seti_1PiS6FFY0qyl6XeWeiWitQvy.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6FFY0qyl6XeWeiWitQvy\?client_secret=seti_1PiS6FFY0qyl6XeWeiWitQvy_secret_QZbI2L6N6MfGECsQ1jzJHQnMyF2Zk8W&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Q4XKe9gtG30E2T +Content-Length: 1554 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6FFY0qyl6XeWeiWitQvy", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS6FFY0qyl6XeWmBhLjPbS", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392231, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIymnfYzEO61" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392231, + "client_secret" : "seti_1PiS6FFY0qyl6XeWeiWitQvy_secret_QZbI2L6N6MfGECsQ1jzJHQnMyF2Zk8W", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0016_get_v1_setup_intents_seti_1PiS6FFY0qyl6XeWeiWitQvy.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0016_get_v1_setup_intents_seti_1PiS6FFY0qyl6XeWeiWitQvy.tail new file mode 100644 index 00000000..aefc578c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0016_get_v1_setup_intents_seti_1PiS6FFY0qyl6XeWeiWitQvy.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6FFY0qyl6XeWeiWitQvy\?client_secret=seti_1PiS6FFY0qyl6XeWeiWitQvy_secret_QZbI2L6N6MfGECsQ1jzJHQnMyF2Zk8W$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Tr9xVTUYsdxDEk +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6FFY0qyl6XeWeiWitQvy", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS6FFY0qyl6XeWmBhLjPbS", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392231, + "client_secret" : "seti_1PiS6FFY0qyl6XeWeiWitQvy_secret_QZbI2L6N6MfGECsQ1jzJHQnMyF2Zk8W", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0017_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0017_get_v1_payment_methods.tail new file mode 100644 index 00000000..3ca7287d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIdeferredIntentssc/0017_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIymnfYzEO61&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NDXlbmcbJLa5gT +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS6FFY0qyl6XeWmBhLjPbS", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392231, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIymnfYzEO61" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..6915bb46 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=t0UJt6SGmxmdDXM6zx2pq914pun7aOWDKgI64fu4Ki4wqI4Xx6UHWO6kCYndAlXR1%2B3AX0UHfDNOCCdBotNIi%2BBeve%2FECdqI0KWMkwjVszAypJ8YoyjWHXs8koEW6dEs796hw1X4pgVsNXFdwCVkZ%2FVF%2BoNp7Hx13ZUZEMJUGOShIEAaM2uXVPj8WUwoxN0o5Z5BV7VC0MZeF9s%2BlS6sJKeK8i8WRnCAkzCBqm%2BcP%2FU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 03bad8b1aceca89205c0f649b09bdfb9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHJWYWM3NnR0MGZPakxOMHBIak5XVnFTNHI4ckxvZkY_00Jmt0Rmj8","customer":"cus_QZbIKU2oCnWeSj"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..f1c9fcbe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=QQmn8sr%2BHaH%2B3O6VVV6uf0mbFALJGlQ2QgSMWdKj1quR%2BPbcv6yh0H78CDSHu6V%2BFp1AXjZ%2BxGnbhtOBx%2Ftzn3Eo4%2BKopYwz4PmzN0Inpl%2BnzVdtBylULXixJKrlcTi9AFfzAad4UCop83C3DwYU13uiuDXfnbprjwOMFtL6TUeaDSsyT0uy2TmioinE73ifL8A8STXji6oeY4F5oAlsv96RGPXRABdR8%2Bw%2FV7I1zxg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 15ffffb525ec9790d3b45bfbd8f023d1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS6IFY0qyl6XeWWT82QhxK","secret":"seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0002_get_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0002_get_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK.tail new file mode 100644 index 00000000..f8cdfdd9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0002_get_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6IFY0qyl6XeWWT82QhxK\?client_secret=seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vwMJ0lAerazIbN +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6IFY0qyl6XeWWT82QhxK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392234, + "client_secret" : "seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0003_post_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0003_post_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK_confirm.tail new file mode 100644 index 00000000..0bcb05cc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0003_post_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6IFY0qyl6XeWWT82QhxK\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JOYXPf3b93OJsT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1553 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:15 GMT +original-request: req_JOYXPf3b93OJsT +stripe-version: 2020-08-27 +idempotency-key: 1378cafb-5954-474c-8de8-3e4a6ac00784 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=always&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS6IFY0qyl6XeWWT82QhxK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS6IFY0qyl6XeWcGSuGuqV", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392234, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIKU2oCnWeSj" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392234, + "client_secret" : "seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0004_get_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0004_get_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK.tail new file mode 100644 index 00000000..0567d3e0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0004_get_v1_setup_intents_seti_1PiS6IFY0qyl6XeWWT82QhxK.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6IFY0qyl6XeWWT82QhxK\?client_secret=seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qcj0nnzDibxnhq +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6IFY0qyl6XeWWT82QhxK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS6IFY0qyl6XeWcGSuGuqV", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392234, + "client_secret" : "seti_1PiS6IFY0qyl6XeWWT82QhxK_secret_QZbIgDnXgasjCjayOoXf3RPKv0kh4rp", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..691b1dd4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIKU2oCnWeSj&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IWJuXEY2lCTXVH +Content-Length: 1270 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS6IFY0qyl6XeWcGSuGuqV", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392234, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QZbIKU2oCnWeSj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..46dc3de2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=nZmi8S3%2FgEDwOIKqzMqt0nv8kPnp0Vg9UdbMSiiJrThKE47%2BA6%2BCPZEljR432Y3wApFRE5ee0ELr9V8KfNU5nrhzvTUo3iLXT1v6mb80rz53m8BEy%2Bbv5ovC57rqVSC1PLwLHKJLU3lBbbDG0J4Mr29Mbis4TJ3exWxE0VMfgZ7eN2zWHKA9meMVi0RnaJgyaSqugQjEJTqqCgFu9o6QQOeCCLjNZwDUX9OhLgnC69I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 370edecef7f1d5afb50f246068346015;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFZsS0tCN2U2SnRUNThDNEwyN1ZrR2d2YW04R2U4Q0I_00jxq3reGE","customer":"cus_QZbITJNmtTGph1"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0007_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0007_post_create_setup_intent.tail new file mode 100644 index 00000000..55e1fbc2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0007_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=HfGG7wdxokyOdWLrmr2st6amlNn9pIVyz5OPkV1bw7EpeSU%2F4Sj4N3zMSVPkyyhYxoOIn2vU1YPDWPe70dJzWGpfVk%2F%2BrwAHxT5knAZoO6a54QztqdSsdMfKytgO%2Fha1ibAiWNxBQUtcpDQWFQzGkZ5F6kuoPb1a%2Fmzk4tdi5NeZ6xwz1yOV11OC0Kq4ERXGMavsGr9ZnHAxjs7mDACXplB%2Fzr60O8dQhxI92L1OQEY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bd595d364b30e8408122a0ce7993e3bc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:17 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS6LFY0qyl6XeWfwqQkcpe","secret":"seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0008_get_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0008_get_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe.tail new file mode 100644 index 00000000..e6bf504c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0008_get_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6LFY0qyl6XeWfwqQkcpe\?client_secret=seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dox1fRLtHJzGRC +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6LFY0qyl6XeWfwqQkcpe", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392237, + "client_secret" : "seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0009_post_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0009_post_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe_confirm.tail new file mode 100644 index 00000000..0183b35f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0009_post_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6LFY0qyl6XeWfwqQkcpe\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AQ5LiWih4NL6jQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1554 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:18 GMT +original-request: req_AQ5LiWih4NL6jQ +stripe-version: 2020-08-27 +idempotency-key: 1182520a-05a8-4d34-9612-187f4beed51f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=limited&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS6LFY0qyl6XeWfwqQkcpe", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS6LFY0qyl6XeWmcqhoCtz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392238, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbITJNmtTGph1" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392237, + "client_secret" : "seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0010_get_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0010_get_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe.tail new file mode 100644 index 00000000..28827f50 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0010_get_v1_setup_intents_seti_1PiS6LFY0qyl6XeWfwqQkcpe.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6LFY0qyl6XeWfwqQkcpe\?client_secret=seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_brWS1gfWGbhex7 +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6LFY0qyl6XeWfwqQkcpe", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS6LFY0qyl6XeWmcqhoCtz", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392237, + "client_secret" : "seti_1PiS6LFY0qyl6XeWfwqQkcpe_secret_QZbIiQI2AU8DolNVRJXRWGc6PvkMvxU", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..a73099dd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0011_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbITJNmtTGph1&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vQ22Z1ujvK5dIi +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS6LFY0qyl6XeWmcqhoCtz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392238, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbITJNmtTGph1" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0012_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0012_post_create_ephemeral_key.tail new file mode 100644 index 00000000..a20fd9de --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0012_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=kO%2BSq2G6gMI0DrwOZMF3wDdsl6CzYLYZvM9U4UknB34078PQnzV0D6ETVb33xWwOF5tQIedHCxZCAFhb2FTIMI4mWTeRe4CiL3NrLv4kltkvd%2Bg3eXGoeklWP5z%2FUIl1K%2BaR0spzFuvfC5Zh2H0Hcy7HzHXIRsnM3LeN5GtoSQ9HO3%2BjTomcsMFcskGzbpQ4GZ5c80xUtwf2ImozA8nqVPSNSe0G3yUSw5sNNnkAKsg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 214fd94927bf04b9ca7413831eb74ceb +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:20 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHZRU00xbzR6Z3RBcDhIUGtwS3h2d05jU2VFN2NLdWQ_005NAuqxi5","customer":"cus_QZbIkjflHZnZhc"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0013_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0013_post_create_setup_intent.tail new file mode 100644 index 00000000..b57a21c3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0013_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=XSed9ox5Ain0G8DK%2B4KefZcO8gWe6bLTGPWOySWcd376HqJ4Ly18RTAz%2FENHcfa%2FMeVxV3LmaEc1HHXWcgQmRKIs%2F8gg6AlDpqLvH7Kq9xuMyraP3lZrdgyRmGqN%2Bgzh0t3SQRmH%2F5GiRUHi1A4oRpDH4%2Bte9ipmqaHV3Gs6q%2F5%2F%2BhUX%2Baj%2FObLI3y0ztybASkcwzC6iojxtB%2Fc29UJg4muxFw1fBsmRIMWnqqBbwNY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2e0bc779d7904de75010984984ec9275 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:17:20 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS6OFY0qyl6XeWeafPdyl1","secret":"seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0014_get_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0014_get_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1.tail new file mode 100644 index 00000000..d69b5a4e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0014_get_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6OFY0qyl6XeWeafPdyl1\?client_secret=seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_r2KRtXYOABwAte +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6OFY0qyl6XeWeafPdyl1", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392240, + "client_secret" : "seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0015_post_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0015_post_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1_confirm.tail new file mode 100644 index 00000000..1ecdc7ef --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0015_post_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6OFY0qyl6XeWeafPdyl1\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_e7ORdqibiEoYr9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1554 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:21 GMT +original-request: req_e7ORdqibiEoYr9 +stripe-version: 2020-08-27 +idempotency-key: 9d1abfcf-1032-4a86-a5c0-06261427d534 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=limited&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiS6OFY0qyl6XeWeafPdyl1", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS6OFY0qyl6XeWofcMqApX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392241, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIkjflHZnZhc" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392240, + "client_secret" : "seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0016_get_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0016_get_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1.tail new file mode 100644 index 00000000..4dbe1c0a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0016_get_v1_setup_intents_seti_1PiS6OFY0qyl6XeWeafPdyl1.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS6OFY0qyl6XeWeafPdyl1\?client_secret=seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JukkQRJjCZqRBQ +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:21 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS6OFY0qyl6XeWeafPdyl1", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS6OFY0qyl6XeWofcMqApX", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722392240, + "client_secret" : "seti_1PiS6OFY0qyl6XeWeafPdyl1_secret_QZbJ6QF6DY0CoJAnnBCUXrafg7TjbSX", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0017_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0017_get_v1_payment_methods.tail new file mode 100644 index 00000000..fb8d6866 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaySIintentFirstcsc/0017_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbIkjflHZnZhc&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LXMbLTJpMh2nDo +Content-Length: 1271 +Vary: Origin +Date: Wed, 31 Jul 2024 02:17:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS6OFY0qyl6XeWofcMqApX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392241, + "allow_redisplay" : "limited", + "type" : "card", + "customer" : "cus_QZbIkjflHZnZhc" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..edfc2cb4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Ugt3S8rBo0iOxzn%2B%2FVz4b9CYryNu2xPvbKDK0pD%2Fx6R8hL5H%2BVxhjJWUkVkV00%2Fj6SF2JCc%2BeRq8sE%2BMfalT9GPnvMvjxCUT7yXbn5VjfiwAlKqHtybb%2FXdP8GF0SYBawImQjIpoSCQz%2BcJRBWd6RpBXCGqOwfr%2BRLQzL24dTqE6hHuaDZeQHpEMjrqTfm1Sd%2F%2FfK4VFLqW0fLvdeBsodzFwhfbXm1eBFpxui2lzYmk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ac36552c1754e1f6f8f9baafa409c5f5;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:47 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGt0czJnVVVZaUZ0ekg3ZFFiM1NMYTFBRXNod3JhZDM_006AKl4PQW","customer":"cus_QaEVviOceX4bLh"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..7194774e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=FM9%2B9dcYUqUcAO6Fq8InZLHBFcLzZdcIyMVbP0rWBpWaY%2BYgKPlP0k6PbfBVAFe5lnQIJawYrtZqYDReg13IMFNwRPI1c%2F4yd9OUBnciY0Mceww3uXOr74sIKcCAZnRksb5nT3X%2BdN92KWxUJPVzQqS28%2F2OP1H7o8Xuewh5v0zoyTbVhqHhUtkUukUgPYudAR9jFa9Hy%2Fw1L0qGtPaT%2FAidjYxY1osziQUFKWuUdXM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e4c997ab7064db1e49a5a7945cb60b13 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:47 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pj42RFY0qyl6XeW1G4psghK","secret":"pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0002_get_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0002_get_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK.tail new file mode 100644 index 00000000..85db77aa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0002_get_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42RFY0qyl6XeW1G4psghK\?client_secret=pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aHVX4zwdoHJOIL +Content-Length: 898 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy", + "id" : "pi_3Pj42RFY0qyl6XeW1G4psghK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538067, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0003_post_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0003_post_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK_confirm.tail new file mode 100644 index 00000000..1469795c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0003_post_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42RFY0qyl6XeW1G4psghK\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gdh8Sd6Wb3ZhT3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1918 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:49 GMT +original-request: req_gdh8Sd6Wb3ZhT3 +stripe-version: 2020-08-27 +idempotency-key: d4df975d-b033-4b48-be16-ed2f90184005 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=always&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj42SFY0qyl6XeWv9Wlojqx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538068, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVviOceX4bLh" + }, + "client_secret" : "pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy", + "id" : "pi_3Pj42RFY0qyl6XeW1G4psghK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538067, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0004_get_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0004_get_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK.tail new file mode 100644 index 00000000..cb73a65d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0004_get_v1_payment_intents_pi_3Pj42RFY0qyl6XeW1G4psghK.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42RFY0qyl6XeW1G4psghK\?client_secret=pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PFCHVnAnv854za +Content-Length: 909 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pj42SFY0qyl6XeWv9Wlojqx", + "client_secret" : "pi_3Pj42RFY0qyl6XeW1G4psghK_secret_FbAp2w0xB2sDTpMKgJtYlOkCy", + "id" : "pi_3Pj42RFY0qyl6XeW1G4psghK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538067, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..14489b69 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QaEVviOceX4bLh&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dQKGYHyHPE5loP +Content-Length: 1270 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:50 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1Pj42SFY0qyl6XeWv9Wlojqx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538068, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVviOceX4bLh" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..3c72b7ce --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=UyLUnozKnRbgUbDrVBTveBI5HdkWg19g4RM1p07YUm5YRtxlcCzYa14x87KO2ZQnh5AEZwGjDOXMaJYnAEEt0qKMwxSGrwKy1tTb2rnG01IokwH7UbvF60%2FFE4PaVXyg4IdGYFq80Q2hYexUzqaZ3UPAuYKrQoelb7TuKVj0bdKmFgjnuKjcwpX1HMNHUAkV92ro5QznKsOWR6QWuLZkftphPIVNCOAckE1vAql75ro%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0d164e4c575b3f3ba6b4179810713d7a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFpTcGZRclJFMUg2QmZUZ25JQmRaRE9ZSExSdlhJRFo_00CA3YINbW","customer":"cus_QaEV92ecjYwOHo"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..1687837b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_P7P074C9HuByX3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:51 GMT +original-request: req_P7P074C9HuByX3 +stripe-version: 2020-08-27 +idempotency-key: fbb65d90-c6b6-4b17-89de-b964428cf58f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1Pj42UFY0qyl6XeWg1B5jtav", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538071, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..d693a51a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=RCORFc0dx08aBUPCZVEw0pWXrhWTQzoE6Jb%2BS5hPham2JRgakjr%2FBmjbljsHzITVCnz2U3LUBAkG0%2B9LdJvBnOPRkRbfI2uzA4bCqvvhqZXkiVL7ciF6JjatK0Q1aPdhN4ldxsZfpEvKFrv5uhvDKYbr0jgG4V9zZw%2BmgMfzmvIIAqALoF7%2B5m0ORG5AZFn%2F6BzPWdcERV7hV4GfsWYUxmTRNcFRTvbzQmvDelc6H%2FU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8898fb5b999177843c97f31324fef0b4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:51 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pj42VFY0qyl6XeW1S6Bvk4O","secret":"pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0009_get_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0009_get_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O.tail new file mode 100644 index 00000000..5864fbf8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0009_get_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42VFY0qyl6XeW1S6Bvk4O\?client_secret=pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_txasMMzVJlYgCk +Content-Length: 898 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3", + "id" : "pi_3Pj42VFY0qyl6XeW1S6Bvk4O", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538071, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0010_post_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0010_post_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O_confirm.tail new file mode 100644 index 00000000..f40a3f9f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0010_post_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42VFY0qyl6XeW1S6Bvk4O\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tRrgLtJnhVyxfP +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1918 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:52 GMT +original-request: req_tRrgLtJnhVyxfP +stripe-version: 2020-08-27 +idempotency-key: 64784869-6ac9-41e4-990d-d3949b815f16 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3&expand\[0]=payment_method&payment_method=pm_1Pj42UFY0qyl6XeWg1B5jtav&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj42UFY0qyl6XeWg1B5jtav", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538071, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEV92ecjYwOHo" + }, + "client_secret" : "pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3", + "id" : "pi_3Pj42VFY0qyl6XeW1S6Bvk4O", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538071, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0011_get_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0011_get_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O.tail new file mode 100644 index 00000000..7eea5300 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0011_get_v1_payment_intents_pi_3Pj42VFY0qyl6XeW1S6Bvk4O.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42VFY0qyl6XeW1S6Bvk4O\?client_secret=pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2MqRvXSma6aUC1 +Content-Length: 909 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pj42UFY0qyl6XeWg1B5jtav", + "client_secret" : "pi_3Pj42VFY0qyl6XeW1S6Bvk4O_secret_jtsldGoKqf0EumkcMzowXsyl3", + "id" : "pi_3Pj42VFY0qyl6XeW1S6Bvk4O", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538071, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0012_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0012_get_v1_payment_methods.tail new file mode 100644 index 00000000..2bc7d80b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0012_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QaEV92ecjYwOHo&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GirD9hfNi4wVwY +Content-Length: 1270 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1Pj42UFY0qyl6XeWg1B5jtav", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538071, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEV92ecjYwOHo" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0013_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0013_post_create_ephemeral_key.tail new file mode 100644 index 00000000..3ba8d4c4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0013_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=rHfGRH1jjGJojQhI3iL0hjTKHYUKs2FglHlglwdcnRyLgEgzCXowXuXndN5wVu9C1nfJr77aRjvZSJQ8JnxUxshXhpuRMGMXD%2BsNexoHynAKE38ASK9AJtMQNbsCTFs6rqz0YqXqY0knW%2BTnN7pbkiEkgAQIkH10rT214B8Mxk5pLC5T%2FyGLoEI0pnlmGie0imMzQiezkv62duM8XCMtJydPsSYmg36sqDtHni%2Ff%2BBE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 008729b20d7af1e387e60d3b343998e5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:54 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFJ3UHM4aDBLNFJId2x5YnhDUkhsVk9nRXNMMjRUcWI_00UnCjVyEt","customer":"cus_QaEVI6sAy7qc5j"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0014_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0014_post_v1_payment_methods.tail new file mode 100644 index 00000000..6d6bfa0d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0014_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uy2MiITqNmY8Dr +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:54 GMT +original-request: req_uy2MiITqNmY8Dr +stripe-version: 2020-08-27 +idempotency-key: d0f0dae2-0599-473b-8a4a-5645efdc0674 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1Pj42YFY0qyl6XeWEtXIIojJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538074, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0015_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0015_post_create_payment_intent.tail new file mode 100644 index 00000000..3262b74f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0015_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=tbUxpdO9uy0e4PUU%2FAKg1pSq1RfnOupeWPhnZhmJXQC7%2FjlnSiNBzC7uElKCyKcB%2BI7HEfLJo5eowBGTKoZcxAJGHpsphDoIC6573s6My%2B3qeyvn4qhEProHoD%2F76hfwydiLH8tEyq%2FYQpX9Ttqjgx2Ozx6us3uVpkp5SMaOBVObWHEW2Sr8%2FhZnclyUivR8rgJWx5ffvZIp%2F9osKTX5uoCmvHts9wW%2FveMRc8KBUaA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 48a84b3f0dd841f91f98c02bfa86ef64 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:54 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pj42YFY0qyl6XeW0TuZwW2K","secret":"pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0016_get_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0016_get_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K.tail new file mode 100644 index 00000000..3258e72d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0016_get_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42YFY0qyl6XeW0TuZwW2K\?client_secret=pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vEN0CC5Sy8Wj7E +Content-Length: 898 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:55 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4", + "id" : "pi_3Pj42YFY0qyl6XeW0TuZwW2K", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538074, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0017_post_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0017_post_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K_confirm.tail new file mode 100644 index 00000000..bcdcb135 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0017_post_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42YFY0qyl6XeW0TuZwW2K\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_llgpgKOVhRJWCs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1918 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:56 GMT +original-request: req_llgpgKOVhRJWCs +stripe-version: 2020-08-27 +idempotency-key: 92bdeee0-ab55-4b78-84fe-0d777bd469bc +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4&expand\[0]=payment_method&payment_method=pm_1Pj42YFY0qyl6XeWEtXIIojJ&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj42YFY0qyl6XeWEtXIIojJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538074, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVI6sAy7qc5j" + }, + "client_secret" : "pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4", + "id" : "pi_3Pj42YFY0qyl6XeW0TuZwW2K", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538074, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0018_get_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0018_get_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K.tail new file mode 100644 index 00000000..bff4cce9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0018_get_v1_payment_intents_pi_3Pj42YFY0qyl6XeW0TuZwW2K.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj42YFY0qyl6XeW0TuZwW2K\?client_secret=pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RTeeWBFqKP8BQf +Content-Length: 909 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pj42YFY0qyl6XeWEtXIIojJ", + "client_secret" : "pi_3Pj42YFY0qyl6XeW0TuZwW2K_secret_P4R0XW4ytTXE0EbDiHiiaGuN4", + "id" : "pi_3Pj42YFY0qyl6XeW0TuZwW2K", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722538074, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0019_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0019_get_v1_payment_methods.tail new file mode 100644 index 00000000..c2a50752 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0019_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QaEVI6sAy7qc5j&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cO8hl9ZM5R2Iu8 +Content-Length: 1270 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1Pj42YFY0qyl6XeWEtXIIojJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538074, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVI6sAy7qc5j" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0020_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0020_post_create_ephemeral_key.tail new file mode 100644 index 00000000..1fe2a29f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0020_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=G9vWWHANqAouzMQP3qF8UdtFh%2BcsqiZAjwx0bTYDNDPL2aWN2%2BAILa%2Fxg1W92U5w2mTJ227d5ABl9JnxM80k%2Fadn0OaT%2Fhswxj%2FcRJyQiv%2FFvHkmIbz1eznIuCveKnAcaas%2FpKWFkqoKs%2B5NkWZfEBprFrKNIa3YL%2BF4JLIsC5ZlXGBEfMVLSHZdigK8aYFC%2BDAuK%2FPUtFQEWw4uAx4WH4skKPRFEJHUWHoyiooqyHM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bee703207137b2f4ef1ba5d4aac81e2b;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:57 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLE56Y3RuNnpGTE5XaG9DTUl3eXNSZFJBWmNyeHcwOFI_00V3eHXegC","customer":"cus_QaEVwA8wzuFdiE"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0021_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0021_post_create_setup_intent.tail new file mode 100644 index 00000000..19df3baf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0021_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9iKsBcaGRLFPx0bWOt7IDKO3ENbTemQmQR%2BsGjvBb9A8xOwV1E%2BE5%2FL3qbg7vfp8IcBAlQcP%2FBUCwokSDALcqqq11bGr2ecTbeyO7C5xgG3WWQla5hpN4GICoMnYH8ZV3i3X88Ril%2BBoHbNNO%2BsFSpAjZjDBkoqohSB8xwIplWykB%2FcPl%2FLuWib7ICNM%2BL41ftaNbDPm%2BsomoYmwB1oz%2FYM95iDetfsyqf4XA1poeS0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 72458ad10f5340f3f66b4efd16424ead +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:47:57 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Pj42bFY0qyl6XeWW7izP2aM","secret":"seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0022_get_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0022_get_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM.tail new file mode 100644 index 00000000..1c77897f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0022_get_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42bFY0qyl6XeWW7izP2aM\?client_secret=seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PdtERv2DpLFtNh +Content-Length: 533 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Pj42bFY0qyl6XeWW7izP2aM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538077, + "client_secret" : "seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0023_post_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0023_post_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM_confirm.tail new file mode 100644 index 00000000..5676c1be --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0023_post_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42bFY0qyl6XeWW7izP2aM\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9gmRZpfGcRgNOs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1553 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:59 GMT +original-request: req_9gmRZpfGcRgNOs +stripe-version: 2020-08-27 +idempotency-key: 50a5890d-82c5-475d-8e98-bc84efd57110 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=always&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1Pj42bFY0qyl6XeWW7izP2aM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj42cFY0qyl6XeWvYaY10zp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538078, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVwA8wzuFdiE" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538077, + "client_secret" : "seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0024_get_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0024_get_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM.tail new file mode 100644 index 00000000..10d1b0f0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0024_get_v1_setup_intents_seti_1Pj42bFY0qyl6XeWW7izP2aM.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42bFY0qyl6XeWW7izP2aM\?client_secret=seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1rGssjEraBPaz9 +Content-Length: 544 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Pj42bFY0qyl6XeWW7izP2aM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1Pj42cFY0qyl6XeWvYaY10zp", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538077, + "client_secret" : "seti_1Pj42bFY0qyl6XeWW7izP2aM_secret_QaEVeFxKtxFyjCJMX0X25GfluWrOBAK", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0025_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0025_get_v1_payment_methods.tail new file mode 100644 index 00000000..3bd55a27 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0025_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QaEVwA8wzuFdiE&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_h1xH0fmVRuhHDB +Content-Length: 1270 +Vary: Origin +Date: Thu, 01 Aug 2024 18:47:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1Pj42cFY0qyl6XeWvYaY10zp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538078, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVwA8wzuFdiE" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0026_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0026_post_create_ephemeral_key.tail new file mode 100644 index 00000000..16702589 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0026_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Yi31WmQ0UOLR4mqxZh40lEEtaUDdEbRrcbeNakMyqzsJ97PZucYSRAEPlWHzwcEjem75hXsitbPQmuII9Ah3LA3x3ilDdUlmadEbkF%2Fu9xKMDZjwQntdiXxQfHUpLcZhBwXnwB1XFtxWX3amvsGSwj3vEJbiye5NsMlB8VrzadOzb5NvUjc%2Firetuw83fAoJ5r%2FIX3RuaIgTlscYyTqbFUw7cW%2BzRVmdcCZBvHIuiMI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0bebaeba1e066e7367e43d0b9ea641a3 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:48:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLEJITUdaZWNFckNFZE1EQzQyaktqRWRmdjhPanJDSFU_00vvFrLd3U","customer":"cus_QaEVJ8h8C1Uhyk"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..7a0a429a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0027_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7I0yCmdVcelkil +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:00 GMT +original-request: req_7I0yCmdVcelkil +stripe-version: 2020-08-27 +idempotency-key: 32b45eec-6dd4-4aba-a585-484c7824833a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1Pj42eFY0qyl6XeWom4CnqIW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538080, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0028_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0028_post_create_setup_intent.tail new file mode 100644 index 00000000..b0808891 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0028_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2BUy9A3iobOVsqVUsCLosnVxgsBCDazshXpicI7N2v3F9j5%2BSMJhK5MzrVBAf4tYwS%2F73guMCFZJjv0tio6jXZfSxYoh8%2B0xKnEDZ5XxUPVoYAJeijyYKZkx9Z7IIhINT5MSirIIzxBO0cDheUJcDdVA4kWdAqDv%2BekZ7yidWQ8MM2K%2FiCqOvgSkMEGZH%2BCrxsWVJ8sIJNuUhr8R%2B7m1grg2w3F4BC2WlL9SMBOonJMs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1e05b1b5d67b32d0c0be145f43b7cfad +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:48:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Pj42fFY0qyl6XeWpJZpd8qv","secret":"seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0029_get_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0029_get_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv.tail new file mode 100644 index 00000000..35bb228d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0029_get_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42fFY0qyl6XeWpJZpd8qv\?client_secret=seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VdZoo4JGyLr2b7 +Content-Length: 533 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Pj42fFY0qyl6XeWpJZpd8qv", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538081, + "client_secret" : "seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0030_post_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0030_post_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv_confirm.tail new file mode 100644 index 00000000..d2daa85f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0030_post_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42fFY0qyl6XeWpJZpd8qv\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_e3K80wkCa5UuYJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1553 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:02 GMT +original-request: req_e3K80wkCa5UuYJ +stripe-version: 2020-08-27 +idempotency-key: bfb0dfa6-93c2-4b90-9962-5c6e227ab047 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH&expand\[0]=payment_method&payment_method=pm_1Pj42eFY0qyl6XeWom4CnqIW&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1Pj42fFY0qyl6XeWpJZpd8qv", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj42eFY0qyl6XeWom4CnqIW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538080, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVJ8h8C1Uhyk" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538081, + "client_secret" : "seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0031_get_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0031_get_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv.tail new file mode 100644 index 00000000..22be3bfd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0031_get_v1_setup_intents_seti_1Pj42fFY0qyl6XeWpJZpd8qv.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42fFY0qyl6XeWpJZpd8qv\?client_secret=seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jxGFgS8AX26HTa +Content-Length: 544 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Pj42fFY0qyl6XeWpJZpd8qv", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1Pj42eFY0qyl6XeWom4CnqIW", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538081, + "client_secret" : "seti_1Pj42fFY0qyl6XeWpJZpd8qv_secret_QaEVD6z8VwzmLxbAVTiwJhIDqbXuZQH", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0032_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0032_get_v1_payment_methods.tail new file mode 100644 index 00000000..3460f164 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0032_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QaEVJ8h8C1Uhyk&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_S3kjLsDJqjxOsf +Content-Length: 1270 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1Pj42eFY0qyl6XeWom4CnqIW", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538080, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVJ8h8C1Uhyk" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0033_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0033_post_create_ephemeral_key.tail new file mode 100644 index 00000000..c732ba24 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0033_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=F6rN0AQJFFw8n9RLG6SfBePdXkprLSuLSYGZeCpl%2B7oMNV%2FIfCggeqLu2LAvoQN6l5SE0mANe86FCySQvDUa98OnZLwX5e%2FeJ2Sz%2BEtzp8XP1cLDVs68KpdIS2bwLy0xQrosjFXk%2FNAXJZZmyr0ynuARLrHck2mduTcbxr4Y5%2FUxsGgX3k%2BcX595ZG683on31s%2FxNy6GDkNdMo5AK55z8dEg1jOZZdGhncQQN%2FqcmUU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e2afbba2b545a5b2232b44b488e8bfff +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:48:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFBPeGhqekh6UWtQY3FndGNHY0t3TW1oZVRRZk9BdE8_00hGri0S4O","customer":"cus_QaEVikrjZckFeF"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0034_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0034_post_v1_payment_methods.tail new file mode 100644 index 00000000..b742df32 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0034_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PXOygK82dRR2SU +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 930 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:04 GMT +original-request: req_PXOygK82dRR2SU +stripe-version: 2020-08-27 +idempotency-key: 4b251ccc-08d5-46cc-835c-6332618188ef +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=always&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1Pj42hFY0qyl6XeWKnBe0nzh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538084, + "allow_redisplay" : "always", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0035_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0035_post_create_setup_intent.tail new file mode 100644 index 00000000..8c2e319f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0035_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9ZCMMK9POg0yQiqCASwhxMIy%2FEtjmhYB34juOF2Hzjpyg2Xk501Yxjk3nUO4BsiffWlSHMCecKpiivzLYDu%2BuxqQbrR7edDTrlmE9%2FV9VS0UAs7AhRbzzj%2BUyHGbPl74fRFCexovOkM3gjeEtr1ptOHqCFU3yY3N2XOwOkHLSlQ%2Bt727%2FtIG0lrVMwEjasBsQ7cGq2FHJiXYhq%2BIY2PBLvxF1AwlhESXNFOmqi8yUiw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 42b816457ad1f56682f4617e652c6708 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 01 Aug 2024 18:48:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Pj42iFY0qyl6XeWO5zkclKJ","secret":"seti_1Pj42iFY0qyl6XeWO5zkclKJ_secret_QaEVx5J1kidRCQrtFaXUL8xv8kBvLOb","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0036_get_v1_setup_intents_seti_1Pj42iFY0qyl6XeWO5zkclKJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0036_get_v1_setup_intents_seti_1Pj42iFY0qyl6XeWO5zkclKJ.tail new file mode 100644 index 00000000..b221a752 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0036_get_v1_setup_intents_seti_1Pj42iFY0qyl6XeWO5zkclKJ.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42iFY0qyl6XeWO5zkclKJ\?client_secret=seti_1Pj42iFY0qyl6XeWO5zkclKJ_secret_QaEVx5J1kidRCQrtFaXUL8xv8kBvLOb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vWRO0dg4I3yMjM +Content-Length: 1553 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Pj42iFY0qyl6XeWO5zkclKJ", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj42hFY0qyl6XeWKnBe0nzh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538084, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVikrjZckFeF" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538084, + "client_secret" : "seti_1Pj42iFY0qyl6XeWO5zkclKJ_secret_QaEVx5J1kidRCQrtFaXUL8xv8kBvLOb", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0037_get_v1_setup_intents_seti_1Pj42iFY0qyl6XeWO5zkclKJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0037_get_v1_setup_intents_seti_1Pj42iFY0qyl6XeWO5zkclKJ.tail new file mode 100644 index 00000000..e746c007 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0037_get_v1_setup_intents_seti_1Pj42iFY0qyl6XeWO5zkclKJ.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Pj42iFY0qyl6XeWO5zkclKJ\?client_secret=seti_1Pj42iFY0qyl6XeWO5zkclKJ_secret_QaEVx5J1kidRCQrtFaXUL8xv8kBvLOb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nXpUotsqbInwxf +Content-Length: 544 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Pj42iFY0qyl6XeWO5zkclKJ", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1Pj42hFY0qyl6XeWKnBe0nzh", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722538084, + "client_secret" : "seti_1Pj42iFY0qyl6XeWO5zkclKJ_secret_QaEVx5J1kidRCQrtFaXUL8xv8kBvLOb", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0038_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0038_get_v1_payment_methods.tail new file mode 100644 index 00000000..cb7f69ca --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplayallowOverride/0038_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QaEVikrjZckFeF&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0phnP8jQn7Dxun +Content-Length: 1270 +Vary: Origin +Date: Thu, 01 Aug 2024 18:48:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1Pj42hFY0qyl6XeWKnBe0nzh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722538084, + "allow_redisplay" : "always", + "type" : "card", + "customer" : "cus_QaEVikrjZckFeF" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..1c336470 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=R7ixJaewOfTnOu3OHiqPodX%2F89gF%2FFerJyFNr2BnOhgZv8Py5tGU%2FFxvW1UW6JmJiWps%2BszvMcGk60buSJyCQcgQy2SLlwG%2BOPcEnix0GGm9GeNBNRNl474bK4xHE11B791RNWeQnrpOwKYRdTw78qbbY6UFP8LVtcgO1sEiPgYbgFd5BwcJ1cN%2BGFKQfqxHa0rMxt0X2jeiTP40Ie7EkiPnJoCqsrTBTBJCxJ0soSU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f5a4f9afce67c989af54de2e6ef275e3;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGJLaWV0ZUN5cmpGaDV5UndvVjBWSG93RG1QUnJwNUI_00QrWOvR4f","customer":"cus_QZbHl76LimMPYl"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..43f0d2ed --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=108Zipzjn4zd6PjxOfomdlTgkS2c9aHBvpO6J0eGIT9jyAAL8Pe0S4rPckesnMc25n40IRzM1M8zQkola1luGMmZ9yk6Dan0awbrbYY9zfppY2yEUjkc2to7HormZt1XBMMWajaHdC9kvlwRFzO9JSqcmSWeGneun1O15KuNveL82Z9L3PpSw3YBzZMMC7S%2BEWzq5eka99bcjZzsPI0kavrnSZR%2Bpy2Dt5ZrvPpb2Q0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e3d0f021a4aadef01c2d89b852ba3fdb +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4YFY0qyl6XeW00bjI1bV","secret":"pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV.tail new file mode 100644 index 00000000..85bb0c11 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4YFY0qyl6XeW00bjI1bV\?client_secret=pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OFxfb1uNMHkIaU +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy", + "id" : "pi_3PiS4YFY0qyl6XeW00bjI1bV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392126, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV_confirm.tail new file mode 100644 index 00000000..4c08fab9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4YFY0qyl6XeW00bjI1bV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pJFQ1lyLrcvlPZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:28 GMT +original-request: req_pJFQ1lyLrcvlPZ +stripe-version: 2020-08-27 +idempotency-key: dc9badb9-f908-474f-9feb-f382e7fe64eb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4ZFY0qyl6XeWhdbDmwcr", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392127, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbHl76LimMPYl" + }, + "client_secret" : "pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy", + "id" : "pi_3PiS4YFY0qyl6XeW00bjI1bV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392126, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV.tail new file mode 100644 index 00000000..758ac2bf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4YFY0qyl6XeW00bjI1bV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4YFY0qyl6XeW00bjI1bV\?client_secret=pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_iUTiBKnpEo9ju6 +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4ZFY0qyl6XeWhdbDmwcr", + "client_secret" : "pi_3PiS4YFY0qyl6XeW00bjI1bV_secret_GzXrOriKjfzZSHHMvAXJa0qXy", + "id" : "pi_3PiS4YFY0qyl6XeW00bjI1bV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392126, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..99e569ed --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbHl76LimMPYl&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9A72D5vcgRzIHL +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4ZFY0qyl6XeWhdbDmwcr", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392127, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbHl76LimMPYl" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..89395692 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=EN4iMZ3pqQS7tA3r4m1LwqqpW%2BFSfcO%2FiFkSeVhJ%2BeJJDFdANB3okzZJrVh8Laq9x%2BLlYRKEn4JLXswTgqaNekgsdXpFF8ppP7qb95hwCdAQ8BGOcPHZwQf892ky5Jt19Czg27OpajInxCUPKMhhbVmbu0A%2Bo8XCfXMfydNgO6LJLzwmqmvSCqdW2%2FQWINNbGSq01XpgUaJikSJoRNFvy1L%2BiOSBIW7BRlD41PtDyc8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b9e60bac62c77fdb6edbcc00595f5785 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLE4xYmE4Qm05SDhFblM5QUJCeWpiTTBaclhIdG9nd2I_00ZyzyErk0","customer":"cus_QZbHlp9tafHSmF"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..a76ea922 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QBdFvizSJtOLEg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:29 GMT +original-request: req_QBdFvizSJtOLEg +stripe-version: 2020-08-27 +idempotency-key: 0b52d852-019c-488c-a9d4-94a8a41b0a6d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4bFY0qyl6XeW0961rA2J", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392129, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..344498e3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=qTTqvtNo0Mk1GL0Ww3QaJfkGdIWawWtYu4Gg8fr%2Fg%2FFpDiGM%2F5%2BcbdYQ9U4jKUDhsWhTwFo1WyFCnNfN3P1s%2F%2BA2A2eKTVLKW07w%2FbRwCRlCiMnkpeLWp%2Bz6KFu%2F4GNKXqDrtEsK4dIrXbUC2vL%2Fr4pevB22zT%2BTwu9sOxd5ZgaMNddz6wQSOTr6P%2FfyEGKxSDpFTMcWx7I7OIKEefLp5vP8q1rM2YqH%2BvYUUtUTU94%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5ce2df9ff47c334bed78ce5a2f1c8484 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4cFY0qyl6XeW1SkdNnUV","secret":"pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV.tail new file mode 100644 index 00000000..620019d5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4cFY0qyl6XeW1SkdNnUV\?client_secret=pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LMpesZdf6lrpcY +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1", + "id" : "pi_3PiS4cFY0qyl6XeW1SkdNnUV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392130, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV_confirm.tail new file mode 100644 index 00000000..34d15bdb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4cFY0qyl6XeW1SkdNnUV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OlWqdGJ8sUqSnM +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:32 GMT +original-request: req_OlWqdGJ8sUqSnM +stripe-version: 2020-08-27 +idempotency-key: 0078407d-f2b5-4f1e-8040-65b8eb9c8cd8 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1&expand\[0]=payment_method&payment_method=pm_1PiS4bFY0qyl6XeW0961rA2J&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4bFY0qyl6XeW0961rA2J", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392129, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbHlp9tafHSmF" + }, + "client_secret" : "pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1", + "id" : "pi_3PiS4cFY0qyl6XeW1SkdNnUV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392130, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV.tail new file mode 100644 index 00000000..6aa14947 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4cFY0qyl6XeW1SkdNnUV.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4cFY0qyl6XeW1SkdNnUV\?client_secret=pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2QsL2gMnMkrmVB +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4bFY0qyl6XeW0961rA2J", + "client_secret" : "pi_3PiS4cFY0qyl6XeW1SkdNnUV_secret_J9jmHB4QIVnF4tU88pQsQUIl1", + "id" : "pi_3PiS4cFY0qyl6XeW1SkdNnUV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392130, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0012_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0012_get_v1_payment_methods.tail new file mode 100644 index 00000000..2d5ed69d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0012_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbHlp9tafHSmF&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QPEyqyTE6RnLN8 +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4bFY0qyl6XeW0961rA2J", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392129, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbHlp9tafHSmF" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0013_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0013_post_create_ephemeral_key.tail new file mode 100644 index 00000000..e7268c6b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0013_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=A58DajQ8ZlHD2FRsGkKUIWJFLj%2BiexnslkyfpgeAqQyHQ3bRgd8Gom8HypBcxA1SBBvE7RUm8unMbQ2hShF9%2B%2B0YcKeiah3YvDWLNH9YpL%2BgO59xawcUJR7nxOsEnuS3UcqXCWyDieg%2Bj3FaYcthh85%2FxKdCl0Vwyfuoj8aYIbnAAE3SPaQux7qdWVAUEKTZ1veERLoeT%2F9zlUG6HBn%2FJUWb0h%2FD8bUbRRF1XlCmAEE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d9ae66bfe45f842539d3d3dc85c79da5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHVUTXZiNXUwdmhLVGF2RE02MWMydmduTGxWRnFFWTU_00rzKKmE8S","customer":"cus_QZbH6nFiqn0QoC"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0014_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0014_post_v1_payment_methods.tail new file mode 100644 index 00000000..b84687e2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0014_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mQ4feEUCI7EPQO +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:33 GMT +original-request: req_mQ4feEUCI7EPQO +stripe-version: 2020-08-27 +idempotency-key: f5166929-2d48-4876-a2fa-24adb0feecf5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4fFY0qyl6XeW4HqSJrq2", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392133, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0015_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0015_post_create_payment_intent.tail new file mode 100644 index 00000000..00aa29c3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0015_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2BXnxpVxZpXXX%2B9SQxMELpWM6BENRTuTmG3lrurMgK6KCsEwR7pzS1NZJf1%2BJ9x%2FitNlNOmANXgvXWdcJ4hTicXpj%2Bk%2FVsJLYirIEx%2Bnu1Jjvi2YaBMteiY0tZQjYJzwXIvwLOSayqQHBrmZ8JnXcwrsJw1bYGtClIqZfi1SBV0U2r7%2BHmm3zd7mhUe3Aoe1eNLJ5SBUR2wIPcuwukeGrcn0fxVADFpf%2BiPs3PKUjzok%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9cfb24c8a478a4adf0327df6588f5ae0 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:34 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4fFY0qyl6XeW1bFCizTK","secret":"pi_3PiS4fFY0qyl6XeW1bFCizTK_secret_7TJrFp5NrH586NZGJPVG2NGLF","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4fFY0qyl6XeW1bFCizTK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4fFY0qyl6XeW1bFCizTK.tail new file mode 100644 index 00000000..20e63897 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4fFY0qyl6XeW1bFCizTK.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4fFY0qyl6XeW1bFCizTK\?client_secret=pi_3PiS4fFY0qyl6XeW1bFCizTK_secret_7TJrFp5NrH586NZGJPVG2NGLF&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ISZb3lQSyhayRw +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4fFY0qyl6XeW4HqSJrq2", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392133, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbH6nFiqn0QoC" + }, + "client_secret" : "pi_3PiS4fFY0qyl6XeW1bFCizTK_secret_7TJrFp5NrH586NZGJPVG2NGLF", + "id" : "pi_3PiS4fFY0qyl6XeW1bFCizTK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392133, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4fFY0qyl6XeW1bFCizTK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4fFY0qyl6XeW1bFCizTK.tail new file mode 100644 index 00000000..b2ab4106 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4fFY0qyl6XeW1bFCizTK.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4fFY0qyl6XeW1bFCizTK\?client_secret=pi_3PiS4fFY0qyl6XeW1bFCizTK_secret_7TJrFp5NrH586NZGJPVG2NGLF$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0wtA8MQM2MkLr4 +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4fFY0qyl6XeW4HqSJrq2", + "client_secret" : "pi_3PiS4fFY0qyl6XeW1bFCizTK_secret_7TJrFp5NrH586NZGJPVG2NGLF", + "id" : "pi_3PiS4fFY0qyl6XeW1bFCizTK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392133, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0018_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0018_get_v1_payment_methods.tail new file mode 100644 index 00000000..afedcdaa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSFUlegacyEphemeralKey/0018_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbH6nFiqn0QoC&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_91sC7StlUcO4sb +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4fFY0qyl6XeW4HqSJrq2", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392133, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbH6nFiqn0QoC" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..65d13f4a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=G9rwpVByxhWtPhzSCo%2FrF4IN13ChGG7jZXrg4n2aMK4wk5LyUZOcUTrmgaAdB8EOA4HKtAIMggpsCqG86ZZEV2hxZaY4Gp0YCDS5P5JR%2Fp%2BRXSQWuVW%2FrfXHArGkL%2BUrgfaWhzloEasgYSBJwwloUO6FtChGtiyhSdH%2BxlgwUL2kgnNEu0FjzK0hsWto6HGCTnE4cw9NiC9bD640wkRec8luXOsY6QYBjxJsMytfgW0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 298eab95af2fe866310ce2222a1a0165;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFRpeG9oVkFWMjlLMmhIZUVuVzJjT2EzajI3OW9SckM_00qbw1OIqI","customer":"cus_QZbH3p1kNccxa3"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..ddfc179d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8NKIVCt9JwsxDEP8hbRnOUyQOIswULrzK%2BoI1gLABZWBhAz%2B3ChflKVJ6Hzl9ifeWQfmcGWiidkEMdzhxTxW10LxT5dk6E4F%2FxPHP4BfDLxGu8kSYKwwbcbMtlWnDJGvRqfbBZzUe%2FQGT3QKHaYft5K71c4wqqy6%2ByEf%2BWx5rjyr8glYiPD5LFqWz8QU5MjChx2wOfNslQYQze7SUwNRkhzJpfnvhH7YcGbhtuLLUmY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ca27f982d1a9bbf7032a17074af7b819 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4iFY0qyl6XeW1SY1RjLl","secret":"pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl.tail new file mode 100644 index 00000000..060f735f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4iFY0qyl6XeW1SY1RjLl\?client_secret=pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IpnTqWgZYoDzeK +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr", + "id" : "pi_3PiS4iFY0qyl6XeW1SY1RjLl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392136, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl_confirm.tail new file mode 100644 index 00000000..7d302818 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4iFY0qyl6XeW1SY1RjLl\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5Ux281Xpmm4h9V +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:38 GMT +original-request: req_5Ux281Xpmm4h9V +stripe-version: 2020-08-27 +idempotency-key: f5dee414-d4fd-4a59-9e4e-11433b37fd51 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4jFY0qyl6XeWeHFQQ57M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392137, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbH3p1kNccxa3" + }, + "client_secret" : "pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr", + "id" : "pi_3PiS4iFY0qyl6XeW1SY1RjLl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392136, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl.tail new file mode 100644 index 00000000..3e098527 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4iFY0qyl6XeW1SY1RjLl.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4iFY0qyl6XeW1SY1RjLl\?client_secret=pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0QCeKJA4Z7PsLC +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4jFY0qyl6XeWeHFQQ57M", + "client_secret" : "pi_3PiS4iFY0qyl6XeW1SY1RjLl_secret_oJkb2l056SdAPnJvK2u9wjygr", + "id" : "pi_3PiS4iFY0qyl6XeW1SY1RjLl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392136, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..0c7b3d96 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbH3p1kNccxa3&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QTl4UJM6x9Meq6 +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4jFY0qyl6XeWeHFQQ57M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392137, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbH3p1kNccxa3" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..3bc8c488 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=KKEXvscvGaMRbOv0TAPeobT6yawylkSuJrVcHaMSNvUPLKmukoy1Uzr7NyVxXdac0RyB8UeQfwbH2VBFUduBljjkAr%2BOb3gmxgOb7FdxWsIhkfithiyAaOCkFcNYrIR9nR5AgTAhnihSmkILoLwoBynIqPOYQWCEU8x%2B4XtCiCERplvFatXPLnrQsFHTcaVM%2BNW0mMQC88m5HJuTHfPlElK%2FzEhLQKR0Pc2JgANqRAc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a03ee943205c76bf82f8933313d0fdea +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:39 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGE5U0Z4Q1F3ZDJuT3g0SlM4UGdGZ2NDSHUzdjB5VUs_00Tdi5gXAp","customer":"cus_QZbH7hxC1GGgzD"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..acc4c1b7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DyOxl4Rh45Uopw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:40 GMT +original-request: req_DyOxl4Rh45Uopw +stripe-version: 2020-08-27 +idempotency-key: ef45d963-c36d-4f8b-bc38-185a805dec0a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4lFY0qyl6XeWcxgyfg6H", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392139, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..3c1051d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=dnTxw4PywIIUbojhbKk8mzc3vKT0RD6uzJI9sCVlTOEl3VxhEP5ZbinKkQjNdkqOASqGJhdrLWI6OuOuWLz0FXH3seh2nVpllYa3SpNqwt6mcv%2FZud4ry39ggbYkwD49vAVE0iAoSEv9ClwHLc1NXLuRxbzy5lQhQztiNJeX0X9o3ivYCNZDYVCDFUoWmDVZNaeDH7Xsxy7QgC1yrxOA57Vu65px0DrNB8zmnrS18NI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7abb2bf014cd5f4f8512ef736cc0ec3f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4mFY0qyl6XeW18rpc2LS","secret":"pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS.tail new file mode 100644 index 00000000..9957bce7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4mFY0qyl6XeW18rpc2LS\?client_secret=pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_K47VyU1LS4DErS +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ", + "id" : "pi_3PiS4mFY0qyl6XeW18rpc2LS", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392140, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS_confirm.tail new file mode 100644 index 00000000..8665f2b6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4mFY0qyl6XeW18rpc2LS\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_b56BgZsGBys99Z +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:41 GMT +original-request: req_b56BgZsGBys99Z +stripe-version: 2020-08-27 +idempotency-key: aeb6ba74-1ea8-4d64-b670-d23c1212fe2a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ&expand\[0]=payment_method&payment_method=pm_1PiS4lFY0qyl6XeWcxgyfg6H&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4lFY0qyl6XeWcxgyfg6H", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392139, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbH7hxC1GGgzD" + }, + "client_secret" : "pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ", + "id" : "pi_3PiS4mFY0qyl6XeW18rpc2LS", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392140, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS.tail new file mode 100644 index 00000000..e18a0573 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4mFY0qyl6XeW18rpc2LS.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4mFY0qyl6XeW18rpc2LS\?client_secret=pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_e6sGYyQs7TrlG3 +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4lFY0qyl6XeWcxgyfg6H", + "client_secret" : "pi_3PiS4mFY0qyl6XeW18rpc2LS_secret_tbWZb6X1TuXhPbFKtExx8MiwQ", + "id" : "pi_3PiS4mFY0qyl6XeW18rpc2LS", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392140, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0012_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0012_get_v1_payment_methods.tail new file mode 100644 index 00000000..7a41d539 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0012_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbH7hxC1GGgzD&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BqvEntii6uwLP1 +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4lFY0qyl6XeWcxgyfg6H", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392139, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbH7hxC1GGgzD" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0013_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0013_post_create_ephemeral_key.tail new file mode 100644 index 00000000..86ff05f6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0013_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=pFZOIriZgAmV1dm7iCRb382vcS%2BEI3YEQlID1Y90ZGEt3dxM5HHpN4IvVH6ZV3jSwlu7hh9nYq%2F7g3NfIx3yogut9A%2F3ThpJoVTmcAcGtGClhX2lr4jXUKtBJxfk5VGRACgPLvIEaYGO6SZwLCwZEu2xnSH89XV27d2GBSyQ13O2nKCSlkF1FzwxHyqNfvXQKNKCoxBsGhyDFYY7ply%2Bbs4NANCDwrXJ%2FjCrPOQI1eI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cb381290113ec61e912d3d048b97c1d8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFhjTzNyaUVJcnlEVDFMemZCNTkwa3UwN1c5Z1J1V1A_00UzFrqeBx","customer":"cus_QZbHbCEvOWLGtE"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0014_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0014_post_v1_payment_methods.tail new file mode 100644 index 00000000..82977ed5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0014_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zODEexSDXAq9BH +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:43 GMT +original-request: req_zODEexSDXAq9BH +stripe-version: 2020-08-27 +idempotency-key: 4e818fb4-b0cd-401b-96d6-2598610462a2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4pFY0qyl6XeWAnTnK3ZD", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392143, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0015_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0015_post_create_payment_intent.tail new file mode 100644 index 00000000..79fb1dcb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0015_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=4JoRQOLuuV88S83eP0z2aJBkanHo36Fd9x%2FLw81YgDJqmQEFn3%2FNMP5FEWwBBYv2WNjcRYgj3JbpSLtrdHJnKQmcBluw8Uw2wzif9bH43GytDa3ijABivFXbb5NMTxL3%2FKSGM12LTgALhAPuGLFD5kgwl2XSPa5nXHMIQoqrB56Y1nl%2BynUcmMYJfdbxlBV45ykNlxCoKrW9wE0hmowkxfziVpgPBZbTcnlDCI7WclQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cdd5b9f8570c400f8bf0f470a100e0bc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:44 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4pFY0qyl6XeW0QFV0awd","secret":"pi_3PiS4pFY0qyl6XeW0QFV0awd_secret_DxQmrkuzUYL4cUkRTlCy49dpi","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4pFY0qyl6XeW0QFV0awd.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4pFY0qyl6XeW0QFV0awd.tail new file mode 100644 index 00000000..159bef0c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4pFY0qyl6XeW0QFV0awd.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4pFY0qyl6XeW0QFV0awd\?client_secret=pi_3PiS4pFY0qyl6XeW0QFV0awd_secret_DxQmrkuzUYL4cUkRTlCy49dpi&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6wGArIQPGPDoga +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4pFY0qyl6XeWAnTnK3ZD", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392143, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbHbCEvOWLGtE" + }, + "client_secret" : "pi_3PiS4pFY0qyl6XeW0QFV0awd_secret_DxQmrkuzUYL4cUkRTlCy49dpi", + "id" : "pi_3PiS4pFY0qyl6XeW0QFV0awd", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392143, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4pFY0qyl6XeW0QFV0awd.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4pFY0qyl6XeW0QFV0awd.tail new file mode 100644 index 00000000..a52300e4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4pFY0qyl6XeW0QFV0awd.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4pFY0qyl6XeW0QFV0awd\?client_secret=pi_3PiS4pFY0qyl6XeW0QFV0awd_secret_DxQmrkuzUYL4cUkRTlCy49dpi$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_oDLQFqjExcVA0q +Content-Length: 909 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4pFY0qyl6XeWAnTnK3ZD", + "client_secret" : "pi_3PiS4pFY0qyl6XeW0QFV0awd_secret_DxQmrkuzUYL4cUkRTlCy49dpi", + "id" : "pi_3PiS4pFY0qyl6XeW0QFV0awd", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722392143, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0018_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0018_get_v1_payment_methods.tail new file mode 100644 index 00000000..ed777748 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentSIlegacyEphemeralKey/0018_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbHbCEvOWLGtE&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3HBbt5PR6WZQNe +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4pFY0qyl6XeWAnTnK3ZD", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392143, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbHbCEvOWLGtE" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..67439990 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=mZsdfHrqVY8DaWCl4JGSUUOO3vxwapnXJtGAOJQXAaNnYMyuqTi5LmONOjNAb%2BvQ28jlpqHWDgFDejvjPcfOOoNZ%2F9XLfYs5S%2BAJMvDFzThRZm5sbfdVHVel6QnJHPZMkNLZmXSaZMQZcsAWKmOJrlfXgLRFY2P%2FAUk5jmgbZsWYNMXfHwQslvXcu225ueTp5mKXiTxCsNhtVHEZpC0vZuDpQgBW8CZFtjV78rzdiUQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: efc6ba4d98bcad1f28358d5f3fd759c7;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLGZTMGlmeVI3bDc2V1pnaUphSFF2OEZUaG4yVVBwaEg_00dY6ROShe","customer":"cus_QZbGjWcbGNfWUT"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..a222ee99 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=A2X051dva1f3hjcP%2B2pd5ExStUZOwWa3WHiE%2F7iBOR99ioqEyC655UBEp5QVW1uIgThw9BcpbXh5%2FabqCvAX61pHC69zRoluTML4lduI6Mn4QvEcR6BoMyINDGibX6zzSp7n1ZHsz0uY1xCFMYhW4lj%2B9GJdIg7KotCufdRXErOFRVb5LD0yBxtaKYHXHUyRVDHoXXGdlkFKZzjkZsQTX8F6D24Nn7lnM1oeXvVv%2Ffw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9a29b5ee056fde7f1a7dd6c0b65ebeed +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4EFY0qyl6XeW0yOJYEqG","secret":"pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG.tail new file mode 100644 index 00000000..1646c08e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0002_get_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4EFY0qyl6XeW0yOJYEqG\?client_secret=pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4d7XQ15818Rqm3 +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a", + "id" : "pi_3PiS4EFY0qyl6XeW0yOJYEqG", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392106, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG_confirm.tail new file mode 100644 index 00000000..c8646bf6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0003_post_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG_confirm.tail @@ -0,0 +1,119 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4EFY0qyl6XeW0yOJYEqG\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VddkjuBfHD1WiU +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2011 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:08 GMT +original-request: req_VddkjuBfHD1WiU +stripe-version: 2020-08-27 +idempotency-key: 87486a22-2ab3-4f7e-b15b-7140206e8e3d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=off_session&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4EFY0qyl6XeWVrJcwt1w", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392107, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbGjWcbGNfWUT" + }, + "client_secret" : "pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS4EFY0qyl6XeW0yOJYEqG", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392106, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG.tail new file mode 100644 index 00000000..6e09278b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0004_get_v1_payment_intents_pi_3PiS4EFY0qyl6XeW0yOJYEqG.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4EFY0qyl6XeW0yOJYEqG\?client_secret=pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kBgSXK062iigxR +Content-Length: 997 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS4EFY0qyl6XeWVrJcwt1w", + "client_secret" : "pi_3PiS4EFY0qyl6XeW0yOJYEqG_secret_NH0Z9lWQRWscdHLHrsbeZRr5a", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS4EFY0qyl6XeW0yOJYEqG", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392106, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..4abdaeda --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0005_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbGjWcbGNfWUT&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KWF1aSc997NNFm +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4EFY0qyl6XeWVrJcwt1w", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392107, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbGjWcbGNfWUT" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0006_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0006_post_create_ephemeral_key.tail new file mode 100644 index 00000000..04e1eeee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0006_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=pkOHDocJ%2FwOQVD%2BDHxe2ci3x1dsVSFZxkz8Vf4xwyBr7TVqXMYfXwWv6U5Bw3LBE%2B0kje8GaalkSK8Qxji%2B0Q6WdUVfXFtZ4keHAbpMbwnDG2tiesepEcmCwUZKyWJNELNCRyyI%2FaAfCKFc%2BWQvHyWv%2F6Pca5GXWr%2FUykLZH0agDHEStrHOs3tu%2FT5FHqC9ODCETSeyqrKdE9y62vkZ%2F7%2Ffta7EA%2FB3r%2FXqIypzoqhc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3e276298c88d250872761e5ba857e747 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLExWYzltamhrTkNibXZEVVhmdExJRTRnaTlsTzVrS0k_00Gtlci6yY","customer":"cus_QZbGeV85jh224T"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..c9ff833d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MMl4M3vo1YrVtK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:09 GMT +original-request: req_MMl4M3vo1YrVtK +stripe-version: 2020-08-27 +idempotency-key: 6d3d09f3-36e9-4461-ac7b-009dbe7d0745 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4HFY0qyl6XeWPkIhoq4j", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392109, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..4113b09f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=J2AaPWv015tfDrX2sDfZPwkPawQ%2BNmaNN5y%2FFbFWfRWCI9rzakWRCv3XcPBOpdonZaywaeuNZnQomQM4LF7PzNJUuXursc5vZfQ2FroSsfbeVDV88h0C8RJ1lchSMBOXyPqEo2OMQYdVV%2FBw7d9YqYdJ%2FSYwcvaWMk2uCg6MuKpHktYfXNFJe%2BviFMYkjx%2FdMlo6J9P7wbu0a56rm0gQZSyj2oOhGanJ83sC1gWIWuQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 35f8b4ba9179448a7ceda6341d37acc1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4HFY0qyl6XeW0xhKArcl","secret":"pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl.tail new file mode 100644 index 00000000..54aaaf59 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0009_get_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4HFY0qyl6XeW0xhKArcl\?client_secret=pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fa5Gu34M5jn03a +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx", + "id" : "pi_3PiS4HFY0qyl6XeW0xhKArcl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392109, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl_confirm.tail new file mode 100644 index 00000000..5a1b015a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0010_post_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl_confirm.tail @@ -0,0 +1,119 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4HFY0qyl6XeW0xhKArcl\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tTw57SyHC0hNUz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2011 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:11 GMT +original-request: req_tTw57SyHC0hNUz +stripe-version: 2020-08-27 +idempotency-key: 1ddf9090-99fc-4850-9a46-645788436893 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx&expand\[0]=payment_method&payment_method=pm_1PiS4HFY0qyl6XeWPkIhoq4j&payment_method_options\[card]\[setup_future_usage]=off_session&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4HFY0qyl6XeWPkIhoq4j", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392109, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbGeV85jh224T" + }, + "client_secret" : "pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS4HFY0qyl6XeW0xhKArcl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392109, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl.tail new file mode 100644 index 00000000..b27002f3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0011_get_v1_payment_intents_pi_3PiS4HFY0qyl6XeW0xhKArcl.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4HFY0qyl6XeW0xhKArcl\?client_secret=pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IqVhM4ZeNmCGwK +Content-Length: 997 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS4HFY0qyl6XeWPkIhoq4j", + "client_secret" : "pi_3PiS4HFY0qyl6XeW0xhKArcl_secret_Tb7MqKfucMsRdTx6KfnMg8BRx", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS4HFY0qyl6XeW0xhKArcl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392109, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0012_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0012_get_v1_payment_methods.tail new file mode 100644 index 00000000..e745b1d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0012_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbGeV85jh224T&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CD1tbKyDICI8C8 +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4HFY0qyl6XeWPkIhoq4j", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392109, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbGeV85jh224T" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0013_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0013_post_create_ephemeral_key.tail new file mode 100644 index 00000000..5160fb9f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0013_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=dNdpDuxo56PnRAwJVlDCYmJ9OGuJXrnvhQw%2BjuhR9OyaFac%2BLzmN%2FepKwp1oDUQtpz6%2FkQoZUsxxxZtSZfY%2F4TAXGdDTlFc%2BkhiCenubWMcU9GDXpFv4Y41ovjnGptO9sjHC1DQOmdFh%2BtpxbyJWUa16ykSlghlkc1D%2FyHT8R35jqMMIdbScJxt7XllG2rq5X4M9QUvcNdwf1inoLGio9iIWoXNMW%2BKt12WKkfFUN%2F4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e3e76e2ae5fa963578aa6273b421675c +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLEt2aERuaVZ0UDlQNHFYVGJPeFA4SWpvSEl2aVVBbVo_00xtlCIgrr","customer":"cus_QZbGHUP4N6LSVY"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0014_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0014_post_v1_payment_methods.tail new file mode 100644 index 00000000..3c305210 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0014_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dYwlcwJmiUrEn7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:13 GMT +original-request: req_dYwlcwJmiUrEn7 +stripe-version: 2020-08-27 +idempotency-key: a46c4ee6-ae81-4085-9ac3-eb5f7d550145 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4LFY0qyl6XeWj1lba1al", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392113, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0015_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0015_post_create_payment_intent.tail new file mode 100644 index 00000000..8acc26f7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0015_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=h12NrNlWdKzmejsiStew6%2FfKQn3ttL3Hv2tnud9PYWfSo%2Fey68mA7ZX8x67j1jEFW4UOHZzFCENf7xYkCDdIWvBdPlh5nkcG3n66Pumwcc%2FHM%2FJtrteT00PIXYAFjAjybwSOlmF6dItyR%2F%2BVjZ5It25LiqeRgTyTNXpr9cpYX2LS5BKTWSNxP1p%2Ftkd7T6rKmUI9s7YRp2wMlJx96K%2BXVee%2FrDqoCyUyCjeiI4YdKAM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 04e45ed12e01e03c5797cee1df7de373 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4LFY0qyl6XeW0IUAqohP","secret":"pi_3PiS4LFY0qyl6XeW0IUAqohP_secret_XPnStTmRFKzM8wkwA9WwkxbrU","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4LFY0qyl6XeW0IUAqohP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4LFY0qyl6XeW0IUAqohP.tail new file mode 100644 index 00000000..78e65c1b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0016_get_v1_payment_intents_pi_3PiS4LFY0qyl6XeW0IUAqohP.tail @@ -0,0 +1,115 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4LFY0qyl6XeW0IUAqohP\?client_secret=pi_3PiS4LFY0qyl6XeW0IUAqohP_secret_XPnStTmRFKzM8wkwA9WwkxbrU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TXoc1lD4c6tkby +Content-Length: 2011 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4LFY0qyl6XeWj1lba1al", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392113, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbGHUP4N6LSVY" + }, + "client_secret" : "pi_3PiS4LFY0qyl6XeW0IUAqohP_secret_XPnStTmRFKzM8wkwA9WwkxbrU", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS4LFY0qyl6XeW0IUAqohP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392113, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4LFY0qyl6XeW0IUAqohP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4LFY0qyl6XeW0IUAqohP.tail new file mode 100644 index 00000000..f4ef9f7f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0017_get_v1_payment_intents_pi_3PiS4LFY0qyl6XeW0IUAqohP.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4LFY0qyl6XeW0IUAqohP\?client_secret=pi_3PiS4LFY0qyl6XeW0IUAqohP_secret_XPnStTmRFKzM8wkwA9WwkxbrU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fuQwQ5oA5aPmB4 +Content-Length: 997 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "card" : { + "setup_future_usage" : "off_session" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : "pm_1PiS4LFY0qyl6XeWj1lba1al", + "client_secret" : "pi_3PiS4LFY0qyl6XeW0IUAqohP_secret_XPnStTmRFKzM8wkwA9WwkxbrU", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS4LFY0qyl6XeW0IUAqohP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392113, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0018_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0018_get_v1_payment_methods.tail new file mode 100644 index 00000000..f9ad104f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0018_get_v1_payment_methods.tail @@ -0,0 +1,81 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbGHUP4N6LSVY&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3nmaF5r80BDrls +Content-Length: 1275 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PiS4LFY0qyl6XeWj1lba1al", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392113, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QZbGHUP4N6LSVY" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0019_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0019_post_create_ephemeral_key.tail new file mode 100644 index 00000000..c913102f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0019_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=gHksPbKIFMDsW8%2F6VLAnikerlVLKf0My0br51zIY0V5S1P2zDafSxlRe3DE5eobI9WyqGjoD5jaQzfTLY5av6UQ3l6PlvZuXhtToOtqGHXHbQLDgawnLt%2BFXHfRpIKcpVlKNMd2Agy22cyoUtqfi6SU2B6sQfNxR7bBpLscC%2BbztasRKdpMPCXI55p4ndFg9jHUZPWXHDRtzDaC00vyybgiTJk1WaDT8hSQOsKek%2FUo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 381614d796bc20f05f0b334440ee8bd7;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLEdNTzc2YzFXZGFBQk5SZGZIRG01dXlDOUtoclRxRzY_00i21kdbRN","customer":"cus_QZbGG52vjlfM2A"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0020_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0020_post_create_payment_intent.tail new file mode 100644 index 00000000..affd7c55 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0020_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OgI9zjxWumXGm4vwg8isOXK4tCr%2FaaFzyfZkTcJMc1QtFteR2XtQXrU5XrQ6C9KDH4rs9biQ%2FYeCUgD9vM7t%2F8uNr%2Fu7ojD2mrkIhxErDNbDNcI1hEyI8W5ESIsVIHHwCnlotc0SvRk0exBkEcjsxaCzxG01ndZCWSJ0FNmO7UnaQUHfVDavQ46HIvhlLa%2Bntn7tKB6LJzYeOWHGFaNrVeUjTlfjGM%2BMetPoSWkOYbQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2a8081153bd14cfdb26d954c5f0ffacc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4OFY0qyl6XeW1QmNfV3P","secret":"pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0021_get_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0021_get_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P.tail new file mode 100644 index 00000000..19c7e963 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0021_get_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4OFY0qyl6XeW1QmNfV3P\?client_secret=pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2OYcdDnuZozvIp +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb", + "id" : "pi_3PiS4OFY0qyl6XeW1QmNfV3P", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392116, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0022_post_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0022_post_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P_confirm.tail new file mode 100644 index 00000000..0a975efe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0022_post_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4OFY0qyl6XeW1QmNfV3P\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_F5NWJMuz4tl68p +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:18 GMT +original-request: req_F5NWJMuz4tl68p +stripe-version: 2020-08-27 +idempotency-key: eccee3b7-e969-44b4-af92-f8e111acb535 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[postal_code]=65432&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=32&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4PFY0qyl6XeWBZJS1ruw", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392117, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb", + "id" : "pi_3PiS4OFY0qyl6XeW1QmNfV3P", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392116, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0023_get_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0023_get_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P.tail new file mode 100644 index 00000000..6a1c2af1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0023_get_v1_payment_intents_pi_3PiS4OFY0qyl6XeW1QmNfV3P.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4OFY0qyl6XeW1QmNfV3P\?client_secret=pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_opkBl8OxVQ4keN +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4PFY0qyl6XeWBZJS1ruw", + "client_secret" : "pi_3PiS4OFY0qyl6XeW1QmNfV3P_secret_iuuQ3mgLADodZJbqzGmbEPnRb", + "id" : "pi_3PiS4OFY0qyl6XeW1QmNfV3P", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392116, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0024_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0024_get_v1_payment_methods.tail new file mode 100644 index 00000000..b950f294 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0024_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbGG52vjlfM2A&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0bLmuRNGSuzpGc +Content-Length: 89 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0025_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0025_post_create_ephemeral_key.tail new file mode 100644 index 00000000..9441da1d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0025_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=HY3Vr9ZSNxxBAGmlHUoMLp3rR0J9c%2FGqgW0sfhjAhqdQceViF6cSdCSI3RwZlKM%2BuE1jHJY%2B9qKv0LoFw1qsDMPki3FIalZ5olSVq9rFZsn4pDysHeowQaJh0JnSj%2BTUUrWbW%2BPCeHWNK1XVz79rzJnuMwAr4RHoCIku2%2FR1aTeutMLOoMkfXV%2Fc3w7CznM03XqT9xLzgEytEu01TTh3%2FCmayRdn2%2B3Ka34WGlANNdE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ccb553d4c5109dfd467a8f1957242f8e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFRtRVo0WFR1OTdKWE9pUjRwb1pqWlR2SkhFY2REREM_00boj4FQ5h","customer":"cus_QZbGAvdrmBLEO4"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0026_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0026_post_v1_payment_methods.tail new file mode 100644 index 00000000..52adf95a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0026_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2Lz6GAoKUDAU26 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:19 GMT +original-request: req_2Lz6GAoKUDAU26 +stripe-version: 2020-08-27 +idempotency-key: 19b10e34-c369-4b89-bf19-ed5a797e1fb2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4RFY0qyl6XeWMH8gg2V5", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392119, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0027_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0027_post_create_payment_intent.tail new file mode 100644 index 00000000..48e5dff5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0027_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=DYqK%2Fc5R0C2LQld83aFFfO%2BLbcDbKCMTJHkictT2Xm0N817VpdGTuJABnoXzOEkCr4tqd8Ur6AFPA1pOzrNSlewb1BdJLNRbKjjoatr5sS1k7Z8jW1EADMueImCRDZjvfYOpA4%2F09n06kIQu3I3987SlGFwZzixqw730MWQZ0vE9bfhC7ejL1V4YKELvP6a90S090z83thNTqylDYRZisYhF0qTJb66I%2BiJzIBlPFXY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 631d6ee703321439b50f6033b027f920 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:20 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4SFY0qyl6XeW1pnsNTSc","secret":"pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0028_get_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0028_get_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc.tail new file mode 100644 index 00000000..ba99c9b4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0028_get_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4SFY0qyl6XeW1pnsNTSc\?client_secret=pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_v9knplv4erf3Qo +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI", + "id" : "pi_3PiS4SFY0qyl6XeW1pnsNTSc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392120, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0029_post_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0029_post_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc_confirm.tail new file mode 100644 index 00000000..92a70d8d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0029_post_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4SFY0qyl6XeW1pnsNTSc\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GeUik5hKmMF46s +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:21 GMT +original-request: req_GeUik5hKmMF46s +stripe-version: 2020-08-27 +idempotency-key: d69289a3-1b2c-45c5-b7bd-25e1a40ff770 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI&expand\[0]=payment_method&payment_method=pm_1PiS4RFY0qyl6XeWMH8gg2V5&payment_method_options\[card]\[setup_future_usage]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4RFY0qyl6XeWMH8gg2V5", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392119, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI", + "id" : "pi_3PiS4SFY0qyl6XeW1pnsNTSc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392120, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0030_get_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0030_get_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc.tail new file mode 100644 index 00000000..82f88aaa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0030_get_v1_payment_intents_pi_3PiS4SFY0qyl6XeW1pnsNTSc.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4SFY0qyl6XeW1pnsNTSc\?client_secret=pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_M7q4254N1wfZUy +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4RFY0qyl6XeWMH8gg2V5", + "client_secret" : "pi_3PiS4SFY0qyl6XeW1pnsNTSc_secret_jamt8LkYAfDaFP5BSDEGDMRUI", + "id" : "pi_3PiS4SFY0qyl6XeW1pnsNTSc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392120, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0031_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0031_get_v1_payment_methods.tail new file mode 100644 index 00000000..081842a8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0031_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbGAvdrmBLEO4&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2EGXSNt7biWskM +Content-Length: 89 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0032_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0032_post_create_ephemeral_key.tail new file mode 100644 index 00000000..a5d14f28 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0032_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=nYACNiBOYNqAL3tjbIq6UzY3nhcPWhhk1D8LYC2VzSNA%2Fzhg796pVoIEDtv9819Axg%2FjZLFtFGygHLDLByQ0vqFc8ktYuRYGHHeM4RO1xWDLeKeTXgTaoIrOFe2%2BCRZygUssqn1NFDAEAs6RI7cCTpgCh3lAj%2FHvmsjMePrUh59bZ7M3LINT96DEKyfORYu5tY6ZgU4%2BwhfBVpRujrhdQac7YLCUduLsoASxS1WJlw0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8405d49bf88d6974fba802944e69ab72 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHR1S0NTRnNCaHlHVDFPTVFLSUJjRmNRRGZPUlI1WU0_00TV9kjxxu","customer":"cus_QZbHSlCE3n5xAS"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0033_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0033_post_v1_payment_methods.tail new file mode 100644 index 00000000..1b41fa2e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0033_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BQp9eqE0CTDcqL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 935 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:23 GMT +original-request: req_BQp9eqE0CTDcqL +stripe-version: 2020-08-27 +idempotency-key: e06bf0ca-325c-42bc-80a9-8b9683684a00 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[postal_code]=65432&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=32&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS4VFY0qyl6XeWKaASdjin", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392123, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0034_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0034_post_create_payment_intent.tail new file mode 100644 index 00000000..1796dc5d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0034_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=MG86dJIyLvuYkwQklvOA7i6vsnmWTMo4UUSo97ItCMd7LMkv2ROa%2BFuRrF6YbQUuVJLe3pctqdHAiSP7Xq%2Fp%2FC0CpW%2BRIwj2w4YY9oj%2Bga0s1FGFDLMi3%2BH%2FfTityg5buEY8A5P70cSKQtUOSqkScCDVfsSn4VA2nnrCwE3UzYVzsZdr0wNzjtOhZCVPnQbOABW3ULHjagpJx0ofVr4TXl%2FqX5COmga7vKCQwnr351o%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 21e62d69c1162d82840813872b8b6eb7 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:15:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS4VFY0qyl6XeW1khrwbQ6","secret":"pi_3PiS4VFY0qyl6XeW1khrwbQ6_secret_8lYn33zBJO1Hf6NeWzhBwr3Tg","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0035_get_v1_payment_intents_pi_3PiS4VFY0qyl6XeW1khrwbQ6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0035_get_v1_payment_intents_pi_3PiS4VFY0qyl6XeW1khrwbQ6.tail new file mode 100644 index 00000000..88de08f3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0035_get_v1_payment_intents_pi_3PiS4VFY0qyl6XeW1khrwbQ6.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4VFY0qyl6XeW1khrwbQ6\?client_secret=pi_3PiS4VFY0qyl6XeW1khrwbQ6_secret_8lYn33zBJO1Hf6NeWzhBwr3Tg&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kELw9gVaDJ5HzO +Content-Length: 1898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS4VFY0qyl6XeWKaASdjin", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : "65432" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2032, + "country" : "US" + }, + "livemode" : false, + "created" : 1722392123, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiS4VFY0qyl6XeW1khrwbQ6_secret_8lYn33zBJO1Hf6NeWzhBwr3Tg", + "id" : "pi_3PiS4VFY0qyl6XeW1khrwbQ6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392123, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0036_get_v1_payment_intents_pi_3PiS4VFY0qyl6XeW1khrwbQ6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0036_get_v1_payment_intents_pi_3PiS4VFY0qyl6XeW1khrwbQ6.tail new file mode 100644 index 00000000..5ca58d04 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0036_get_v1_payment_intents_pi_3PiS4VFY0qyl6XeW1khrwbQ6.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS4VFY0qyl6XeW1khrwbQ6\?client_secret=pi_3PiS4VFY0qyl6XeW1khrwbQ6_secret_8lYn33zBJO1Hf6NeWzhBwr3Tg$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Vudbai14EDj4Bb +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS4VFY0qyl6XeWKaASdjin", + "client_secret" : "pi_3PiS4VFY0qyl6XeW1khrwbQ6_secret_8lYn33zBJO1Hf6NeWzhBwr3Tg", + "id" : "pi_3PiS4VFY0qyl6XeW1khrwbQ6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722392123, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0037_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0037_get_v1_payment_methods.tail new file mode 100644 index 00000000..b8bd7b11 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetGDPRConfirmFlowTests/testAllowRedisplaypaymentIntentlegacyEphemeralKey/0037_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QZbHSlCE3n5xAS&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TmntgycQEJ4sFV +Content-Length: 89 +Vary: Origin +Date: Wed, 31 Jul 2024 02:15:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..ec565bc4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=woTiHNP4cdKDtZcc31Lmeq%2Fv%2Bg7aLThbiYfO3vn3m57OU9%2FwoGxdFKZFLf3nSODWi8WIkJa8CiYhYnZ00JGRKWvdAbcNb%2BnPhyxEmOrC%2FdsxWSl9RHfl9MXVgLhSWTbqMOIftuo0LIQpjV9ITdg7T5mWs0ZfJhz%2FQWIAaHJl1rbf7naVUjWtivaijALnBFWNx6HB1OR5VkmEfXtX%2FVl8p7fPuBZ8HcN6Oyk0K5CRdQ8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 260d7be9fcb81b4760ff6f4783028080;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:38 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFJF7QokQdxBy1oLWJSGM","secret":"pi_3PiTFJF7QokQdxBy1oLWJSGM_secret_UMXOZvu0hB3bWqTwBpQQM8OLt","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFJF7QokQdxBy1oLWJSGM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFJF7QokQdxBy1oLWJSGM.tail new file mode 100644 index 00000000..cb57039b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFJF7QokQdxBy1oLWJSGM.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFJF7QokQdxBy1oLWJSGM\?client_secret=pi_3PiTFJF7QokQdxBy1oLWJSGM_secret_UMXOZvu0hB3bWqTwBpQQM8OLt$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pvaMgDiUuy4PLI +Content-Length: 799 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFJF7QokQdxBy1oLWJSGM_secret_UMXOZvu0hB3bWqTwBpQQM8OLt", + "id" : "pi_3PiTFJF7QokQdxBy1oLWJSGM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396637, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFJF7QokQdxBy1oLWJSGM_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFJF7QokQdxBy1oLWJSGM_confirm.tail new file mode 100644 index 00000000..836abcab --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFJF7QokQdxBy1oLWJSGM_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFJF7QokQdxBy1oLWJSGM\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xhdnLFxuHP7Bcs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1417 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:39 GMT +original-request: req_xhdnLFxuHP7Bcs +stripe-version: 2020-08-27 +idempotency-key: defa758d-2ba0-4da9-9452-e599a9bdb649 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFJF7QokQdxBy1oLWJSGM_secret_UMXOZvu0hB3bWqTwBpQQM8OLt&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[au_becs_debit%5Baccount_number%5D]=000123456&payment_method_data\[au_becs_debit%5Bbsb_number%5D]=000000&payment_method_data\[billing_details%5Bemail%5D]=example%40link\.com&payment_method_data\[billing_details%5Bname%5D]=Tester%20McTesterface&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=au_becs_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFKF7QokQdxByZ01WKGZa", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396638, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTFJF7QokQdxBy1oLWJSGM_secret_UMXOZvu0hB3bWqTwBpQQM8OLt", + "id" : "pi_3PiTFJF7QokQdxBy1oLWJSGM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396637, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..720c8e2f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0003_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gyff0xyJjrBbAg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 583 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:39 GMT +original-request: req_gyff0xyJjrBbAg +stripe-version: 2020-08-27 +idempotency-key: 09dfaacf-5b4e-4579-b949-8798473d3d98 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit%5Baccount_number%5D=000123456&au_becs_debit%5Bbsb_number%5D=000000&billing_details%5Bemail%5D=example%40link\.com&billing_details%5Bname%5D=Tester%20McTesterface&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFLF7QokQdxByG1lzveXr", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396639, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..ec5bf65b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=V5auCirSLC7dKlT9hvYsJBO7dg%2FfT%2Bp%2BCIucszDA37nxXcJon8O9TrEO8O9rL0zTW%2FFs01fhNSOyaGLsfSU10VkJ4pNTHHqFHBDiWU93Tr6k4f%2BRwAj5MsFxqcfFNKKJ9aCS%2FjD4inOxImwvzoD1LsCUkDaWsj8gwMODf8lZIbB4FP99nGiWE5Rk9pIzpwajQTE7wS7Y3kzCKBv%2FLK%2BF%2FjYzPNlk4oXWSpKGxF7GE%2F8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9ac44237d5fefd50dd325b9f2777221f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:39 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFLF7QokQdxBy2skk4jd2","secret":"pi_3PiTFLF7QokQdxBy2skk4jd2_secret_1ZvX5mFt5bLCbrdpzE80Ojedo","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFLF7QokQdxBy2skk4jd2.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFLF7QokQdxBy2skk4jd2.tail new file mode 100644 index 00000000..b707364f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFLF7QokQdxBy2skk4jd2.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFLF7QokQdxBy2skk4jd2\?client_secret=pi_3PiTFLF7QokQdxBy2skk4jd2_secret_1ZvX5mFt5bLCbrdpzE80Ojedo&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_iXg3BJ9PmmwS6Y +Content-Length: 799 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFLF7QokQdxBy2skk4jd2_secret_1ZvX5mFt5bLCbrdpzE80Ojedo", + "id" : "pi_3PiTFLF7QokQdxBy2skk4jd2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396639, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFLF7QokQdxBy2skk4jd2_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFLF7QokQdxBy2skk4jd2_confirm.tail new file mode 100644 index 00000000..957a43b7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFLF7QokQdxBy2skk4jd2_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFLF7QokQdxBy2skk4jd2\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kGppyLjpTgSIDv +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1417 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:41 GMT +original-request: req_kGppyLjpTgSIDv +stripe-version: 2020-08-27 +idempotency-key: 5b195683-c000-4e3b-a735-eac183a58d6b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFLF7QokQdxBy2skk4jd2_secret_1ZvX5mFt5bLCbrdpzE80Ojedo&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFLF7QokQdxByG1lzveXr&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFLF7QokQdxByG1lzveXr", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396639, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTFLF7QokQdxBy2skk4jd2_secret_1ZvX5mFt5bLCbrdpzE80Ojedo", + "id" : "pi_3PiTFLF7QokQdxBy2skk4jd2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396639, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..038ff46b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0007_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wLogSa7BzhaC1p +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 583 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:41 GMT +original-request: req_wLogSa7BzhaC1p +stripe-version: 2020-08-27 +idempotency-key: 942e4bd3-adca-499f-a557-a2867287730e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit%5Baccount_number%5D=000123456&au_becs_debit%5Bbsb_number%5D=000000&billing_details%5Bemail%5D=example%40link\.com&billing_details%5Bname%5D=Tester%20McTesterface&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFNF7QokQdxByTeXRiGZz", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396641, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..ee0c1ac6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=CPRP3hddqHsX7Fg%2BjByvAY25eg%2BbDMqkERiQISCBaBFZqPp3iffKJSyv7mGmuwTlMsEFE2gO%2Fx5GkLFQSHh2IwSbZwmeeezJLg6Yq54H2j0Xq7UY4zKHrEBogkr7o37TCGi9u%2FZe3pQxCOxvS7yNCNJo8aljXCqitTkaa%2FndsyVTHYk%2BdU554blioWPp3Omg7wOtuXZIQnkI9RAO4BHwRl%2F%2FZ4hjL5to5JHMJNhJFRQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fd8b00da5a9b9ecccc2895c37dcecd92 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFNF7QokQdxBy0o0gue0K","secret":"pi_3PiTFNF7QokQdxBy0o0gue0K_secret_VNkTX9S7Ag2eCQUcciWXGvxU9","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0009_get_v1_payment_intents_pi_3PiTFNF7QokQdxBy0o0gue0K.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0009_get_v1_payment_intents_pi_3PiTFNF7QokQdxBy0o0gue0K.tail new file mode 100644 index 00000000..a71b6e44 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0009_get_v1_payment_intents_pi_3PiTFNF7QokQdxBy0o0gue0K.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFNF7QokQdxBy0o0gue0K\?client_secret=pi_3PiTFNF7QokQdxBy0o0gue0K_secret_VNkTX9S7Ag2eCQUcciWXGvxU9&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0OuODAsTdWjNor +Content-Length: 1417 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFNF7QokQdxByTeXRiGZz", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396641, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTFNF7QokQdxBy0o0gue0K_secret_VNkTX9S7Ag2eCQUcciWXGvxU9", + "id" : "pi_3PiTFNF7QokQdxBy0o0gue0K", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396641, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..3b517e82 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8WAKrz1JtXzZUpjM7kjJiG7NVqZ4Z%2F6u6oBOJQVujpwadQDEGQLXJXR8HlgHsGODOYFS6nO1U6cYiHN9GtIwVu8L2EiVxM2Fgynt62RV%2FCfSEE1sklYS4atO728gy965Ld%2FWK2bKDwUaZvOqpJFO%2FxPjo%2FTCxrjalu8HLs65jNBbYdCx98p0sQtNYGQK8kcTWGz4jX1qms0MRPPb2Bg9m3PGZxRmhKRr2sz661cWPQM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7311e3618423b43ab23755a5ebf06643 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFOF7QokQdxBy2DeLSlCw","secret":"pi_3PiTFOF7QokQdxBy2DeLSlCw_secret_YkGRBJ89ge237UV4EMWghZ8VC","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFOF7QokQdxBy2DeLSlCw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFOF7QokQdxBy2DeLSlCw.tail new file mode 100644 index 00000000..18bebf2d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFOF7QokQdxBy2DeLSlCw.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFOF7QokQdxBy2DeLSlCw\?client_secret=pi_3PiTFOF7QokQdxBy2DeLSlCw_secret_YkGRBJ89ge237UV4EMWghZ8VC$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jnKQNHCjfs6b2j +Content-Length: 808 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFOF7QokQdxBy2DeLSlCw_secret_YkGRBJ89ge237UV4EMWghZ8VC", + "id" : "pi_3PiTFOF7QokQdxBy2DeLSlCw", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396642, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0012_post_v1_payment_intents_pi_3PiTFOF7QokQdxBy2DeLSlCw_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0012_post_v1_payment_intents_pi_3PiTFOF7QokQdxBy2DeLSlCw_confirm.tail new file mode 100644 index 00000000..a0c4b31e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0012_post_v1_payment_intents_pi_3PiTFOF7QokQdxBy2DeLSlCw_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFOF7QokQdxBy2DeLSlCw\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_836gdCloYg1mMK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1426 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:43 GMT +original-request: req_836gdCloYg1mMK +stripe-version: 2020-08-27 +idempotency-key: 9de5b46a-eead-4284-9bca-466bb9d64f96 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFOF7QokQdxBy2DeLSlCw_secret_YkGRBJ89ge237UV4EMWghZ8VC&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[au_becs_debit%5Baccount_number%5D]=000123456&payment_method_data\[au_becs_debit%5Bbsb_number%5D]=000000&payment_method_data\[billing_details%5Bemail%5D]=example%40link\.com&payment_method_data\[billing_details%5Bname%5D]=Tester%20McTesterface&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=au_becs_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFPF7QokQdxByfVbzJbs5", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396643, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTFOF7QokQdxBy2DeLSlCw_secret_YkGRBJ89ge237UV4EMWghZ8VC", + "id" : "pi_3PiTFOF7QokQdxBy2DeLSlCw", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396642, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..37fa0345 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0013_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xR0Y8EHkmT5XGq +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 583 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:44 GMT +original-request: req_xR0Y8EHkmT5XGq +stripe-version: 2020-08-27 +idempotency-key: 1e42a094-d524-4666-8445-6fca3638da36 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit%5Baccount_number%5D=000123456&au_becs_debit%5Bbsb_number%5D=000000&billing_details%5Bemail%5D=example%40link\.com&billing_details%5Bname%5D=Tester%20McTesterface&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFQF7QokQdxByHRyU1odG", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396644, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..7b67f959 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=AHD9eTcVA16ojthX4jK2ikRBwkDpZUtqLM9357aGNPvQHPKehAh7ojpgVoAjHjybyKBwSl2SWxsOGvWP6X8vJh6rqZQFsAogBqBv%2FSE2twKhZ13WYjKBLwVbgwz9kWEgxzmowbAcvG3OZkZmO%2Bud84EIW1sQqzrcH%2Bd16CjkiAujNlkiPtiSyg5yDPkkt3XNMiX4Mx1mXkVyiXq5MeqacwV8pEw1B1%2F8Zg8%2B6u%2B5QW8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 626c15033b23e615fa1e955fda4adfc5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:44 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFQF7QokQdxBy1mcIi24C","secret":"pi_3PiTFQF7QokQdxBy1mcIi24C_secret_6hMyza2HsNMc3Vh5UqvbaHAbv","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0015_get_v1_payment_intents_pi_3PiTFQF7QokQdxBy1mcIi24C.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0015_get_v1_payment_intents_pi_3PiTFQF7QokQdxBy1mcIi24C.tail new file mode 100644 index 00000000..3c274211 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0015_get_v1_payment_intents_pi_3PiTFQF7QokQdxBy1mcIi24C.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFQF7QokQdxBy1mcIi24C\?client_secret=pi_3PiTFQF7QokQdxBy1mcIi24C_secret_6hMyza2HsNMc3Vh5UqvbaHAbv&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TY8xw0e2vkJVPh +Content-Length: 808 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFQF7QokQdxBy1mcIi24C_secret_6hMyza2HsNMc3Vh5UqvbaHAbv", + "id" : "pi_3PiTFQF7QokQdxBy1mcIi24C", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396644, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0016_post_v1_payment_intents_pi_3PiTFQF7QokQdxBy1mcIi24C_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0016_post_v1_payment_intents_pi_3PiTFQF7QokQdxBy1mcIi24C_confirm.tail new file mode 100644 index 00000000..df72a597 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0016_post_v1_payment_intents_pi_3PiTFQF7QokQdxBy1mcIi24C_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFQF7QokQdxBy1mcIi24C\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qsTZOqmRFFNuH3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1426 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:45 GMT +original-request: req_qsTZOqmRFFNuH3 +stripe-version: 2020-08-27 +idempotency-key: 3d67eb64-b033-4d8f-9001-da47f1de1071 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFQF7QokQdxBy1mcIi24C_secret_6hMyza2HsNMc3Vh5UqvbaHAbv&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFQF7QokQdxByHRyU1odG&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFQF7QokQdxByHRyU1odG", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396644, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTFQF7QokQdxBy1mcIi24C_secret_6hMyza2HsNMc3Vh5UqvbaHAbv", + "id" : "pi_3PiTFQF7QokQdxBy1mcIi24C", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396644, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..c25cae72 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_deIJFdfzJXOPiT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 583 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:46 GMT +original-request: req_deIJFdfzJXOPiT +stripe-version: 2020-08-27 +idempotency-key: 8106b011-3b17-4e8f-8e7e-c65e2b120941 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit%5Baccount_number%5D=000123456&au_becs_debit%5Bbsb_number%5D=000000&billing_details%5Bemail%5D=example%40link\.com&billing_details%5Bname%5D=Tester%20McTesterface&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFSF7QokQdxBySqBRis6X", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396646, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..423cd779 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=LEywcC5W2pBIAuvINB9yCkub58brqZ4iXk9dgacc5OSmu5cziux8SzenQHr4oqeekkWWnHqMNEXDxF6VL08gpH962WW5XRQPIi2wyHzPaL9dKsVLaLjk6ZtfI8Go3cVsN6l7d9QY62%2FRYRSTPHHf5n524Ao4TsaLn%2BuBaFP53cc27lejgAge2IEITGuTwBtzULyBt01B98rioGILQhKgnAm9vMMWakz4opjpu9MXrGo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cda9711a8ba759235cab3d475bbd8ee0 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:47 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFSF7QokQdxBy2aCZCbRj","secret":"pi_3PiTFSF7QokQdxBy2aCZCbRj_secret_b3JFk3CMaBdPz45GqGEM8k4iZ","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFSF7QokQdxBy2aCZCbRj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFSF7QokQdxBy2aCZCbRj.tail new file mode 100644 index 00000000..29fe99ad --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFSF7QokQdxBy2aCZCbRj.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFSF7QokQdxBy2aCZCbRj\?client_secret=pi_3PiTFSF7QokQdxBy2aCZCbRj_secret_b3JFk3CMaBdPz45GqGEM8k4iZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7unbit9fAQWzwM +Content-Length: 1426 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFSF7QokQdxBySqBRis6X", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396646, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTFSF7QokQdxBy2aCZCbRj_secret_b3JFk3CMaBdPz45GqGEM8k4iZ", + "id" : "pi_3PiTFSF7QokQdxBy2aCZCbRj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396646, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0020_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0020_post_create_setup_intent.tail new file mode 100644 index 00000000..d75fb3b8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0020_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=gFPu9jKA8WwQ4a5gLo7juLh62s0gf%2F8VBlHH9PcVu%2FoTNrvjJfQnkNe76EZxnprk7SB6AyMZZ%2FBBhnQ%2BJ37h92N9R9Uv5Rc90m48%2B18soSVrpJDka%2FY8XeJG7NPDl56bL5ubD97L%2BNJxZuyFcA1SJ%2B0uLxkBJ5tsea9hGA3RdGJWsUbSbMwpY7ozznTKx9fjwXfWq93FtvW6g1tw3R1uiXg5IFqs0fUycpohpsQzy4A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4e6f5e8465fe1f8cab25ac08508e1d64 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:47 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTFTF7QokQdxBy85OjWngM","secret":"seti_1PiTFTF7QokQdxBy85OjWngM_secret_QZcUPKJct7PhpBoS79M0KkvVq9tiLA6","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0021_get_v1_setup_intents_seti_1PiTFTF7QokQdxBy85OjWngM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0021_get_v1_setup_intents_seti_1PiTFTF7QokQdxBy85OjWngM.tail new file mode 100644 index 00000000..1748ae35 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0021_get_v1_setup_intents_seti_1PiTFTF7QokQdxBy85OjWngM.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFTF7QokQdxBy85OjWngM\?client_secret=seti_1PiTFTF7QokQdxBy85OjWngM_secret_QZcUPKJct7PhpBoS79M0KkvVq9tiLA6$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ykgp7VFLz3aQIg +Content-Length: 542 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFTF7QokQdxBy85OjWngM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "au_becs_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396647, + "client_secret" : "seti_1PiTFTF7QokQdxBy85OjWngM_secret_QZcUPKJct7PhpBoS79M0KkvVq9tiLA6", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0022_post_v1_setup_intents_seti_1PiTFTF7QokQdxBy85OjWngM_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0022_post_v1_setup_intents_seti_1PiTFTF7QokQdxBy85OjWngM_confirm.tail new file mode 100644 index 00000000..1801fafd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0022_post_v1_setup_intents_seti_1PiTFTF7QokQdxBy85OjWngM_confirm.tail @@ -0,0 +1,75 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFTF7QokQdxBy85OjWngM\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IgDNzkCWByHell +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1159 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:48 GMT +original-request: req_IgDNzkCWByHell +stripe-version: 2020-08-27 +idempotency-key: 34dfed20-6442-437f-ac8c-8c82f9c97cf0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTFTF7QokQdxBy85OjWngM_secret_QZcUPKJct7PhpBoS79M0KkvVq9tiLA6&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[au_becs_debit%5Baccount_number%5D]=000123456&payment_method_data\[au_becs_debit%5Bbsb_number%5D]=000000&payment_method_data\[billing_details%5Bemail%5D]=example%40link\.com&payment_method_data\[billing_details%5Bname%5D]=Tester%20McTesterface&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=au_becs_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTFTF7QokQdxBy85OjWngM", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFUF7QokQdxByINZSMvOO", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396648, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "au_becs_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396647, + "client_secret" : "seti_1PiTFTF7QokQdxBy85OjWngM_secret_QZcUPKJct7PhpBoS79M0KkvVq9tiLA6", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0023_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0023_post_v1_payment_methods.tail new file mode 100644 index 00000000..51d2a247 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0023_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LGkgw7hjPxPXbS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 583 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:49 GMT +original-request: req_LGkgw7hjPxPXbS +stripe-version: 2020-08-27 +idempotency-key: d628d73b-970c-49af-b30b-7e17c92a57b0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit%5Baccount_number%5D=000123456&au_becs_debit%5Bbsb_number%5D=000000&billing_details%5Bemail%5D=example%40link\.com&billing_details%5Bname%5D=Tester%20McTesterface&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFUF7QokQdxByuZEqTeQ2", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396648, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0024_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0024_post_create_setup_intent.tail new file mode 100644 index 00000000..e2cd1279 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0024_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fXuPdwCzcHP2tezKeel%2Bz%2FXaCRP0ZrctwVuu%2FIWsVt3bUhI2wO5eYTlAnDDgk%2FZa1X936WNaGZV%2F%2BPcAp2yo1VMem8X9a9zrp2SYA6buGJOiRjCIQDGS0z9%2B4ieMt5ARFiwkd5r3X0sxb7PDEoJtklEGmNXsuGOhZDn4sVL66u4d7xu43TfJ0iYRQ5STOLEEvqPPOqz6%2BBtuCZim1UDhBYlIXWxw2JNwNSLUh%2Ff6jV8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5719ff9b030f21819e7b3b1591f81057;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:49 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTFVF7QokQdxByjRz9RlYV","secret":"seti_1PiTFVF7QokQdxByjRz9RlYV_secret_QZcUonNNeJAOPvRWDCO2IeAIwefWski","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0025_get_v1_setup_intents_seti_1PiTFVF7QokQdxByjRz9RlYV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0025_get_v1_setup_intents_seti_1PiTFVF7QokQdxByjRz9RlYV.tail new file mode 100644 index 00000000..ec8d2743 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0025_get_v1_setup_intents_seti_1PiTFVF7QokQdxByjRz9RlYV.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFVF7QokQdxByjRz9RlYV\?client_secret=seti_1PiTFVF7QokQdxByjRz9RlYV_secret_QZcUonNNeJAOPvRWDCO2IeAIwefWski&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dhhlTAeLjGXu39 +Content-Length: 542 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFVF7QokQdxByjRz9RlYV", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "au_becs_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396649, + "client_secret" : "seti_1PiTFVF7QokQdxByjRz9RlYV_secret_QZcUonNNeJAOPvRWDCO2IeAIwefWski", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0026_post_v1_setup_intents_seti_1PiTFVF7QokQdxByjRz9RlYV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0026_post_v1_setup_intents_seti_1PiTFVF7QokQdxByjRz9RlYV_confirm.tail new file mode 100644 index 00000000..2a941f79 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0026_post_v1_setup_intents_seti_1PiTFVF7QokQdxByjRz9RlYV_confirm.tail @@ -0,0 +1,75 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFVF7QokQdxByjRz9RlYV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5Mk2UbSVaCTeYK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1159 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:50 GMT +original-request: req_5Mk2UbSVaCTeYK +stripe-version: 2020-08-27 +idempotency-key: 0855c454-9dcf-4ffc-858e-dd222d4ccf95 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTFVF7QokQdxByjRz9RlYV_secret_QZcUonNNeJAOPvRWDCO2IeAIwefWski&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFUF7QokQdxByuZEqTeQ2&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTFVF7QokQdxByjRz9RlYV", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFUF7QokQdxByuZEqTeQ2", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396648, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "au_becs_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396649, + "client_secret" : "seti_1PiTFVF7QokQdxByjRz9RlYV_secret_QZcUonNNeJAOPvRWDCO2IeAIwefWski", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..e4ba7613 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0027_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8R3HuNkLJeJOxH +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 583 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:50 GMT +original-request: req_8R3HuNkLJeJOxH +stripe-version: 2020-08-27 +idempotency-key: 92b401a7-b4ab-41f9-8faa-6638a9f304b1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit%5Baccount_number%5D=000123456&au_becs_debit%5Bbsb_number%5D=000000&billing_details%5Bemail%5D=example%40link\.com&billing_details%5Bname%5D=Tester%20McTesterface&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFWF7QokQdxBy1nuQg9sq", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396650, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0028_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0028_post_create_setup_intent.tail new file mode 100644 index 00000000..5d5c4cb5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0028_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2BLWXCtROWe8pB4ajIgr04EZuLKbcV0RKr7eByvhhHktV0wQ2Gt%2BvKTN4xc4UzOg2Nk4aX1Qnw93hvda4Laj2Q86ZF4pZjOQoJCQGkDpdy%2FC9rR%2FHm6nQC8RbgDYdUGaopWkVKsuArJXDTvSjnqUYwVnAeUIvfeJe8XyzlumRlUvJ8PtQJpBWNMLqbPjCLkpV8tSqN8pGN9xo2UnrPLIXJAO40DqCkKGn33V4Vvcv0AU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b81c3bdc43f17d2385a1155416345a8a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTFWF7QokQdxByLZXVizQJ","secret":"seti_1PiTFWF7QokQdxByLZXVizQJ_secret_QZcUCsQRtK1K5z4wOHuo8hjnvOI2wB5","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0029_get_v1_setup_intents_seti_1PiTFWF7QokQdxByLZXVizQJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0029_get_v1_setup_intents_seti_1PiTFWF7QokQdxByLZXVizQJ.tail new file mode 100644 index 00000000..3e6e7b39 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAUBecsDebitConfirmFlows/0029_get_v1_setup_intents_seti_1PiTFWF7QokQdxByLZXVizQJ.tail @@ -0,0 +1,71 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFWF7QokQdxByLZXVizQJ\?client_secret=seti_1PiTFWF7QokQdxByLZXVizQJ_secret_QZcUCsQRtK1K5z4wOHuo8hjnvOI2wB5&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TsoyPKYUwBeruS +Content-Length: 1159 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFWF7QokQdxByLZXVizQJ", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFWF7QokQdxBy1nuQg9sq", + "billing_details" : { + "email" : "example@link.com", + "phone" : null, + "name" : "Tester McTesterface", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722396650, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "au_becs_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396650, + "client_secret" : "seti_1PiTFWF7QokQdxByLZXVizQJ_secret_QZcUCsQRtK1K5z4wOHuo8hjnvOI2wB5", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..0e814c8b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=cDZjsYdzfENfCSUoEH0dNIsypwj64OXIOM2%2Bnzjm2Ig6ch092uEcCY84T9LHyWu8zESlyCjBy70Q3yaviLtYWIvvtySQeMmzKfY1CNXOQg%2FQwqA8UFfmD557rBhgkaRcqKs79rakcqijMOYQwv0Yrc1uZ%2BuXIhxvgWf5wMY1Yc1jB98jApZekunYIM9ywA497IAmrSCK%2BXJb3CjmAz3h4jmwYJq6F3PDzevpOvXMIW8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7e3c4394d14b233f816e67692995ddd7;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEEFY0qyl6XeW0s1Y1qGj","secret":"pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj.tail new file mode 100644 index 00000000..767bf692 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEEFY0qyl6XeW0s1Y1qGj\?client_secret=pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lIASzL3eVNo2Ym +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2", + "id" : "pi_3PiTEEFY0qyl6XeW0s1Y1qGj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396570, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj_confirm.tail new file mode 100644 index 00000000..8534db45 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEEFY0qyl6XeW0s1Y1qGj\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tGR2DIL4xEwyP5 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1688 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:32 GMT +original-request: req_tGR2DIL4xEwyP5 +stripe-version: 2020-08-27 +idempotency-key: 3e0d9b52-9e25-41ee-b6cc-1a799285599d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=alipay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiTEFFY0qyl6XeWHzJeZVfM?client_secret=src_client_secret_jOJR3in1md0quvdsRJvigpb2", + "return_url" : "https:\/\/foo.com" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEFFY0qyl6XeWwbq3wVlZ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396571, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null + }, + "client_secret" : "pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2", + "id" : "pi_3PiTEEFY0qyl6XeW0s1Y1qGj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396570, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj.tail new file mode 100644 index 00000000..c0830256 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEEFY0qyl6XeW0s1Y1qGj.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEEFY0qyl6XeW0s1Y1qGj\?client_secret=pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4kWXxkyN2OHz8d +Content-Length: 1688 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiTEFFY0qyl6XeWHzJeZVfM?client_secret=src_client_secret_jOJR3in1md0quvdsRJvigpb2", + "return_url" : "https:\/\/foo.com" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEFFY0qyl6XeWwbq3wVlZ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396571, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null + }, + "client_secret" : "pi_3PiTEEFY0qyl6XeW0s1Y1qGj_secret_Kg0qSq682FOf3vUIFfr1EJSS2", + "id" : "pi_3PiTEEFY0qyl6XeW0s1Y1qGj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396570, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..3fffcdac --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YIfG2JCGejALNr +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:32 GMT +original-request: req_YIfG2JCGejALNr +stripe-version: 2020-08-27 +idempotency-key: bc0be689-e0bc-45f8-9437-ae554cd60707 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alipay + +{ + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEGFY0qyl6XeWOu2U8OWp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396572, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..71a1cc75 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FKCxjoZZIpgMVfsA8piYHXuQ8LffEAIXYGNR7HLGPgcIz9joOyk4ZU3d6ej1CUjwRnmGDrGLhHCU0o1rJGOQ1BMRoz8Jpfi0SDostPaRzq9USQlVTQVVL8CugBzIEimV%2FcKe50mYL2x3VFXYyedi1jTDG2arFp%2FsuEoOYc7ELeuXShzwvosLeZWruwqTdfjkOr0pwsGA4Si0Nrz04OzSoL80wM5f1P%2BXgiHhgHuGMqQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 34f476aa00a7803443717037bdb9a883 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEGFY0qyl6XeW042Gfi2g","secret":"pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g.tail new file mode 100644 index 00000000..69f21bec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEGFY0qyl6XeW042Gfi2g\?client_secret=pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LxdlpwyXSfIpQI +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE", + "id" : "pi_3PiTEGFY0qyl6XeW042Gfi2g", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396572, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g_confirm.tail new file mode 100644 index 00000000..bade9878 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEGFY0qyl6XeW042Gfi2g\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5Xf2kK6dq7LCJN +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1688 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:34 GMT +original-request: req_5Xf2kK6dq7LCJN +stripe-version: 2020-08-27 +idempotency-key: d87322cb-e086-495f-9160-e5ed8e376010 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE&expand\[0]=payment_method&payment_method=pm_1PiTEGFY0qyl6XeWOu2U8OWp&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiTEHFY0qyl6XeWlIXEG5nm?client_secret=src_client_secret_M3RocHQ8gP19B9RNxJyrGZwX", + "return_url" : "https:\/\/foo.com" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEGFY0qyl6XeWOu2U8OWp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396572, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null + }, + "client_secret" : "pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE", + "id" : "pi_3PiTEGFY0qyl6XeW042Gfi2g", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396572, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g.tail new file mode 100644 index 00000000..6ab500b5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEGFY0qyl6XeW042Gfi2g.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEGFY0qyl6XeW042Gfi2g\?client_secret=pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2Kv0W37Ut7FdZE +Content-Length: 1688 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiTEHFY0qyl6XeWlIXEG5nm?client_secret=src_client_secret_M3RocHQ8gP19B9RNxJyrGZwX", + "return_url" : "https:\/\/foo.com" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEGFY0qyl6XeWOu2U8OWp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396572, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null + }, + "client_secret" : "pi_3PiTEGFY0qyl6XeW042Gfi2g_secret_ddf09MH9VBrEA2TNd8wfc9SZE", + "id" : "pi_3PiTEGFY0qyl6XeW042Gfi2g", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396572, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..627dbf69 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OEobioYLbaUUb4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:34 GMT +original-request: req_OEobioYLbaUUb4 +stripe-version: 2020-08-27 +idempotency-key: 0ac2d0a1-7ce6-4162-99a5-3cd66096f6d2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alipay + +{ + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEIFY0qyl6XeWuIga5V3f", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396574, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..bbb0fd23 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=TYLx1mmi8K8r%2FVSo6CEOqmp54z2hXJcnOrm5%2FD%2FSjB8aSA3gjXKfR6rev6vVkqn4JU%2BtKTcU4k4CLIHp3RAGp8vcugJK%2FyU4FZ4VtfEUYR3DxyDeQkDms72KZnPUU16X%2FOtu5pwXfj7ekdqeh7HnC2wqs1mDWrUARN8kXtO69wcY6jevY%2BrxpJRYgYu8OQijloYx%2FpfWKjPC3%2FXNtMlUSjQ469RykTS1wISQ5HqOmX0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3e1647a1a29c50f9b32df5a478d0c1f4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEJFY0qyl6XeW1GTJWEDq","secret":"pi_3PiTEJFY0qyl6XeW1GTJWEDq_secret_9kl0Fc9lD5AcZ53IgobFmhhMr","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEJFY0qyl6XeW1GTJWEDq.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEJFY0qyl6XeW1GTJWEDq.tail new file mode 100644 index 00000000..5f84c37e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEJFY0qyl6XeW1GTJWEDq.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEJFY0qyl6XeW1GTJWEDq\?client_secret=pi_3PiTEJFY0qyl6XeW1GTJWEDq_secret_9kl0Fc9lD5AcZ53IgobFmhhMr&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8bKeBzX9q4HUjp +Content-Length: 1682 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiTEJFY0qyl6XeWfyvZT9jS?client_secret=src_client_secret_RCk44XlVaDkJnRluqyRiwhkl", + "return_url" : "foo:\/\/bar" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEIFY0qyl6XeWuIga5V3f", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396574, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null + }, + "client_secret" : "pi_3PiTEJFY0qyl6XeW1GTJWEDq_secret_9kl0Fc9lD5AcZ53IgobFmhhMr", + "id" : "pi_3PiTEJFY0qyl6XeW1GTJWEDq", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396575, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTEJFY0qyl6XeW1GTJWEDq.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTEJFY0qyl6XeW1GTJWEDq.tail new file mode 100644 index 00000000..b5a3ac46 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlipayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTEJFY0qyl6XeW1GTJWEDq.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEJFY0qyl6XeW1GTJWEDq\?client_secret=pi_3PiTEJFY0qyl6XeW1GTJWEDq_secret_9kl0Fc9lD5AcZ53IgobFmhhMr&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_50EjjifBScnhQx +Content-Length: 1682 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiTEJFY0qyl6XeWfyvZT9jS?client_secret=src_client_secret_RCk44XlVaDkJnRluqyRiwhkl", + "return_url" : "foo:\/\/bar" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiTEIFY0qyl6XeWuIga5V3f", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396574, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null + }, + "client_secret" : "pi_3PiTEJFY0qyl6XeW1GTJWEDq_secret_9kl0Fc9lD5AcZ53IgobFmhhMr", + "id" : "pi_3PiTEJFY0qyl6XeW1GTJWEDq", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1722396575, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..fd1936da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OuMLLPWSd%2Bohl8mh0mvVq55erm4EVHDnH%2FMwxN2NraFrE86JcTsYOjI5JMVet%2BP%2F%2B7zwcIe6wscpAp4xQOafJStL3m3z1OtoCPKjkayV4nq2g5hltNc8At3SeMNG3SQIHn3av6zh2VKCNCRok7XWvT0GaPO9RsKrNnfxshgVRsK96se0XEAU8gIoA%2FqCJLZjUC50lInQnHWBugJ1skvFceqTV3PBbnS8uY7xSO2oF98%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 82707e51f734b9c38df34dc224f9e9af +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEKKG6vc7r7YC1tBz8NmF","secret":"pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF.tail new file mode 100644 index 00000000..3cc6a9af --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEKKG6vc7r7YC1tBz8NmF\?client_secret=pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XJYRJcXCFemYvd +Content-Length: 790 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc", + "id" : "pi_3PiTEKKG6vc7r7YC1tBz8NmF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396576, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF_confirm.tail new file mode 100644 index 00000000..16b587f8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEKKG6vc7r7YC1tBz8NmF\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xcGl2IhhS3CRoQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1488 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:37 GMT +original-request: req_xcGl2IhhS3CRoQ +stripe-version: 2020-08-27 +idempotency-key: b71dae55-11a0-485a-952c-597c5c662105 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=alma&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcT8qnFJPtPSKo9NjoLhmCokTtDF0Z" + } + }, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTELKG6vc7r7YCeqYxjF46", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396577, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc", + "id" : "pi_3PiTEKKG6vc7r7YC1tBz8NmF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396576, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF.tail new file mode 100644 index 00000000..ef15f033 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEKKG6vc7r7YC1tBz8NmF.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEKKG6vc7r7YC1tBz8NmF\?client_secret=pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_eFHEiaglJgsrUT +Content-Length: 1488 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcT8qnFJPtPSKo9NjoLhmCokTtDF0Z" + } + }, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTELKG6vc7r7YCeqYxjF46", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396577, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3PiTEKKG6vc7r7YC1tBz8NmF_secret_1osf1y9ZStgQCtbwyPyJ5jgRc", + "id" : "pi_3PiTEKKG6vc7r7YC1tBz8NmF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396576, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..d9eaf763 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QdHsqTrPFn5zul +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 444 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:38 GMT +original-request: req_QdHsqTrPFn5zul +stripe-version: 2020-08-27 +idempotency-key: 19daf47c-1f44-4232-a5b8-0e1ef094d2df +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alma + +{ + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTEMKG6vc7r7YCvDhgd4l8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396578, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..7ab4c0b8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=dDjDXKofg%2BfmNYI5U2drXiTW8b9EosdJdTcGqaEmM5hKjQ5nkVmhGo4fBLJb10gavgYkATSJHgMpgTbm9xuleikL0vkmEg4BKBrPyEP3PVlRqFcfR4os0uW0yJ296TaEjGHBIseqebjvFgwx%2FeKG4zblh%2BargUoysmIIGJAk6ZCdFBkyZqjV3bpxUq7KqiiOAFL3VRxZjFT5uw4yd91kSC52Y0OXOZ6oLBA85Jl2zaQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 19e8fc9ca2d7c5c1edc37b874658a868 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:38 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEMKG6vc7r7YC0w1UTQQ7","secret":"pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7.tail new file mode 100644 index 00000000..c0a6a22d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEMKG6vc7r7YC0w1UTQQ7\?client_secret=pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lWczz4uTWamJkE +Content-Length: 790 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi", + "id" : "pi_3PiTEMKG6vc7r7YC0w1UTQQ7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396578, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0007_post_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0007_post_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7_confirm.tail new file mode 100644 index 00000000..ec418cd7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0007_post_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEMKG6vc7r7YC0w1UTQQ7\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yTt3WlyMNnwJts +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1488 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:39 GMT +original-request: req_yTt3WlyMNnwJts +stripe-version: 2020-08-27 +idempotency-key: ed038297-1707-45df-8831-ea93ae1e61b0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi&expand\[0]=payment_method&payment_method=pm_1PiTEMKG6vc7r7YCvDhgd4l8&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcTFc88cDElHoWgfSvpdsvbRvbOWxv" + } + }, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTEMKG6vc7r7YCvDhgd4l8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396578, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi", + "id" : "pi_3PiTEMKG6vc7r7YC0w1UTQQ7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396578, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7.tail new file mode 100644 index 00000000..94d84a9e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEMKG6vc7r7YC0w1UTQQ7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEMKG6vc7r7YC0w1UTQQ7\?client_secret=pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dwJ9WMQxVY08tQ +Content-Length: 1488 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcTFc88cDElHoWgfSvpdsvbRvbOWxv" + } + }, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTEMKG6vc7r7YCvDhgd4l8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396578, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3PiTEMKG6vc7r7YC0w1UTQQ7_secret_gjwtY3saTdqzJLWvYls57IEKi", + "id" : "pi_3PiTEMKG6vc7r7YC0w1UTQQ7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396578, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..5a16fd2a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_g0UYabLWvgjaeU +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 444 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:39 GMT +original-request: req_g0UYabLWvgjaeU +stripe-version: 2020-08-27 +idempotency-key: cbc3e85e-1ea9-4065-a6bf-f7a4b7560e7a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alma + +{ + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTENKG6vc7r7YCYncIoFic", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396579, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..dde309f6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=MHzvCvP6mZBp36%2Bjbzx51jQ9ly20S4wp94ALNMMJaRQ3y5dqFwnMbL7KYUNEs%2B%2FnW%2BfIQNaPE03dehlGehPLQchtUSDD37xtPLpjQ7qSggH%2Fa0llx%2FdV7b8rBBqqKjX06C6KjUS7lJBhyliwqG7j2oNht3shfgWccpOBDzqIzUtJanTVzJuitz2V6Rl9ypmH6J16rFpUbktYxIor0ePNO6GVb%2B0kYVoacxLNW07CkCY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6c75dee946c2ebecc54b368ae57054d1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEOKG6vc7r7YC1MHc68qb","secret":"pi_3PiTEOKG6vc7r7YC1MHc68qb_secret_7sn1mGEm4NsCSu3PzGDUKv6QE","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEOKG6vc7r7YC1MHc68qb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEOKG6vc7r7YC1MHc68qb.tail new file mode 100644 index 00000000..c311d75d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEOKG6vc7r7YC1MHc68qb.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEOKG6vc7r7YC1MHc68qb\?client_secret=pi_3PiTEOKG6vc7r7YC1MHc68qb_secret_7sn1mGEm4NsCSu3PzGDUKv6QE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Y3w9ScAuWnwxNn +Content-Length: 1482 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcT7FPOvxqNEWZGEjUmuYT8hptFV5m" + } + }, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTENKG6vc7r7YCYncIoFic", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396579, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3PiTEOKG6vc7r7YC1MHc68qb_secret_7sn1mGEm4NsCSu3PzGDUKv6QE", + "id" : "pi_3PiTEOKG6vc7r7YC1MHc68qb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396580, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0012_get_v1_payment_intents_pi_3PiTEOKG6vc7r7YC1MHc68qb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0012_get_v1_payment_intents_pi_3PiTEOKG6vc7r7YC1MHc68qb.tail new file mode 100644 index 00000000..6334d420 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAlmaConfirmFlows/0012_get_v1_payment_intents_pi_3PiTEOKG6vc7r7YC1MHc68qb.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEOKG6vc7r7YC1MHc68qb\?client_secret=pi_3PiTEOKG6vc7r7YC1MHc68qb_secret_7sn1mGEm4NsCSu3PzGDUKv6QE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2buCzBjjfdKW8b +Content-Length: 1482 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcT7FPOvxqNEWZGEjUmuYT8hptFV5m" + } + }, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiTENKG6vc7r7YCYncIoFic", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396579, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3PiTEOKG6vc7r7YC1MHc68qb_secret_7sn1mGEm4NsCSu3PzGDUKv6QE", + "id" : "pi_3PiTEOKG6vc7r7YC1MHc68qb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1722396580, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..0ba03cf2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6%2FUUuQiRhGg%2F9biH1le9t07OoMvNhxTC9Lxg9mSjE90d8dIAilLXSKUFWatcdCUn5hAQAkfkID3dglxHUwsIlGZA9ESETxVXcgoQF3v403z9g%2BpdpjKu6MbI1sk3xzeebGLO1rskc1UNvrGSFrA2Plbg6%2FNj%2BC%2BDE3NpTaD7b9x6qwK6w%2FxL%2BEchD9KScM0UjvVlxYZ1UfmJ0VNSNYTAoBds3Pnw5%2FAzaMmK8y9NG0A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: de780f76e2d3f2611a6c1fe09042daa9;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:29:41 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEPFY0qyl6XeW1MDTgfBY","secret":"pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..a6685d94 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YBhFS1R498vZl4 +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY_confirm.tail new file mode 100644 index 00000000..63a6d0ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OkC9RIhWNFaCrF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:42 GMT +original-request: req_OkC9RIhWNFaCrF +stripe-version: 2020-08-27 +idempotency-key: 0b8581cd-9452-451d-b9a1-b31ffbe1fc47 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=amazon_pay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..c9a7f31b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bvzP3rEiurc4Z0 +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0004_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0004_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..7268fde9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0004_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RpeGGfT7vXuW6G +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0005_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0005_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..4b4d613e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0005_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tJ4nQh8u8PKEGl +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..b336b594 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DdhJHghf4fy5hf +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0007_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0007_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..b13743fa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0007_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xJACjZdkQhe4mv +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail new file mode 100644 index 00000000..840a93ab --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTEPFY0qyl6XeW1MDTgfBY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEPFY0qyl6XeW1MDTgfBY\?client_secret=pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_npxtASds49vNrA +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:29:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTetd7PMXY4xoHlVQOKv4kKwzznXP" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEQFY0qyl6XeWPTHV7I4M", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396582, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY_secret_eIecLb0eY9jAsuG309wm8p6E2", + "id" : "pi_3PiTEPFY0qyl6XeW1MDTgfBY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396581, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..8ed5e8f1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vKU6mdhRDBUQHw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 456 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:00 GMT +original-request: req_vKU6mdhRDBUQHw +stripe-version: 2020-08-27 +idempotency-key: 67286a8a-c9f1-4fb1-bec6-a513b60ce434 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=amazon_pay + +{ + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..770e1512 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Gf9%2FfjFB%2BnlsMebMKhPnbLQbVKfSsuzCuR61VsXVbAKiQKt4DMmgB4e7bCsk3XGF4AMXQZ4hXbhBUEXa%2Bw6U%2BxBuO%2BcTC8nUrjOOcZ3zF4799YtOZgwv2MAOcFV%2F69G36BZ%2B0AZhEX2DclA9oKLo9sFhlRQams%2BnApG5avwm5Hm77128Iy40XV7XnxB0DDCywzw8gchdaOsjh2PeWcD2FcSKyyah%2FMhcU7j6bWYVpi4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b2f69ec715a2eaee48dbde818e59e2e1;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTEiFY0qyl6XeW086jB16T","secret":"pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..e6aac6eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aeaKHemLXBlhkF +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0012_post_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0012_post_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T_confirm.tail new file mode 100644 index 00000000..22ef665b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0012_post_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4AkOfi8p1C9dep +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:01 GMT +original-request: req_4AkOfi8p1C9dep +stripe-version: 2020-08-27 +idempotency-key: a6ab6df1-fec5-43a2-af35-63e1879224f5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand\[0]=payment_method&payment_method=pm_1PiTEiFY0qyl6XeW97y26btE&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0013_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0013_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..d2586b19 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0013_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6FhHi6IfufrvwC +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0014_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0014_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..1bf94ba7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0014_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_po56tUC5djC55v +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0015_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0015_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..211b28ac --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0015_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5Z4ZR6FvsGr7kw +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0016_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0016_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..33cd4384 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0016_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Jl0Iv33mIF4LXh +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0017_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0017_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..b77df924 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0017_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_C0zyULChiI4KNj +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0018_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0018_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail new file mode 100644 index 00000000..bde44cd5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0018_get_v1_payment_intents_pi_3PiTEiFY0qyl6XeW086jB16T.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTEiFY0qyl6XeW086jB16T\?client_secret=pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6qQW8p8uxUk3MY +Content-Length: 1605 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTv9u6eJJFVlpqG2OO6wMmbDUIO6u" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTEiFY0qyl6XeW97y26btE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396600, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTEiFY0qyl6XeW086jB16T_secret_W51Uoc387gTJn98PELh0Mlnzx", + "id" : "pi_3PiTEiFY0qyl6XeW086jB16T", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396600, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0019_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0019_post_v1_payment_methods.tail new file mode 100644 index 00000000..be3609d6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0019_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5Ap0PY4vh46k1J +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 456 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:19 GMT +original-request: req_5Ap0PY4vh46k1J +stripe-version: 2020-08-27 +idempotency-key: 6945e04c-52cc-4613-9d77-e6ae7b46f898 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=amazon_pay + +{ + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0020_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0020_post_create_payment_intent.tail new file mode 100644 index 00000000..aec602cb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0020_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=lNZnhWGCrf%2B82cgKvpVNlZb3oPS94aI9tMgUrQ2V1lPf5%2B5oGzZeRsLYUx8GbNNDiFU3%2F0kQ%2F1NDBc%2BuRqAQ6TCoCQANCGn7qTfyZPjaJhmD5uZkPOcBUF1rRBX2KL1kcK4RyCVYBMYGFxg1kKvnpSwyVp1q3uxgc8qwW9PyLsEvTl3g5wjBHtlvP6AqKZE%2FjMF5tatSej%2BwNhDpsSAcsADQVSG7yuOVyIsOvKHpxtk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c504456d83a5b08783a8e3cae1514998;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTF1FY0qyl6XeW0inpEO26","secret":"pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0021_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0021_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..3bc19f7f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0021_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3FTzPGPbHGIW7l +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0022_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0022_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..4ca535de --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0022_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_a3EogLWiv5s8DP +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0023_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0023_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..97762915 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0023_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rRBO5VenO1sEzu +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0024_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0024_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..aa1f91d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0024_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gaREpR3qmKt4ab +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0025_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0025_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..6c2070bf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0025_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_w5wIp1uMnB6Ek2 +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0026_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0026_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..84909927 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0026_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OKziKyx9JopWqk +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0027_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0027_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail new file mode 100644 index 00000000..eee52e6a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testAmazonPayConfirmFlows/0027_get_v1_payment_intents_pi_3PiTF1FY0qyl6XeW0inpEO26.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTF1FY0qyl6XeW0inpEO26\?client_secret=pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_74yGW0V6FHvWkP +Content-Length: 1599 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcTeUpDT7AkbQfusLmlqFdOFKHji71" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiTF0FY0qyl6XeWKANNINPF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396618, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3PiTF1FY0qyl6XeW0inpEO26_secret_FiXYIaC0og4FkBjReuCerGkEl", + "id" : "pi_3PiTF1FY0qyl6XeW0inpEO26", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1722396619, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..998749f2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=M65GxQuu7yzLKbcTxSOHMHp6OitO5dGeWSL%2BRBGSQtZVexvzWs%2FAXKNB8umNRpTeLFA45hHpptXochL%2Fp4vKyeNygLryW8o6QRDflgbx4qJBJLBGB2iT5pe%2Br27p44P46aLu38oC2OtHk26tT%2Fpmh5rF60KLHEBga0XWB81q6WqXP9UyV9J09KW3rsiIQLcVhm8Yeny6OjPUsP5fr8RNbOSF1RzjeIhfyoKGUZZI%2B3I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0ff6755f1c304636f5fcc3374759cf95 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFwArGMi59tL41WJNey9A","secret":"pi_3PiTFwArGMi59tL41WJNey9A_secret_u8Ne9lfIhWiinX0YCBoFxtQp4","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFwArGMi59tL41WJNey9A.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFwArGMi59tL41WJNey9A.tail new file mode 100644 index 00000000..690270ad --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFwArGMi59tL41WJNey9A.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFwArGMi59tL41WJNey9A\?client_secret=pi_3PiTFwArGMi59tL41WJNey9A_secret_u8Ne9lfIhWiinX0YCBoFxtQp4$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ay5mr0h48jXi7C +Content-Length: 790 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "pln", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFwArGMi59tL41WJNey9A_secret_u8Ne9lfIhWiinX0YCBoFxtQp4", + "id" : "pi_3PiTFwArGMi59tL41WJNey9A", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "blik" + ], + "setup_future_usage" : null, + "created" : 1722396676, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFwArGMi59tL41WJNey9A_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFwArGMi59tL41WJNey9A_confirm.tail new file mode 100644 index 00000000..279f82c1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFwArGMi59tL41WJNey9A_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFwArGMi59tL41WJNey9A\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_59kMckjIEJ9j43 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1296 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:17 GMT +original-request: req_59kMckjIEJ9j43 +stripe-version: 2020-08-27 +idempotency-key: d7c3fe3b-2c75-4283-9e5a-d5825547d8a5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFwArGMi59tL41WJNey9A_secret_u8Ne9lfIhWiinX0YCBoFxtQp4&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=blik&payment_method_options\[blik]\[code]=123456&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "pln", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "blik_authorize" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFxArGMi59tL4gvafxDoV", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "blik" : { + + }, + "allow_redisplay" : "unspecified", + "created" : 1722396677, + "customer" : null, + "type" : "blik" + }, + "client_secret" : "pi_3PiTFwArGMi59tL41WJNey9A_secret_u8Ne9lfIhWiinX0YCBoFxtQp4", + "id" : "pi_3PiTFwArGMi59tL41WJNey9A", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "blik" + ], + "setup_future_usage" : null, + "created" : 1722396676, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..92c21c96 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0003_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tJ1F4HsDXosMDX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 444 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:18 GMT +original-request: req_tJ1F4HsDXosMDX +stripe-version: 2020-08-27 +idempotency-key: 96504540-a24c-4739-89c2-f1cc836ae60d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=blik + +{ + "object" : "payment_method", + "id" : "pm_1PiTFyArGMi59tL45nqe9BJA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "blik" : { + + }, + "allow_redisplay" : "unspecified", + "created" : 1722396678, + "customer" : null, + "type" : "blik" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..4a4b7518 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6WmCIS8pn5h5T8VvU%2F8NFHXpK%2F%2Bum6k0eXTbVXOKPRruT38Ibeedh4pR6iywpNzxOYQBnCRDvWOcwiSb4tId4rYDK9GISPzSDV8cnfVFBX4cy5dHp0Gpe7pZPxkooAIHHZlOedCGrK3U4kwnRaGSNZZ%2FNq6io9QppXnlpsODfIPq%2FXjJDr9pKsVhNf1mM9mhWPLbbBELQKkJmIyAbTuEQ%2F%2Fe4eWYHYLB8EakqF%2BLcr4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fe8a4720e23cfcea41bb64a647c5def8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:18 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFyArGMi59tL40XEPxieD","secret":"pi_3PiTFyArGMi59tL40XEPxieD_secret_25BvztienfMEIjngAKxD3REAb","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFyArGMi59tL40XEPxieD.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFyArGMi59tL40XEPxieD.tail new file mode 100644 index 00000000..6648ae66 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFyArGMi59tL40XEPxieD.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFyArGMi59tL40XEPxieD\?client_secret=pi_3PiTFyArGMi59tL40XEPxieD_secret_25BvztienfMEIjngAKxD3REAb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WkzpvZTiMd05yC +Content-Length: 790 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "pln", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFyArGMi59tL40XEPxieD_secret_25BvztienfMEIjngAKxD3REAb", + "id" : "pi_3PiTFyArGMi59tL40XEPxieD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "blik" + ], + "setup_future_usage" : null, + "created" : 1722396678, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFyArGMi59tL40XEPxieD_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFyArGMi59tL40XEPxieD_confirm.tail new file mode 100644 index 00000000..a2a9d57f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBLIKConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFyArGMi59tL40XEPxieD_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFyArGMi59tL40XEPxieD\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4TL1cmqtDe6Mbt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1296 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:19 GMT +original-request: req_4TL1cmqtDe6Mbt +stripe-version: 2020-08-27 +idempotency-key: 2b331acd-ece9-4125-959e-2bd258752466 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFyArGMi59tL40XEPxieD_secret_25BvztienfMEIjngAKxD3REAb&expand\[0]=payment_method&payment_method=pm_1PiTFyArGMi59tL45nqe9BJA&payment_method_options\[blik]\[code]=123456&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "pln", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "blik_authorize" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFyArGMi59tL45nqe9BJA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "blik" : { + + }, + "allow_redisplay" : "unspecified", + "created" : 1722396678, + "customer" : null, + "type" : "blik" + }, + "client_secret" : "pi_3PiTFyArGMi59tL40XEPxieD_secret_25BvztienfMEIjngAKxD3REAb", + "id" : "pi_3PiTFyArGMi59tL40XEPxieD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "blik" + ], + "setup_future_usage" : null, + "created" : 1722396678, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..c80a0ab9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=T7CGtf35BYXLc5llRNZyYESJP%2F9%2BF2VnfcJC1z5xuCA00EWmE8jEAqNXH3UhxjI0ZVEmmEJkueM2wlbaolizM98Z%2FM9vHpXeg8HNLo903Yj6Bmml2B8igXMyCzJgOXwJurOhjA3sRXOgeUjkuuYMXJG0mgc31BZKZ5Ko3KajAKSHne4oj6BsFwjzLQjTAsJz9muWX3wSN8Tt%2FSGBhDBqJlNm8%2Fmpj1Ofn5Sdm6fdMlk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6e0b3e52592bc3b3eb0f4369c02f976e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:51 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFXGoesj9fw9Q1Xou3zeP","secret":"pi_3PiTFXGoesj9fw9Q1Xou3zeP_secret_YfnddRXUgseZxH9Mh8cpPImlD","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFXGoesj9fw9Q1Xou3zeP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFXGoesj9fw9Q1Xou3zeP.tail new file mode 100644 index 00000000..9b451f59 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFXGoesj9fw9Q1Xou3zeP.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFXGoesj9fw9Q1Xou3zeP\?client_secret=pi_3PiTFXGoesj9fw9Q1Xou3zeP_secret_YfnddRXUgseZxH9Mh8cpPImlD$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lkMwPdxdn18b0s +Content-Length: 796 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFXGoesj9fw9Q1Xou3zeP_secret_YfnddRXUgseZxH9Mh8cpPImlD", + "id" : "pi_3PiTFXGoesj9fw9Q1Xou3zeP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396651, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFXGoesj9fw9Q1Xou3zeP_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFXGoesj9fw9Q1Xou3zeP_confirm.tail new file mode 100644 index 00000000..ed735c1c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFXGoesj9fw9Q1Xou3zeP_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFXGoesj9fw9Q1Xou3zeP\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QpwmNLGY54Hqva +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1385 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:52 GMT +original-request: req_QpwmNLGY54Hqva +stripe-version: 2020-08-27 +idempotency-key: 67c5d916-8f8f-4c08-a7f4-a757723bff60 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFXGoesj9fw9Q1Xou3zeP_secret_YfnddRXUgseZxH9Mh8cpPImlD&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[bacs_debit]\[account_number]=00012345&payment_method_data\[bacs_debit]\[sort_code]=108800&payment_method_data\[billing_details]\[address]\[city]=asdf&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=asdf&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=AL&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=bacs_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFYGoesj9fw9QQoL6GNwk", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396652, + "customer" : null, + "type" : "bacs_debit" + }, + "client_secret" : "pi_3PiTFXGoesj9fw9Q1Xou3zeP_secret_YfnddRXUgseZxH9Mh8cpPImlD", + "id" : "pi_3PiTFXGoesj9fw9Q1Xou3zeP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396651, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..7b845146 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0003_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RYzO1PKu6KF8bg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 554 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:53 GMT +original-request: req_RYzO1PKu6KF8bg +stripe-version: 2020-08-27 +idempotency-key: 3a84886c-4bbe-426b-8164-dbac91783054 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&bacs_debit\[account_number]=00012345&bacs_debit\[sort_code]=108800&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bacs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFZGoesj9fw9QSvQY1lFs", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396653, + "customer" : null, + "type" : "bacs_debit" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..8fbb7604 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8BemZZj%2FwJb6YoF5APOiH5qVC8dM%2FqDC2aLtU1hKOj%2BvnvWY83SsTvK8Z0aBortIs804t4PIfJHEddbdCbsFYu%2BCrKCJhClw4zqAv%2BE58BHg%2FUmP1AkVRM5soN%2BaTAaZu652gAf1wCYIr%2FPOHKKvIMv2Ojycw1WcybKiS6VezKIhBVXx8Nyc9HivL31pq7FDP%2BYlNDOr2Rmu1FRMSnqbQdOS52ZcebFAQ5QNPYkGobY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: aae8a7b6aadae73c2589af4bcb5950c8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:53 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFZGoesj9fw9Q1ZrT7KZV","secret":"pi_3PiTFZGoesj9fw9Q1ZrT7KZV_secret_fk62sgUJymtnKwqHmJWulnstf","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFZGoesj9fw9Q1ZrT7KZV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFZGoesj9fw9Q1ZrT7KZV.tail new file mode 100644 index 00000000..a43898e2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0005_get_v1_payment_intents_pi_3PiTFZGoesj9fw9Q1ZrT7KZV.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFZGoesj9fw9Q1ZrT7KZV\?client_secret=pi_3PiTFZGoesj9fw9Q1ZrT7KZV_secret_fk62sgUJymtnKwqHmJWulnstf&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YMy9KAaZ08kObP +Content-Length: 796 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFZGoesj9fw9Q1ZrT7KZV_secret_fk62sgUJymtnKwqHmJWulnstf", + "id" : "pi_3PiTFZGoesj9fw9Q1ZrT7KZV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396653, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFZGoesj9fw9Q1ZrT7KZV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFZGoesj9fw9Q1ZrT7KZV_confirm.tail new file mode 100644 index 00000000..d9b1543d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0006_post_v1_payment_intents_pi_3PiTFZGoesj9fw9Q1ZrT7KZV_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFZGoesj9fw9Q1ZrT7KZV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jJUuVNDUzfRhPI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1385 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:54 GMT +original-request: req_jJUuVNDUzfRhPI +stripe-version: 2020-08-27 +idempotency-key: ce7cb82f-2f5c-49a0-9966-7ee4de642264 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFZGoesj9fw9Q1ZrT7KZV_secret_fk62sgUJymtnKwqHmJWulnstf&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFZGoesj9fw9QSvQY1lFs&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFZGoesj9fw9QSvQY1lFs", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396653, + "customer" : null, + "type" : "bacs_debit" + }, + "client_secret" : "pi_3PiTFZGoesj9fw9Q1ZrT7KZV_secret_fk62sgUJymtnKwqHmJWulnstf", + "id" : "pi_3PiTFZGoesj9fw9Q1ZrT7KZV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396653, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..bf1f578e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0007_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2YBIt1jsNQTO6S +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 554 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:55 GMT +original-request: req_2YBIt1jsNQTO6S +stripe-version: 2020-08-27 +idempotency-key: 966c20c6-1fab-498b-9714-40eb4a67ac4f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&bacs_debit\[account_number]=00012345&bacs_debit\[sort_code]=108800&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bacs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFaGoesj9fw9QyN4KCcj0", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396655, + "customer" : null, + "type" : "bacs_debit" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..890e72d4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FnyaxcWKVxIgjcjEUlxtBKY7fMyY%2BjSsh3UWOxl7%2FRg2Nv8Y%2F4%2BxHvHhu44GQzK3vWk3hbNsAcirG%2BE6neDv2XkWQll4MV%2Fi1iVHI%2FwCLrgYiWjWjsZWA7j1hZOWvCIZOck6IHhvTeh45gqM3tCajuQ3XNdhmiQaXNhbUKNUC7sEdRc5RaV9AwluNPV71C1FM2zvEFpe0akES7gG47ETot6hhSupq39tTVSP3muD7%2Bs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3119676617deb8a53df29c59bb4ec76f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:55 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFbGoesj9fw9Q1Ex7WyLP","secret":"pi_3PiTFbGoesj9fw9Q1Ex7WyLP_secret_qIeVirZxNrsHZV1Nl2qsKhzxe","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0009_get_v1_payment_intents_pi_3PiTFbGoesj9fw9Q1Ex7WyLP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0009_get_v1_payment_intents_pi_3PiTFbGoesj9fw9Q1Ex7WyLP.tail new file mode 100644 index 00000000..81d157cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0009_get_v1_payment_intents_pi_3PiTFbGoesj9fw9Q1Ex7WyLP.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFbGoesj9fw9Q1Ex7WyLP\?client_secret=pi_3PiTFbGoesj9fw9Q1Ex7WyLP_secret_qIeVirZxNrsHZV1Nl2qsKhzxe&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ymFaZJt8Pc1sIL +Content-Length: 1385 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:55 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFaGoesj9fw9QyN4KCcj0", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396655, + "customer" : null, + "type" : "bacs_debit" + }, + "client_secret" : "pi_3PiTFbGoesj9fw9Q1Ex7WyLP_secret_qIeVirZxNrsHZV1Nl2qsKhzxe", + "id" : "pi_3PiTFbGoesj9fw9Q1Ex7WyLP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : null, + "created" : 1722396655, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..38dd5def --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Zinoxh3QRCKuaJJB3kv%2BKXBhk%2Fg%2BhtG3Q4l41QQU4ELQFpdGEiCCx2Rnxd1oScm6c%2FYVsoMbsnI9YkiVc8zMdq5CustHlwA7ELZs24xzzi9UfTkJX0huAY2uYIsKu9PFVIWmU16HXuzh8vRW0zZaG%2BBdMqJniaVTQqA6kU0I4uuGVg2C%2FspKLBodSXD60ZuLBbVb2C4sjWz%2FvNqejj2GXSSylIKcAd3KUA9lPui6JvQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bd0826bae22a284cb95d354332f461e4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:56 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFcGoesj9fw9Q0toFtRG6","secret":"pi_3PiTFcGoesj9fw9Q0toFtRG6_secret_wo4mQH4CNReUwtT7QJ2INaKSo","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFcGoesj9fw9Q0toFtRG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFcGoesj9fw9Q0toFtRG6.tail new file mode 100644 index 00000000..e1baddfe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFcGoesj9fw9Q0toFtRG6.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFcGoesj9fw9Q0toFtRG6\?client_secret=pi_3PiTFcGoesj9fw9Q0toFtRG6_secret_wo4mQH4CNReUwtT7QJ2INaKSo$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1C6w9c4pqMlsLe +Content-Length: 805 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFcGoesj9fw9Q0toFtRG6_secret_wo4mQH4CNReUwtT7QJ2INaKSo", + "id" : "pi_3PiTFcGoesj9fw9Q0toFtRG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396656, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0012_post_v1_payment_intents_pi_3PiTFcGoesj9fw9Q0toFtRG6_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0012_post_v1_payment_intents_pi_3PiTFcGoesj9fw9Q0toFtRG6_confirm.tail new file mode 100644 index 00000000..5ec942a6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0012_post_v1_payment_intents_pi_3PiTFcGoesj9fw9Q0toFtRG6_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFcGoesj9fw9Q0toFtRG6\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lXIPgVItQy8lBi +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1394 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:57 GMT +original-request: req_lXIPgVItQy8lBi +stripe-version: 2020-08-27 +idempotency-key: f9efb0e1-fbaf-4cda-b339-e301cd8599ea +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFcGoesj9fw9Q0toFtRG6_secret_wo4mQH4CNReUwtT7QJ2INaKSo&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[bacs_debit]\[account_number]=00012345&payment_method_data\[bacs_debit]\[sort_code]=108800&payment_method_data\[billing_details]\[address]\[city]=asdf&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=asdf&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=AL&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=bacs_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFdGoesj9fw9QepjBvXpT", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396657, + "customer" : null, + "type" : "bacs_debit" + }, + "client_secret" : "pi_3PiTFcGoesj9fw9Q0toFtRG6_secret_wo4mQH4CNReUwtT7QJ2INaKSo", + "id" : "pi_3PiTFcGoesj9fw9Q0toFtRG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396656, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..a5d6f06f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0013_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_noRayIYtBffw9v +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 554 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:58 GMT +original-request: req_noRayIYtBffw9v +stripe-version: 2020-08-27 +idempotency-key: 29d8e6be-4d16-42de-a417-0000b47bb7c6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&bacs_debit\[account_number]=00012345&bacs_debit\[sort_code]=108800&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bacs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFdGoesj9fw9QTKBamGPo", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396658, + "customer" : null, + "type" : "bacs_debit" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..5f7c0cde --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9LBe19hZQcmUPaP0lEuuf%2BBr6B%2BNb7ubyQwBEMLD%2FmjcNn8Rgi2%2FG2h5Y8mDFIoospGeiD7itIHwhxTcvpo4UtitJU6r%2F35b4N6PoGRqo04JKqdWd8sRUOmww8T9Z4LODYG24k%2B3qRIv%2FIDbK7J%2FDo9wk41mjLHq3F0bzrcV4SDO314Ihd5sfL6ENiMJicml1iYHhaOH62YDlUSauWRLE2VwIy%2BKRmKaDQ066GPztjc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b3952189efca966f6807291a6f02f9b1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:30:58 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFeGoesj9fw9Q1UcMUUqn","secret":"pi_3PiTFeGoesj9fw9Q1UcMUUqn_secret_mQRT5dEUv0MBmkRuFoYg3w1yI","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0015_get_v1_payment_intents_pi_3PiTFeGoesj9fw9Q1UcMUUqn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0015_get_v1_payment_intents_pi_3PiTFeGoesj9fw9Q1UcMUUqn.tail new file mode 100644 index 00000000..d1f4a48c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0015_get_v1_payment_intents_pi_3PiTFeGoesj9fw9Q1UcMUUqn.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFeGoesj9fw9Q1UcMUUqn\?client_secret=pi_3PiTFeGoesj9fw9Q1UcMUUqn_secret_mQRT5dEUv0MBmkRuFoYg3w1yI&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZhXCAcvKUsWSql +Content-Length: 805 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFeGoesj9fw9Q1UcMUUqn_secret_mQRT5dEUv0MBmkRuFoYg3w1yI", + "id" : "pi_3PiTFeGoesj9fw9Q1UcMUUqn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396658, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0016_post_v1_payment_intents_pi_3PiTFeGoesj9fw9Q1UcMUUqn_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0016_post_v1_payment_intents_pi_3PiTFeGoesj9fw9Q1UcMUUqn_confirm.tail new file mode 100644 index 00000000..10bddacf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0016_post_v1_payment_intents_pi_3PiTFeGoesj9fw9Q1UcMUUqn_confirm.tail @@ -0,0 +1,89 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFeGoesj9fw9Q1UcMUUqn\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YMk5PS2n6DiVdz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1394 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:59 GMT +original-request: req_YMk5PS2n6DiVdz +stripe-version: 2020-08-27 +idempotency-key: 4b18c15d-bce4-410b-9c50-3e937a4bfdb8 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFeGoesj9fw9Q1UcMUUqn_secret_mQRT5dEUv0MBmkRuFoYg3w1yI&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFdGoesj9fw9QTKBamGPo&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFdGoesj9fw9QTKBamGPo", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396658, + "customer" : null, + "type" : "bacs_debit" + }, + "client_secret" : "pi_3PiTFeGoesj9fw9Q1UcMUUqn_secret_mQRT5dEUv0MBmkRuFoYg3w1yI", + "id" : "pi_3PiTFeGoesj9fw9Q1UcMUUqn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396658, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..d1f687a3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tHx4cHQVDzkVmW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 554 +Vary: Origin +Date: Wed, 31 Jul 2024 03:30:59 GMT +original-request: req_tHx4cHQVDzkVmW +stripe-version: 2020-08-27 +idempotency-key: 0a6c38e7-290a-4428-bf8d-a157b823bdcd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&bacs_debit\[account_number]=00012345&bacs_debit\[sort_code]=108800&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bacs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiTFfGoesj9fw9QgcWYZj0W", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396659, + "customer" : null, + "type" : "bacs_debit" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..84387bb1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NpvJoA0f5e7Dq9PpNgSSlJD9cXqI%2Fc037JYU8nteInrcQWLRftZYR0lthbtJCN0mRJF9QTuUB5UL0a4E5NrJV8%2BB8U%2BLic7168k0EqutqwlVnvKywNSIoaspMnwbhBIlbFBTjU3b69MfeC84OblIZawyYGSEyaCCHhkrMNbd1ByCqkpAVguxZuti2kTKpfJ7QENDPY8RcWZon080Zw%2F9fTYQK%2BSzn9xdIUmYlSkbowE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7fe79154dd69b3461055a52246cd41e6;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFgGoesj9fw9Q1Adpxm6L","secret":"pi_3PiTFgGoesj9fw9Q1Adpxm6L_secret_5F8DaYb97pkNHKld9FJnUsuRn","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFgGoesj9fw9Q1Adpxm6L.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFgGoesj9fw9Q1Adpxm6L.tail new file mode 100644 index 00000000..9a49a0af --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBacsDDConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFgGoesj9fw9Q1Adpxm6L.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFgGoesj9fw9Q1Adpxm6L\?client_secret=pi_3PiTFgGoesj9fw9Q1Adpxm6L_secret_5F8DaYb97pkNHKld9FJnUsuRn&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_M1slACDT1ZZclP +Content-Length: 1394 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFfGoesj9fw9QgcWYZj0W", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "t1p3TcYKGDRQAC4o", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722396659, + "customer" : null, + "type" : "bacs_debit" + }, + "client_secret" : "pi_3PiTFgGoesj9fw9Q1Adpxm6L_secret_5F8DaYb97pkNHKld9FJnUsuRn", + "id" : "pi_3PiTFgGoesj9fw9Q1Adpxm6L", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bacs_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396660, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..7397cd59 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=pRA9GZ%2FX4G12kjvFNfES3WjBKMI8ya1oqxGYmVFa1V3BLUGlfjkSFbdTeCJ1NdaddL10b1FuSoEcuTcbRJY71sLcnoiMMyAjvu6GA20i20st29CV5PrEd4xamyioKQkEhrIfn6vWXkikeUINTNXK6DSeuMbUPngPv%2FnextdtcMyK7CPbCghmPxVeORVm8jrk%2BcNDccy8CyiuPfTDe1EU4I1hsJ7sKt1PNsCvjxh340A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5ea013f91fbc154fb70da60b017223f6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFhFY0qyl6XeW0jmVo7PO","secret":"pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO.tail new file mode 100644 index 00000000..da85f180 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFhFY0qyl6XeW0jmVo7PO\?client_secret=pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yJsDErK4nCpD2a +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO", + "id" : "pi_3PiTFhFY0qyl6XeW0jmVo7PO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396661, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO_confirm.tail new file mode 100644 index 00000000..5366211c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFhFY0qyl6XeW0jmVo7PO\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OsxfoiuxdFxyRZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1606 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:02 GMT +original-request: req_OsxfoiuxdFxyRZ +stripe-version: 2020-08-27 +idempotency-key: 62dac5e2-52cd-4ad1-b67a-579f2a1b1685 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=bancontact&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUmfLBMKUzisXdZV1zYQrRkrGxIHT" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFhFY0qyl6XeW0qVz5JdT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396661, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO", + "id" : "pi_3PiTFhFY0qyl6XeW0jmVo7PO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396661, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0003_get_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0003_get_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO.tail new file mode 100644 index 00000000..76638fa1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0003_get_v1_payment_intents_pi_3PiTFhFY0qyl6XeW0jmVo7PO.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFhFY0qyl6XeW0jmVo7PO\?client_secret=pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XamB0t09UzfSjL +Content-Length: 1606 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUmfLBMKUzisXdZV1zYQrRkrGxIHT" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFhFY0qyl6XeW0qVz5JdT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396661, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFhFY0qyl6XeW0jmVo7PO_secret_dE9YPgHXewY50EUnwVMTyicIO", + "id" : "pi_3PiTFhFY0qyl6XeW0jmVo7PO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396661, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..35228f3d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fHS8446onPejv6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:03 GMT +original-request: req_fHS8446onPejv6 +stripe-version: 2020-08-27 +idempotency-key: 1c375335-14fb-4a70-8a9f-c948a6195ef9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Foo&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiTFjFY0qyl6XeWsSneKP3h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396663, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..1534601c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=yD9LCtDySF2383eWKq0n0HJ1ws9CeHbh15xZt4lraHH%2BRObXRUVsM%2BCFSLPe5w5WHEIZb4UghlKePEUbj71EV5CCv32VU143DUKe2PGMYYQzGGjsF5TToDYz5ZCMUhAcv64T2fbT%2FwvNS%2BD5TpWh5ju8a2Xei8eIUuIRiUmCBkUOmaLJ1xpfZ2YehHzxlrGknsa0l1blgtknE0LbFLrQhtCuGePNTbARUIvsrv1brwg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 79afbea92b498d6d248cbe323fac8d9b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFjFY0qyl6XeW1fcdU0OJ","secret":"pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0006_get_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0006_get_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ.tail new file mode 100644 index 00000000..792581f3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0006_get_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFjFY0qyl6XeW1fcdU0OJ\?client_secret=pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_U6POnVw2TP2boQ +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd", + "id" : "pi_3PiTFjFY0qyl6XeW1fcdU0OJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396663, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0007_post_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0007_post_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ_confirm.tail new file mode 100644 index 00000000..bff0e7d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0007_post_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFjFY0qyl6XeW1fcdU0OJ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WVZuMLRG9rsx9R +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1606 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:04 GMT +original-request: req_WVZuMLRG9rsx9R +stripe-version: 2020-08-27 +idempotency-key: 8f2f80e8-7dda-4fa8-9df3-c249075dae36 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFjFY0qyl6XeWsSneKP3h&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUXzNTR4z6OoRSrFSiPFOB2Oso1QI" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFjFY0qyl6XeWsSneKP3h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396663, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd", + "id" : "pi_3PiTFjFY0qyl6XeW1fcdU0OJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396663, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0008_get_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0008_get_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ.tail new file mode 100644 index 00000000..926699f9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0008_get_v1_payment_intents_pi_3PiTFjFY0qyl6XeW1fcdU0OJ.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFjFY0qyl6XeW1fcdU0OJ\?client_secret=pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kYrHfoe9Mx3hrj +Content-Length: 1606 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUXzNTR4z6OoRSrFSiPFOB2Oso1QI" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFjFY0qyl6XeWsSneKP3h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396663, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFjFY0qyl6XeW1fcdU0OJ_secret_xCCLqpjeqAJNs3QpAaNPpTHRd", + "id" : "pi_3PiTFjFY0qyl6XeW1fcdU0OJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396663, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..cb003d8b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BqdvVLAmLZLtJI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:05 GMT +original-request: req_BqdvVLAmLZLtJI +stripe-version: 2020-08-27 +idempotency-key: 33ecf76f-b7f0-447f-8515-dc171c85012f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Foo&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiTFlFY0qyl6XeWpI66P5HC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396665, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..8b20cc9f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=73qdY%2FveAx7nLfB77V7kwg3KcMhfYMkdAsixpdf6thArBS6U3CT4Pg3o5aqsqEVRPynElVdHVyYmDwCaiNPBS00980H3D9d7cueDlqB2yRTJOUkCm2Dxg2Iv%2FvX%2BKojmpKkT%2FMaCtIWntkqF6KNklqe12rhDin8I3NoxcualriJyowdFKBosiXbqhoc0ZbUCOdfdvCaWmLCMbUKgkLkmfGeSCwvLJMM3lfc%2BCW1VpXE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 70fbe0998ded02b62469ed8018b20503 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFlFY0qyl6XeW02rwAJgQ","secret":"pi_3PiTFlFY0qyl6XeW02rwAJgQ_secret_8jNxLDugVoTc40CJejIne9ERX","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFlFY0qyl6XeW02rwAJgQ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFlFY0qyl6XeW02rwAJgQ.tail new file mode 100644 index 00000000..9d94e9f1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0011_get_v1_payment_intents_pi_3PiTFlFY0qyl6XeW02rwAJgQ.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFlFY0qyl6XeW02rwAJgQ\?client_secret=pi_3PiTFlFY0qyl6XeW02rwAJgQ_secret_8jNxLDugVoTc40CJejIne9ERX&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lvh3z3e83TpoDw +Content-Length: 1600 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUDgjUZChd3qG3aRm6gX0JT0kudem" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFlFY0qyl6XeWpI66P5HC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396665, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFlFY0qyl6XeW02rwAJgQ_secret_8jNxLDugVoTc40CJejIne9ERX", + "id" : "pi_3PiTFlFY0qyl6XeW02rwAJgQ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396665, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0012_get_v1_payment_intents_pi_3PiTFlFY0qyl6XeW02rwAJgQ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0012_get_v1_payment_intents_pi_3PiTFlFY0qyl6XeW02rwAJgQ.tail new file mode 100644 index 00000000..4f180c43 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0012_get_v1_payment_intents_pi_3PiTFlFY0qyl6XeW02rwAJgQ.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFlFY0qyl6XeW02rwAJgQ\?client_secret=pi_3PiTFlFY0qyl6XeW02rwAJgQ_secret_8jNxLDugVoTc40CJejIne9ERX&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_w0EHY4dHKbqwG4 +Content-Length: 1600 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUDgjUZChd3qG3aRm6gX0JT0kudem" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFlFY0qyl6XeWpI66P5HC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396665, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFlFY0qyl6XeW02rwAJgQ_secret_8jNxLDugVoTc40CJejIne9ERX", + "id" : "pi_3PiTFlFY0qyl6XeW02rwAJgQ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1722396665, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..3eff4ccd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=CaT1caNM%2BpYS6yPJuu1nyHWtHizlnxW1CtcM04JCgvatVudUT3LEtrTwf7eGI28tvNWkyRM4wmeD9vqoHfzVDdxP4Y77%2BMEBq9PeNrmTmbmPdoIku58E6eLe28eD7Yn0dqsLfEp9lMRfwCCf%2F7Ur0XKZbyeqCNM718ivBT6yaExfzPFUnIyQ2rO3opvTwx6yQASoJ2JGtvYTbb6S1vjAecVAf0Z%2BBw%2Fzgk%2FzhV76HLU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4a92b6123910efafa7beb2160b8b3d66 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFmFY0qyl6XeW0ktOZq7I","secret":"pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0014_get_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0014_get_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I.tail new file mode 100644 index 00000000..314b93df --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0014_get_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFmFY0qyl6XeW0ktOZq7I\?client_secret=pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8InVvgwDL3kJUj +Content-Length: 904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2", + "id" : "pi_3PiTFmFY0qyl6XeW0ktOZq7I", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396666, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0015_post_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0015_post_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I_confirm.tail new file mode 100644 index 00000000..83359272 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0015_post_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFmFY0qyl6XeW0ktOZq7I\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Lii1OWtjmFuRwP +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1618 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:07 GMT +original-request: req_Lii1OWtjmFuRwP +stripe-version: 2020-08-27 +idempotency-key: 15926604-0bde-4d72-a155-da56f6df339e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=bancontact&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUfkEBPZ5LDwV9QlZMUMsjo8qwODS" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFnFY0qyl6XeWssOywnGK", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396667, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2", + "id" : "pi_3PiTFmFY0qyl6XeW0ktOZq7I", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396666, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0016_get_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0016_get_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I.tail new file mode 100644 index 00000000..ae93f76a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0016_get_v1_payment_intents_pi_3PiTFmFY0qyl6XeW0ktOZq7I.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFmFY0qyl6XeW0ktOZq7I\?client_secret=pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hnaQJXh0A2EtDS +Content-Length: 1618 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUfkEBPZ5LDwV9QlZMUMsjo8qwODS" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFnFY0qyl6XeWssOywnGK", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396667, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFmFY0qyl6XeW0ktOZq7I_secret_SnBVg95fEbOKALZ70zckkrmw2", + "id" : "pi_3PiTFmFY0qyl6XeW0ktOZq7I", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396666, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..43c5aba7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8UF9DSNsZh2afS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 460 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:08 GMT +original-request: req_8UF9DSNsZh2afS +stripe-version: 2020-08-27 +idempotency-key: 4e48a236-15e7-4149-97ff-6756430c45fb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiTFoFY0qyl6XeW65kVKD9l", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396668, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..17c9ae72 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=E%2B9BQ%2B6u5NImkq8uRW5%2BGeWKmeKtIfSTFbE5tLf%2BaYFuJ1bbafIKJ0Mh1cFives1FjVSZxOhxCY8YuGys0%2BBYfF1okyDhNResxAKWylktVSeM56704RLAsL9vjFvf7mUr9PhBJD8XpQv2NlFx2krIlIciRdKHMxDRiisuFCzFVCiEhGTe8I3i5lR2ucIXQmQIziTK6hGIUYwegM41zY%2FAjTlpfvWaSvvmJcSVmEK44g%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0ccccc923a00649f4ffe87d56f9d5c9a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFoFY0qyl6XeW0IzVfqHs","secret":"pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs.tail new file mode 100644 index 00000000..1091e2eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0019_get_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFoFY0qyl6XeW0IzVfqHs\?client_secret=pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5icq2zHkzvxApb +Content-Length: 904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp", + "id" : "pi_3PiTFoFY0qyl6XeW0IzVfqHs", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396668, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0020_post_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0020_post_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs_confirm.tail new file mode 100644 index 00000000..436b5477 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0020_post_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFoFY0qyl6XeW0IzVfqHs\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EZzIO5cmPmJRm7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1618 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:09 GMT +original-request: req_EZzIO5cmPmJRm7 +stripe-version: 2020-08-27 +idempotency-key: 6ba8e160-51b7-4903-ba29-073b8dbd40f4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFoFY0qyl6XeW65kVKD9l&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUylh1vzrRHAcqPVW2zE40ypYEYvt" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFoFY0qyl6XeW65kVKD9l", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396668, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp", + "id" : "pi_3PiTFoFY0qyl6XeW0IzVfqHs", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396668, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0021_get_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0021_get_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs.tail new file mode 100644 index 00000000..e1600d21 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0021_get_v1_payment_intents_pi_3PiTFoFY0qyl6XeW0IzVfqHs.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFoFY0qyl6XeW0IzVfqHs\?client_secret=pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_piOVWL4G8dVzZo +Content-Length: 1618 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUylh1vzrRHAcqPVW2zE40ypYEYvt" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFoFY0qyl6XeW65kVKD9l", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396668, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFoFY0qyl6XeW0IzVfqHs_secret_NbVv9BevVMgoOql26ynuvSCUp", + "id" : "pi_3PiTFoFY0qyl6XeW0IzVfqHs", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396668, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0022_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0022_post_v1_payment_methods.tail new file mode 100644 index 00000000..733dfbd8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0022_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Kmhw2sGnqOvBKB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 460 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:10 GMT +original-request: req_Kmhw2sGnqOvBKB +stripe-version: 2020-08-27 +idempotency-key: d5c0469c-c601-48f5-9324-a51ac7ae3373 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiTFqFY0qyl6XeW41AWCkPZ", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396670, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0023_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0023_post_create_payment_intent.tail new file mode 100644 index 00000000..cd7034e4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0023_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Ra6zbR9K8TqZo58XuKm7vmDDPelIEGTde%2FhdTtPUY%2FrlYz6gPFl0gbznJkBP0KppTgWvkqcILbG54OYdCX6tOcmkfSnXIgEam5zjMA5naf2XUG4UDePsR97VpwbOTn1LWSVnk%2BPF541Cb81FPhTrv3CxVuqJB%2Bnms5aq5ZLKRh%2Bm5RXbhzc2nlUsUtpE8k%2F%2FE45Q1teNjhCW1uf6CTt0NTdRCGsu2c1xfm4x3hnSPHQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7c3174b8e12a24498eebf0d5a9fd5810;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFqFY0qyl6XeW0n428V7D","secret":"pi_3PiTFqFY0qyl6XeW0n428V7D_secret_DyHlzp4JnXNOzKl9pzfkIc6hB","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0024_get_v1_payment_intents_pi_3PiTFqFY0qyl6XeW0n428V7D.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0024_get_v1_payment_intents_pi_3PiTFqFY0qyl6XeW0n428V7D.tail new file mode 100644 index 00000000..7f5b595c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0024_get_v1_payment_intents_pi_3PiTFqFY0qyl6XeW0n428V7D.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFqFY0qyl6XeW0n428V7D\?client_secret=pi_3PiTFqFY0qyl6XeW0n428V7D_secret_DyHlzp4JnXNOzKl9pzfkIc6hB&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WxpoQVc1NTErDE +Content-Length: 1612 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUYXgZXSFQ8YQyN5WcBRYCqfxDn4U" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFqFY0qyl6XeW41AWCkPZ", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396670, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFqFY0qyl6XeW0n428V7D_secret_DyHlzp4JnXNOzKl9pzfkIc6hB", + "id" : "pi_3PiTFqFY0qyl6XeW0n428V7D", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396670, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0025_get_v1_payment_intents_pi_3PiTFqFY0qyl6XeW0n428V7D.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0025_get_v1_payment_intents_pi_3PiTFqFY0qyl6XeW0n428V7D.tail new file mode 100644 index 00000000..a6bb23e9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0025_get_v1_payment_intents_pi_3PiTFqFY0qyl6XeW0n428V7D.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFqFY0qyl6XeW0n428V7D\?client_secret=pi_3PiTFqFY0qyl6XeW0n428V7D_secret_DyHlzp4JnXNOzKl9pzfkIc6hB&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_h9UGnDELp53r9g +Content-Length: 1612 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcUYXgZXSFQ8YQyN5WcBRYCqfxDn4U" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFqFY0qyl6XeW41AWCkPZ", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396670, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_3PiTFqFY0qyl6XeW0n428V7D_secret_DyHlzp4JnXNOzKl9pzfkIc6hB", + "id" : "pi_3PiTFqFY0qyl6XeW0n428V7D", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : "off_session", + "created" : 1722396670, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0026_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0026_post_create_setup_intent.tail new file mode 100644 index 00000000..f3cd4738 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0026_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OyNKt5cClmECJW8r1QLZ1Nx0DgNNjpR8feCwGeihx1rIiAu9ddN%2B9w7D34M9400Zpee08%2BYrA7Yp2x1e%2F33fVpU28yzoXz6GJIN6rvkPEfzIIvbRj2A%2BeW7mLwONxx5Osa5cbdtY0AnwCeUo0hfb2d0NWxfnHwXXP5aqVF8wGkqG%2BOQwyejkJummp6QuW%2BgKg8TduubUlp7BIllFKjnXwDp%2FCW6n6a2SingJOmCzR48%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: de0b7800f64b9b626dc6a1ea3a60c070 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTFsFY0qyl6XeW7jaV90Md","secret":"seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0027_get_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0027_get_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md.tail new file mode 100644 index 00000000..44148ad7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0027_get_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFsFY0qyl6XeW7jaV90Md\?client_secret=seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IYKiNUGn0j9Yd3 +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFsFY0qyl6XeW7jaV90Md", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396672, + "client_secret" : "seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0028_post_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0028_post_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md_confirm.tail new file mode 100644 index 00000000..e80eeee4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0028_post_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFsFY0qyl6XeW7jaV90Md\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VcimhwcF4fxCHc +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1253 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:13 GMT +original-request: req_VcimhwcF4fxCHc +stripe-version: 2020-08-27 +idempotency-key: fb5958b3-d405-4741-abed-5635f04392c2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=bancontact&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTFsFY0qyl6XeW7jaV90Md", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcURWqpybnbeLCmqrIyhRFtAKGIDhs" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFsFY0qyl6XeWnthXR0sW", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396672, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396672, + "client_secret" : "seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0029_get_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0029_get_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md.tail new file mode 100644 index 00000000..2861d861 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0029_get_v1_setup_intents_seti_1PiTFsFY0qyl6XeW7jaV90Md.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFsFY0qyl6XeW7jaV90Md\?client_secret=seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SPaPDMPz56QQ3w +Content-Length: 1253 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFsFY0qyl6XeW7jaV90Md", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcURWqpybnbeLCmqrIyhRFtAKGIDhs" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFsFY0qyl6XeWnthXR0sW", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396672, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396672, + "client_secret" : "seti_1PiTFsFY0qyl6XeW7jaV90Md_secret_QZcUtvXa3CvJWYpIH1RITT6GRnV89cO", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0030_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0030_post_v1_payment_methods.tail new file mode 100644 index 00000000..6a67d277 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0030_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RiUp4V4QcMIJ7Z +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 460 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:13 GMT +original-request: req_RiUp4V4QcMIJ7Z +stripe-version: 2020-08-27 +idempotency-key: dc8a6914-13dc-4fd6-837c-0369b2dcc4a0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiTFtFY0qyl6XeWM8xIfxrr", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396673, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0031_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0031_post_create_setup_intent.tail new file mode 100644 index 00000000..aaea1508 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0031_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=KbtvUNABf6%2BFCjhoCdbav0LATtsnLJbJHSQx6nkhLGD9Jizb50KQiFOZmzyPNTrFZA3k1chiGKpW6nvjCPQBrdnppWRi%2B%2F1KbHFSCRsySCSYjPW0e%2FuXhK54u7MXfXQJ%2FN1xnU0brvXCmAzJQX8TSN%2BdY41q%2BjeE6teV9t04%2FN%2FDJkRveU00KwQVb%2FYmFtUXcv98EXWmR83NdvS9uu%2FAb5EWphtnvEwbOvzJSzZqzvU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2dbe8b7ad9a846c9a1c62b8a9b7254f7 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTFtFY0qyl6XeWBaOXa6Hu","secret":"seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0032_get_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0032_get_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu.tail new file mode 100644 index 00000000..c94e9eda --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0032_get_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFtFY0qyl6XeWBaOXa6Hu\?client_secret=seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UaR2rLqO1xw5hG +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFtFY0qyl6XeWBaOXa6Hu", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396673, + "client_secret" : "seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0033_post_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0033_post_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu_confirm.tail new file mode 100644 index 00000000..05871657 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0033_post_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFtFY0qyl6XeWBaOXa6Hu\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tr654rxh5avLIn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1253 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:14 GMT +original-request: req_tr654rxh5avLIn +stripe-version: 2020-08-27 +idempotency-key: d6dae7d6-f664-455f-82ca-eec5d439396d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTFtFY0qyl6XeWM8xIfxrr&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTFtFY0qyl6XeWBaOXa6Hu", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcUkP1tjVJEUCe8qbNoym28lTypRyx" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFtFY0qyl6XeWM8xIfxrr", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396673, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396673, + "client_secret" : "seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0034_get_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0034_get_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu.tail new file mode 100644 index 00000000..a6f51f65 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0034_get_v1_setup_intents_seti_1PiTFtFY0qyl6XeWBaOXa6Hu.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFtFY0qyl6XeWBaOXa6Hu\?client_secret=seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_b8HTC80v4Dd8ZT +Content-Length: 1253 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFtFY0qyl6XeWBaOXa6Hu", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcUkP1tjVJEUCe8qbNoym28lTypRyx" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFtFY0qyl6XeWM8xIfxrr", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396673, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396673, + "client_secret" : "seti_1PiTFtFY0qyl6XeWBaOXa6Hu_secret_QZcUH1U6rZPRHULU9J2OHo4OoSDGJEL", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0035_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0035_post_v1_payment_methods.tail new file mode 100644 index 00000000..228ec015 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0035_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_z2OhkLD6t9hLPR +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 460 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:15 GMT +original-request: req_z2OhkLD6t9hLPR +stripe-version: 2020-08-27 +idempotency-key: bb0801ee-b0bb-4ed4-8db1-a3f569aa1162 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiTFvFY0qyl6XeWjcum2ynN", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396675, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0036_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0036_post_create_setup_intent.tail new file mode 100644 index 00000000..f29c49a1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0036_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=P1mq4mnKHRkwCyhxa6P8qYLiy9ZTjuTDGWJRN3jGnqIyjPwZHCXYVeJJpla2pxcxdX6ug4Bx6jBWM9JCukoBofUj5I5LIvdHHedjaktpEIgmjVcIu%2Bf6U8dE%2B7B1pZIWZ3Qi2LTVXWGc8T2hlekpwzNu5ybWo7xCJn0qPsV30lHmUacc9EvBsj2z3OdMj9vR8CbinuNAIYtXD1RjMyz86fe8G3ghGfQ8he0MByBIDfg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d698c02f747395f0644b1b1bbe1fc4ea +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:15 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTFvFY0qyl6XeWtjYpzRrr","secret":"seti_1PiTFvFY0qyl6XeWtjYpzRrr_secret_QZcUJOrCWz105q7Boeb25gkTBpMaLYM","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0037_get_v1_setup_intents_seti_1PiTFvFY0qyl6XeWtjYpzRrr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0037_get_v1_setup_intents_seti_1PiTFvFY0qyl6XeWtjYpzRrr.tail new file mode 100644 index 00000000..09399514 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0037_get_v1_setup_intents_seti_1PiTFvFY0qyl6XeWtjYpzRrr.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFvFY0qyl6XeWtjYpzRrr\?client_secret=seti_1PiTFvFY0qyl6XeWtjYpzRrr_secret_QZcUJOrCWz105q7Boeb25gkTBpMaLYM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xHxvLgUEWGzWYm +Content-Length: 1247 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFvFY0qyl6XeWtjYpzRrr", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcUmsq0TvW9Y54INckuEjqyAgr4m7B" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFvFY0qyl6XeWjcum2ynN", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396675, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396675, + "client_secret" : "seti_1PiTFvFY0qyl6XeWtjYpzRrr_secret_QZcUJOrCWz105q7Boeb25gkTBpMaLYM", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0038_get_v1_setup_intents_seti_1PiTFvFY0qyl6XeWtjYpzRrr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0038_get_v1_setup_intents_seti_1PiTFvFY0qyl6XeWtjYpzRrr.tail new file mode 100644 index 00000000..aadb01f9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBancontactConfirmFlows/0038_get_v1_setup_intents_seti_1PiTFvFY0qyl6XeWtjYpzRrr.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTFvFY0qyl6XeWtjYpzRrr\?client_secret=seti_1PiTFvFY0qyl6XeWtjYpzRrr_secret_QZcUJOrCWz105q7Boeb25gkTBpMaLYM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YvktLPmJTBUdPU +Content-Length: 1247 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTFvFY0qyl6XeWtjYpzRrr", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcUmsq0TvW9Y54INckuEjqyAgr4m7B" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTFvFY0qyl6XeWjcum2ynN", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396675, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "bancontact" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396675, + "client_secret" : "seti_1PiTFvFY0qyl6XeWtjYpzRrr_secret_QZcUJOrCWz105q7Boeb25gkTBpMaLYM", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..915e4bb0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=FHWhfzJjaXWdCEZojTrYMYHnnEpJAY0boBle6KUbq3n1EAaZcjfstVPtyJ1SRwAY28j2cEMJA3BUfxUFS4wFa0uTxzFkFZu5d554d9R9pOP%2BMIx55GhsbKEybSz6fob9MSj0EsHkWFsZEvDuuT%2Bu8ydtX48GvMaOPy8vDXF4gHVoqcGy7Wm1z1tvKEe76AeXm0%2BQuY8zwzKGwCk8xctPn9R3pPUh1Xbb6NdMRr3W3zI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e68cbf5a535bbbdc894e2650691752d6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 26 Jul 2024 21:42:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pgvu8Goesj9fw9Q0IOK5vPe","secret":"pi_3Pgvu8Goesj9fw9Q0IOK5vPe_secret_NFDSK09UwFzYDlO9dwCWHDQek","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0001_get_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0001_get_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe.tail new file mode 100644 index 00000000..361c8917 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0001_get_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pgvu8Goesj9fw9Q0IOK5vPe\?client_secret=pi_3Pgvu8Goesj9fw9Q0IOK5vPe_secret_NFDSK09UwFzYDlO9dwCWHDQek$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_q3tqdnuUsmw8LG +Content-Length: 797 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Pgvu8Goesj9fw9Q0IOK5vPe_secret_NFDSK09UwFzYDlO9dwCWHDQek", + "id" : "pi_3Pgvu8Goesj9fw9Q0IOK5vPe", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030144, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0002_post_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0002_post_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe_confirm.tail new file mode 100644 index 00000000..5b1356e0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0002_post_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe_confirm.tail @@ -0,0 +1,92 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pgvu8Goesj9fw9Q0IOK5vPe\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bHhQZamVJ28yHA +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1509 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:26 GMT +original-request: req_bHhQZamVJ28yHA +stripe-version: 2020-08-27 +idempotency-key: 5178cf8b-5e27-4610-b3ce-130cf481704b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QY1yiL7DYx2QlKoHAXYF6pfWHCSO6MN" + } + }, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1Pgvu8Goesj9fw9QmWWXiORC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030144, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3Pgvu8Goesj9fw9Q0IOK5vPe_secret_NFDSK09UwFzYDlO9dwCWHDQek", + "id" : "pi_3Pgvu8Goesj9fw9Q0IOK5vPe", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030144, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0003_get_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0003_get_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe.tail new file mode 100644 index 00000000..1488bbf9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0003_get_v1_payment_intents_pi_3Pgvu8Goesj9fw9Q0IOK5vPe.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pgvu8Goesj9fw9Q0IOK5vPe\?client_secret=pi_3Pgvu8Goesj9fw9Q0IOK5vPe_secret_NFDSK09UwFzYDlO9dwCWHDQek&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KXD1322rkIRKkW +Content-Length: 1509 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QY1yiL7DYx2QlKoHAXYF6pfWHCSO6MN" + } + }, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1Pgvu8Goesj9fw9QmWWXiORC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030144, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3Pgvu8Goesj9fw9Q0IOK5vPe_secret_NFDSK09UwFzYDlO9dwCWHDQek", + "id" : "pi_3Pgvu8Goesj9fw9Q0IOK5vPe", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030144, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..e205dca6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,54 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dP1Z6tgitvPrRa +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 458 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:26 GMT +original-request: req_dP1Z6tgitvPrRa +stripe-version: 2020-08-27 +idempotency-key: bbc21d2a-a5e5-4563-875e-f3098b8fd35d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PgvuAGoesj9fw9QR4OkgfII", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030146, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..ebd4a314 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=SDxIL8wvIyYa3WU3VltxDZDZjZ8zBwxkkhi8nLgr56%2F1ylEXUWznbNXHy38S6b7NyNq5IMxmxXu5hRldw3AVcRd0UOq%2FjdrqeVUsmQtjWjnRJgPXyBfzOO949gDYde1cyRcSRhDhOJC12I4k3DXq4IwA%2BXbexrpg5dScEuu2SMqFxqSr7N8YAiq%2FdBWNTLWF%2BEN59vf5TaDBGF2i%2FXl10Qg9pexWk%2Fkau6DjvtbFzXY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cfa13cd9547f70308fa95b9cda09400d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 26 Jul 2024 21:42:27 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PgvuAGoesj9fw9Q0HWarpDa","secret":"pi_3PgvuAGoesj9fw9Q0HWarpDa_secret_LNPzGgo19UBygpt0mepoRUuT6","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0006_get_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0006_get_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa.tail new file mode 100644 index 00000000..3f9b8e6b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0006_get_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PgvuAGoesj9fw9Q0HWarpDa\?client_secret=pi_3PgvuAGoesj9fw9Q0HWarpDa_secret_LNPzGgo19UBygpt0mepoRUuT6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Q0f8uGpPI6x8pJ +Content-Length: 797 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PgvuAGoesj9fw9Q0HWarpDa_secret_LNPzGgo19UBygpt0mepoRUuT6", + "id" : "pi_3PgvuAGoesj9fw9Q0HWarpDa", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030146, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0007_post_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0007_post_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa_confirm.tail new file mode 100644 index 00000000..7c3ce499 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0007_post_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa_confirm.tail @@ -0,0 +1,92 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PgvuAGoesj9fw9Q0HWarpDa\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LrXiHGm2Z1JRmA +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1509 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:28 GMT +original-request: req_LrXiHGm2Z1JRmA +stripe-version: 2020-08-27 +idempotency-key: a365dfcf-4da9-4aa3-a33c-93165c64c5cb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QY1yxXlDy1lRykMmxuJhTCsyxvFWO9V" + } + }, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PgvuAGoesj9fw9QR4OkgfII", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030146, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3PgvuAGoesj9fw9Q0HWarpDa_secret_LNPzGgo19UBygpt0mepoRUuT6", + "id" : "pi_3PgvuAGoesj9fw9Q0HWarpDa", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030146, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0008_get_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0008_get_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa.tail new file mode 100644 index 00000000..cf13bddd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0008_get_v1_payment_intents_pi_3PgvuAGoesj9fw9Q0HWarpDa.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PgvuAGoesj9fw9Q0HWarpDa\?client_secret=pi_3PgvuAGoesj9fw9Q0HWarpDa_secret_LNPzGgo19UBygpt0mepoRUuT6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_d7ETkigP9Pei5d +Content-Length: 1509 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QY1yxXlDy1lRykMmxuJhTCsyxvFWO9V" + } + }, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PgvuAGoesj9fw9QR4OkgfII", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030146, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3PgvuAGoesj9fw9Q0HWarpDa_secret_LNPzGgo19UBygpt0mepoRUuT6", + "id" : "pi_3PgvuAGoesj9fw9Q0HWarpDa", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030146, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..b6653293 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,54 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BdSaRSAgHiroO8 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 458 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:29 GMT +original-request: req_BdSaRSAgHiroO8 +stripe-version: 2020-08-27 +idempotency-key: 904839a6-74ec-46b2-8c73-9ad68ee45cb4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PgvuDGoesj9fw9QKFxE2HX4", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030149, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..ec3d6014 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FbmrVk8utLlM326L9k3c16%2Bsvkn6%2FUF4sPb69x%2FTPsqGUY4XgEZKzSyQtAfqvZlAAncm9ZstGSPy%2FVYMptNFVJZ2vUVWsbKO8gKUnqeItg8PeUDdOGbHpIY3Bj%2FehxGPUvCyu2f3tWEpxmt%2FswARILAvkpJAkfwylSQQfSCCoNmbpgKjrygdYUbqObueyuqVmAGCA%2BSwcQwd3EV4IJ6uQz3yrOTGcY%2FItQreqS1cZoE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0de1dfe0798b8015df00d27082825782 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 26 Jul 2024 21:42:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PgvuDGoesj9fw9Q1rBUIspY","secret":"pi_3PgvuDGoesj9fw9Q1rBUIspY_secret_PfHSb07JHRjgPRdqPyRolu0ZZ","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0011_get_v1_payment_intents_pi_3PgvuDGoesj9fw9Q1rBUIspY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0011_get_v1_payment_intents_pi_3PgvuDGoesj9fw9Q1rBUIspY.tail new file mode 100644 index 00000000..4ab1d459 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0011_get_v1_payment_intents_pi_3PgvuDGoesj9fw9Q1rBUIspY.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PgvuDGoesj9fw9Q1rBUIspY\?client_secret=pi_3PgvuDGoesj9fw9Q1rBUIspY_secret_PfHSb07JHRjgPRdqPyRolu0ZZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WrJfJkOivMTdBv +Content-Length: 1503 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QY1yYzfwqiKW0PKuzM7KrMc9SF4mwFJ" + } + }, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PgvuDGoesj9fw9QKFxE2HX4", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030149, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3PgvuDGoesj9fw9Q1rBUIspY_secret_PfHSb07JHRjgPRdqPyRolu0ZZ", + "id" : "pi_3PgvuDGoesj9fw9Q1rBUIspY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030149, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0012_get_v1_payment_intents_pi_3PgvuDGoesj9fw9Q1rBUIspY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0012_get_v1_payment_intents_pi_3PgvuDGoesj9fw9Q1rBUIspY.tail new file mode 100644 index 00000000..80442706 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBillieConfirmFlows/0012_get_v1_payment_intents_pi_3PgvuDGoesj9fw9Q1rBUIspY.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PgvuDGoesj9fw9Q1rBUIspY\?client_secret=pi_3PgvuDGoesj9fw9Q1rBUIspY_secret_PfHSb07JHRjgPRdqPyRolu0ZZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XuH3jyajB7rAyg +Content-Length: 1503 +Vary: Origin +Date: Fri, 26 Jul 2024 21:42:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QY1yYzfwqiKW0PKuzM7KrMc9SF4mwFJ" + } + }, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PgvuDGoesj9fw9QKFxE2HX4", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722030149, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3PgvuDGoesj9fw9Q1rBUIspY_secret_PfHSb07JHRjgPRdqPyRolu0ZZ", + "id" : "pi_3PgvuDGoesj9fw9Q1rBUIspY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1722030149, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..60df0eb8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=K4aXBQOGnK2K%2FFnAH%2BATWNItVJFDDhc58FjbBdXZHYGlx5EH4jwKDF%2B8SkZLL7gEyyU53b5hkVDmZG%2BJOOrFH56wc19Qdm9jQjVlPx73a%2BVdWksNCrvofliGBZ8%2FA2TNruIu%2B1oI8JNGt%2B9S6q1RmOdtu%2FXSyovzu7vU3cRvw0ab5mC1JoePrPhcNhl0Cs3Gedp2j5NRe356OMdxDUBfBfLcxSi%2FY9JxoP1TMNbS1PE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 11b8cf731e5de4f56f3115e8f8310bcf +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTFzJQVROkWvqT2AHpcsRS","secret":"pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS.tail new file mode 100644 index 00000000..40dd230d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0001_get_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFzJQVROkWvqT2AHpcsRS\?client_secret=pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vZtPHN9vq5HyrC +Content-Length: 792 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A", + "id" : "pi_3PiTFzJQVROkWvqT2AHpcsRS", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396679, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS_confirm.tail new file mode 100644 index 00000000..0237a12e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0002_post_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFzJQVROkWvqT2AHpcsRS\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sCdd7wyzvTowyt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1870 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:21 GMT +original-request: req_sCdd7wyzvTowyt +stripe-version: 2020-08-27 +idempotency-key: fdb43ad8-08b4-4cbf-9e7e-564890ec14fa +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=City&payment_method_data\[billing_details]\[address]\[country]=BR&payment_method_data\[billing_details]\[address]\[line1]=123%20fake%20st&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=11111111&payment_method_data\[billing_details]\[address]\[state]=AC&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[boleto]\[tax_id]=00000000000&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=boleto&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396861, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWdlV1NFBaQjZ2cjdPQmpYVnE0TjN5VktCSGFk0100Osc9LflD\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWdlV1NFBaQjZ2cjdPQmpYVnE0TjN5VktCSGFk0100Osc9LflD" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG0JQVROkWvqTeRFC4qAE", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396680, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A", + "id" : "pi_3PiTFzJQVROkWvqT2AHpcsRS", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396679, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0003_get_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0003_get_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS.tail new file mode 100644 index 00000000..f9776745 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0003_get_v1_payment_intents_pi_3PiTFzJQVROkWvqT2AHpcsRS.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTFzJQVROkWvqT2AHpcsRS\?client_secret=pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_S6GKJpAl9sCwve +Content-Length: 1870 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:21 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396861, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWdlV1NFBaQjZ2cjdPQmpYVnE0TjN5VktCSGFk0100Osc9LflD\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWdlV1NFBaQjZ2cjdPQmpYVnE0TjN5VktCSGFk0100Osc9LflD" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG0JQVROkWvqTeRFC4qAE", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396680, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTFzJQVROkWvqT2AHpcsRS_secret_vhjBZEdI8dgsy7fQssfEbjL8A", + "id" : "pi_3PiTFzJQVROkWvqT2AHpcsRS", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396679, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..e42d8086 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WuozJM8ajCqYe1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 548 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:22 GMT +original-request: req_WuozJM8ajCqYe1 +stripe-version: 2020-08-27 +idempotency-key: ffcfeb71-5695-4329-8ff4-2b8fffc260da +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=City&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=123%20fake%20st&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=11111111&billing_details\[address]\[state]=AC&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&boleto\[tax_id]=00000000000&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG1JQVROkWvqTfTLhPDCs", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396681, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..a3ec7c23 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=o5pprp7LAdz%2B%2BEo%2BCEREKSz3zRWoKq8HzbD26mrjeouy0o3cn6qWMR1GPMZRLvFQ7D8WpL7fA2%2FITOsguqITwsVSYm24UbAju6VEEDl%2FBAhLDRFDxClWxGTAMt1%2Bc1IgMvYVAfLmAsVWmrUjEkhgubY6rOaj9aTzD5LAMvoCalFrgf1mdGRdPXuvihwZZ4ZPJqmA%2FUL3jnDqy3Yv3xBYnKpyT1Exah5TNrhG%2BSI098k%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c204c5484e23f61b97ab80862c102f11;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:22 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTG2JQVROkWvqT2PNPBtsB","secret":"pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0006_get_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0006_get_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB.tail new file mode 100644 index 00000000..53ce81d7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0006_get_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG2JQVROkWvqT2PNPBtsB\?client_secret=pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4LIZvRT4ZHnbuz +Content-Length: 792 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM", + "id" : "pi_3PiTG2JQVROkWvqT2PNPBtsB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396682, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0007_post_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0007_post_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB_confirm.tail new file mode 100644 index 00000000..9c9e179a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0007_post_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG2JQVROkWvqT2PNPBtsB\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_eO8WxmCwI8JMIS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1870 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:23 GMT +original-request: req_eO8WxmCwI8JMIS +stripe-version: 2020-08-27 +idempotency-key: 945f027c-c335-4ef9-abe6-9d6535823bda +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM&expand\[0]=payment_method&payment_method=pm_1PiTG1JQVROkWvqTfTLhPDCs&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396863, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWTHNuc25zelZOZXhsRUxZVGpieHR6c0Z2cW5101004DbA8onJ\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWTHNuc25zelZOZXhsRUxZVGpieHR6c0Z2cW5101004DbA8onJ" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG1JQVROkWvqTfTLhPDCs", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396681, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM", + "id" : "pi_3PiTG2JQVROkWvqT2PNPBtsB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396682, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0008_get_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0008_get_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB.tail new file mode 100644 index 00000000..3dc95bdb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0008_get_v1_payment_intents_pi_3PiTG2JQVROkWvqT2PNPBtsB.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG2JQVROkWvqT2PNPBtsB\?client_secret=pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TaXJojgwY07cA7 +Content-Length: 1870 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396863, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWTHNuc25zelZOZXhsRUxZVGpieHR6c0Z2cW5101004DbA8onJ\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWTHNuc25zelZOZXhsRUxZVGpieHR6c0Z2cW5101004DbA8onJ" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG1JQVROkWvqTfTLhPDCs", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396681, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG2JQVROkWvqT2PNPBtsB_secret_IOA7AgDq6nF1sFBmZuJf7IONM", + "id" : "pi_3PiTG2JQVROkWvqT2PNPBtsB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396682, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..681c1fcd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OxQyoUJ3UvAOHC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 548 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:24 GMT +original-request: req_OxQyoUJ3UvAOHC +stripe-version: 2020-08-27 +idempotency-key: 0de0622b-81f0-4897-b10d-615d9fe431ec +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=City&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=123%20fake%20st&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=11111111&billing_details\[address]\[state]=AC&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&boleto\[tax_id]=00000000000&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG3JQVROkWvqTksEbMriv", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396684, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..87b0ad41 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=7Re9vjbOYVPAvVOnKeF04YlHtZ6gHH8eD59XZDV5kv756CZUYBMiMvoNU6QfAYuEhbfuKkkck1afk%2BwkyaUnunmEZFh0bnrbYPNLEfnsjDGf6A5fUp1f65meoqQIUZfamP73Yn3HppNwxDKvbkoyJqYJnSGcTVRbSoOAu9EReb41NJTj%2BlOU%2BRPdRhmudDHnC5AY2INQ2kYOoFP72VrwdlLVFXl4YsiKf8sUAIewT3M%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 65cd965c4b0e9f298fc5f7ae63a734f3 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTG4JQVROkWvqT2DcJBbmi","secret":"pi_3PiTG4JQVROkWvqT2DcJBbmi_secret_JsD6h4CS7LQIJScufnj9IoDMY","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0011_get_v1_payment_intents_pi_3PiTG4JQVROkWvqT2DcJBbmi.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0011_get_v1_payment_intents_pi_3PiTG4JQVROkWvqT2DcJBbmi.tail new file mode 100644 index 00000000..715b67a8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0011_get_v1_payment_intents_pi_3PiTG4JQVROkWvqT2DcJBbmi.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG4JQVROkWvqT2DcJBbmi\?client_secret=pi_3PiTG4JQVROkWvqT2DcJBbmi_secret_JsD6h4CS7LQIJScufnj9IoDMY&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ekhwx7zSI8ew0k +Content-Length: 1870 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396864, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWQ3NkUXVLS0h6ampsb1YyS2FxRnpJMDU4OTBN010077h4ITUI\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWQ3NkUXVLS0h6ampsb1YyS2FxRnpJMDU4OTBN010077h4ITUI" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG3JQVROkWvqTksEbMriv", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396684, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG4JQVROkWvqT2DcJBbmi_secret_JsD6h4CS7LQIJScufnj9IoDMY", + "id" : "pi_3PiTG4JQVROkWvqT2DcJBbmi", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396684, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0012_get_v1_payment_intents_pi_3PiTG4JQVROkWvqT2DcJBbmi.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0012_get_v1_payment_intents_pi_3PiTG4JQVROkWvqT2DcJBbmi.tail new file mode 100644 index 00000000..942654b1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0012_get_v1_payment_intents_pi_3PiTG4JQVROkWvqT2DcJBbmi.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG4JQVROkWvqT2DcJBbmi\?client_secret=pi_3PiTG4JQVROkWvqT2DcJBbmi_secret_JsD6h4CS7LQIJScufnj9IoDMY&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7yG5m7yMwhwp8x +Content-Length: 1870 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396864, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWQ3NkUXVLS0h6ampsb1YyS2FxRnpJMDU4OTBN010077h4ITUI\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWQ3NkUXVLS0h6ampsb1YyS2FxRnpJMDU4OTBN010077h4ITUI" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG3JQVROkWvqTksEbMriv", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396684, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG4JQVROkWvqT2DcJBbmi_secret_JsD6h4CS7LQIJScufnj9IoDMY", + "id" : "pi_3PiTG4JQVROkWvqT2DcJBbmi", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1722396684, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..8dd5539c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ad9ZDMHscgR%2BRd0JC35PDGNqMb4ZNGXPMNeRkrIP9gGAFANPDOoe%2FzRrhYmHBLLIqhXEiV1xQ8akHo%2BTQRlzZM1Bb%2BEuLkOZiA7lxeIVEJiAmqZivgWMHI1cSn0p8k0iJFDnSZjw1JFzCNJmICSe%2FLE4hdViUOItpeaVHQqE0cw2%2BkWUxFRzxcl%2Fm0xM0Nryr35xh8%2B7k9gCss6COVrDSfXBDkkHKFxiYhuZl1Igj9I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6f7f012b29b7bb54e61cded82e0e64a8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTG5JQVROkWvqT1aduWbdJ","secret":"pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0014_get_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0014_get_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ.tail new file mode 100644 index 00000000..c0ca4d26 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0014_get_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG5JQVROkWvqT1aduWbdJ\?client_secret=pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ahvsqfEeH0S7Td +Content-Length: 801 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR", + "id" : "pi_3PiTG5JQVROkWvqT1aduWbdJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396685, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0015_post_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0015_post_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ_confirm.tail new file mode 100644 index 00000000..aa8a854f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0015_post_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG5JQVROkWvqT1aduWbdJ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6y2c6dfyki2C6b +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1879 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:26 GMT +original-request: req_6y2c6dfyki2C6b +stripe-version: 2020-08-27 +idempotency-key: 52843529-fd94-40b8-8514-cc4f8d4c5467 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=City&payment_method_data\[billing_details]\[address]\[country]=BR&payment_method_data\[billing_details]\[address]\[line1]=123%20fake%20st&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=11111111&payment_method_data\[billing_details]\[address]\[state]=AC&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[boleto]\[tax_id]=00000000000&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=boleto&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396866, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWS1h3a1JUY0FFN3ByUXR3M2dKUHIwdmVoQ09z0100KCJXpOSd\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWS1h3a1JUY0FFN3ByUXR3M2dKUHIwdmVoQ09z0100KCJXpOSd" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG6JQVROkWvqT6MMdqRG8", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396686, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR", + "id" : "pi_3PiTG5JQVROkWvqT1aduWbdJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396685, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0016_get_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0016_get_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ.tail new file mode 100644 index 00000000..8642fc94 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0016_get_v1_payment_intents_pi_3PiTG5JQVROkWvqT1aduWbdJ.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG5JQVROkWvqT1aduWbdJ\?client_secret=pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tJTDivxAgcnnKw +Content-Length: 1879 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396866, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWS1h3a1JUY0FFN3ByUXR3M2dKUHIwdmVoQ09z0100KCJXpOSd\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWS1h3a1JUY0FFN3ByUXR3M2dKUHIwdmVoQ09z0100KCJXpOSd" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG6JQVROkWvqT6MMdqRG8", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396686, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG5JQVROkWvqT1aduWbdJ_secret_l1pZ9nkRCivg42puk7gw4yIeR", + "id" : "pi_3PiTG5JQVROkWvqT1aduWbdJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396685, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..8f4f79fa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7cUPqP75EHufT4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 548 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:27 GMT +original-request: req_7cUPqP75EHufT4 +stripe-version: 2020-08-27 +idempotency-key: c4e189cd-8c69-4ab9-ae67-2005e5331a1d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=City&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=123%20fake%20st&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=11111111&billing_details\[address]\[state]=AC&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&boleto\[tax_id]=00000000000&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG7JQVROkWvqT1I9mFRns", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396687, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..53b04463 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=PcYmHLQQlIQSeZDi0qgsMNd1twoxVXHqUJ%2BWRU8qEETYTPQ480G9IEB4ZklrGFN8fyz3FfEFcZw%2BqCeUydalifQI4EwxyDDT8I6pmI%2BA6dNqvihTneO%2BH2lF8I2ygMNYrX4UhpQQgXHEclMIf0Iw2Axm0biXw6V4QMX86vrSQ%2BEBKJeB%2F1jyRf%2BW2cuSVAkAUtD5n5OxJQLzrjgI7TPNmr%2B0d9agPn%2FVIbmiShb3Vzc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a93c9625aa24b0a1eedb7d13ae58ac28 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTG8JQVROkWvqT1ZjX5xPZ","secret":"pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0019_get_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0019_get_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ.tail new file mode 100644 index 00000000..49c68f83 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0019_get_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG8JQVROkWvqT1ZjX5xPZ\?client_secret=pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XeOu3TY1BKSyoe +Content-Length: 801 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG", + "id" : "pi_3PiTG8JQVROkWvqT1ZjX5xPZ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396688, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0020_post_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0020_post_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ_confirm.tail new file mode 100644 index 00000000..7ab4e230 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0020_post_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG8JQVROkWvqT1ZjX5xPZ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZtsXkZtaLy76Dg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1879 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:29 GMT +original-request: req_ZtsXkZtaLy76Dg +stripe-version: 2020-08-27 +idempotency-key: 227e5e59-d275-4e8c-81aa-8c280d8b29b9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG&expand\[0]=payment_method&payment_method=pm_1PiTG7JQVROkWvqT1I9mFRns&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396868, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWNkc0ZTRKMkNJSXBjSERjWGdXVmowOXNzSHgx0100CZmW9duy\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWNkc0ZTRKMkNJSXBjSERjWGdXVmowOXNzSHgx0100CZmW9duy" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG7JQVROkWvqT1I9mFRns", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396687, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG", + "id" : "pi_3PiTG8JQVROkWvqT1ZjX5xPZ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396688, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0021_get_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0021_get_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ.tail new file mode 100644 index 00000000..6da38790 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0021_get_v1_payment_intents_pi_3PiTG8JQVROkWvqT1ZjX5xPZ.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTG8JQVROkWvqT1ZjX5xPZ\?client_secret=pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aGn1fRw3Durzzi +Content-Length: 1879 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396868, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWNkc0ZTRKMkNJSXBjSERjWGdXVmowOXNzSHgx0100CZmW9duy\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWNkc0ZTRKMkNJSXBjSERjWGdXVmowOXNzSHgx0100CZmW9duy" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG7JQVROkWvqT1I9mFRns", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396687, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTG8JQVROkWvqT1ZjX5xPZ_secret_1RCgIzUhOFyfy3CzWgo1IOxSG", + "id" : "pi_3PiTG8JQVROkWvqT1ZjX5xPZ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396688, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0022_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0022_post_v1_payment_methods.tail new file mode 100644 index 00000000..1fe651d6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0022_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kzgVIVUVHCtaAY +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 548 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:29 GMT +original-request: req_kzgVIVUVHCtaAY +stripe-version: 2020-08-27 +idempotency-key: 1b73a01a-eddc-455f-9476-7761fb6db022 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=City&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=123%20fake%20st&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=11111111&billing_details\[address]\[state]=AC&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&boleto\[tax_id]=00000000000&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG9JQVROkWvqTbV3o7y6Y", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396689, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0023_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0023_post_create_payment_intent.tail new file mode 100644 index 00000000..1c6133f8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0023_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Xxgk3ZDP1UQMaQ%2FtIG%2FvnNEPxCY2r9ymuZL4Ho%2FMIHsu8LbDQqcRWFk%2B5sJvqbteC6hpOQGeoJexPPsDdBu748AcJIOC4cuCi44ZkKaph%2FZeLPEVZnm%2FCuDPQXh9020OYyJ3Ez6qDSbsasqszn9xIt19gtpS0V4F9kGaYL19Cj81HBDXTwKjn%2BnE%2FnLHd%2BwSYy1oQM58EfIZ3w6u9qDi%2BtJEO4v6vpS3WkU5PN8nS68%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7da8662e8fa9aac37cb2952b094060c4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGAJQVROkWvqT2tTBSBLu","secret":"pi_3PiTGAJQVROkWvqT2tTBSBLu_secret_1Cc4ggu9dV5EtmPwSrHS5LP5Q","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0024_get_v1_payment_intents_pi_3PiTGAJQVROkWvqT2tTBSBLu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0024_get_v1_payment_intents_pi_3PiTGAJQVROkWvqT2tTBSBLu.tail new file mode 100644 index 00000000..9cbd3606 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0024_get_v1_payment_intents_pi_3PiTGAJQVROkWvqT2tTBSBLu.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGAJQVROkWvqT2tTBSBLu\?client_secret=pi_3PiTGAJQVROkWvqT2tTBSBLu_secret_1Cc4ggu9dV5EtmPwSrHS5LP5Q&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2H0Ac2SjnMFlte +Content-Length: 1879 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396870, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWWGJMOWdrNTh5YldWU1M3TDE4ZW5BWFlNSUhs0100N7P1P826\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWWGJMOWdrNTh5YldWU1M3TDE4ZW5BWFlNSUhs0100N7P1P826" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG9JQVROkWvqTbV3o7y6Y", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396689, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTGAJQVROkWvqT2tTBSBLu_secret_1Cc4ggu9dV5EtmPwSrHS5LP5Q", + "id" : "pi_3PiTGAJQVROkWvqT2tTBSBLu", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396690, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0025_get_v1_payment_intents_pi_3PiTGAJQVROkWvqT2tTBSBLu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0025_get_v1_payment_intents_pi_3PiTGAJQVROkWvqT2tTBSBLu.tail new file mode 100644 index 00000000..6366d5e1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0025_get_v1_payment_intents_pi_3PiTGAJQVROkWvqT2tTBSBLu.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGAJQVROkWvqT2tTBSBLu\?client_secret=pi_3PiTGAJQVROkWvqT2tTBSBLu_secret_1Cc4ggu9dV5EtmPwSrHS5LP5Q&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VcDXotvYbSJ5Vf +Content-Length: 1879 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "boleto_display_details" : { + "expires_at" : 1722396870, + "number" : "01010101010101010101010101010101010101010101010", + "pdf" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWWGJMOWdrNTh5YldWU1M3TDE4ZW5BWFlNSUhs0100N7P1P826\/pdf", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/boleto\/voucher\/test_YWNjdF8xSllGRmpKUVZST2tXdnFULF9RWmNWWGJMOWdrNTh5YldWU1M3TDE4ZW5BWFlNSUhs0100N7P1P826" + }, + "type" : "boleto_display_details" + }, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTG9JQVROkWvqTbV3o7y6Y", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396689, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3PiTGAJQVROkWvqT2tTBSBLu_secret_1Cc4ggu9dV5EtmPwSrHS5LP5Q", + "id" : "pi_3PiTGAJQVROkWvqT2tTBSBLu", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : "off_session", + "created" : 1722396690, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0026_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0026_post_create_setup_intent.tail new file mode 100644 index 00000000..a4022b37 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0026_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=O4geGd%2FZPIKWtvzne9qqTbSGd%2FoP9H0SbWjWzHWB23lQH3jMn476h8BVY1MSA8dSqKAt2umBD9knRKf6V6nqxcA%2B7IqACpuAHh%2B4HhFxmQhs8%2Foitn6ZFJNl5bLV8p9wcrZE6UKcAdgGNPSAfBwlMM9AtBOCHsNa6N0apmgfswraIqAvFk0a9gwYHlyyWoFD%2FNWLsKRR2zChhh1xQlV0AecUmY7oYeoFSL32BsXANnk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fe437c9089c704540a5d81bb76d98c14 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:31 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGBJQVROkWvqTTzTGuL9A","secret":"seti_1PiTGBJQVROkWvqTTzTGuL9A_secret_QZcVz7gXAztRZrTi5VhlBj6qdkp66Is","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0027_get_v1_setup_intents_seti_1PiTGBJQVROkWvqTTzTGuL9A.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0027_get_v1_setup_intents_seti_1PiTGBJQVROkWvqTTzTGuL9A.tail new file mode 100644 index 00000000..1e020f71 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0027_get_v1_setup_intents_seti_1PiTGBJQVROkWvqTTzTGuL9A.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGBJQVROkWvqTTzTGuL9A\?client_secret=seti_1PiTGBJQVROkWvqTTzTGuL9A_secret_QZcVz7gXAztRZrTi5VhlBj6qdkp66Is$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3eauWEOKc6djMP +Content-Length: 535 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGBJQVROkWvqTTzTGuL9A", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "boleto" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396691, + "client_secret" : "seti_1PiTGBJQVROkWvqTTzTGuL9A_secret_QZcVz7gXAztRZrTi5VhlBj6qdkp66Is", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0028_post_v1_setup_intents_seti_1PiTGBJQVROkWvqTTzTGuL9A_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0028_post_v1_setup_intents_seti_1PiTGBJQVROkWvqTTzTGuL9A_confirm.tail new file mode 100644 index 00000000..36b8e195 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0028_post_v1_setup_intents_seti_1PiTGBJQVROkWvqTTzTGuL9A_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGBJQVROkWvqTTzTGuL9A\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_W89ZxxH6M8Vwt9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1115 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:32 GMT +original-request: req_W89ZxxH6M8Vwt9 +stripe-version: 2020-08-27 +idempotency-key: f1e7ec2f-359e-4d62-ada7-c1acee8f2a8e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTGBJQVROkWvqTTzTGuL9A_secret_QZcVz7gXAztRZrTi5VhlBj6qdkp66Is&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=City&payment_method_data\[billing_details]\[address]\[country]=BR&payment_method_data\[billing_details]\[address]\[line1]=123%20fake%20st&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=11111111&payment_method_data\[billing_details]\[address]\[state]=AC&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[boleto]\[tax_id]=00000000000&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=boleto&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTGBJQVROkWvqTTzTGuL9A", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTGCJQVROkWvqTx0W7Q4gL", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396692, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "boleto" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396691, + "client_secret" : "seti_1PiTGBJQVROkWvqTTzTGuL9A_secret_QZcVz7gXAztRZrTi5VhlBj6qdkp66Is", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0029_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0029_post_v1_payment_methods.tail new file mode 100644 index 00000000..7e6f6a47 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0029_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_i8nnHXbVNQxqRp +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 548 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:33 GMT +original-request: req_i8nnHXbVNQxqRp +stripe-version: 2020-08-27 +idempotency-key: eef41c32-a3b3-434d-9a8d-d784907adf51 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=City&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=123%20fake%20st&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=11111111&billing_details\[address]\[state]=AC&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&boleto\[tax_id]=00000000000&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTGCJQVROkWvqTVOphImRw", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396692, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0030_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0030_post_create_setup_intent.tail new file mode 100644 index 00000000..f5e4ed80 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0030_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=H4762cZQsw1AxUjkouOj8DvPUkrWHAWjzrMQKTTVJAq6hj96Skecn0cwxJdo%2BapK7gWHS7HyEXBzeDh2W812%2B4oVrKvv%2F18cvxSDi6OVM0M3kTVVHZwtu0I1iTudTOUyPwYDOqn0LmfwedJ6T9GIzTTDy8nfYusb39OURMu15UsuLnCgXvN9dnHNTZ7jyhXuwxEYacWqcuQ2rNKuLC2aRfFsC3GiYXtUDfkSc99tLDM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 95212190c38425242aa653baec21b979;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGDJQVROkWvqTentcyB7A","secret":"seti_1PiTGDJQVROkWvqTentcyB7A_secret_QZcV98QfCQ1dNfdOixGPnjJf3HZgzBA","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0031_get_v1_setup_intents_seti_1PiTGDJQVROkWvqTentcyB7A.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0031_get_v1_setup_intents_seti_1PiTGDJQVROkWvqTentcyB7A.tail new file mode 100644 index 00000000..0d451bb4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0031_get_v1_setup_intents_seti_1PiTGDJQVROkWvqTentcyB7A.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGDJQVROkWvqTentcyB7A\?client_secret=seti_1PiTGDJQVROkWvqTentcyB7A_secret_QZcV98QfCQ1dNfdOixGPnjJf3HZgzBA&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VY40gKnlvY8rnK +Content-Length: 535 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGDJQVROkWvqTentcyB7A", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "boleto" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396693, + "client_secret" : "seti_1PiTGDJQVROkWvqTentcyB7A_secret_QZcV98QfCQ1dNfdOixGPnjJf3HZgzBA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0032_post_v1_setup_intents_seti_1PiTGDJQVROkWvqTentcyB7A_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0032_post_v1_setup_intents_seti_1PiTGDJQVROkWvqTentcyB7A_confirm.tail new file mode 100644 index 00000000..48d91245 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0032_post_v1_setup_intents_seti_1PiTGDJQVROkWvqTentcyB7A_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGDJQVROkWvqTentcyB7A\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_duUWFDPjGQ5SaY +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1115 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:34 GMT +original-request: req_duUWFDPjGQ5SaY +stripe-version: 2020-08-27 +idempotency-key: 1dd66371-7d03-400c-883f-3eaae9a98c9f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTGDJQVROkWvqTentcyB7A_secret_QZcV98QfCQ1dNfdOixGPnjJf3HZgzBA&expand\[0]=payment_method&payment_method=pm_1PiTGCJQVROkWvqTVOphImRw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTGDJQVROkWvqTentcyB7A", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTGCJQVROkWvqTVOphImRw", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396692, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "boleto" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396693, + "client_secret" : "seti_1PiTGDJQVROkWvqTentcyB7A_secret_QZcV98QfCQ1dNfdOixGPnjJf3HZgzBA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0033_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0033_post_v1_payment_methods.tail new file mode 100644 index 00000000..5f325d15 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0033_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lmRfqEWqfksJCG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 548 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:34 GMT +original-request: req_lmRfqEWqfksJCG +stripe-version: 2020-08-27 +idempotency-key: b6657463-4a70-43fa-876c-200fb9e3b7fd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=City&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=123%20fake%20st&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=11111111&billing_details\[address]\[state]=AC&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&boleto\[tax_id]=00000000000&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTGEJQVROkWvqTCuy9Q2sG", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396694, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0034_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0034_post_create_setup_intent.tail new file mode 100644 index 00000000..129da18b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0034_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=b1lT9zAGgCiRKJqgpZnUfLDH430B6p9c3ZkMZ%2FohicRGngoGnWcLelRAJDlOdjziUE58VszezssBWW4HmJNNxEQU59xur599UxxGeDFC7tMuWXTVoH8v1Dvo%2BxeHI1s3OqCX5BQTciEUep%2BfrmLYYOL85xijg0Njl8ew3gWQ9Pp1Wbmmjlqw2hgdzXNzqP7zkrrX%2B8QUp9lTWtOW8SiNb%2F7xVSQarwhMoq1X%2FGeSZxk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1010e3fb824f2930d654a25977cc8f0f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGEJQVROkWvqT2JMW6jDA","secret":"seti_1PiTGEJQVROkWvqT2JMW6jDA_secret_QZcVRVVXfkJ79kdOzA78wTiOoSdGb4g","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0035_get_v1_setup_intents_seti_1PiTGEJQVROkWvqT2JMW6jDA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0035_get_v1_setup_intents_seti_1PiTGEJQVROkWvqT2JMW6jDA.tail new file mode 100644 index 00000000..fe0fed21 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testBoletoConfirmFlows/0035_get_v1_setup_intents_seti_1PiTGEJQVROkWvqT2JMW6jDA.tail @@ -0,0 +1,70 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGEJQVROkWvqT2JMW6jDA\?client_secret=seti_1PiTGEJQVROkWvqT2JMW6jDA_secret_QZcVRVVXfkJ79kdOzA78wTiOoSdGb4g&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ggUvrTYIL3pGb8 +Content-Length: 1115 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGEJQVROkWvqT2JMW6jDA", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : "48wcl030Axz32v7b", + "tax_id" : "00000000000" + }, + "id" : "pm_1PiTGEJQVROkWvqTCuy9Q2sG", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : "AC", + "country" : "BR", + "line2" : "", + "city" : "City", + "line1" : "123 fake st", + "postal_code" : "11111111" + } + }, + "livemode" : false, + "created" : 1722396694, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "boleto" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396694, + "client_secret" : "seti_1PiTGEJQVROkWvqT2JMW6jDA_secret_QZcVRVVXfkJ79kdOzA78wTiOoSdGb4g", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..2c1ea343 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=zkaZSxyItDvO%2FEUdxEj80FV%2BfAkpXpO%2F%2F9TFmMX8AHXcMvr1g9ppaCR3%2BIZdJTKad3QK5PXkOz3wl68cV2SX%2BRIm0Kk8s9v03m4ponuB5iEf9GJor9HLjRlt0RzBnYdAGH6TDfPdqli3RkiAlpqGNEsXoUrh4m3XUuD796zlMME%2BV5i15TSVmpo9o1q%2Fz6TQl8nReNsFjwBoGz7GPKYa24hwOmiPkJNElLJLKXf3bQQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ab6ca778e4729eb20bf7d556174c7ae3 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGFFY0qyl6XeW0ftVWFhB","secret":"pi_3PiTGFFY0qyl6XeW0ftVWFhB_secret_TaediTbVLdAemSbTu7Dql49EK","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0001_get_v1_payment_intents_pi_3PiTGFFY0qyl6XeW0ftVWFhB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0001_get_v1_payment_intents_pi_3PiTGFFY0qyl6XeW0ftVWFhB.tail new file mode 100644 index 00000000..e1f8e83e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0001_get_v1_payment_intents_pi_3PiTGFFY0qyl6XeW0ftVWFhB.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGFFY0qyl6XeW0ftVWFhB\?client_secret=pi_3PiTGFFY0qyl6XeW0ftVWFhB_secret_TaediTbVLdAemSbTu7Dql49EK$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_C5aMW7FRIvfCI2 +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGFFY0qyl6XeW0ftVWFhB_secret_TaediTbVLdAemSbTu7Dql49EK", + "id" : "pi_3PiTGFFY0qyl6XeW0ftVWFhB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396695, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0002_post_v1_payment_intents_pi_3PiTGFFY0qyl6XeW0ftVWFhB_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0002_post_v1_payment_intents_pi_3PiTGFFY0qyl6XeW0ftVWFhB_confirm.tail new file mode 100644 index 00000000..36a81d77 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0002_post_v1_payment_intents_pi_3PiTGFFY0qyl6XeW0ftVWFhB_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGFFY0qyl6XeW0ftVWFhB\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vsdUJYWlTj1Faj +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1951 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:37 GMT +original-request: req_vsdUJYWlTj1Faj +stripe-version: 2020-08-27 +idempotency-key: 8eb5d5b7-db29-4a77-b167-297850f45fa2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGFFY0qyl6XeW0ftVWFhB_secret_TaediTbVLdAemSbTu7Dql49EK&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=San%20Francisco&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=123%20Main%20Street&payment_method_data\[billing_details]\[address]\[line2]=line%202&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=CA&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[billing_details]\[phone]=%2B13105551234&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=28&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGGFY0qyl6XeWOwAz3695", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396696, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGFFY0qyl6XeW0ftVWFhB_secret_TaediTbVLdAemSbTu7Dql49EK", + "id" : "pi_3PiTGFFY0qyl6XeW0ftVWFhB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396695, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..634dc2b2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0003_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VAVtCH1tNkzW5r +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 988 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:37 GMT +original-request: req_VAVtCH1tNkzW5r +stripe-version: 2020-08-27 +idempotency-key: c91589e7-57a6-4771-9b22-bb2be2100d93 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=123%20Main%20Street&billing_details\[address]\[line2]=line%202&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=CA&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&billing_details\[phone]=%2B13105551234&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGHFY0qyl6XeWEhV4cx7q", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396697, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..333de773 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=3f86VM23elKimKUpSaWUoPw42rWGMhhxH5t8vD6ibAqYK%2FntdeB9WAWcxBuWYz%2FNV%2Bv8%2FS9z2%2BmGJJodF0teAkMMPHnzLCfHEwrgkA59f61RXDwGIYVU6NIJCW4W4qag73PntpgX8eECD6hGuDVHDXnlWIAsfBac5YgLBKDgBGJvYe4yGC%2FQ2mD0%2FPTNK62GKNhnkECBAb4vvB083gAZtavaLKHcQYaNVq8vQXB7LEc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8124ce0e29c845018284569abad33a23 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:38 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGIFY0qyl6XeW1M7cc3hi","secret":"pi_3PiTGIFY0qyl6XeW1M7cc3hi_secret_oYjz4nDArbOBBBVgTmXv10VCZ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0005_get_v1_payment_intents_pi_3PiTGIFY0qyl6XeW1M7cc3hi.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0005_get_v1_payment_intents_pi_3PiTGIFY0qyl6XeW1M7cc3hi.tail new file mode 100644 index 00000000..447cd987 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0005_get_v1_payment_intents_pi_3PiTGIFY0qyl6XeW1M7cc3hi.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGIFY0qyl6XeW1M7cc3hi\?client_secret=pi_3PiTGIFY0qyl6XeW1M7cc3hi_secret_oYjz4nDArbOBBBVgTmXv10VCZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wujZYPCm6UnmGu +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGIFY0qyl6XeW1M7cc3hi_secret_oYjz4nDArbOBBBVgTmXv10VCZ", + "id" : "pi_3PiTGIFY0qyl6XeW1M7cc3hi", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396698, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0006_post_v1_payment_intents_pi_3PiTGIFY0qyl6XeW1M7cc3hi_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0006_post_v1_payment_intents_pi_3PiTGIFY0qyl6XeW1M7cc3hi_confirm.tail new file mode 100644 index 00000000..55a0e3e3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0006_post_v1_payment_intents_pi_3PiTGIFY0qyl6XeW1M7cc3hi_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGIFY0qyl6XeW1M7cc3hi\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_d6YqHGQhZxnBEX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1951 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:39 GMT +original-request: req_d6YqHGQhZxnBEX +stripe-version: 2020-08-27 +idempotency-key: 5b8e4b66-3d6b-4990-b008-a5d6ce3d4358 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGIFY0qyl6XeW1M7cc3hi_secret_oYjz4nDArbOBBBVgTmXv10VCZ&expand\[0]=payment_method&payment_method=pm_1PiTGHFY0qyl6XeWEhV4cx7q&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGHFY0qyl6XeWEhV4cx7q", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396697, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGIFY0qyl6XeW1M7cc3hi_secret_oYjz4nDArbOBBBVgTmXv10VCZ", + "id" : "pi_3PiTGIFY0qyl6XeW1M7cc3hi", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396698, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..b5d51dea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Niq2v3d78PEjKv +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 988 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:40 GMT +original-request: req_Niq2v3d78PEjKv +stripe-version: 2020-08-27 +idempotency-key: cea833e9-87e6-498e-8a63-c83f930a12d9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=123%20Main%20Street&billing_details\[address]\[line2]=line%202&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=CA&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&billing_details\[phone]=%2B13105551234&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGKFY0qyl6XeW2ppySJJc", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396700, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..531d339c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=xcAhZlr4PqoiHQr%2FKP%2Bx6MP0W2J7appqx5Ftuc9Zi17Fex70wWQjXkJdEAjWaZ0Uc7uAHAOsRSherCJDRgMg0uTd0OgnV17oOlHaKF9dHf3X52rHgOmqvhwGVUqnY%2B2lFlSggN9lYd6ln4ArGQWW4kIGSlbrozAKcDJeTJhF7k44ZP44gXCnKj45jEiWvpgZb8HdOFmzc7GwwQP9DMc6oKm6scheK9IjRYRYRppuO3I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 47d55c36b7127496f48a99f2687198a1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:41 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGKFY0qyl6XeW1IYJLkNz","secret":"pi_3PiTGKFY0qyl6XeW1IYJLkNz_secret_TIpXTlOKacfdNHv8tZONabruR","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0009_get_v1_payment_intents_pi_3PiTGKFY0qyl6XeW1IYJLkNz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0009_get_v1_payment_intents_pi_3PiTGKFY0qyl6XeW1IYJLkNz.tail new file mode 100644 index 00000000..00b5a15a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0009_get_v1_payment_intents_pi_3PiTGKFY0qyl6XeW1IYJLkNz.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGKFY0qyl6XeW1IYJLkNz\?client_secret=pi_3PiTGKFY0qyl6XeW1IYJLkNz_secret_TIpXTlOKacfdNHv8tZONabruR&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_V8tYQ6pAipsryq +Content-Length: 1951 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGKFY0qyl6XeW2ppySJJc", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396700, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGKFY0qyl6XeW1IYJLkNz_secret_TIpXTlOKacfdNHv8tZONabruR", + "id" : "pi_3PiTGKFY0qyl6XeW1IYJLkNz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396700, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..07efd6b5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YhPpN%2BQaV0F%2FxAMm9o17c3YV8Xzm7OvYwGxHJzXjEcpI4Xc1WQtOX0F30QbVH6hWVMdLhZ8k7lEL3Z%2B%2FnhGtLbvQpmyHLttPHXFQBAYEehsxqRFRpxJPz0%2FocsKdXt6gloz0bWgmSCU0E3FvWs1ZHkSW9wWC%2Bqi0aDhADFZbE7MAAQQn6Ky45KvbHRDAT0AdG1p9Ntjz6Cc98BFcGd1WWRpErLI%2BLRfjuutP1SrnPA0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 14bb37360d034f2e5dcf46f2512cac30 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGMFY0qyl6XeW0BsWTvpQ","secret":"pi_3PiTGMFY0qyl6XeW0BsWTvpQ_secret_b7PNc6RgXqMxJjyDi3ZBmZ715","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0011_get_v1_payment_intents_pi_3PiTGMFY0qyl6XeW0BsWTvpQ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0011_get_v1_payment_intents_pi_3PiTGMFY0qyl6XeW0BsWTvpQ.tail new file mode 100644 index 00000000..fad7d8fa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0011_get_v1_payment_intents_pi_3PiTGMFY0qyl6XeW0BsWTvpQ.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGMFY0qyl6XeW0BsWTvpQ\?client_secret=pi_3PiTGMFY0qyl6XeW0BsWTvpQ_secret_b7PNc6RgXqMxJjyDi3ZBmZ715$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RsksjZe1wKMIBR +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGMFY0qyl6XeW0BsWTvpQ_secret_b7PNc6RgXqMxJjyDi3ZBmZ715", + "id" : "pi_3PiTGMFY0qyl6XeW0BsWTvpQ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396702, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0012_post_v1_payment_intents_pi_3PiTGMFY0qyl6XeW0BsWTvpQ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0012_post_v1_payment_intents_pi_3PiTGMFY0qyl6XeW0BsWTvpQ_confirm.tail new file mode 100644 index 00000000..68ff3765 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0012_post_v1_payment_intents_pi_3PiTGMFY0qyl6XeW0BsWTvpQ_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGMFY0qyl6XeW0BsWTvpQ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_j0W5U6QbnvMoRW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1960 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:43 GMT +original-request: req_j0W5U6QbnvMoRW +stripe-version: 2020-08-27 +idempotency-key: 407d4d86-912b-4847-a4c2-0dae12cd2563 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGMFY0qyl6XeW0BsWTvpQ_secret_b7PNc6RgXqMxJjyDi3ZBmZ715&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=San%20Francisco&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=123%20Main%20Street&payment_method_data\[billing_details]\[address]\[line2]=line%202&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=CA&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[billing_details]\[phone]=%2B13105551234&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=28&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGNFY0qyl6XeWHul9Yt0V", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396703, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGMFY0qyl6XeW0BsWTvpQ_secret_b7PNc6RgXqMxJjyDi3ZBmZ715", + "id" : "pi_3PiTGMFY0qyl6XeW0BsWTvpQ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396702, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..1712f773 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0013_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Bu16JWzn0olN9l +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 988 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:44 GMT +original-request: req_Bu16JWzn0olN9l +stripe-version: 2020-08-27 +idempotency-key: afce4bcc-8ebc-4fca-87d9-e037fcba5a16 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=123%20Main%20Street&billing_details\[address]\[line2]=line%202&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=CA&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&billing_details\[phone]=%2B13105551234&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGOFY0qyl6XeWnPAQi43B", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396704, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..794b66d7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=3yqMzr48H3bYgqk%2F4rBAEiXzyPxySnBXdAFMY%2FUQlot%2BDl0j6gUYOm7Et5FFsxBotI2PCtgeZGTnYdGrbYlp6HLKnoUv%2BP1hlhwI2T6oP9wuncr%2FCdVw94Oez6%2BSwRyEhlAokXEkcDvycVtQp8J49ajA2lgL3Bt5KHtbRazmxQxkapdO151JvKgelE2bCJir3aTXEYxpOKf6YLBqLivI9HMLOrl98%2FLaZ%2FHJoIEgTk0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bc766294b5f46b3b2af31a84e519a5db;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:44 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGOFY0qyl6XeW1dFZRTpk","secret":"pi_3PiTGOFY0qyl6XeW1dFZRTpk_secret_1P0rrAEcASau8pRqffs2oiwmH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0015_get_v1_payment_intents_pi_3PiTGOFY0qyl6XeW1dFZRTpk.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0015_get_v1_payment_intents_pi_3PiTGOFY0qyl6XeW1dFZRTpk.tail new file mode 100644 index 00000000..7752f9eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0015_get_v1_payment_intents_pi_3PiTGOFY0qyl6XeW1dFZRTpk.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGOFY0qyl6XeW1dFZRTpk\?client_secret=pi_3PiTGOFY0qyl6XeW1dFZRTpk_secret_1P0rrAEcASau8pRqffs2oiwmH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fIwnQBHbCjddMC +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGOFY0qyl6XeW1dFZRTpk_secret_1P0rrAEcASau8pRqffs2oiwmH", + "id" : "pi_3PiTGOFY0qyl6XeW1dFZRTpk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396704, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0016_post_v1_payment_intents_pi_3PiTGOFY0qyl6XeW1dFZRTpk_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0016_post_v1_payment_intents_pi_3PiTGOFY0qyl6XeW1dFZRTpk_confirm.tail new file mode 100644 index 00000000..3d8d28bd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0016_post_v1_payment_intents_pi_3PiTGOFY0qyl6XeW1dFZRTpk_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGOFY0qyl6XeW1dFZRTpk\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BIzEmPFgWJnAbg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1960 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:46 GMT +original-request: req_BIzEmPFgWJnAbg +stripe-version: 2020-08-27 +idempotency-key: 7c1c3b9f-771b-4d2c-afc6-2f1ac4bd4810 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGOFY0qyl6XeW1dFZRTpk_secret_1P0rrAEcASau8pRqffs2oiwmH&expand\[0]=payment_method&payment_method=pm_1PiTGOFY0qyl6XeWnPAQi43B&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGOFY0qyl6XeWnPAQi43B", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396704, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGOFY0qyl6XeW1dFZRTpk_secret_1P0rrAEcASau8pRqffs2oiwmH", + "id" : "pi_3PiTGOFY0qyl6XeW1dFZRTpk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396704, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..da529754 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0017_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RQYvEZozxhViDW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 988 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:46 GMT +original-request: req_RQYvEZozxhViDW +stripe-version: 2020-08-27 +idempotency-key: 5ba9dc94-fa8d-4a87-9231-714902492e9a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=123%20Main%20Street&billing_details\[address]\[line2]=line%202&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=CA&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&billing_details\[phone]=%2B13105551234&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGQFY0qyl6XeWTjMcaMKz", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396706, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..23d9cb24 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=UR%2FTMxNtdKwC7RbuSQVsJ1hlrUOMndlCSLBVG%2BYcPqOH5pjp401DSrgoSrtDclCw%2Fsbf6u6KiYrENE4nf98Dy%2FYNMyDitvE%2BNeIkZ1IV8scQKeyh78czUgJ6nMPLwXmVs73%2FJiHYvoUZbl%2B8lgeBnDlKZQ1WLphdzgAU752Vkr%2FcU9vixA5A%2FWSGk9BLgfE%2BSFcn5Px2EI6%2BELFmLBdzHmymPEYl8pGvdd9Ax1u2d1o%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d328f5c5e10b3ed0edb38694fbac7610 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:48 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGRFY0qyl6XeW0qDrruu9","secret":"pi_3PiTGRFY0qyl6XeW0qDrruu9_secret_La45Fcm6srVVhCPgKROXjyms3","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0019_get_v1_payment_intents_pi_3PiTGRFY0qyl6XeW0qDrruu9.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0019_get_v1_payment_intents_pi_3PiTGRFY0qyl6XeW0qDrruu9.tail new file mode 100644 index 00000000..c92e815f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0019_get_v1_payment_intents_pi_3PiTGRFY0qyl6XeW0qDrruu9.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGRFY0qyl6XeW0qDrruu9\?client_secret=pi_3PiTGRFY0qyl6XeW0qDrruu9_secret_La45Fcm6srVVhCPgKROXjyms3&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SsA50KAtupGfa3 +Content-Length: 1960 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGQFY0qyl6XeWTjMcaMKz", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396706, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGRFY0qyl6XeW0qDrruu9_secret_La45Fcm6srVVhCPgKROXjyms3", + "id" : "pi_3PiTGRFY0qyl6XeW0qDrruu9", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396707, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0020_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0020_post_create_setup_intent.tail new file mode 100644 index 00000000..2dab7dc0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0020_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=iFdFMJqt0s3H0pcrXyGFyfcwH1i3iOq0wPD6DdX4MOJFkD928%2Fr0Xbi%2B4pYAoKUf6vqObDRDEuqXr47t33FQpB8CpBU9jjfBV2UCGWT4bvlFMnhlLoFb6hKl6U4Kakey0NCPJmjemgCwtNZfaeDzSRMJeb0MxRR08Abs5nJYnGvbFMKrKokL3f4BP2Qdz2jXSjOhkMMqOdwB1VCuhnCVx%2BpAm7gKn6sAqL5wQgqDbLA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9f95bbbb0d9647f79e8b0619e6304d33 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:48 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGSFY0qyl6XeWMTPiPbvt","secret":"seti_1PiTGSFY0qyl6XeWMTPiPbvt_secret_QZcVTXu6XyZOlYoVhIhpZzQQm7hzkdY","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0021_get_v1_setup_intents_seti_1PiTGSFY0qyl6XeWMTPiPbvt.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0021_get_v1_setup_intents_seti_1PiTGSFY0qyl6XeWMTPiPbvt.tail new file mode 100644 index 00000000..469c51cc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0021_get_v1_setup_intents_seti_1PiTGSFY0qyl6XeWMTPiPbvt.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGSFY0qyl6XeWMTPiPbvt\?client_secret=seti_1PiTGSFY0qyl6XeWMTPiPbvt_secret_QZcVTXu6XyZOlYoVhIhpZzQQm7hzkdY$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_T6zT2DsphHgIwf +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGSFY0qyl6XeWMTPiPbvt", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396708, + "client_secret" : "seti_1PiTGSFY0qyl6XeWMTPiPbvt_secret_QZcVTXu6XyZOlYoVhIhpZzQQm7hzkdY", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0022_post_v1_setup_intents_seti_1PiTGSFY0qyl6XeWMTPiPbvt_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0022_post_v1_setup_intents_seti_1PiTGSFY0qyl6XeWMTPiPbvt_confirm.tail new file mode 100644 index 00000000..160bbdad --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0022_post_v1_setup_intents_seti_1PiTGSFY0qyl6XeWMTPiPbvt_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGSFY0qyl6XeWMTPiPbvt\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lp7wpyw0d40vUA +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1595 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:50 GMT +original-request: req_lp7wpyw0d40vUA +stripe-version: 2020-08-27 +idempotency-key: cf6b8649-f45d-4464-8eac-0c40fce7ade7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTGSFY0qyl6XeWMTPiPbvt_secret_QZcVTXu6XyZOlYoVhIhpZzQQm7hzkdY&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=San%20Francisco&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=123%20Main%20Street&payment_method_data\[billing_details]\[address]\[line2]=line%202&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=CA&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[billing_details]\[phone]=%2B13105551234&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=28&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "id" : "seti_1PiTGSFY0qyl6XeWMTPiPbvt", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGTFY0qyl6XeW0eAWb6S7", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396709, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396708, + "client_secret" : "seti_1PiTGSFY0qyl6XeWMTPiPbvt_secret_QZcVTXu6XyZOlYoVhIhpZzQQm7hzkdY", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0023_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0023_post_v1_payment_methods.tail new file mode 100644 index 00000000..59c39860 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0023_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xv9yf1erqWIiZk +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 988 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:50 GMT +original-request: req_xv9yf1erqWIiZk +stripe-version: 2020-08-27 +idempotency-key: a6c04a84-985e-48b6-9b62-3c7ae56568c5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=123%20Main%20Street&billing_details\[address]\[line2]=line%202&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=CA&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&billing_details\[phone]=%2B13105551234&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGUFY0qyl6XeWLYGpN6On", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396710, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0024_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0024_post_create_setup_intent.tail new file mode 100644 index 00000000..268b1ef3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0024_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=y6jFaTqcCc92Q2UmGzanFR2k%2BzixEcPoTzg%2BtoeTwK0O2NWGTrlR3Y%2F9PCVpwqC4%2B4uDpixJFUrC%2FTwqPmg%2BGhiCsB4nckfmgtnz00pKVJoGN62C4pXgk3SyDH916pKM%2FnK6gdbbrhF490%2F3WCrk9bF1Vv9qeL75Dbzb50m%2FiqE1menoiy3dIn%2BeoaIWfhlkwkj%2Fn3S7i0lFOz6EefuZyJit5ICPRnmExnQst4ttA8s%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 29eaf2d395b44fb7e83dc4b574c6b582 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGUFY0qyl6XeWTyqCLhFp","secret":"seti_1PiTGUFY0qyl6XeWTyqCLhFp_secret_QZcVc1e2c65yTnBylEhTJ6gwSUbdtDz","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0025_get_v1_setup_intents_seti_1PiTGUFY0qyl6XeWTyqCLhFp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0025_get_v1_setup_intents_seti_1PiTGUFY0qyl6XeWTyqCLhFp.tail new file mode 100644 index 00000000..b43ffb4b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0025_get_v1_setup_intents_seti_1PiTGUFY0qyl6XeWTyqCLhFp.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGUFY0qyl6XeWTyqCLhFp\?client_secret=seti_1PiTGUFY0qyl6XeWTyqCLhFp_secret_QZcVc1e2c65yTnBylEhTJ6gwSUbdtDz&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rg650new7W7e1P +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGUFY0qyl6XeWTyqCLhFp", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396710, + "client_secret" : "seti_1PiTGUFY0qyl6XeWTyqCLhFp_secret_QZcVc1e2c65yTnBylEhTJ6gwSUbdtDz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0026_post_v1_setup_intents_seti_1PiTGUFY0qyl6XeWTyqCLhFp_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0026_post_v1_setup_intents_seti_1PiTGUFY0qyl6XeWTyqCLhFp_confirm.tail new file mode 100644 index 00000000..5218a8b7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0026_post_v1_setup_intents_seti_1PiTGUFY0qyl6XeWTyqCLhFp_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGUFY0qyl6XeWTyqCLhFp\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bArgpa8vdTvkKI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1595 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:52 GMT +original-request: req_bArgpa8vdTvkKI +stripe-version: 2020-08-27 +idempotency-key: 8aadaa8f-9362-44cc-8fc9-2c19a61f3f5a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTGUFY0qyl6XeWTyqCLhFp_secret_QZcVc1e2c65yTnBylEhTJ6gwSUbdtDz&expand\[0]=payment_method&payment_method=pm_1PiTGUFY0qyl6XeWLYGpN6On&use_stripe_sdk=true + +{ + "id" : "seti_1PiTGUFY0qyl6XeWTyqCLhFp", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGUFY0qyl6XeWLYGpN6On", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396710, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396710, + "client_secret" : "seti_1PiTGUFY0qyl6XeWTyqCLhFp_secret_QZcVc1e2c65yTnBylEhTJ6gwSUbdtDz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..06eb1176 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0027_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9zsGxUK1laxhpL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 988 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:52 GMT +original-request: req_9zsGxUK1laxhpL +stripe-version: 2020-08-27 +idempotency-key: e0ca462e-c0cf-4636-9a01-fe938b698d86 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=123%20Main%20Street&billing_details\[address]\[line2]=line%202&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=CA&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&billing_details\[phone]=%2B13105551234&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGWFY0qyl6XeWgmR37VRW", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396712, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0028_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0028_post_create_setup_intent.tail new file mode 100644 index 00000000..dc15dac4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0028_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=4acOeafXYpTNIkH7rPY%2B%2BswiarlP3z32KYtmd0qCOkqCRRevM0C%2FwCgxFFVe%2FKHErxtijYlOAgQMPAij23zHzPkqE5cjX3djWM88hdv7cdMbaTQse6xOXwgP5hJ4fByCQgas4%2BJiQrm1Xej5ENaPfGOBVwDNLEaqZBcPYX9gDNwZoCFl6ih2LvMu8yP4nv85zajn5cAwO3mhkprBLhDKjRFQAT9%2F%2F7IvECVrgJ90U8A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7bb52a93decc6e2a0f7946e8c518cf1d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:53 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGWFY0qyl6XeWx3jWxDK0","secret":"seti_1PiTGWFY0qyl6XeWx3jWxDK0_secret_QZcVIjE4Y2BymvE5c2vaZMmXjdWXkB0","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0029_get_v1_setup_intents_seti_1PiTGWFY0qyl6XeWx3jWxDK0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0029_get_v1_setup_intents_seti_1PiTGWFY0qyl6XeWx3jWxDK0.tail new file mode 100644 index 00000000..9dbc13dd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardAllFieldsWithDefaults/0029_get_v1_setup_intents_seti_1PiTGWFY0qyl6XeWx3jWxDK0.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGWFY0qyl6XeWx3jWxDK0\?client_secret=seti_1PiTGWFY0qyl6XeWx3jWxDK0_secret_QZcVIjE4Y2BymvE5c2vaZMmXjdWXkB0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OEhqpUhjVf5GjA +Content-Length: 1595 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGWFY0qyl6XeWx3jWxDK0", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGWFY0qyl6XeWgmR37VRW", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : "+13105551234", + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "line 2", + "city" : "San Francisco", + "line1" : "123 Main Street", + "postal_code" : "12345" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396712, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396712, + "client_secret" : "seti_1PiTGWFY0qyl6XeWx3jWxDK0_secret_QZcVIjE4Y2BymvE5c2vaZMmXjdWXkB0", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..b6560305 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=bPgySk2l7HG8mLsjHHyuG%2FnhxNfQTfJWz1mSaldmA0TVAdwz%2BOlBv3iugj%2Fe7ubQN6xO9fhbELqLU85sCSDYoEYPpuASevy%2BZ%2Ba8cu8OKCmvdcirbOyedTM6l5SXBVKqIDLwOkdSJ0vHtBC9xZbCwdESmySY0ZjDZWgWmXK472ZzbKnI3dRxsN2Asi1NeAozcTsDRyul0bT4OHaSDBgK65aYZq2R6mY0o3VzPERFPg8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 48db4194736377a8611cb7096d6b698b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:54 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGYFY0qyl6XeW1w5uq2LD","secret":"pi_3PiTGYFY0qyl6XeW1w5uq2LD_secret_omzHcFf9NtWEa39Cfl7puY3AB","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0001_get_v1_payment_intents_pi_3PiTGYFY0qyl6XeW1w5uq2LD.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0001_get_v1_payment_intents_pi_3PiTGYFY0qyl6XeW1w5uq2LD.tail new file mode 100644 index 00000000..a05f7124 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0001_get_v1_payment_intents_pi_3PiTGYFY0qyl6XeW1w5uq2LD.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGYFY0qyl6XeW1w5uq2LD\?client_secret=pi_3PiTGYFY0qyl6XeW1w5uq2LD_secret_omzHcFf9NtWEa39Cfl7puY3AB$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IZi3IPImFg9MqO +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGYFY0qyl6XeW1w5uq2LD_secret_omzHcFf9NtWEa39Cfl7puY3AB", + "id" : "pi_3PiTGYFY0qyl6XeW1w5uq2LD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396714, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0002_post_v1_payment_intents_pi_3PiTGYFY0qyl6XeW1w5uq2LD_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0002_post_v1_payment_intents_pi_3PiTGYFY0qyl6XeW1w5uq2LD_confirm.tail new file mode 100644 index 00000000..7600788a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0002_post_v1_payment_intents_pi_3PiTGYFY0qyl6XeW1w5uq2LD_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGYFY0qyl6XeW1w5uq2LD\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dEg4iPMWdO1V8F +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:55 GMT +original-request: req_dEg4iPMWdO1V8F +stripe-version: 2020-08-27 +idempotency-key: 1f6b5db6-dadd-44e5-aff3-948ee69505d1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGYFY0qyl6XeW1w5uq2LD_secret_omzHcFf9NtWEa39Cfl7puY3AB&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=28&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGZFY0qyl6XeWCsEFmMEG", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396715, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGYFY0qyl6XeW1w5uq2LD_secret_omzHcFf9NtWEa39Cfl7puY3AB", + "id" : "pi_3PiTGYFY0qyl6XeW1w5uq2LD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396714, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..c4178e44 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0003_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HJjCVd9obspILl +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:56 GMT +original-request: req_HJjCVd9obspILl +stripe-version: 2020-08-27 +idempotency-key: d8abb452-b987-4dff-b530-cf95693d73ce +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGaFY0qyl6XeWaSOGpgXt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396716, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..e51c7dfd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=k6peUt9hbcKxMv5LRz1bRS6Z2aCesasSiHBIYi%2BhqOpPfK3yIBQQVqblrZb4h3Y5QuMsBg23y4EJm%2FrrqCogICswKfC46ddVsaOwnSmAnw0USkGd6cUxEkkzEGX%2FSJSlr1pyg2GqU6U8EmLmu4EN%2BmGviwKwYqmNL3DWYVhFLmfC3KnDHZbTGr%2FN1jOgXN5jgtBm0L8TEZ%2FIHF7NGadw6aLc8HQH0N4xGLOi3xbjLUA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 975f2551a60dc58f098fd406a71a2b71;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:56 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGaFY0qyl6XeW1UjPiJbG","secret":"pi_3PiTGaFY0qyl6XeW1UjPiJbG_secret_VRXbs1mdGwY95yyiwhPIQ6cfE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0005_get_v1_payment_intents_pi_3PiTGaFY0qyl6XeW1UjPiJbG.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0005_get_v1_payment_intents_pi_3PiTGaFY0qyl6XeW1UjPiJbG.tail new file mode 100644 index 00000000..e21af744 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0005_get_v1_payment_intents_pi_3PiTGaFY0qyl6XeW1UjPiJbG.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGaFY0qyl6XeW1UjPiJbG\?client_secret=pi_3PiTGaFY0qyl6XeW1UjPiJbG_secret_VRXbs1mdGwY95yyiwhPIQ6cfE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Leg42f6kG3GxZA +Content-Length: 889 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGaFY0qyl6XeW1UjPiJbG_secret_VRXbs1mdGwY95yyiwhPIQ6cfE", + "id" : "pi_3PiTGaFY0qyl6XeW1UjPiJbG", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396716, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0006_post_v1_payment_intents_pi_3PiTGaFY0qyl6XeW1UjPiJbG_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0006_post_v1_payment_intents_pi_3PiTGaFY0qyl6XeW1UjPiJbG_confirm.tail new file mode 100644 index 00000000..19f8ae68 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0006_post_v1_payment_intents_pi_3PiTGaFY0qyl6XeW1UjPiJbG_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGaFY0qyl6XeW1UjPiJbG\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XXfX6VsKgfTGQ1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:58 GMT +original-request: req_XXfX6VsKgfTGQ1 +stripe-version: 2020-08-27 +idempotency-key: 140913c6-baa7-4728-af37-37359b51c15e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGaFY0qyl6XeW1UjPiJbG_secret_VRXbs1mdGwY95yyiwhPIQ6cfE&expand\[0]=payment_method&payment_method=pm_1PiTGaFY0qyl6XeWaSOGpgXt&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGaFY0qyl6XeWaSOGpgXt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396716, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGaFY0qyl6XeW1UjPiJbG_secret_VRXbs1mdGwY95yyiwhPIQ6cfE", + "id" : "pi_3PiTGaFY0qyl6XeW1UjPiJbG", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396716, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..d8e7de7e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0007_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_weFtxEnIdh7mSf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 03:31:58 GMT +original-request: req_weFtxEnIdh7mSf +stripe-version: 2020-08-27 +idempotency-key: 729d544f-339e-4e71-af72-20c03c33c820 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGcFY0qyl6XeWw3y8q38a", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396718, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..eb1a42bb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6lbS2PuR3QfcfrRXf37gCrS4p3%2BA08vQoqNnbhEt6fHslBKte0X4%2F5abE%2F7h9u2myk8vrOj6MEnT0ZCewPCRqlj5xapr3hk8LKPiqwHoAjyFZr2XlsrQBtjWvLrB4scK3ky301GA6wl%2FgihCdS%2BxpwfLfZBa0Q4EQQmGdsebN6mdMdDMiFoo0Melb1xzPEug81LtwMbSx%2BmufF14jsy8K3UnV4Aj1T%2FA3ZR4SxrERHw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8ee0c082c6bfa86d89e6e783ee412708 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:31:59 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGdFY0qyl6XeW1SqUIwh0","secret":"pi_3PiTGdFY0qyl6XeW1SqUIwh0_secret_Q68tFglBuY9S4xrGZEZX5U6hk","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0009_get_v1_payment_intents_pi_3PiTGdFY0qyl6XeW1SqUIwh0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0009_get_v1_payment_intents_pi_3PiTGdFY0qyl6XeW1SqUIwh0.tail new file mode 100644 index 00000000..b63c9d85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0009_get_v1_payment_intents_pi_3PiTGdFY0qyl6XeW1SqUIwh0.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGdFY0qyl6XeW1SqUIwh0\?client_secret=pi_3PiTGdFY0qyl6XeW1SqUIwh0_secret_Q68tFglBuY9S4xrGZEZX5U6hk&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qbQgs7o4Lsvmwm +Content-Length: 1895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGcFY0qyl6XeWw3y8q38a", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396718, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGdFY0qyl6XeW1SqUIwh0_secret_Q68tFglBuY9S4xrGZEZX5U6hk", + "id" : "pi_3PiTGdFY0qyl6XeW1SqUIwh0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722396719, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..262a5de9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=R7XgzJNlZXaGvaOrZZOJoNc4GPGT%2BFK8O0QpzlJJd7XrZIHuKAJYX5VEzSFVp64Uu7UuhxIVhFvbB1V7r639BABdSpwyIV2%2F97HQPlhB2CBwxvQ8QG%2FrFzpYvSrrrZl%2FqX7YYMKtgSWExShgYlIIq4ON%2FVaS16SaezZcgZvK%2FJooWNxyeKr2HtFG1xwwcVjn7pgJsZFHpvduWG%2BPTlshhZ7uhZHV%2F8SlWCiAHOESW3o%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 12753aa80a20d8bbd9a597784413fde9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGeFY0qyl6XeW14vbbzHp","secret":"pi_3PiTGeFY0qyl6XeW14vbbzHp_secret_ONr9RJFZy3pmtfF98t6nhZrE1","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0011_get_v1_payment_intents_pi_3PiTGeFY0qyl6XeW14vbbzHp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0011_get_v1_payment_intents_pi_3PiTGeFY0qyl6XeW14vbbzHp.tail new file mode 100644 index 00000000..8499887f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0011_get_v1_payment_intents_pi_3PiTGeFY0qyl6XeW14vbbzHp.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGeFY0qyl6XeW14vbbzHp\?client_secret=pi_3PiTGeFY0qyl6XeW14vbbzHp_secret_ONr9RJFZy3pmtfF98t6nhZrE1$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FFsAfPBFpNolVz +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGeFY0qyl6XeW14vbbzHp_secret_ONr9RJFZy3pmtfF98t6nhZrE1", + "id" : "pi_3PiTGeFY0qyl6XeW14vbbzHp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396720, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0012_post_v1_payment_intents_pi_3PiTGeFY0qyl6XeW14vbbzHp_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0012_post_v1_payment_intents_pi_3PiTGeFY0qyl6XeW14vbbzHp_confirm.tail new file mode 100644 index 00000000..6a4e0e39 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0012_post_v1_payment_intents_pi_3PiTGeFY0qyl6XeW14vbbzHp_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGeFY0qyl6XeW14vbbzHp\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7dJBsIOihJv6Ju +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:02 GMT +original-request: req_7dJBsIOihJv6Ju +stripe-version: 2020-08-27 +idempotency-key: 80bee27e-6e8f-4a95-8f53-bd7ed5528a98 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGeFY0qyl6XeW14vbbzHp_secret_ONr9RJFZy3pmtfF98t6nhZrE1&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=28&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGfFY0qyl6XeWd2TuKWvG", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396721, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGeFY0qyl6XeW14vbbzHp_secret_ONr9RJFZy3pmtfF98t6nhZrE1", + "id" : "pi_3PiTGeFY0qyl6XeW14vbbzHp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396720, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..afc7b1d7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0013_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wtvVtvuZUNMYhO +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:03 GMT +original-request: req_wtvVtvuZUNMYhO +stripe-version: 2020-08-27 +idempotency-key: 9ee4b4c6-89e0-4a9e-9b0a-06101be0e902 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGgFY0qyl6XeWyBy7A3pX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396723, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..b822f5d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=RlmqoFkm8lnqUX9tdy%2FQjOTUDP2i3Lr66rBvbfeMUhuQI1faR5DEdf0LTSA496JQzUAIA8ryxlKK%2BL898cFH42F%2FihtKG0HlBHE%2B5k8hgno0EJJoF7ykE%2FjKVEG2kyGKL6EccpuvxxoyHrQHQoWTJAK13d%2FL7LINlwXnEZEkSvZzet8k%2BlK9BAsnju2hW%2BMTmoNbzp92PSrr9KPSNWrju%2FO94mOQ8o%2FV9XED3IxONWk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d27e607d42b63314de3068b6c34fb4c2 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGhFY0qyl6XeW1ANdoojK","secret":"pi_3PiTGhFY0qyl6XeW1ANdoojK_secret_IuC8P92mEOCV8aEdZuNrpgYgs","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0015_get_v1_payment_intents_pi_3PiTGhFY0qyl6XeW1ANdoojK.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0015_get_v1_payment_intents_pi_3PiTGhFY0qyl6XeW1ANdoojK.tail new file mode 100644 index 00000000..cfd31e6e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0015_get_v1_payment_intents_pi_3PiTGhFY0qyl6XeW1ANdoojK.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGhFY0qyl6XeW1ANdoojK\?client_secret=pi_3PiTGhFY0qyl6XeW1ANdoojK_secret_IuC8P92mEOCV8aEdZuNrpgYgs&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gF5knYux26okFA +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTGhFY0qyl6XeW1ANdoojK_secret_IuC8P92mEOCV8aEdZuNrpgYgs", + "id" : "pi_3PiTGhFY0qyl6XeW1ANdoojK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396723, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0016_post_v1_payment_intents_pi_3PiTGhFY0qyl6XeW1ANdoojK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0016_post_v1_payment_intents_pi_3PiTGhFY0qyl6XeW1ANdoojK_confirm.tail new file mode 100644 index 00000000..13f707c8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0016_post_v1_payment_intents_pi_3PiTGhFY0qyl6XeW1ANdoojK_confirm.tail @@ -0,0 +1,114 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGhFY0qyl6XeW1ANdoojK\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jbiSnxnIpKYwL9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:05 GMT +original-request: req_jbiSnxnIpKYwL9 +stripe-version: 2020-08-27 +idempotency-key: 2a2c2c15-94b7-4c52-9c6b-1c5d28ae61f6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTGhFY0qyl6XeW1ANdoojK_secret_IuC8P92mEOCV8aEdZuNrpgYgs&expand\[0]=payment_method&payment_method=pm_1PiTGgFY0qyl6XeWyBy7A3pX&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGgFY0qyl6XeWyBy7A3pX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396723, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGhFY0qyl6XeW1ANdoojK_secret_IuC8P92mEOCV8aEdZuNrpgYgs", + "id" : "pi_3PiTGhFY0qyl6XeW1ANdoojK", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396723, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..f365c3c4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0017_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xIaptqkCA24oIE +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:05 GMT +original-request: req_xIaptqkCA24oIE +stripe-version: 2020-08-27 +idempotency-key: 07b94e15-cf26-4135-8353-8d37e74ade58 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGjFY0qyl6XeWo8MCFSwN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396725, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..b9573b5e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6iJDEiRqpoKdZC6vzg62UclBSQTG7OobKRsHBIlblyBqLL7V6y03efL3T%2FeFVPGHCjyrhAZp3yvEHeEMHK1S8OeWm%2BLUBjCT0bSfVV%2B0I2Nkr8F5%2FEeMMDKYOL6s9YG7rG%2Bjl%2B9CEKo2QDdUCgeYujUbIKwaC9Gic7xm4O9jfSxLJJMvFu9ljlod53fRd%2B1xcIU1jpzwd8RYVQzTJ5qtowEGxD9ZGqT4Qxz40gYFlxY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 36abf68742627c842a2ae5c7cdbfdee6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTGkFY0qyl6XeW0nLUKYR4","secret":"pi_3PiTGkFY0qyl6XeW0nLUKYR4_secret_fgI9ShA0ZEVpxAzcGq8iNKvZD","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0019_get_v1_payment_intents_pi_3PiTGkFY0qyl6XeW0nLUKYR4.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0019_get_v1_payment_intents_pi_3PiTGkFY0qyl6XeW0nLUKYR4.tail new file mode 100644 index 00000000..800b3984 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0019_get_v1_payment_intents_pi_3PiTGkFY0qyl6XeW0nLUKYR4.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTGkFY0qyl6XeW0nLUKYR4\?client_secret=pi_3PiTGkFY0qyl6XeW0nLUKYR4_secret_fgI9ShA0ZEVpxAzcGq8iNKvZD&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_53SRlJQasYiCeJ +Content-Length: 1904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGjFY0qyl6XeWo8MCFSwN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396725, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiTGkFY0qyl6XeW0nLUKYR4_secret_fgI9ShA0ZEVpxAzcGq8iNKvZD", + "id" : "pi_3PiTGkFY0qyl6XeW0nLUKYR4", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : "off_session", + "created" : 1722396726, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0020_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0020_post_create_setup_intent.tail new file mode 100644 index 00000000..13555d37 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0020_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=VJzN1AlMNcQHBGidrAslFGGnayY4Y96ZrPxg1%2BTZpAds9hh0sqR8aODLqhp0dhjbs8%2FbfZOz1%2BoxcYyM8eBXxAlwcyQgwUk28i%2BnnyYWgfCbunc%2FVSjWYIEU6oXVGXOCPCBE%2Fv0uFuyHteJtIj0oBIltamcUffxKRgExLW%2BYfO%2BvKC5G%2Fz5X9KCjY%2FRNelqr%2Fxlhk1rpWdlaaLDE6uIiQ7evlFK1F0EA2gAt0HfRoqA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e28c463625974fd63f1052d3660bc581;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGlFY0qyl6XeWDBSwU0Ji","secret":"seti_1PiTGlFY0qyl6XeWDBSwU0Ji_secret_QZcVyGGIQCPHkfuoF03fSMoIHvLBGmc","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0021_get_v1_setup_intents_seti_1PiTGlFY0qyl6XeWDBSwU0Ji.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0021_get_v1_setup_intents_seti_1PiTGlFY0qyl6XeWDBSwU0Ji.tail new file mode 100644 index 00000000..168fb5ff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0021_get_v1_setup_intents_seti_1PiTGlFY0qyl6XeWDBSwU0Ji.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGlFY0qyl6XeWDBSwU0Ji\?client_secret=seti_1PiTGlFY0qyl6XeWDBSwU0Ji_secret_QZcVyGGIQCPHkfuoF03fSMoIHvLBGmc$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_i65QCitbmlOLa2 +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGlFY0qyl6XeWDBSwU0Ji", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396727, + "client_secret" : "seti_1PiTGlFY0qyl6XeWDBSwU0Ji_secret_QZcVyGGIQCPHkfuoF03fSMoIHvLBGmc", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0022_post_v1_setup_intents_seti_1PiTGlFY0qyl6XeWDBSwU0Ji_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0022_post_v1_setup_intents_seti_1PiTGlFY0qyl6XeWDBSwU0Ji_confirm.tail new file mode 100644 index 00000000..0b2500bc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0022_post_v1_setup_intents_seti_1PiTGlFY0qyl6XeWDBSwU0Ji_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGlFY0qyl6XeWDBSwU0Ji\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zOhTVKl1ccuuQd +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:09 GMT +original-request: req_zOhTVKl1ccuuQd +stripe-version: 2020-08-27 +idempotency-key: 3a2c08f5-5ec2-4553-8e5c-6a81ab57ad2e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTGlFY0qyl6XeWDBSwU0Ji_secret_QZcVyGGIQCPHkfuoF03fSMoIHvLBGmc&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=28&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "id" : "seti_1PiTGlFY0qyl6XeWDBSwU0Ji", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGmFY0qyl6XeWC9HgYfEL", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396728, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396727, + "client_secret" : "seti_1PiTGlFY0qyl6XeWDBSwU0Ji_secret_QZcVyGGIQCPHkfuoF03fSMoIHvLBGmc", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0023_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0023_post_v1_payment_methods.tail new file mode 100644 index 00000000..10006390 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0023_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4aS2tK96Kv9EL0 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:09 GMT +original-request: req_4aS2tK96Kv9EL0 +stripe-version: 2020-08-27 +idempotency-key: 807acf17-a855-401c-9edb-2f094b967be0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGnFY0qyl6XeWByNro8BF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396729, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0024_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0024_post_create_setup_intent.tail new file mode 100644 index 00000000..60413b7f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0024_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FtjKqdOLgqawUXRM%2BPIIjVSr8%2FhlLfRYf9AbQiS8IE6MLiMkysnsw8MqLi3nip5gmYdDb4mhEdk8cnhyXtjO0JkVcTXQv%2B%2BB2Y9eM7%2BODMmz1FDH4zNlEyhQxL89hW1KLRAXSpdFgTW%2FXtXIz%2BLAoUqUisK5rN%2FZSyAnabP9n2CPFG7LbMJQidzpOt7TDhc4MXywbO2hphrJawiigZ0JMlxApDQ%2B9ycMbgDsxwRUAfg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 28d855fe02b84ad9b24e060a87160419 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGoFY0qyl6XeWtNO3Gq7J","secret":"seti_1PiTGoFY0qyl6XeWtNO3Gq7J_secret_QZcVHZveqSHOKWHE4j9f0HEaywOlyYz","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0025_get_v1_setup_intents_seti_1PiTGoFY0qyl6XeWtNO3Gq7J.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0025_get_v1_setup_intents_seti_1PiTGoFY0qyl6XeWtNO3Gq7J.tail new file mode 100644 index 00000000..64a62305 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0025_get_v1_setup_intents_seti_1PiTGoFY0qyl6XeWtNO3Gq7J.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGoFY0qyl6XeWtNO3Gq7J\?client_secret=seti_1PiTGoFY0qyl6XeWtNO3Gq7J_secret_QZcVHZveqSHOKWHE4j9f0HEaywOlyYz&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cUa6jzdyCMBNkc +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGoFY0qyl6XeWtNO3Gq7J", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396730, + "client_secret" : "seti_1PiTGoFY0qyl6XeWtNO3Gq7J_secret_QZcVHZveqSHOKWHE4j9f0HEaywOlyYz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0026_post_v1_setup_intents_seti_1PiTGoFY0qyl6XeWtNO3Gq7J_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0026_post_v1_setup_intents_seti_1PiTGoFY0qyl6XeWtNO3Gq7J_confirm.tail new file mode 100644 index 00000000..c06a903e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0026_post_v1_setup_intents_seti_1PiTGoFY0qyl6XeWtNO3Gq7J_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGoFY0qyl6XeWtNO3Gq7J\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4mRHijYKPSllKW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:11 GMT +original-request: req_4mRHijYKPSllKW +stripe-version: 2020-08-27 +idempotency-key: 52db53b3-c2e8-4a5a-9bb5-bf8e75bb8674 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTGoFY0qyl6XeWtNO3Gq7J_secret_QZcVHZveqSHOKWHE4j9f0HEaywOlyYz&expand\[0]=payment_method&payment_method=pm_1PiTGnFY0qyl6XeWByNro8BF&use_stripe_sdk=true + +{ + "id" : "seti_1PiTGoFY0qyl6XeWtNO3Gq7J", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGnFY0qyl6XeWByNro8BF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396729, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396730, + "client_secret" : "seti_1PiTGoFY0qyl6XeWtNO3Gq7J_secret_QZcVHZveqSHOKWHE4j9f0HEaywOlyYz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..078acb25 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0027_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_eCuQm7RbswP69J +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:12 GMT +original-request: req_eCuQm7RbswP69J +stripe-version: 2020-08-27 +idempotency-key: 208a2f62-1994-4241-8811-d297640b5767 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=28&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiTGqFY0qyl6XeWFDGSMFxE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396732, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0028_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0028_post_create_setup_intent.tail new file mode 100644 index 00000000..1f4924ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0028_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ixye%2FOQUqoTmvmW3LtUJ7bSw0fKHJWtfe%2B8yo3yN4aHBr0EJH55p64DlkMByh0XjUKIf3CDAxk0QwuQI%2FMZhh7%2FL1NbvghyfRf%2B%2BTjoUTOGknShBHcnwqQerCaBLqK0VsZzhA4tqfbA2HEUUEXEQZv70lChOTWJK%2FcVzi1pv9vpVn1LLj5XiTz4syntq%2FMuM2wPwcFtoWn8r0Ml2rQapAV7KW37ycMx9vT9W%2FPykoqI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ddb3c3a41201bb03d04477049bcd2f82 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTGqFY0qyl6XeWqSYQnSMj","secret":"seti_1PiTGqFY0qyl6XeWqSYQnSMj_secret_QZcVdBbHSUkhkV6PFiyNfFUQJFZ6v5T","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0029_get_v1_setup_intents_seti_1PiTGqFY0qyl6XeWqSYQnSMj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0029_get_v1_setup_intents_seti_1PiTGqFY0qyl6XeWqSYQnSMj.tail new file mode 100644 index 00000000..f118f9d1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCardOnlyCardInfoWithDefaults/0029_get_v1_setup_intents_seti_1PiTGqFY0qyl6XeWqSYQnSMj.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTGqFY0qyl6XeWqSYQnSMj\?client_secret=seti_1PiTGqFY0qyl6XeWqSYQnSMj_secret_QZcVdBbHSUkhkV6PFiyNfFUQJFZ6v5T&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BAi9gwznsGEUu6 +Content-Length: 1539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTGqFY0qyl6XeWqSYQnSMj", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTGqFY0qyl6XeWFDGSMFxE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722396732, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396732, + "client_secret" : "seti_1PiTGqFY0qyl6XeWqSYQnSMj_secret_QZcVdBbHSUkhkV6PFiyNfFUQJFZ6v5T", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..2065b594 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=sxgwdB72Cag%2BqClQM4mF0p2n8TS9vH8aSQguyPSH5XkuE4h53719aTZjhRb6jG5rEVB9fN1nAWS0IyfTOWsN%2F6r9A4P0wtxr0lwaYfQKyeiOf9Y8dPtfbXyr%2BqrwWqGueu4HpWjeAsmFf8V3cFghSasdjgdE7lfOyKU%2F4Z29s3QUo0tv8MDblX4W56q1vtIi2HvLHMfy2mKx2t%2BtMG46HsptmW8WFNMshv428wQLf%2F4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9913a134f875636c4e86e8aeef42c533;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5BIsFY0qyl6XeW1j7fd10o","secret":"pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0001_get_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0001_get_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o.tail new file mode 100644 index 00000000..514a26d0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0001_get_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIsFY0qyl6XeW1j7fd10o\?client_secret=pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3skE9PgujUaBoR +Content-Length: 892 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz", + "id" : "pi_3Q5BIsFY0qyl6XeW1j7fd10o", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809210, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0002_post_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0002_post_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o_confirm.tail new file mode 100644 index 00000000..d02c291a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0002_post_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o_confirm.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIsFY0qyl6XeW1j7fd10o\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lbiZjEg4R4VkjG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2226 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:13 GMT +original-request: req_lbiZjEg4R4VkjG +stripe-version: 2020-08-27 +idempotency-key: 58a4756e-df59-4b49-a994-d8a8f31e660a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=cashapp&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUSThReVlTQ1dzY3cxZmRuT1RwdkluN2JDMjZw0100lMGfT220.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUSThReVlTQ1dzY3cxZmRuT1RwdkluN2JDMjZw0100lMGfT220.svg", + "expires_at" : 1727809233 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKL2N8bcGMgZnWRt6dPo6MJVmqQqm0ZFw4v8fPqDMVqJ1-wf-Tty-u2b1GcsAcE7R2pSW2KKFEnKNe8dBZU1z8w", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5Ts1tjUYg4uRxv4XfkSPWs6tVhwKj" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BIuFY0qyl6XeWpD3z5xKl", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809212, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz", + "id" : "pi_3Q5BIsFY0qyl6XeW1j7fd10o", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809210, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0003_post_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0003_post_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o_refresh.tail new file mode 100644 index 00000000..f720085c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0003_post_v1_payment_intents_pi_3Q5BIsFY0qyl6XeW1j7fd10o_refresh.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIsFY0qyl6XeW1j7fd10o\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MBXlOinDhghPg3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2248 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:13 GMT +original-request: req_MBXlOinDhghPg3 +stripe-version: 2020-08-27 +idempotency-key: d0bda864-ebf8-4479-9897-e1d5babf1bd2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUSThReVlTQ1dzY3cxZmRuT1RwdkluN2JDMjZw0100lMGfT220.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUSThReVlTQ1dzY3cxZmRuT1RwdkluN2JDMjZw0100lMGfT220.svg", + "expires_at" : 1727809233 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKL2N8bcGMga-mP3bKoQ6MJXTbu6k3gaxO8UepLM4wnPYa4etlLmvefnUNZsQt-1EVTV5EhGcKz3y66cJO7Ex_w", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5Ts1tjUYg4uRxv4XfkSPWs6tVhwKj" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BIuFY0qyl6XeWpD3z5xKl", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809212, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "client_secret" : "pi_3Q5BIsFY0qyl6XeW1j7fd10o_secret_L58xftChRVHXOD0zEX11mKAtz", + "id" : "pi_3Q5BIsFY0qyl6XeW1j7fd10o", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809210, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..3cb8f628 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5BL7CK4gYocRwO +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:14 GMT +original-request: req_5BL7CK4gYocRwO +stripe-version: 2020-08-27 +idempotency-key: e2d23d0a-04bf-4196-a98e-380db3141cf5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1Q5BIwFY0qyl6XeWoSZIuQ5l", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809214, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..9cfde328 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=0Coi%2B4OOvxopIVwxMrSWvZB6d9UbJFStvCdZr533yWxK5EH5dUJjxt1Yz5JTVllmsmNSzSw5D%2FUsqwM1Lwjto%2BsrXs1XSe1Bg8oAGrrny4WPiLNlavULDO0oG3Hg7GBrsaHNnmgWnuFRfuWstPK25bxJS7QA%2FiyjqxPteZpZIK8JaQ3uUdAfKJRMFmOondnD2Dp7VcpTwGDSD6jda07TcozgSBhvnjYUUo62tucPYX0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 456fdf03767eb6e51192d6a2f2c3da33 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5BIwFY0qyl6XeW010T3TIm","secret":"pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0006_get_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0006_get_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm.tail new file mode 100644 index 00000000..b10cc9db --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0006_get_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIwFY0qyl6XeW010T3TIm\?client_secret=pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nBTo7AtQKVIBGo +Content-Length: 892 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV", + "id" : "pi_3Q5BIwFY0qyl6XeW010T3TIm", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809214, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0007_post_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0007_post_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm_confirm.tail new file mode 100644 index 00000000..4511deba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0007_post_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm_confirm.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIwFY0qyl6XeW010T3TIm\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_APe2YavpUcX763 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2226 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:16 GMT +original-request: req_APe2YavpUcX763 +stripe-version: 2020-08-27 +idempotency-key: 0a56f458-29e1-4b65-845b-e8becc09b24c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV&expand\[0]=payment_method&payment_method=pm_1Q5BIwFY0qyl6XeWoSZIuQ5l&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUb2VFTkhaUW82RlkwVkdydGVKVmdCZm8yWTZZ0100HMElHoU6.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUb2VFTkhaUW82RlkwVkdydGVKVmdCZm8yWTZZ0100HMElHoU6.svg", + "expires_at" : 1727809236 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMCN8bcGMgbShRgH9cw6MJVYOYeUDUlOrYMECJzbkU7AAha7RegxaFxeFHly3DtpH2eqLTGafFU7UvCDONHGig", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5TIEGbmiZatfODE0dBKjkgaQD3t0X" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BIwFY0qyl6XeWoSZIuQ5l", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809214, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV", + "id" : "pi_3Q5BIwFY0qyl6XeW010T3TIm", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809214, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0008_post_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0008_post_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm_refresh.tail new file mode 100644 index 00000000..eeb512c1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0008_post_v1_payment_intents_pi_3Q5BIwFY0qyl6XeW010T3TIm_refresh.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIwFY0qyl6XeW010T3TIm\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zsTu1xXNl96vki +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2248 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:17 GMT +original-request: req_zsTu1xXNl96vki +stripe-version: 2020-08-27 +idempotency-key: 7add574a-0c3e-42c5-a874-fe54bb8d2593 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUb2VFTkhaUW82RlkwVkdydGVKVmdCZm8yWTZZ0100HMElHoU6.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUb2VFTkhaUW82RlkwVkdydGVKVmdCZm8yWTZZ0100HMElHoU6.svg", + "expires_at" : 1727809237 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMGN8bcGMgYAkQRUz406MJVXRNzehFKNUU1FUw8n1U-L2gB7tQE9Nc0_kzD9_1Ds0HXigrVFk9bDdsM-qm7hzQ", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5TIEGbmiZatfODE0dBKjkgaQD3t0X" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BIwFY0qyl6XeWoSZIuQ5l", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809214, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "client_secret" : "pi_3Q5BIwFY0qyl6XeW010T3TIm_secret_CyK8N2MiN1Wh52JqevyevrVLV", + "id" : "pi_3Q5BIwFY0qyl6XeW010T3TIm", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809214, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..4a27d551 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dp1iSwgPzi0uAa +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:17 GMT +original-request: req_dp1iSwgPzi0uAa +stripe-version: 2020-08-27 +idempotency-key: ec49e7fe-6410-468d-a887-be6b76c9d274 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1Q5BIzFY0qyl6XeWrmGNWrgC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809217, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..dea797ae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=lu%2Brcd7%2Fi%2FQqD0yDZTiRRzSlK5KR8%2BPb5fBsAEoU98Ubr0ocenm32mUAGM7TSALQXK4fdPMomjIL%2FjLsYDYI5%2FycjzM3aAJzd1Fcp%2FY10rhi5sndnJ%2BSf%2BWlTcAar5ibrcpYgJLMTVHvXweRcXjhuh8N1mgMc%2BZarTaZY8l3HPd6DUUEo6r82JhlfKwt3TI0xVUL0rKIN6G8bNyHlf%2BwcHt0CsgITvWN06gQDLHgoTk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f474194c773ba2db90d385e96744c9cb +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5BIzFY0qyl6XeW1rwABgZ2","secret":"pi_3Q5BIzFY0qyl6XeW1rwABgZ2_secret_8L12WDTiEbtXI9AIRO1OfRxzH","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0011_get_v1_payment_intents_pi_3Q5BIzFY0qyl6XeW1rwABgZ2.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0011_get_v1_payment_intents_pi_3Q5BIzFY0qyl6XeW1rwABgZ2.tail new file mode 100644 index 00000000..3d9212b5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0011_get_v1_payment_intents_pi_3Q5BIzFY0qyl6XeW1rwABgZ2.tail @@ -0,0 +1,101 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIzFY0qyl6XeW1rwABgZ2\?client_secret=pi_3Q5BIzFY0qyl6XeW1rwABgZ2_secret_8L12WDTiEbtXI9AIRO1OfRxzH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6S7PKt4paYshrW +Content-Length: 2226 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUWU5YZkk3eU4wWnJyZE9OdFoyYVRiTWhkQnhi0100KNqP3WqS.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUWU5YZkk3eU4wWnJyZE9OdFoyYVRiTWhkQnhi0100KNqP3WqS.svg", + "expires_at" : 1727809239 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMON8bcGMgbbj0uxb4I6MJWycKqMNQWl6X548sKyAv-UPbl1vr4J6s9chHy0Ifl3Q3CREhnock3ALfs1puTOdQ", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5TmCe8EV8wWpzUrikAEOS98RsA67b" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BIzFY0qyl6XeWrmGNWrgC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809217, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3Q5BIzFY0qyl6XeW1rwABgZ2_secret_8L12WDTiEbtXI9AIRO1OfRxzH", + "id" : "pi_3Q5BIzFY0qyl6XeW1rwABgZ2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809217, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0012_post_v1_payment_intents_pi_3Q5BIzFY0qyl6XeW1rwABgZ2_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0012_post_v1_payment_intents_pi_3Q5BIzFY0qyl6XeW1rwABgZ2_refresh.tail new file mode 100644 index 00000000..94b65e15 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0012_post_v1_payment_intents_pi_3Q5BIzFY0qyl6XeW1rwABgZ2_refresh.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BIzFY0qyl6XeW1rwABgZ2\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QYB92GD8GiVX73 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2248 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:19 GMT +original-request: req_QYB92GD8GiVX73 +stripe-version: 2020-08-27 +idempotency-key: a6cfc38e-e4a6-4761-9a15-f7805ca7baef +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BIzFY0qyl6XeW1rwABgZ2_secret_8L12WDTiEbtXI9AIRO1OfRxzH&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUWU5YZkk3eU4wWnJyZE9OdFoyYVRiTWhkQnhi0100KNqP3WqS.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVUWU5YZkk3eU4wWnJyZE9OdFoyYVRiTWhkQnhi0100KNqP3WqS.svg", + "expires_at" : 1727809239 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMON8bcGMgYf_nQyN0I6MJWRG6S0qoyvBmZh7ygiz3ARS3PSh6990chXaHN8d8-Ik-QLz3TPvkuHZwlctuUpfw", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5TmCe8EV8wWpzUrikAEOS98RsA67b" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BIzFY0qyl6XeWrmGNWrgC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809217, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "client_secret" : "pi_3Q5BIzFY0qyl6XeW1rwABgZ2_secret_8L12WDTiEbtXI9AIRO1OfRxzH", + "id" : "pi_3Q5BIzFY0qyl6XeW1rwABgZ2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1727809217, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..106ab73c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=JIJscHTt44K1b%2FtjFruOWdCgsTd9pQERPl8MBeBzxcDyn0cw5mcVDvBNu9aO4iQKRAcg%2FcYfy0eQXYbBh39Hpmtgb0cluXyoGCYVX87aVygoVbtsIz5qPX%2FMEC3Z%2F55sIJZ9hLEL77Z0SmB2lu97o%2Fk7KSV41JUaMrlr8TYkYiy%2FyP7X2gww5x3hBFVk79KCFmXM8F6%2FoJKDFIQUZKyUBdm2B4qZ78KCMft6ZMK4qOs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 13a6d0254fd824a1b301d95285c9bbc4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:20 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5BJ2FY0qyl6XeW0YgU1qNj","secret":"pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0014_get_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0014_get_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj.tail new file mode 100644 index 00000000..082cbdfa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0014_get_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ2FY0qyl6XeW0YgU1qNj\?client_secret=pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mzekZKAxm7ZAZG +Content-Length: 901 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho", + "id" : "pi_3Q5BJ2FY0qyl6XeW0YgU1qNj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809220, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0015_post_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0015_post_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_confirm.tail new file mode 100644 index 00000000..7062a1aa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0015_post_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_confirm.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ2FY0qyl6XeW0YgU1qNj\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TQf4RBiYoi4CJQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2235 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:22 GMT +original-request: req_TQf4RBiYoi4CJQ +stripe-version: 2020-08-27 +idempotency-key: fe3d1003-bbb7-4837-84ba-a001e07c9570 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=cashapp&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVUTY0Mk5rWERqUjRkNGZoOFpySHZwdlVRSlZY0100VEZrTDTR.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVUTY0Mk5rWERqUjRkNGZoOFpySHZwdlVRSlZY0100VEZrTDTR.svg", + "expires_at" : 1727809242 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMaN8bcGMgZp--m39AE6MJWzuNvBfxCzBDcAlSjwr28b12X0aCVycWaUS_eOodg5stFtDb5Mg0SBD2P0zvVl7Q", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5UYfgSp3lybMvXKfiCwLH4YGXQXzv" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJ3FY0qyl6XeW080hbR7V", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809221, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho", + "id" : "pi_3Q5BJ2FY0qyl6XeW0YgU1qNj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809220, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0016_post_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0016_post_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_refresh.tail new file mode 100644 index 00000000..15d4d875 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0016_post_v1_payment_intents_pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_refresh.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ2FY0qyl6XeW0YgU1qNj\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NYfarYGs0q1POa +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2257 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:22 GMT +original-request: req_NYfarYGs0q1POa +stripe-version: 2020-08-27 +idempotency-key: e368720c-4bfa-4412-9b40-3b71e3ca73a6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVUTY0Mk5rWERqUjRkNGZoOFpySHZwdlVRSlZY0100VEZrTDTR.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVUTY0Mk5rWERqUjRkNGZoOFpySHZwdlVRSlZY0100VEZrTDTR.svg", + "expires_at" : 1727809242 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMaN8bcGMgaxjfSRDIQ6MJXsDrcvOxeg7vDWPTA93iFd-kJ6QstYdn-oICGB0n1J20PQp_llfNF79yNbwufO9w", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5UYfgSp3lybMvXKfiCwLH4YGXQXzv" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJ3FY0qyl6XeW080hbR7V", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809221, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "client_secret" : "pi_3Q5BJ2FY0qyl6XeW0YgU1qNj_secret_WXnxNk5CxXGqg1b6wTlbCr3ho", + "id" : "pi_3Q5BJ2FY0qyl6XeW0YgU1qNj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809220, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..ff15eb68 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_y2cskVI44f7sSb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:23 GMT +original-request: req_y2cskVI44f7sSb +stripe-version: 2020-08-27 +idempotency-key: f550536a-0e82-4596-85df-e7c9a20a3eee +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1Q5BJ5FY0qyl6XeWxAPSjtau", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809223, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..18112b2c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Yu59AthgPu6zmYX%2Bbm8ZLnLOk0j63tAlxOEnJyx3zaROj7jSzosboCvsEXaQYacPRV7mGEKNBirpDwp3U26mOvVwRfJAmkX4piKHBRTJmBTsdRTaHYVJ1m%2BspUUkBLCJjXOoBvK%2BK0HkadP0D9neVpD0qY7JvyDCVpvUsaxE8gtEtEsRQo%2Fkh5jNfVjlMqw1gibnrSdj5zAXTH1U9RXNwlGRGXfAVY4KGCoed0%2F9ku8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 96dc3cd97a943bd4722a9d82de47a849;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5BJ5FY0qyl6XeW0J51TxAT","secret":"pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0019_get_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0019_get_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT.tail new file mode 100644 index 00000000..f8c39b25 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0019_get_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ5FY0qyl6XeW0J51TxAT\?client_secret=pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_R1OmoizmWgy3O7 +Content-Length: 901 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe", + "id" : "pi_3Q5BJ5FY0qyl6XeW0J51TxAT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809223, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0020_post_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0020_post_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT_confirm.tail new file mode 100644 index 00000000..28d96288 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0020_post_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT_confirm.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ5FY0qyl6XeW0J51TxAT\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vbr2UrMtnpBcNt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2235 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:25 GMT +original-request: req_vbr2UrMtnpBcNt +stripe-version: 2020-08-27 +idempotency-key: af76adf1-2126-4ff7-bcdf-3dc30ee2dfe3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1Q5BJ5FY0qyl6XeWxAPSjtau&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVeU1zemdZUWpLSnAxQ2RoS3FjeUhFSHFQVVoz0100RPTNUXKj.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVeU1zemdZUWpLSnAxQ2RoS3FjeUhFSHFQVVoz0100RPTNUXKj.svg", + "expires_at" : 1727809245 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMmN8bcGMgb4ekNtDj46MJU9LQX75-6l3IKU-lvg2BE0SEAlQdtQ_wnJysDA5X3WAsKNEQ-PhHqmpYxAyubHyA", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5UUBphwSlorP7tb7I8oW6WEQAVgx8" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJ5FY0qyl6XeWxAPSjtau", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809223, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe", + "id" : "pi_3Q5BJ5FY0qyl6XeW0J51TxAT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809223, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0021_post_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0021_post_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT_refresh.tail new file mode 100644 index 00000000..deb9a733 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0021_post_v1_payment_intents_pi_3Q5BJ5FY0qyl6XeW0J51TxAT_refresh.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ5FY0qyl6XeW0J51TxAT\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2qc4cePoRX745N +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2257 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:26 GMT +original-request: req_2qc4cePoRX745N +stripe-version: 2020-08-27 +idempotency-key: 92f1cd99-32e8-4280-bea4-93ddd2116f3a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVeU1zemdZUWpLSnAxQ2RoS3FjeUhFSHFQVVoz0100RPTNUXKj.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVeU1zemdZUWpLSnAxQ2RoS3FjeUhFSHFQVVoz0100RPTNUXKj.svg", + "expires_at" : 1727809246 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMqN8bcGMgZ8KplnVOw6MJWtVfCx9qLPykPhlEoqvs90vL-YCv6cZ-EOjR9GD5EPMh-o6W-WwnTMms7R6JcPkg", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5UUBphwSlorP7tb7I8oW6WEQAVgx8" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJ5FY0qyl6XeWxAPSjtau", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809223, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "client_secret" : "pi_3Q5BJ5FY0qyl6XeW0J51TxAT_secret_eGsbFjnEjkSnMaDSyEbuLBcfe", + "id" : "pi_3Q5BJ5FY0qyl6XeW0J51TxAT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809223, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0022_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0022_post_v1_payment_methods.tail new file mode 100644 index 00000000..c9af9306 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0022_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SoGplTspWBJ332 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:26 GMT +original-request: req_SoGplTspWBJ332 +stripe-version: 2020-08-27 +idempotency-key: 57610bc5-1255-4ff5-b95b-81dfdada6a40 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1Q5BJ8FY0qyl6XeWh491FtFf", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809226, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0023_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0023_post_create_payment_intent.tail new file mode 100644 index 00000000..e597297e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0023_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=1Moe%2FGmUVkNI5ebh8S4JUiUKYTFwIShMjNYjjmRXP7sUpICHucA115gBlo9GXiz4H4BbQiyOtI7tcqFZCQF1L0ipeFSPsH6OZ3MhNtN2MzjRUXhpDuSjKZS0ntsqMYwZSTuqoyNje2HO94%2FQlr6I6IVZuDmWbjuDDoEv7TJ268nPp34QmrIOK2iG8%2F6koe%2BgyTcKPRdLuKmntwZpUgAwukgFCbGJ%2BK1vwQduC4FJNFM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: da8d3cb9abaa362d09a1364fa8905790 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5BJ8FY0qyl6XeW07fg3EYa","secret":"pi_3Q5BJ8FY0qyl6XeW07fg3EYa_secret_b9f9vK1ADg0xeTZrgb7vDjbTH","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0024_get_v1_payment_intents_pi_3Q5BJ8FY0qyl6XeW07fg3EYa.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0024_get_v1_payment_intents_pi_3Q5BJ8FY0qyl6XeW07fg3EYa.tail new file mode 100644 index 00000000..e9296411 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0024_get_v1_payment_intents_pi_3Q5BJ8FY0qyl6XeW07fg3EYa.tail @@ -0,0 +1,101 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ8FY0qyl6XeW07fg3EYa\?client_secret=pi_3Q5BJ8FY0qyl6XeW07fg3EYa_secret_b9f9vK1ADg0xeTZrgb7vDjbTH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uAXU7DvoPKDco4 +Content-Length: 2235 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVQng5R2owSHZNT2x1UXRycGlNUmFURTF6NzJ60100yevmBPTR.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVQng5R2owSHZNT2x1UXRycGlNUmFURTF6NzJ60100yevmBPTR.svg", + "expires_at" : 1727809248 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMyN8bcGMgZdeqEtOm06MJWzUU0zH9fqdTJG7ubt_UaKgkIsV3zYp4PTf0jezdM0EXvwZvINwl7rDHTGgir9dQ", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5UHhLlOuJ049y6pdeSpQriOtcQSBW" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJ8FY0qyl6XeWh491FtFf", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809226, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3Q5BJ8FY0qyl6XeW07fg3EYa_secret_b9f9vK1ADg0xeTZrgb7vDjbTH", + "id" : "pi_3Q5BJ8FY0qyl6XeW07fg3EYa", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809226, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0025_post_v1_payment_intents_pi_3Q5BJ8FY0qyl6XeW07fg3EYa_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0025_post_v1_payment_intents_pi_3Q5BJ8FY0qyl6XeW07fg3EYa_refresh.tail new file mode 100644 index 00000000..dfaca908 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0025_post_v1_payment_intents_pi_3Q5BJ8FY0qyl6XeW07fg3EYa_refresh.tail @@ -0,0 +1,105 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5BJ8FY0qyl6XeW07fg3EYa\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WJatGRAgWztNVk +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2257 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:28 GMT +original-request: req_WJatGRAgWztNVk +stripe-version: 2020-08-27 +idempotency-key: ffd7dc69-36da-45f6-a590-eef06b40abf6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5BJ8FY0qyl6XeW07fg3EYa_secret_b9f9vK1ADg0xeTZrgb7vDjbTH&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVQng5R2owSHZNT2x1UXRycGlNUmFURTF6NzJ60100yevmBPTR.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVQng5R2owSHZNT2x1UXRycGlNUmFURTF6NzJ60100yevmBPTR.svg", + "expires_at" : 1727809248 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKMyN8bcGMgZkM1ddZfQ6MJVwIgbAqpRgr-KlVJxrFJe4sl8ZRAkYrRvQueF46hv6fOXO4SCcUNm6s9f-9jPJLQ", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_Qx5UHhLlOuJ049y6pdeSpQriOtcQSBW" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJ8FY0qyl6XeWh491FtFf", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809226, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "client_secret" : "pi_3Q5BJ8FY0qyl6XeW07fg3EYa_secret_b9f9vK1ADg0xeTZrgb7vDjbTH", + "id" : "pi_3Q5BJ8FY0qyl6XeW07fg3EYa", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : "off_session", + "created" : 1727809226, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0026_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0026_post_create_setup_intent.tail new file mode 100644 index 00000000..3e5960f9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0026_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6olK3ZLOlN32UFXdg3ZRQvsDYMGGZ587PJSs7RWXfuBCdw14%2Bh1%2F3Wl6XAZ3H2fRtvS2JhhZpAQM0x6OxSnxmu2PQmSoR7DGrgzvBo3xZiksAHtiHXyKaTSbkMBAOY9NblVpEBTC3AKUmqv%2Ft0OmIIb6pE4ox1v0%2B4bU9zpg0kfHqkdDCSNfuD4oddi6XGsIYGglBuJUrBHogWz8HduyJb%2F%2Fe9tRvSwUGrJL%2BekdUPs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5cbd43919ad4bb04cd7c28dc6732bf1a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Q5BJBFY0qyl6XeWlMsdxJ64","secret":"seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0027_get_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0027_get_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64.tail new file mode 100644 index 00000000..00a16175 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0027_get_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64.tail @@ -0,0 +1,46 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJBFY0qyl6XeWlMsdxJ64\?client_secret=seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tFvd1NXw4RhUmH +Content-Length: 536 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5BJBFY0qyl6XeWlMsdxJ64", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809229, + "client_secret" : "seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0028_post_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0028_post_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64_confirm.tail new file mode 100644 index 00000000..9033210c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0028_post_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64_confirm.tail @@ -0,0 +1,86 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJBFY0qyl6XeWlMsdxJ64\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pMWn3Emj9Adc47 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1870 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:31 GMT +original-request: req_pMWn3Emj9Adc47 +stripe-version: 2020-08-27 +idempotency-key: e3409842-a039-441a-b711-4bdb8e5280c8 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=cashapp&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1Q5BJBFY0qyl6XeWlMsdxJ64", + "description" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVVEQ1cmZ1b1NlQVVmbXVsY0NGT2I0VFJPVWp00100qLbVNvKy.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVVEQ1cmZ1b1NlQVVmbXVsY0NGT2I0VFJPVWp00100qLbVNvKy.svg", + "expires_at" : 1727809250 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKM6N8bcGMgZlYJ7tA086MJVW0A9GY-I_3G14FsM-l90j-ttlt70I9hWuYOMyjiBV30eM41MVX9_N1YSj5KI1OA", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_Qx5UaZrRVZ1NuGuk2ABbDA8N6JOGROp" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJBFY0qyl6XeW1b1luEz6", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809229, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809229, + "client_secret" : "seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0029_post_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0029_post_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64_refresh.tail new file mode 100644 index 00000000..1f128315 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0029_post_v1_setup_intents_seti_1Q5BJBFY0qyl6XeWlMsdxJ64_refresh.tail @@ -0,0 +1,86 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJBFY0qyl6XeWlMsdxJ64\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xSI9wDtIEMn9Jj +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1892 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:31 GMT +original-request: req_xSI9wDtIEMn9Jj +stripe-version: 2020-08-27 +idempotency-key: b8a1a882-c0ce-43b2-8745-e25780219db4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H&expand\[0]=payment_method + +{ + "id" : "seti_1Q5BJBFY0qyl6XeWlMsdxJ64", + "description" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVVEQ1cmZ1b1NlQVVmbXVsY0NGT2I0VFJPVWp00100qLbVNvKy.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVVEQ1cmZ1b1NlQVVmbXVsY0NGT2I0VFJPVWp00100qLbVNvKy.svg", + "expires_at" : 1727809251 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKM-N8bcGMgbX8C0ma0c6MJWS62g3D_l2Rhgh7Hit0F1iLzZcK91sXKsPWHjkTpamwMaJj3My767TjucYL8m-rg", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_Qx5UaZrRVZ1NuGuk2ABbDA8N6JOGROp" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJBFY0qyl6XeW1b1luEz6", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809229, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809229, + "client_secret" : "seti_1Q5BJBFY0qyl6XeWlMsdxJ64_secret_Qx5UkYK5f3qrtvpJPSck3VSHAiDVZ7H", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0030_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0030_post_v1_payment_methods.tail new file mode 100644 index 00000000..eb6ac563 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0030_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mWRsMaR4tzlNgG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:31 GMT +original-request: req_mWRsMaR4tzlNgG +stripe-version: 2020-08-27 +idempotency-key: 632798a9-529f-4c3b-b6a2-8effde8fbddd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1Q5BJDFY0qyl6XeWzVZGj2Eh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809231, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0031_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0031_post_create_setup_intent.tail new file mode 100644 index 00000000..2f491aff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0031_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=h%2FsLEDWoMgDMT0swZTPq3krGrgXtzswKvv2hu1khErcorY1LF2%2BHev9yrVSIzWGCA%2BnMyPrMBP5uNTu4VGnId%2Bsww57YFT1Nt9mUEXtJe3CFsbeFZ00hgLySi8dffglQ9qtvL2HeYLtCfkRBl9FZNYjnebD7d7WkZdh1KPObos85n0xy6GRL4PhyLZ8po8LZ0CDzGfj46NkisP32Dch3vwqt1%2FpUeGFtBZOrD4E%2BokM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3ec6793a1f6c94ed9aba554ad2fddf36 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:31 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Q5BJDFY0qyl6XeWw19mrlHH","secret":"seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0032_get_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0032_get_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH.tail new file mode 100644 index 00000000..d0caf74f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0032_get_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH.tail @@ -0,0 +1,46 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJDFY0qyl6XeWw19mrlHH\?client_secret=seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZfT28zZWwWLcMr +Content-Length: 536 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5BJDFY0qyl6XeWw19mrlHH", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809231, + "client_secret" : "seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0033_post_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0033_post_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH_confirm.tail new file mode 100644 index 00000000..2b5a501a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0033_post_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH_confirm.tail @@ -0,0 +1,86 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJDFY0qyl6XeWw19mrlHH\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GvG0CcVwBSkIYF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1870 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:33 GMT +original-request: req_GvG0CcVwBSkIYF +stripe-version: 2020-08-27 +idempotency-key: 8b221075-618c-4f0c-ae33-d23a3207ba25 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1Q5BJDFY0qyl6XeWzVZGj2Eh&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1Q5BJDFY0qyl6XeWw19mrlHH", + "description" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVTTN2cGtjdTJmYTFuVnRIUEU3VFYwUWtOa3Nx0100itWv1Asr.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVTTN2cGtjdTJmYTFuVnRIUEU3VFYwUWtOa3Nx0100itWv1Asr.svg", + "expires_at" : 1727809253 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKNGN8bcGMgZEKo6OO7s6MJXqFdAOc0FPbsOZS3-qZJreCPkwdJ2diS0QXV-KatrOsx-_UtbYyMZ7AS33UXBFqg", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_Qx5UfQVp3odKPDvqfUZ0RZ57zMrowIe" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJDFY0qyl6XeWzVZGj2Eh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809231, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809231, + "client_secret" : "seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0034_post_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0034_post_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH_refresh.tail new file mode 100644 index 00000000..8b84da6e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0034_post_v1_setup_intents_seti_1Q5BJDFY0qyl6XeWw19mrlHH_refresh.tail @@ -0,0 +1,86 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJDFY0qyl6XeWw19mrlHH\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bHmM0hGlEnrNdN +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1892 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:34 GMT +original-request: req_bHmM0hGlEnrNdN +stripe-version: 2020-08-27 +idempotency-key: 87f7b70c-e589-4ec3-9fb7-a9132c53bbde +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U&expand\[0]=payment_method + +{ + "id" : "seti_1Q5BJDFY0qyl6XeWw19mrlHH", + "description" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVTTN2cGtjdTJmYTFuVnRIUEU3VFYwUWtOa3Nx0100itWv1Asr.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVTTN2cGtjdTJmYTFuVnRIUEU3VFYwUWtOa3Nx0100itWv1Asr.svg", + "expires_at" : 1727809254 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKNKN8bcGMgb4byM0bVQ6MJWpG7VTcJMHMs0NVsFrFr27GJovvDfF4bTJty-FwB27OEG2qdK0aTN7vniVI_AXfA", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_Qx5UfQVp3odKPDvqfUZ0RZ57zMrowIe" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJDFY0qyl6XeWzVZGj2Eh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809231, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809231, + "client_secret" : "seti_1Q5BJDFY0qyl6XeWw19mrlHH_secret_Qx5U2J3vzu11s056lXzSf9l342L547U", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0035_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0035_post_v1_payment_methods.tail new file mode 100644 index 00000000..797b8a2b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0035_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gnS4XhM3Bc4RmR +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:34 GMT +original-request: req_gnS4XhM3Bc4RmR +stripe-version: 2020-08-27 +idempotency-key: c94208a0-2ce7-414b-9563-64f974fae6c6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1Q5BJGFY0qyl6XeWd2Y3X3zX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809234, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0036_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0036_post_create_setup_intent.tail new file mode 100644 index 00000000..caa2500c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0036_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Q5FdpocYYgMl7P74Mu4BFMynptCqhGiDtxCS83JmwnXwZrya2gySFfBqez54LEunPyZFtZtH63iqyUoKb4mlHTIVq8wL7pdmt1ZUvZ%2BB5S%2BmZJTllgEOAM1RRA12yIqHj4POjpV3NC7Bv8uKLGeM5v7ZpNDav8Ogb%2F%2BOxOwMT2tVdvoNJaujMMGekYEvl0aDJyWhODVLDPitxSO40WUYB2IB%2BuEzi0u3OjR%2F%2BzFJNpM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 46d593d0fa4f7dd435ea41e1b5f31ee7;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 01 Oct 2024 19:00:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Q5BJGFY0qyl6XeWjcIhnRdn","secret":"seti_1Q5BJGFY0qyl6XeWjcIhnRdn_secret_Qx5UNtcejHZzNdvZz3K69H2jCLgskhM","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0037_get_v1_setup_intents_seti_1Q5BJGFY0qyl6XeWjcIhnRdn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0037_get_v1_setup_intents_seti_1Q5BJGFY0qyl6XeWjcIhnRdn.tail new file mode 100644 index 00000000..7e003da6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0037_get_v1_setup_intents_seti_1Q5BJGFY0qyl6XeWjcIhnRdn.tail @@ -0,0 +1,82 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJGFY0qyl6XeWjcIhnRdn\?client_secret=seti_1Q5BJGFY0qyl6XeWjcIhnRdn_secret_Qx5UNtcejHZzNdvZz3K69H2jCLgskhM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gigcjRFRDZNJfD +Content-Length: 1870 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5BJGFY0qyl6XeWjcIhnRdn", + "description" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVNEhnaVhwUXhhZFJwcjRyU0NKdHlkRjdNMlhW01005PNCZjL1.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVNEhnaVhwUXhhZFJwcjRyU0NKdHlkRjdNMlhW01005PNCZjL1.svg", + "expires_at" : 1727809256 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKNSN8bcGMgYaaRvqZyY6MJWABIUO5srKBTbNk0qFy1FlH_5moLVMIFHPXlmQwFfyLh6kZBG5uxHBkJZczXmtsA", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_Qx5UzDdiqTyqUmxBZkCuRFLtJGW05ns" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJGFY0qyl6XeWd2Y3X3zX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809234, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809234, + "client_secret" : "seti_1Q5BJGFY0qyl6XeWjcIhnRdn_secret_Qx5UNtcejHZzNdvZz3K69H2jCLgskhM", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0038_post_v1_setup_intents_seti_1Q5BJGFY0qyl6XeWjcIhnRdn_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0038_post_v1_setup_intents_seti_1Q5BJGFY0qyl6XeWjcIhnRdn_refresh.tail new file mode 100644 index 00000000..ad9c7f90 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testCashAppConfirmFlows/0038_post_v1_setup_intents_seti_1Q5BJGFY0qyl6XeWjcIhnRdn_refresh.tail @@ -0,0 +1,86 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5BJGFY0qyl6XeWjcIhnRdn\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_s7W8K6WxdCqIQQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1892 +Vary: Origin +Date: Tue, 01 Oct 2024 19:00:36 GMT +original-request: req_s7W8K6WxdCqIQQ +stripe-version: 2020-08-27 +idempotency-key: 237821f9-060e-4977-9a32-ff1ab69fe7f9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5BJGFY0qyl6XeWjcIhnRdn_secret_Qx5UNtcejHZzNdvZz3K69H2jCLgskhM&expand\[0]=payment_method + +{ + "id" : "seti_1Q5BJGFY0qyl6XeWjcIhnRdn", + "description" : null, + "next_action" : { + "type" : "cashapp_handle_redirect_or_display_qr_code", + "cashapp_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVNEhnaVhwUXhhZFJwcjRyU0NKdHlkRjdNMlhW01005PNCZjL1.png", + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9ReDVVNEhnaVhwUXhhZFJwcjRyU0NKdHlkRjdNMlhW01005PNCZjL1.svg", + "expires_at" : 1727809256 + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/qr\/instructions\/CDQaFwoVYWNjdF8xRzZtMXBGWTBxeWw2WGVXKNSN8bcGMgZf_9hMu7U6MJWICUYXLU0L6tgxjdPaxg-PH4Hx5G6qjJoOPrJXwDl4ExXCQ7hufcMSUntHzoOcaQ", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_Qx5UzDdiqTyqUmxBZkCuRFLtJGW05ns" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Q5BJGFY0qyl6XeWd2Y3X3zX", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727809234, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : "$test_cashtag", + "buyer_id" : "test_buyer_id" + } + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "cashapp" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727809234, + "client_secret" : "seti_1Q5BJGFY0qyl6XeWjcIhnRdn_secret_Qx5UNtcejHZzNdvZz3K69H2jCLgskhM", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..2d38d502 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=dAHCg5h%2FD4gGr4%2FfA7Jx%2FyKKnlK%2BQ9wvL0MePvFyY%2B70v9TNmquYjTfV9xKGc7HN%2BjOYQ5%2Bw%2Be0tDz4ZJWl1j6bjw00G8wAAS2RvlW0%2BLZyJRfrjVDNx6m%2BCjEBkV0wxYCNV1SF%2FGAARHmw%2Fj3QwWszSsG3zdpX%2Br1bMvk9A7RKQ%2FLsDLKFzRcqzQR3u2gghq%2FJ8jX9ESxxfgQWgu11B2IimhGEFZ74jzGZmV6kOXvE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 57f565d057eee5deb468137676b8d94f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTH1INV13NEzqK12Jr5dBU","secret":"pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0001_get_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0001_get_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU.tail new file mode 100644 index 00000000..94623f26 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0001_get_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH1INV13NEzqK12Jr5dBU\?client_secret=pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xWdan1myW6eobG +Content-Length: 888 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO", + "id" : "pi_3PiTH1INV13NEzqK12Jr5dBU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396743, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0002_post_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0002_post_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU_confirm.tail new file mode 100644 index 00000000..62804c56 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0002_post_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU_confirm.tail @@ -0,0 +1,99 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH1INV13NEzqK12Jr5dBU\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EkM6MNDvuK5ndz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1659 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:24 GMT +original-request: req_EkM6MNDvuK5ndz +stripe-version: 2020-08-27 +idempotency-key: 12edaca5-c5ed-4c24-b26b-39f6a4377b17 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[fpx%5Bbank%5D]=affin_bank&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=fpx&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1FMnBmINV13NEzqK\/pa_nonce_QZcWuodBADfm6LgiAITrNixuSMLqXWW" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH2INV13NEzqKGX3mNfm7", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396744, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } + }, + "client_secret" : "pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO", + "id" : "pi_3PiTH1INV13NEzqK12Jr5dBU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396743, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0003_get_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0003_get_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU.tail new file mode 100644 index 00000000..4e77d48c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0003_get_v1_payment_intents_pi_3PiTH1INV13NEzqK12Jr5dBU.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH1INV13NEzqK12Jr5dBU\?client_secret=pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_iTaNzOCVKyWDQQ +Content-Length: 1659 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1FMnBmINV13NEzqK\/pa_nonce_QZcWuodBADfm6LgiAITrNixuSMLqXWW" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH2INV13NEzqKGX3mNfm7", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396744, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } + }, + "client_secret" : "pi_3PiTH1INV13NEzqK12Jr5dBU_secret_orsISGZlBl0nFcPAzehFPtasO", + "id" : "pi_3PiTH1INV13NEzqK12Jr5dBU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396743, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..7e9308e7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VAnlNkDCRMMh1Q +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 511 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:25 GMT +original-request: req_VAnlNkDCRMMh1Q +stripe-version: 2020-08-27 +idempotency-key: ee23e980-c014-48b5-8c8d-44b4200125fe +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&fpx%5Bbank%5D=affin_bank&payment_user_agent=.*&type=fpx + +{ + "object" : "payment_method", + "id" : "pm_1PiTH3INV13NEzqK76MPDWkv", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396745, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..24c38efb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=XEDVf35MzoW%2F9I5KXJiDueSpsIgM%2FI8%2BxFBf32JL8qes5rT0XU39a1dxkqbBn%2FRynlsNaQAI%2FzwINuM8r%2B0ljznlpjrXwUV5VHaXbx01wtkwfzS3Md%2BHCqipTU%2B1x0LXL0hCZBPZnTSbpL6W2aNv9JXu9xXqgqezvFGLq7raQimYH2u%2B%2FFqOT3tePa%2FjKo3hSp%2BIyFbe2lY98UghoBGE34QlkDzypQbHUYPLgG%2FkcVY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 80a085acda8736955956d494c59eb481 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTH3INV13NEzqK1pkJxeaL","secret":"pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0006_get_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0006_get_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL.tail new file mode 100644 index 00000000..b28f846e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0006_get_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH3INV13NEzqK1pkJxeaL\?client_secret=pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9VwhWz90dWti0W +Content-Length: 888 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP", + "id" : "pi_3PiTH3INV13NEzqK1pkJxeaL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396745, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0007_post_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0007_post_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL_confirm.tail new file mode 100644 index 00000000..5bdb06c2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0007_post_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL_confirm.tail @@ -0,0 +1,99 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH3INV13NEzqK1pkJxeaL\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_anIgtJfRr3twKe +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1659 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:26 GMT +original-request: req_anIgtJfRr3twKe +stripe-version: 2020-08-27 +idempotency-key: 01467e22-31d2-4348-800b-81408077a5b5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP&expand\[0]=payment_method&payment_method=pm_1PiTH3INV13NEzqK76MPDWkv&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1FMnBmINV13NEzqK\/pa_nonce_QZcWtV6mzfLdtvoJHrd3PWIsyNJH99V" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH3INV13NEzqK76MPDWkv", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396745, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } + }, + "client_secret" : "pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP", + "id" : "pi_3PiTH3INV13NEzqK1pkJxeaL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396745, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0008_get_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0008_get_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL.tail new file mode 100644 index 00000000..820734c2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0008_get_v1_payment_intents_pi_3PiTH3INV13NEzqK1pkJxeaL.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH3INV13NEzqK1pkJxeaL\?client_secret=pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xfV4cyOBBqdTaT +Content-Length: 1659 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1FMnBmINV13NEzqK\/pa_nonce_QZcWtV6mzfLdtvoJHrd3PWIsyNJH99V" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH3INV13NEzqK76MPDWkv", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396745, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } + }, + "client_secret" : "pi_3PiTH3INV13NEzqK1pkJxeaL_secret_XcTgrJd0xImD3AgcqeaGFrbkP", + "id" : "pi_3PiTH3INV13NEzqK1pkJxeaL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396745, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..103132ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pOOGFsT9phtnkz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 511 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:27 GMT +original-request: req_pOOGFsT9phtnkz +stripe-version: 2020-08-27 +idempotency-key: 8fbc23b7-84cb-4dab-a370-245af7b33a06 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&fpx%5Bbank%5D=affin_bank&payment_user_agent=.*&type=fpx + +{ + "object" : "payment_method", + "id" : "pm_1PiTH5INV13NEzqKcJkDVgF8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396747, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..33455aba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=m218dBSJCpwQs6dj3B99WfipfITASxF0mjhw%2FqHPidL%2Bu3jBYqsIbDLyvIn%2FfzjhHQkbL9FI9a9xOrt%2Btw4NWGx85W6H%2FejTiBlzZE8GwQ8UL0KmXzzgZyH0YsP%2BK5DoQYQNQiZnsxhGoh84sSEL%2B4eYq4mi1Jm3Irwgt%2FCVx1Vxe2gcWWKkLmAelfvvWN7Z%2BRpk%2FIh9ZL72yV6rf11f7TZNpGz%2Bvkqwg4SrvqUjEyk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9268c890f29828325ada97ac6032107f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:27 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTH5INV13NEzqK1SfhebHX","secret":"pi_3PiTH5INV13NEzqK1SfhebHX_secret_aPTdZ2D1Si2109O0pE1oaeOCg","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0011_get_v1_payment_intents_pi_3PiTH5INV13NEzqK1SfhebHX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0011_get_v1_payment_intents_pi_3PiTH5INV13NEzqK1SfhebHX.tail new file mode 100644 index 00000000..ceceff98 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0011_get_v1_payment_intents_pi_3PiTH5INV13NEzqK1SfhebHX.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH5INV13NEzqK1SfhebHX\?client_secret=pi_3PiTH5INV13NEzqK1SfhebHX_secret_aPTdZ2D1Si2109O0pE1oaeOCg&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Cgi519ZGdxIKgU +Content-Length: 1653 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1FMnBmINV13NEzqK\/pa_nonce_QZcW1iys9rgEUbUmB6n0R12cN3M97gE" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH5INV13NEzqKcJkDVgF8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396747, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } + }, + "client_secret" : "pi_3PiTH5INV13NEzqK1SfhebHX_secret_aPTdZ2D1Si2109O0pE1oaeOCg", + "id" : "pi_3PiTH5INV13NEzqK1SfhebHX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396747, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0012_get_v1_payment_intents_pi_3PiTH5INV13NEzqK1SfhebHX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0012_get_v1_payment_intents_pi_3PiTH5INV13NEzqK1SfhebHX.tail new file mode 100644 index 00000000..d428cd56 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testFPXConfirmFlows/0012_get_v1_payment_intents_pi_3PiTH5INV13NEzqK1SfhebHX.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH5INV13NEzqK1SfhebHX\?client_secret=pi_3PiTH5INV13NEzqK1SfhebHX_secret_aPTdZ2D1Si2109O0pE1oaeOCg&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dudM0Fijqb6OFs +Content-Length: 1653 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "myr", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1FMnBmINV13NEzqK\/pa_nonce_QZcW1iys9rgEUbUmB6n0R12cN3M97gE" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH5INV13NEzqKcJkDVgF8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396747, + "allow_redisplay" : "unspecified", + "type" : "fpx", + "customer" : null, + "fpx" : { + "account_holder_type" : "individual", + "bank" : "affin_bank" + } + }, + "client_secret" : "pi_3PiTH5INV13NEzqK1SfhebHX_secret_aPTdZ2D1Si2109O0pE1oaeOCg", + "id" : "pi_3PiTH5INV13NEzqK1SfhebHX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "fpx" + ], + "setup_future_usage" : null, + "created" : 1722396747, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..a769d7c0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=m6paKPf8u70u1V3sEkd2zHSXROVs1mMHu0JD34OmMSBde0Q9BEEWQrNJXVF%2FoOqbVwjQ5REG0zyn5GB9RgYhODXqfbywqDxyby8RKINTy22Pq3WKvRl7g1%2FCV7yHzf8p5V8LysXzkOsmhgHWutxqQSMoDxvwAtVguJiDDWmR6ZMmfrKAV7LGJQkS7UZz6ZCzyLQ9T4lpV2XWgXE8r6VKhGG8gPL1CpmWzMKwNzfSKaM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e7cac05f4c2d8c1f977353b8e747d44c;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTH7AOnZToJom10keWm7Vo","secret":"pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo.tail new file mode 100644 index 00000000..7fd25270 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH7AOnZToJom10keWm7Vo\?client_secret=pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mygTzeTB5BPekO +Content-Length: 793 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv", + "id" : "pi_3PiTH7AOnZToJom10keWm7Vo", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396749, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo_confirm.tail new file mode 100644 index 00000000..49680b9f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH7AOnZToJom10keWm7Vo\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7uFeHoL9VGftbQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1497 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:30 GMT +original-request: req_7uFeHoL9VGftbQ +stripe-version: 2020-08-27 +idempotency-key: f7803695-3344-4978-b9fb-bcdd32954ee0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=grabpay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QZcWL8SpUOYKYH5AaIxh8FoOnS8KzPh" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH8AOnZToJom1Krr6sFaf", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396750, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null + }, + "client_secret" : "pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv", + "id" : "pi_3PiTH7AOnZToJom10keWm7Vo", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396749, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo.tail new file mode 100644 index 00000000..81f4a090 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTH7AOnZToJom10keWm7Vo.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTH7AOnZToJom10keWm7Vo\?client_secret=pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xYOlKS4hLM2eRm +Content-Length: 1497 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QZcWL8SpUOYKYH5AaIxh8FoOnS8KzPh" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH8AOnZToJom1Krr6sFaf", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396750, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null + }, + "client_secret" : "pi_3PiTH7AOnZToJom10keWm7Vo_secret_C6N27BLmn95VgV0dNc8kARwdv", + "id" : "pi_3PiTH7AOnZToJom10keWm7Vo", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396749, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..a37ac340 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NjvmwDnv6dFcFi +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 450 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:31 GMT +original-request: req_NjvmwDnv6dFcFi +stripe-version: 2020-08-27 +idempotency-key: 42e3c3c9-5c13-4f8d-8578-7797c51b6116 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=grabpay + +{ + "object" : "payment_method", + "id" : "pm_1PiTH9AOnZToJom19kSHBA7Y", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396751, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..f2a264ae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=p871b0Be7zDc5XuHBQLQYgEjSD55G6g1o0gH5LhbPr3yY%2FV%2FIAAxKMI6eQhAtmbdG3snlbCQZmqbDqmmR6mtOmJ8BN%2FebTMg3NPe88bTPdsHIvJit21cJbck5ndrcOKHGfzdAbVL0jA%2F7AqSuAQvKwqUt%2FT5K6nf%2FaiqbMaZgXFi25hFAaoQCiD2%2BkFGyYbEfo1QU8qh5fIsAvx2El4SWCRypdinKrkjUkDczIhqgY4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1bd4aa71b7756f5eb7b6cf1130831e99 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:32 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHAAOnZToJom10ZeKkJvu","secret":"pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu.tail new file mode 100644 index 00000000..2c91bf6a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHAAOnZToJom10ZeKkJvu\?client_secret=pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4YJmbtRYQS3q03 +Content-Length: 793 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j", + "id" : "pi_3PiTHAAOnZToJom10ZeKkJvu", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396752, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu_confirm.tail new file mode 100644 index 00000000..b504d8b9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHAAOnZToJom10ZeKkJvu\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rkxxFui5e5Cwb5 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1497 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:33 GMT +original-request: req_rkxxFui5e5Cwb5 +stripe-version: 2020-08-27 +idempotency-key: 175cd0b9-b7df-426b-b22b-99a33bb69db8 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j&expand\[0]=payment_method&payment_method=pm_1PiTH9AOnZToJom19kSHBA7Y&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QZcWHslWCsWguoNLrHKfqj54DT4Wjx2" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH9AOnZToJom19kSHBA7Y", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396751, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null + }, + "client_secret" : "pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j", + "id" : "pi_3PiTHAAOnZToJom10ZeKkJvu", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396752, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu.tail new file mode 100644 index 00000000..9d7b881d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHAAOnZToJom10ZeKkJvu.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHAAOnZToJom10ZeKkJvu\?client_secret=pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_S6DVqGEMa5FTL2 +Content-Length: 1497 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QZcWHslWCsWguoNLrHKfqj54DT4Wjx2" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTH9AOnZToJom19kSHBA7Y", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396751, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null + }, + "client_secret" : "pi_3PiTHAAOnZToJom10ZeKkJvu_secret_B3cqD8YsWpPFNzzecW4Ckd11j", + "id" : "pi_3PiTHAAOnZToJom10ZeKkJvu", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396752, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..b949e61b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_iQSJL4OIne1hyn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 450 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:33 GMT +original-request: req_iQSJL4OIne1hyn +stripe-version: 2020-08-27 +idempotency-key: 3d8db0b7-326c-4fd7-836b-540042f9ac51 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=grabpay + +{ + "object" : "payment_method", + "id" : "pm_1PiTHBAOnZToJom1ZE04PZf2", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396753, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..150ec08b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=k04rxHBrWlE4lpJ2kAuhV3EJRSIGb8Va%2BXMUKS3MaCew36S8BR5UCzNEFOSAz6HESBpgUZQrKjLYsbONvx2h%2FB41SZjg8NVdkbnWPvoNRyLfSuiaKxQF8Sfa83fdZloijt%2BcgCszVFNaATjjty36Rw95gkUbO4MGWUF0K4HLCzND%2B2X74e7UrzWeJD7BnDpWnHt5RHotKH%2BpbkwbsVGFhDq3mpQDqlBOLcZ1xnXMrJc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 359c13225e0a5dd18ed715130255818a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:34 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHCAOnZToJom10MAh7KxU","secret":"pi_3PiTHCAOnZToJom10MAh7KxU_secret_b7OsOrlJbnWVPpQwXLyt33zhL","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHCAOnZToJom10MAh7KxU.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHCAOnZToJom10MAh7KxU.tail new file mode 100644 index 00000000..d6f8ecc5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHCAOnZToJom10MAh7KxU.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHCAOnZToJom10MAh7KxU\?client_secret=pi_3PiTHCAOnZToJom10MAh7KxU_secret_b7OsOrlJbnWVPpQwXLyt33zhL&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RTFj4CzTXQwVGY +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QZcWq3BUNCYTbWMp68XOySBxDRbpM62" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHBAOnZToJom1ZE04PZf2", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396753, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null + }, + "client_secret" : "pi_3PiTHCAOnZToJom10MAh7KxU_secret_b7OsOrlJbnWVPpQwXLyt33zhL", + "id" : "pi_3PiTHCAOnZToJom10MAh7KxU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396754, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHCAOnZToJom10MAh7KxU.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHCAOnZToJom10MAh7KxU.tail new file mode 100644 index 00000000..631b11ff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testGrabPayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHCAOnZToJom10MAh7KxU.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHCAOnZToJom10MAh7KxU\?client_secret=pi_3PiTHCAOnZToJom10MAh7KxU_secret_b7OsOrlJbnWVPpQwXLyt33zhL&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0APfI00we6MxJZ +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QZcWq3BUNCYTbWMp68XOySBxDRbpM62" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHBAOnZToJom1ZE04PZf2", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722396753, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null + }, + "client_secret" : "pi_3PiTHCAOnZToJom10MAh7KxU_secret_b7OsOrlJbnWVPpQwXLyt33zhL", + "id" : "pi_3PiTHCAOnZToJom10MAh7KxU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1722396754, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..0b2ffc4a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=LZlPnCTXkAuAXBWAEG5G%2B98DKLIfzFMFwktxuEWSGVLWunMyumSOw%2FkUIYgY%2Ba%2BeqX6ETmviN76gcJn6poePH6yEtM99YoIsOacva28%2BkbdutO7aEdy2H068JkAaMeLzrvgPtdBx%2FIxi%2Flv7Mlh8u7AcH5ZldCyCin4thqTFiTd9dombn5SeKe9HyUba8n6ykhWP%2FlkxsW2cX0u0A3QiUQt%2Bl59bBmslkX6W%2BMhwrOU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3006744aed5e453af44212c396bbafb8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:51 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHTFY0qyl6XeW04DQWUEN","secret":"pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN.tail new file mode 100644 index 00000000..b9bd0789 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHTFY0qyl6XeW04DQWUEN\?client_secret=pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vuaIUOS4mGiCHL +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM", + "id" : "pi_3PiTHTFY0qyl6XeW04DQWUEN", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396771, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN_confirm.tail new file mode 100644 index 00000000..5962ce1a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHTFY0qyl6XeW04DQWUEN\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FrP6P8E6NCk7bI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1651 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:53 GMT +original-request: req_FrP6P8E6NCk7bI +stripe-version: 2020-08-27 +idempotency-key: c0183faf-406c-44e3-b192-197f8a5c4dc5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details%5Baddress%5D%5Bcountry%5D]=US&payment_method_data\[billing_details%5Bemail%5D]=foo%40bar\.com&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=klarna&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWOgi94iiGXAWwMbz9dSzJ2xAh5S1?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHUFY0qyl6XeWLUolZuEL", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396772, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM", + "id" : "pi_3PiTHTFY0qyl6XeW04DQWUEN", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396771, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0003_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWOgi94iiGXAWwMbz9dSzJ2xAh5S1.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0003_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWOgi94iiGXAWwMbz9dSzJ2xAh5S1.tail new file mode 100644 index 00000000..d6fc920a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0003_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWOgi94iiGXAWwMbz9dSzJ2xAh5S1.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWOgi94iiGXAWwMbz9dSzJ2xAh5S1\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2STPr6j +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:32:53 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0004_get_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0004_get_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN.tail new file mode 100644 index 00000000..558d0c24 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0004_get_v1_payment_intents_pi_3PiTHTFY0qyl6XeW04DQWUEN.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHTFY0qyl6XeW04DQWUEN\?client_secret=pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aDg4xjpCsaj1uH +Content-Length: 1651 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWOgi94iiGXAWwMbz9dSzJ2xAh5S1?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHUFY0qyl6XeWLUolZuEL", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396772, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHTFY0qyl6XeW04DQWUEN_secret_dkDNlqfJBArJtgLixMFEsqQaM", + "id" : "pi_3PiTHTFY0qyl6XeW04DQWUEN", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396771, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0005_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0005_post_v1_payment_methods.tail new file mode 100644 index 00000000..9d4320e6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0005_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UKDjyRZnia6Vr5 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:54 GMT +original-request: req_UKDjyRZnia6Vr5 +stripe-version: 2020-08-27 +idempotency-key: 80ac9bd6-49b9-4434-afd4-2d04a0422843 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Baddress%5D%5Bcountry%5D=US&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHWFY0qyl6XeWAyOL20XS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396774, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..0047b8da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=5uB3%2FKkSF11Of%2BzMDKWOeqxlazVH%2FEXYkCSzd7X0EZ9ZAtb5fw5yHWarelYNQrA%2F0Cx0fVg263KqIozCjLht%2B8mBPZulr6QL2y8RQ5mdEYk2tEiOy2%2Beo%2BLJciypZptjTNcXy2pq%2Br0dtb01esMCf0UAqQsHUeomPDfpScuDEjbWf1NyxSPpjkvwFmOFoaV32rx8Riw%2BjhYWi3%2ByBv%2Fwk%2FFnVfY8lKk5Cfd9Y02N8i0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b50d41814429002acea7d3a3a79674c4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:54 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHWFY0qyl6XeW0MSNKjMC","secret":"pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0007_get_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0007_get_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC.tail new file mode 100644 index 00000000..f8f12a3e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0007_get_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHWFY0qyl6XeW0MSNKjMC\?client_secret=pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Q7lLE9St9iAmec +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY", + "id" : "pi_3PiTHWFY0qyl6XeW0MSNKjMC", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396774, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0008_post_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0008_post_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC_confirm.tail new file mode 100644 index 00000000..d5cc574a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0008_post_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHWFY0qyl6XeW0MSNKjMC\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_24Hw79LBXXS6X7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1651 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:55 GMT +original-request: req_24Hw79LBXXS6X7 +stripe-version: 2020-08-27 +idempotency-key: 9598f1d0-6707-4806-8f7b-db73dd4d67c7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY&expand\[0]=payment_method&payment_method=pm_1PiTHWFY0qyl6XeWAyOL20XS&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcW5TtpMtB8PKkvoqj5BVNruPM7C5S?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHWFY0qyl6XeWAyOL20XS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396774, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY", + "id" : "pi_3PiTHWFY0qyl6XeW0MSNKjMC", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396774, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0009_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcW5TtpMtB8PKkvoqj5BVNruPM7C5S.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0009_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcW5TtpMtB8PKkvoqj5BVNruPM7C5S.tail new file mode 100644 index 00000000..f010327f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0009_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcW5TtpMtB8PKkvoqj5BVNruPM7C5S.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcW5TtpMtB8PKkvoqj5BVNruPM7C5S\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2MZjtWj +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:32:56 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0010_get_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0010_get_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC.tail new file mode 100644 index 00000000..e38b4cb1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0010_get_v1_payment_intents_pi_3PiTHWFY0qyl6XeW0MSNKjMC.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHWFY0qyl6XeW0MSNKjMC\?client_secret=pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0HwN7WLao6ppFj +Content-Length: 1651 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcW5TtpMtB8PKkvoqj5BVNruPM7C5S?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHWFY0qyl6XeWAyOL20XS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396774, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHWFY0qyl6XeW0MSNKjMC_secret_idm64oLiXyy0CraHftzj37ILY", + "id" : "pi_3PiTHWFY0qyl6XeW0MSNKjMC", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396774, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0011_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0011_post_v1_payment_methods.tail new file mode 100644 index 00000000..ead61e33 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0011_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xrH3HG5TY8oiNF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:56 GMT +original-request: req_xrH3HG5TY8oiNF +stripe-version: 2020-08-27 +idempotency-key: 392578bf-85bf-46ac-b3ab-6def8e5604f6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Baddress%5D%5Bcountry%5D=US&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHYFY0qyl6XeWN3LtPjU2", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396776, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0012_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0012_post_create_payment_intent.tail new file mode 100644 index 00000000..c2ec1abd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0012_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OQV9LLpgdbBUrc%2BMbys33H9PRVuYRo%2FgDlInLklWUbHwUG1yRjCCCgDLvtobPzj9j5cIiJ36DvPdNGRs9wSucJckrktm5ziZtEhS5kl6pOQD%2FQYZiipUEfHXjGsi7U%2FrqPbG%2FnSILXFinw3R%2BA2YjqnVQ6HbfxTrGI8ApXcHoSEgta3J8PyNOcV7%2FUjmhoQsWcCY6jCD5M%2BT%2BZBmysKa7Sj8ftyKM%2Fe229a3Hp5G2eU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c47e8431d3c10430b0efeb860748c4db +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:57 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHZFY0qyl6XeW1NaLbUlA","secret":"pi_3PiTHZFY0qyl6XeW1NaLbUlA_secret_7XkkBcXAv5AiaTNx9CaYlQfwV","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0013_get_v1_payment_intents_pi_3PiTHZFY0qyl6XeW1NaLbUlA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0013_get_v1_payment_intents_pi_3PiTHZFY0qyl6XeW1NaLbUlA.tail new file mode 100644 index 00000000..bb76e3a2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0013_get_v1_payment_intents_pi_3PiTHZFY0qyl6XeW1NaLbUlA.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHZFY0qyl6XeW1NaLbUlA\?client_secret=pi_3PiTHZFY0qyl6XeW1NaLbUlA_secret_7XkkBcXAv5AiaTNx9CaYlQfwV&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NW27urqumdtbYX +Content-Length: 1645 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWUmwEYQRrYKqJmZd4KaL5NKsND5I?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHYFY0qyl6XeWN3LtPjU2", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396776, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHZFY0qyl6XeW1NaLbUlA_secret_7XkkBcXAv5AiaTNx9CaYlQfwV", + "id" : "pi_3PiTHZFY0qyl6XeW1NaLbUlA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396777, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0014_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWUmwEYQRrYKqJmZd4KaL5NKsND5I.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0014_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWUmwEYQRrYKqJmZd4KaL5NKsND5I.tail new file mode 100644 index 00000000..8e26942a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0014_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWUmwEYQRrYKqJmZd4KaL5NKsND5I.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWUmwEYQRrYKqJmZd4KaL5NKsND5I\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/28lsbXO +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:32:58 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0015_get_v1_payment_intents_pi_3PiTHZFY0qyl6XeW1NaLbUlA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0015_get_v1_payment_intents_pi_3PiTHZFY0qyl6XeW1NaLbUlA.tail new file mode 100644 index 00000000..5d92a3d0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0015_get_v1_payment_intents_pi_3PiTHZFY0qyl6XeW1NaLbUlA.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHZFY0qyl6XeW1NaLbUlA\?client_secret=pi_3PiTHZFY0qyl6XeW1NaLbUlA_secret_7XkkBcXAv5AiaTNx9CaYlQfwV&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pe9gSqXAJ4jcWl +Content-Length: 1645 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWUmwEYQRrYKqJmZd4KaL5NKsND5I?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHYFY0qyl6XeWN3LtPjU2", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396776, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHZFY0qyl6XeW1NaLbUlA_secret_7XkkBcXAv5AiaTNx9CaYlQfwV", + "id" : "pi_3PiTHZFY0qyl6XeW1NaLbUlA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1722396777, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0016_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0016_post_create_payment_intent.tail new file mode 100644 index 00000000..2a065597 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0016_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=th2pXv2B6fRUm7XhVLRUvqGJZJ5P7k8a9to%2BvHfNxCXw6Y6i7Zz%2B0xOcDoHwDDTN9A3oIZAB5qMKfYRh%2Ba7Ri000x5KL6gTpeRxOGV2QYstXuMBHkAyLJzr6%2B4dDJWDDiFfAlt9%2FGGHkWqAfi30Dyi2PhJdvsPqF%2FrbMUtMh5gGWiENWAmFNkURf1DwGyaLmGBd131sNik8LzKi4rkvLsGI%2Fp2QWYBIjpyReN5LQx4I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6f35439ebc706e28bbbe1eee05be1052 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:59 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHbFY0qyl6XeW0ViaYHn2","secret":"pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0017_get_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0017_get_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2.tail new file mode 100644 index 00000000..49e79a9c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0017_get_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHbFY0qyl6XeW0ViaYHn2\?client_secret=pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VN2LVzDgIS556F +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y", + "id" : "pi_3PiTHbFY0qyl6XeW0ViaYHn2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396779, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0018_post_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0018_post_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2_confirm.tail new file mode 100644 index 00000000..2afad523 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0018_post_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHbFY0qyl6XeW0ViaYHn2\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QZX7TqS96ylTOs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1660 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:00 GMT +original-request: req_QZX7TqS96ylTOs +stripe-version: 2020-08-27 +idempotency-key: 051b4d60-cf51-4c9a-90f4-f6dd4dacbd5d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details%5Baddress%5D%5Bcountry%5D]=US&payment_method_data\[billing_details%5Bemail%5D]=foo%40bar\.com&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=klarna&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWXqGqpErtQo8FfcwxefSINg4DeO7?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHbFY0qyl6XeW5yQ7AsYh", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396779, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y", + "id" : "pi_3PiTHbFY0qyl6XeW0ViaYHn2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396779, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0019_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWXqGqpErtQo8FfcwxefSINg4DeO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0019_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWXqGqpErtQo8FfcwxefSINg4DeO7.tail new file mode 100644 index 00000000..9f341a05 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0019_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWXqGqpErtQo8FfcwxefSINg4DeO7.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWXqGqpErtQo8FfcwxefSINg4DeO7\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2dlhDZh +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:33:00 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0020_get_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0020_get_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2.tail new file mode 100644 index 00000000..3abee9e5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0020_get_v1_payment_intents_pi_3PiTHbFY0qyl6XeW0ViaYHn2.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHbFY0qyl6XeW0ViaYHn2\?client_secret=pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IZh96dyvmS9ZHt +Content-Length: 1660 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWXqGqpErtQo8FfcwxefSINg4DeO7?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHbFY0qyl6XeW5yQ7AsYh", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396779, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHbFY0qyl6XeW0ViaYHn2_secret_qYSzzAb9zFfXVoJM24TX9s74Y", + "id" : "pi_3PiTHbFY0qyl6XeW0ViaYHn2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396779, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0021_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0021_post_v1_payment_methods.tail new file mode 100644 index 00000000..b48de294 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0021_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rjUzoZI27oVloV +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:01 GMT +original-request: req_rjUzoZI27oVloV +stripe-version: 2020-08-27 +idempotency-key: b467d3d7-a4f8-487e-8950-5c2381f8c4e0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Baddress%5D%5Bcountry%5D=US&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHdFY0qyl6XeWA1UPQXuF", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396781, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0022_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0022_post_create_payment_intent.tail new file mode 100644 index 00000000..795208d0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0022_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=VvzssUYFErhy8MgZU485BFuOEgHxjOm%2ByoLyyqcDnGSIaP2sT%2FnM4lyCQcmSjyviT6i4NJJ4h50QxI%2F1LbtD%2Bm9FM%2Bk5vi4uIPLTfCnCAszAtN5Jk%2BwaVx5PRMy1Vd0VRuXFsqyJzjMQxigiXz7LGhYVVHyXs%2FagZ1djp68xjRTPp6JyWr5hc5W%2Fhovj%2Bg0%2FTR8sbsEjA8EDYS6jqptBJhHx1Ru3uiAY027LBe5MltU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cc1738cb30665cd8e3ef45a069b1a989;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHdFY0qyl6XeW0GkwHXZj","secret":"pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0023_get_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0023_get_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj.tail new file mode 100644 index 00000000..5767a1bd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0023_get_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHdFY0qyl6XeW0GkwHXZj\?client_secret=pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_c4ZCh33CJOFAZf +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU", + "id" : "pi_3PiTHdFY0qyl6XeW0GkwHXZj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396781, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0024_post_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0024_post_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj_confirm.tail new file mode 100644 index 00000000..f679050c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0024_post_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHdFY0qyl6XeW0GkwHXZj\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IgjEYmaMHBPhnw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1660 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:03 GMT +original-request: req_IgjEYmaMHBPhnw +stripe-version: 2020-08-27 +idempotency-key: 6607bc2c-f2be-4629-8baf-033026287b76 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTHdFY0qyl6XeWA1UPQXuF&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWjghujsb90cOevtFxttNZUSu4r4y?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHdFY0qyl6XeWA1UPQXuF", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396781, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU", + "id" : "pi_3PiTHdFY0qyl6XeW0GkwHXZj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396781, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0025_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWjghujsb90cOevtFxttNZUSu4r4y.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0025_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWjghujsb90cOevtFxttNZUSu4r4y.tail new file mode 100644 index 00000000..f04f13da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0025_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWjghujsb90cOevtFxttNZUSu4r4y.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWjghujsb90cOevtFxttNZUSu4r4y\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/26HJpFw +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:33:03 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0026_get_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0026_get_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj.tail new file mode 100644 index 00000000..d0de5bbb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0026_get_v1_payment_intents_pi_3PiTHdFY0qyl6XeW0GkwHXZj.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHdFY0qyl6XeW0GkwHXZj\?client_secret=pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dEFkEjFZZQGQEV +Content-Length: 1660 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWjghujsb90cOevtFxttNZUSu4r4y?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHdFY0qyl6XeWA1UPQXuF", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396781, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHdFY0qyl6XeW0GkwHXZj_secret_dIsEoy3z9SgErqo0z0vu7HcbU", + "id" : "pi_3PiTHdFY0qyl6XeW0GkwHXZj", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396781, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..42c83e70 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0027_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MkI7STqzBME0ee +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:04 GMT +original-request: req_MkI7STqzBME0ee +stripe-version: 2020-08-27 +idempotency-key: 6b881d47-4960-4bda-92fa-e6cdc85f04d2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Baddress%5D%5Bcountry%5D=US&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHgFY0qyl6XeWZtDaR6sk", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396784, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0028_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0028_post_create_payment_intent.tail new file mode 100644 index 00000000..dd599d5f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0028_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2BGleynaf9sZOpzKdijFSqIBJkJ%2ByqP6CMaFWUBTYf5tRbaC97Z5co0zBZXcA6JZoCgqyh%2BN8Tnrv45nu%2FJ6M307%2B3PINB7h2H3w7tyTUFgmjgpdmas04uwh1yDVjJ%2FOULwdtJAnK%2FPVTG85Qda9Iu%2FMG43X8zRdR1%2BqQmhZ0RZSkFX5WrsU%2BGk%2FyWv%2FTUKK5Cc%2BO1n4iAahy0U7w7rCGt6VKCqAHxvkPG892pU4w8M0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9074304d95d7cc290d5a185d83cc6f86 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHgFY0qyl6XeW156oy7ob","secret":"pi_3PiTHgFY0qyl6XeW156oy7ob_secret_CV44p7ZGuyFK6C0XOQu1wdkz6","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0029_get_v1_payment_intents_pi_3PiTHgFY0qyl6XeW156oy7ob.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0029_get_v1_payment_intents_pi_3PiTHgFY0qyl6XeW156oy7ob.tail new file mode 100644 index 00000000..8e46b398 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0029_get_v1_payment_intents_pi_3PiTHgFY0qyl6XeW156oy7ob.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHgFY0qyl6XeW156oy7ob\?client_secret=pi_3PiTHgFY0qyl6XeW156oy7ob_secret_CV44p7ZGuyFK6C0XOQu1wdkz6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_H5gnQUaTVqEExX +Content-Length: 1654 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWHf0Wjmo92W7CSbZWyoUZ7Wjd0vq?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHgFY0qyl6XeWZtDaR6sk", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396784, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHgFY0qyl6XeW156oy7ob_secret_CV44p7ZGuyFK6C0XOQu1wdkz6", + "id" : "pi_3PiTHgFY0qyl6XeW156oy7ob", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396784, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0030_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWHf0Wjmo92W7CSbZWyoUZ7Wjd0vq.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0030_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWHf0Wjmo92W7CSbZWyoUZ7Wjd0vq.tail new file mode 100644 index 00000000..55de92ac --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0030_get_authorize_acct_1G6m1pFY0qyl6XeW_pa_nonce_QZcWHf0Wjmo92W7CSbZWyoUZ7Wjd0vq.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWHf0Wjmo92W7CSbZWyoUZ7Wjd0vq\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2u8PvuK +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:33:05 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0031_get_v1_payment_intents_pi_3PiTHgFY0qyl6XeW156oy7ob.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0031_get_v1_payment_intents_pi_3PiTHgFY0qyl6XeW156oy7ob.tail new file mode 100644 index 00000000..9d455a06 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0031_get_v1_payment_intents_pi_3PiTHgFY0qyl6XeW156oy7ob.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHgFY0qyl6XeW156oy7ob\?client_secret=pi_3PiTHgFY0qyl6XeW156oy7ob_secret_CV44p7ZGuyFK6C0XOQu1wdkz6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZGbZTyzFmsWyVo +Content-Length: 1654 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWHf0Wjmo92W7CSbZWyoUZ7Wjd0vq?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHgFY0qyl6XeWZtDaR6sk", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396784, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "client_secret" : "pi_3PiTHgFY0qyl6XeW156oy7ob_secret_CV44p7ZGuyFK6C0XOQu1wdkz6", + "id" : "pi_3PiTHgFY0qyl6XeW156oy7ob", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : "off_session", + "created" : 1722396784, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0032_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0032_post_create_setup_intent.tail new file mode 100644 index 00000000..b65c4867 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0032_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=pyKPP%2BTZzRJKNsoj5ahkacGilv9cRkyl0kHQvWLSYoqqtl2JLKwzRZOkJNwwHXOH1n2be9CAiXZPw%2FJdze3PGpBG9nOdUKKbIg%2F9%2FfXrgYRdqDb%2BLU5luvCnU%2Fj3JBZ9QOxCw%2FzA%2BniiCu45bWe2ak3VjI1nxoZIDwlcldzAdcxZ6z9IrSWQ9BBaw%2BYyIlcRpfbnuZ%2BEIOFF%2BUX0qr4LbDG%2FTOglX3c1SfY%2FnibyaHQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 11ac559b8160bc1ea6576c8a3a3217a9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTHiFY0qyl6XeWvHeP60t4","secret":"seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0033_get_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0033_get_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4.tail new file mode 100644 index 00000000..704e85d5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0033_get_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHiFY0qyl6XeWvHeP60t4\?client_secret=seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LbJtgBZRlGNBgs +Content-Length: 535 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHiFY0qyl6XeWvHeP60t4", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396786, + "client_secret" : "seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0034_post_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0034_post_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4_confirm.tail new file mode 100644 index 00000000..a86046d1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0034_post_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHiFY0qyl6XeWvHeP60t4\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_c2qvltqSPLcNqM +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1295 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:08 GMT +original-request: req_c2qvltqSPLcNqM +stripe-version: 2020-08-27 +idempotency-key: 0f51094c-36b2-478e-aa68-178cd9dbb37b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details%5Baddress%5D%5Bcountry%5D]=US&payment_method_data\[billing_details%5Bemail%5D]=foo%40bar\.com&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=klarna&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTHiFY0qyl6XeWvHeP60t4", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWwsqB422p1Fu3hbu6A7grC7cZIiP?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHjFY0qyl6XeWcismCgWh", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396787, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396786, + "client_secret" : "seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0035_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcWwsqB422p1Fu3hbu6A7grC7cZIiP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0035_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcWwsqB422p1Fu3hbu6A7grC7cZIiP.tail new file mode 100644 index 00000000..057ec605 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0035_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcWwsqB422p1Fu3hbu6A7grC7cZIiP.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWwsqB422p1Fu3hbu6A7grC7cZIiP\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2bbOT5j +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:33:08 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0036_get_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0036_get_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4.tail new file mode 100644 index 00000000..9762c007 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0036_get_v1_setup_intents_seti_1PiTHiFY0qyl6XeWvHeP60t4.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHiFY0qyl6XeWvHeP60t4\?client_secret=seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nZNGJeIwkMssjG +Content-Length: 1295 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHiFY0qyl6XeWvHeP60t4", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWwsqB422p1Fu3hbu6A7grC7cZIiP?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHjFY0qyl6XeWcismCgWh", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396787, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396786, + "client_secret" : "seti_1PiTHiFY0qyl6XeWvHeP60t4_secret_QZcWLBK1w8Fj0i52hQErn7FEVLwbKdA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0037_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0037_post_v1_payment_methods.tail new file mode 100644 index 00000000..e226e273 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0037_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ODIvST4JPH590U +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:09 GMT +original-request: req_ODIvST4JPH590U +stripe-version: 2020-08-27 +idempotency-key: 2bca2251-ae6e-44a1-a1aa-2e0900f6b280 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Baddress%5D%5Bcountry%5D=US&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHlFY0qyl6XeWuekeGnKt", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396789, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0038_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0038_post_create_setup_intent.tail new file mode 100644 index 00000000..44338a13 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0038_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=a%2Forbr%2B%2F0Bk0d4bwC%2BR3rODmk5j0bcWQrxzAhfmJDbXr%2BVV5URpdxPrYWQLDuQxav1GyajGNZV6TU%2FmfQerJX%2FcPfbtd5NAA4LOKeo5XJsLHezLtiMz17gzp2ZCp5ylj59%2B1uDOt9%2Fy1jodx4odaJwg7uk8Uygk%2Fds%2BaGCFxmtws%2B40gZ4XpEcePjCHhj%2F%2BKDEWLqxrY07EhPwflwb9K%2FC0QqYZJnnRgcN25kf%2Bf2Po%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a33eb6eba3e58fe5554dcf2fcf1848f5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTHlFY0qyl6XeWJy48DnwP","secret":"seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0039_get_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0039_get_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP.tail new file mode 100644 index 00000000..8a5c59cb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0039_get_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHlFY0qyl6XeWJy48DnwP\?client_secret=seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fXOJVxinPUsly1 +Content-Length: 535 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHlFY0qyl6XeWJy48DnwP", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396789, + "client_secret" : "seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0040_post_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0040_post_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP_confirm.tail new file mode 100644 index 00000000..efd034fd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0040_post_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHlFY0qyl6XeWJy48DnwP\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_j8b3SrsiXnz2JX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1295 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:10 GMT +original-request: req_j8b3SrsiXnz2JX +stripe-version: 2020-08-27 +idempotency-key: 1bb5a55a-0252-495b-a9ba-e04fd6a74453 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTHlFY0qyl6XeWuekeGnKt&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTHlFY0qyl6XeWJy48DnwP", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcW9RUVWuRkuPLmwGilygNKyjczOeD?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHlFY0qyl6XeWuekeGnKt", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396789, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396789, + "client_secret" : "seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0041_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcW9RUVWuRkuPLmwGilygNKyjczOeD.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0041_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcW9RUVWuRkuPLmwGilygNKyjczOeD.tail new file mode 100644 index 00000000..372c602b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0041_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcW9RUVWuRkuPLmwGilygNKyjczOeD.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcW9RUVWuRkuPLmwGilygNKyjczOeD\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2ScqLWR +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:33:11 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0042_get_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0042_get_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP.tail new file mode 100644 index 00000000..9acee2f3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0042_get_v1_setup_intents_seti_1PiTHlFY0qyl6XeWJy48DnwP.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHlFY0qyl6XeWJy48DnwP\?client_secret=seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5ILQjUwovAlHKu +Content-Length: 1295 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHlFY0qyl6XeWJy48DnwP", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcW9RUVWuRkuPLmwGilygNKyjczOeD?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHlFY0qyl6XeWuekeGnKt", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396789, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396789, + "client_secret" : "seti_1PiTHlFY0qyl6XeWJy48DnwP_secret_QZcWl3eVsh911Z8WO9TrOMepelZGpNQ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0043_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0043_post_v1_payment_methods.tail new file mode 100644 index 00000000..6525e021 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0043_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PWPeKB8s4uBQtq +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 457 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:11 GMT +original-request: req_PWPeKB8s4uBQtq +stripe-version: 2020-08-27 +idempotency-key: ad48acc8-678a-4b5e-8cb5-26d84b9b0760 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Baddress%5D%5Bcountry%5D=US&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHnFY0qyl6XeW2FCdeVx6", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396791, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0044_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0044_post_create_setup_intent.tail new file mode 100644 index 00000000..fdb7ce19 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0044_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=sdI0HNlbByqpNbrDijCKrAtrTmRRHNdouVzaDGfvfuJHNjUlHWPh0TT4NOQsgHYP28CvNwR9V8i7jdd3%2F4i9OALzdYEC7m%2B737sA93tPwAFgBMHuvNdo7mxNp%2BDT3OCqf8O0N8VfGPYsRSs8Ig%2Fh5y4pPiMWpygi4LevViQtRtKwME6N2uBxzfctAZNh4ot1njtdmZy04MjqIBIyer9slnPEnBP6pkyJpxhs3kxQMVk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bb2d7eef233db824be54aa9d02527d39;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTHoFY0qyl6XeWbU801LWM","secret":"seti_1PiTHoFY0qyl6XeWbU801LWM_secret_QZcWAO3PVzKNYmLyDXbpIGmaYNGhae9","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0045_get_v1_setup_intents_seti_1PiTHoFY0qyl6XeWbU801LWM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0045_get_v1_setup_intents_seti_1PiTHoFY0qyl6XeWbU801LWM.tail new file mode 100644 index 00000000..dca553c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0045_get_v1_setup_intents_seti_1PiTHoFY0qyl6XeWbU801LWM.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHoFY0qyl6XeWbU801LWM\?client_secret=seti_1PiTHoFY0qyl6XeWbU801LWM_secret_QZcWAO3PVzKNYmLyDXbpIGmaYNGhae9&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_c8bhAHBQnk5dyS +Content-Length: 1289 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHoFY0qyl6XeWbU801LWM", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWNeuTDwJXuRafcPI0TCsslYF5yEW?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHnFY0qyl6XeW2FCdeVx6", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396791, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396792, + "client_secret" : "seti_1PiTHoFY0qyl6XeWbU801LWM_secret_QZcWAO3PVzKNYmLyDXbpIGmaYNGhae9", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0046_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcWNeuTDwJXuRafcPI0TCsslYF5yEW.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0046_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcWNeuTDwJXuRafcPI0TCsslYF5yEW.tail new file mode 100644 index 00000000..6cdd9c7a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0046_get_authorize_acct_1G6m1pFY0qyl6XeW_sa_nonce_QZcWNeuTDwJXuRafcPI0TCsslYF5yEW.tail @@ -0,0 +1,15 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWNeuTDwJXuRafcPI0TCsslYF5yEW\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +Content-Type: text/plain;charset=utf-8 +Location: https://pay.playground.klarna.com/na/hpp/payments/2lMoyVa +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Date: Wed, 31 Jul 2024 03:33:13 GMT +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Content-Length: 0 + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0047_get_v1_setup_intents_seti_1PiTHoFY0qyl6XeWbU801LWM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0047_get_v1_setup_intents_seti_1PiTHoFY0qyl6XeWbU801LWM.tail new file mode 100644 index 00000000..a6cfa713 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKlarnaConfirmFlows/0047_get_v1_setup_intents_seti_1PiTHoFY0qyl6XeWbU801LWM.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHoFY0qyl6XeWbU801LWM\?client_secret=seti_1PiTHoFY0qyl6XeWbU801LWM_secret_QZcWAO3PVzKNYmLyDXbpIGmaYNGhae9&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cLB2QvW6bVfUWe +Content-Length: 1289 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHoFY0qyl6XeWbU801LWM", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWNeuTDwJXuRafcPI0TCsslYF5yEW?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiTHnFY0qyl6XeW2FCdeVx6", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396791, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "klarna" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396792, + "client_secret" : "seti_1PiTHoFY0qyl6XeWbU801LWM_secret_QZcWAO3PVzKNYmLyDXbpIGmaYNGhae9", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..b784b2ec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=KnrTbuIN6YHNCKidus%2Fi3BCDl7KYt7Y%2BjaHSh31FVetdtOTFzczjNGOEiJ7EGv24JA34EZJCfBdGh%2B0PoPQREB%2FYx7LitGrZosr7xIOQxim6p3pv3Q7d2pE60w0AIwZWXjAs%2BXIKsthr%2BtuRupuHYnk8Slz19ROX1Ke9NSs3IUJfWKbwfIPBucHm2dlTc3n0gDQBUi2edP6bbcRYI6ERFIyRC5O87VhCCzB%2BtzOUMaw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c722eb1a71a2002453f6e6e71452b403 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHpIq2LmpyICo17Aj9ab8","secret":"pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8.tail new file mode 100644 index 00000000..8b77ea9c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHpIq2LmpyICo17Aj9ab8\?client_secret=pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_T92C2y18747zir +Content-Length: 793 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL", + "id" : "pi_3PiTHpIq2LmpyICo17Aj9ab8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396793, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8_confirm.tail new file mode 100644 index 00000000..e279dc37 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8_confirm.tail @@ -0,0 +1,111 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHpIq2LmpyICo17Aj9ab8\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2xFGCaBQjjVCGZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2056 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:14 GMT +original-request: req_2xFGCaBQjjVCGZ +stripe-version: 2020-08-27 +idempotency-key: 4373ffd2-113f-4fef-a559-51c5b8470459 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=konbini&payment_method_options\[konbini]\[confirmation_number]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "konbini_display_details", + "konbini_display_details" : { + "stores" : { + "ministop" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + }, + "seicomart" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + }, + "familymart" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + }, + "lawson" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + } + }, + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/konbini\/voucher\/test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLF9RWmNXWVFtQkl1aWZTOGV0bWpuWmYwM0FHWlhrakxJ0100KUOiSe4f", + "expires_at" : 1722697199 + } + }, + "payment_method" : { + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHqIq2LmpyICox73RBhIQ", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396794, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null + }, + "client_secret" : "pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL", + "id" : "pi_3PiTHpIq2LmpyICo17Aj9ab8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396793, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8.tail new file mode 100644 index 00000000..21981b18 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHpIq2LmpyICo17Aj9ab8.tail @@ -0,0 +1,107 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHpIq2LmpyICo17Aj9ab8\?client_secret=pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FRGdUWnVxk4YW8 +Content-Length: 2056 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "konbini_display_details", + "konbini_display_details" : { + "stores" : { + "ministop" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + }, + "seicomart" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + }, + "familymart" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + }, + "lawson" : { + "confirmation_number" : "77210476074", + "payment_code" : "123456" + } + }, + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/konbini\/voucher\/test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLF9RWmNXWVFtQkl1aWZTOGV0bWpuWmYwM0FHWlhrakxJ0100KUOiSe4f", + "expires_at" : 1722697199 + } + }, + "payment_method" : { + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHqIq2LmpyICox73RBhIQ", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396794, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null + }, + "client_secret" : "pi_3PiTHpIq2LmpyICo17Aj9ab8_secret_vjEm9CIcT3Zasvrur5dRtPDiL", + "id" : "pi_3PiTHpIq2LmpyICo17Aj9ab8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396793, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..2a882590 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RQHZdtift9ktTB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 465 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:15 GMT +original-request: req_RQHZdtift9ktTB +stripe-version: 2020-08-27 +idempotency-key: 035ade86-14c5-4cb2-a478-a0293ed3f458 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&payment_user_agent=.*&type=konbini + +{ + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHrIq2LmpyIComfaXmrIN", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396795, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..5e142b89 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fPRX%2B06OYxuN%2F1Mx9MRrI2kALNUEKubDw5cuJX3QpuZwlW4G%2BmR3slFC7GN7rqls2sNH3kzylwRI0s3tfFtAJSw1%2BDsneKgzvRVYdwt0qjeKCMQLqNzSm%2Fc%2BG9bq6fLEl0WCd031hEU%2FM1GJdQi1%2B2Jo1vuzAzTKwwolSLan75IQKYs5S4yqZXP4%2Ft%2B1nN1Dx%2B1mdg5W8syJH7WH0SpGJmSnSVW3J5H8SHw8ln3sVfg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 66283672a879b08355fb6b11a04f2dff +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:15 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHrIq2LmpyICo0TpElXfc","secret":"pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc.tail new file mode 100644 index 00000000..c1924f2a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHrIq2LmpyICo0TpElXfc\?client_secret=pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NOttwkQAtE6Iki +Content-Length: 793 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT", + "id" : "pi_3PiTHrIq2LmpyICo0TpElXfc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396795, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc_confirm.tail new file mode 100644 index 00000000..23a20d29 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc_confirm.tail @@ -0,0 +1,111 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHrIq2LmpyICo0TpElXfc\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RxezJmJAYzwejF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2056 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:17 GMT +original-request: req_RxezJmJAYzwejF +stripe-version: 2020-08-27 +idempotency-key: 5a90ab4e-5f00-4d95-aa43-0018ec16a4c3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT&expand\[0]=payment_method&payment_method=pm_1PiTHrIq2LmpyIComfaXmrIN&payment_method_options\[konbini]\[confirmation_number]=&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "konbini_display_details", + "konbini_display_details" : { + "stores" : { + "ministop" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + }, + "seicomart" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + }, + "familymart" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + }, + "lawson" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + } + }, + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/konbini\/voucher\/test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLF9RWmNXUVN4aUhKQmdXUU04OVhCTVhtZHhTZ281TGs20100tcAuyGA2", + "expires_at" : 1722697199 + } + }, + "payment_method" : { + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHrIq2LmpyIComfaXmrIN", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396795, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null + }, + "client_secret" : "pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT", + "id" : "pi_3PiTHrIq2LmpyICo0TpElXfc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396795, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc.tail new file mode 100644 index 00000000..dbff634c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHrIq2LmpyICo0TpElXfc.tail @@ -0,0 +1,107 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHrIq2LmpyICo0TpElXfc\?client_secret=pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DCGJ2Rt0RblSiC +Content-Length: 2056 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "konbini_display_details", + "konbini_display_details" : { + "stores" : { + "ministop" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + }, + "seicomart" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + }, + "familymart" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + }, + "lawson" : { + "confirmation_number" : "85442631996", + "payment_code" : "123456" + } + }, + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/konbini\/voucher\/test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLF9RWmNXUVN4aUhKQmdXUU04OVhCTVhtZHhTZ281TGs20100tcAuyGA2", + "expires_at" : 1722697199 + } + }, + "payment_method" : { + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHrIq2LmpyIComfaXmrIN", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396795, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null + }, + "client_secret" : "pi_3PiTHrIq2LmpyICo0TpElXfc_secret_lR8ObUMb6Hdf8wyg3fQVYmCCT", + "id" : "pi_3PiTHrIq2LmpyICo0TpElXfc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396795, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..f9629f0b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cWWYfyxbWZyHIz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 465 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:17 GMT +original-request: req_cWWYfyxbWZyHIz +stripe-version: 2020-08-27 +idempotency-key: 1c6526ee-c84f-4a6c-9a76-7361432f7736 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&payment_user_agent=.*&type=konbini + +{ + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHtIq2LmpyIColU3Tlq2g", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396797, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..e5ffe004 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=W6oslwHnQa3Wggc40PwLtx3IgLpWN%2FZsaKXX9NRkhFHwMKF8bP%2B%2Bpf6uocO5nsbIofsG2npJKLw9a3trVNG18RXl4eXRSZxhFbloyBNjmshH2pjvCjEroAnMKPqykDKwnMBFEjmKIp3jg0vrR9%2B4%2FfBuz%2FZM90SuTWe1Tv2523Fjaifu0m2GK3RG7nlV%2FlJTsdfHm%2BgSsXnH7bdiToL5QOw7CqZcrQI8As1%2B4lho4jg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4fb9056fd815cf514477b5fcbc3ce0c6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:18 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHtIq2LmpyICo0AGAD0LQ","secret":"pi_3PiTHtIq2LmpyICo0AGAD0LQ_secret_yotcr0vzkJuq7Sp3dJnecDHo2","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHtIq2LmpyICo0AGAD0LQ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHtIq2LmpyICo0AGAD0LQ.tail new file mode 100644 index 00000000..97305c0e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHtIq2LmpyICo0AGAD0LQ.tail @@ -0,0 +1,107 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHtIq2LmpyICo0AGAD0LQ\?client_secret=pi_3PiTHtIq2LmpyICo0AGAD0LQ_secret_yotcr0vzkJuq7Sp3dJnecDHo2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_I5pTevAqsslwWa +Content-Length: 2056 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "konbini_display_details", + "konbini_display_details" : { + "stores" : { + "ministop" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + }, + "seicomart" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + }, + "familymart" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + }, + "lawson" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + } + }, + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/konbini\/voucher\/test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLF9RWmNXRGFpaHVpV3Z2NjE2TnJQTnBYVlhhZFhlZTdz0100TbpBZ6lm", + "expires_at" : 1722697199 + } + }, + "payment_method" : { + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHtIq2LmpyIColU3Tlq2g", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396797, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null + }, + "client_secret" : "pi_3PiTHtIq2LmpyICo0AGAD0LQ_secret_yotcr0vzkJuq7Sp3dJnecDHo2", + "id" : "pi_3PiTHtIq2LmpyICo0AGAD0LQ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396797, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHtIq2LmpyICo0AGAD0LQ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHtIq2LmpyICo0AGAD0LQ.tail new file mode 100644 index 00000000..a7f4fa62 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testKonbiniConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHtIq2LmpyICo0AGAD0LQ.tail @@ -0,0 +1,107 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHtIq2LmpyICo0AGAD0LQ\?client_secret=pi_3PiTHtIq2LmpyICo0AGAD0LQ_secret_yotcr0vzkJuq7Sp3dJnecDHo2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LMO2wyZHG8WAHC +Content-Length: 2056 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "jpy", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "konbini_display_details", + "konbini_display_details" : { + "stores" : { + "ministop" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + }, + "seicomart" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + }, + "familymart" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + }, + "lawson" : { + "confirmation_number" : "60954918366", + "payment_code" : "123456" + } + }, + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/konbini\/voucher\/test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLF9RWmNXRGFpaHVpV3Z2NjE2TnJQTnBYVlhhZFhlZTdz0100TbpBZ6lm", + "expires_at" : 1722697199 + } + }, + "payment_method" : { + "object" : "payment_method", + "konbini" : { + + }, + "id" : "pm_1PiTHtIq2LmpyIColU3Tlq2g", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396797, + "allow_redisplay" : "unspecified", + "type" : "konbini", + "customer" : null + }, + "client_secret" : "pi_3PiTHtIq2LmpyICo0AGAD0LQ_secret_yotcr0vzkJuq7Sp3dJnecDHo2", + "id" : "pi_3PiTHtIq2LmpyICo0AGAD0LQ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "konbini" + ], + "setup_future_usage" : null, + "created" : 1722396797, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..47b9b502 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8Pf744shJbP6gKcXio6qxXlODzl0e4uoffl64oaE3o8tpjIHkZwIGUnbVERL4Bcjh6ssoUBNEMeJpTTYM1zoDcX2g374BpLpt1qqjYaVUjMDVtFLMcYORsO%2F8ORmtrSpmm1X1FKE11xqHCP9brQF21Nb9kOad%2BM%2BhAVWzvdX%2B9uzHnNcm9Vu509OBG4U34IrYUbxEvwa1v%2BFCKlo4f%2Bn90vQR0I79VU9qWL6D7VvumU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1aa918bebe8a89c36c38606bacc59fb5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHvKG6vc7r7YC066TAvt0","secret":"pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0.tail new file mode 100644 index 00000000..ece053b3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHvKG6vc7r7YC066TAvt0\?client_secret=pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_j64x8Uy5lkVGbo +Content-Length: 795 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ", + "id" : "pi_3PiTHvKG6vc7r7YC066TAvt0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396799, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0_confirm.tail new file mode 100644 index 00000000..5c7e2ad8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHvKG6vc7r7YC066TAvt0\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cRn4UZXbpqbgkK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1503 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:20 GMT +original-request: req_cRn4UZXbpqbgkK +stripe-version: 2020-08-27 +idempotency-key: ef5dce5b-5740-4ca7-9427-ca3c2dfe44f2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=mobilepay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcXw9HdYTg3tYgLAVa8OUDA0meqvnK" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHvKG6vc7r7YCXYYJzxZN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396799, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ", + "id" : "pi_3PiTHvKG6vc7r7YC066TAvt0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396799, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0.tail new file mode 100644 index 00000000..08873347 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHvKG6vc7r7YC066TAvt0.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHvKG6vc7r7YC066TAvt0\?client_secret=pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Y65kjFkjnuJo0t +Content-Length: 1503 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcXw9HdYTg3tYgLAVa8OUDA0meqvnK" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHvKG6vc7r7YCXYYJzxZN", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396799, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PiTHvKG6vc7r7YC066TAvt0_secret_EAT11GGwEqjkZOS7octMdXnHQ", + "id" : "pi_3PiTHvKG6vc7r7YC066TAvt0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396799, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..98f3a3c2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DvVTGCW9Txiccq +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 454 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:20 GMT +original-request: req_DvVTGCW9Txiccq +stripe-version: 2020-08-27 +idempotency-key: 36fcc3e3-cc16-48b0-aafd-efe73b4a55e9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=mobilepay + +{ + "object" : "payment_method", + "id" : "pm_1PiTHwKG6vc7r7YCywpfrbXd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396800, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..11624d72 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=xbvP3sGfMvrC8vUg10ooQj9yrs2nLbX6MMdd7MlCrDQno1ztr0pYbmkYjDkC8jq3YQeWI1Ee4c5EHbUAp1SuoAjcP3WLmQJKRHZvLywVVoI6UVtMsDrPLcJd7cB1jCrxQJ5CMoFrFi8s75YUwwcNvpZq%2B%2BlOE32XeTbOOFdFzXWBnabeoBjiuyAWAAQwUl2PLm5D3TSM8QFBhkNfRXrFInjhL8xz5fSqRutNFZB3Bi4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cedf1528d47d69bfa4068b8a132595c7 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:21 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHxKG6vc7r7YC0dYkjhBm","secret":"pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm.tail new file mode 100644 index 00000000..9edae203 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHxKG6vc7r7YC0dYkjhBm\?client_secret=pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sAQQJJ11FfRTGm +Content-Length: 795 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:21 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e", + "id" : "pi_3PiTHxKG6vc7r7YC0dYkjhBm", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396801, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm_confirm.tail new file mode 100644 index 00000000..d4564971 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHxKG6vc7r7YC0dYkjhBm\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sok9HFAZ63RnPZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1503 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:22 GMT +original-request: req_sok9HFAZ63RnPZ +stripe-version: 2020-08-27 +idempotency-key: 12b8d53d-6a90-4fba-b438-00fd6e351c57 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e&expand\[0]=payment_method&payment_method=pm_1PiTHwKG6vc7r7YCywpfrbXd&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcX6SrPql0lYb1o7WztsU3UOCZoUgv" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHwKG6vc7r7YCywpfrbXd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396800, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e", + "id" : "pi_3PiTHxKG6vc7r7YC0dYkjhBm", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396801, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm.tail new file mode 100644 index 00000000..c3693410 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHxKG6vc7r7YC0dYkjhBm.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHxKG6vc7r7YC0dYkjhBm\?client_secret=pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2uJRQta5uuI4SV +Content-Length: 1503 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcX6SrPql0lYb1o7WztsU3UOCZoUgv" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHwKG6vc7r7YCywpfrbXd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396800, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PiTHxKG6vc7r7YC0dYkjhBm_secret_OHEK9yzLzteXOrM2Tx4Yj8C0e", + "id" : "pi_3PiTHxKG6vc7r7YC0dYkjhBm", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396801, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..c7d0e82d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_I6pBj99sS5oPVt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 454 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:22 GMT +original-request: req_I6pBj99sS5oPVt +stripe-version: 2020-08-27 +idempotency-key: dabe3de4-def6-4b00-b9ba-22454320a1c7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=mobilepay + +{ + "object" : "payment_method", + "id" : "pm_1PiTHyKG6vc7r7YCFcjah9Ui", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396802, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..66ecbc48 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OtK3NCk4VTNq2IiyIZimzAmEh01W8IzJ756KQmuXA1Zaq9jj30o83lAmCzdTZM8%2FzUX7OzU7LsLoDfMBEjNUR6INfwD7Y%2FBv7rBZC%2FslaDpP0Jc718atpCS0QZFK%2BdrN9pjmqChJJxDLYOvB9AGO3w4ioGJ545xY%2F%2FPFpVM4pZCqbASfxrTQgE%2Bm3veuqgJsYhFJqkRxTsybXUSw4DxxiHigorvNyegvZJUySCWg5UM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a8db59e9bc4838f5caed1f3715b608e5;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHzKG6vc7r7YC0HNzNVOz","secret":"pi_3PiTHzKG6vc7r7YC0HNzNVOz_secret_e9nusPyjTMIwsKSqP68aj7qyR","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHzKG6vc7r7YC0HNzNVOz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHzKG6vc7r7YC0HNzNVOz.tail new file mode 100644 index 00000000..0bb74e85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHzKG6vc7r7YC0HNzNVOz.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHzKG6vc7r7YC0HNzNVOz\?client_secret=pi_3PiTHzKG6vc7r7YC0HNzNVOz_secret_e9nusPyjTMIwsKSqP68aj7qyR&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BsfwhfTcmVf4pm +Content-Length: 1497 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcXeI2W93IxiIOWZ3Hka5KRg2Il22R" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHyKG6vc7r7YCFcjah9Ui", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396802, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PiTHzKG6vc7r7YC0HNzNVOz_secret_e9nusPyjTMIwsKSqP68aj7qyR", + "id" : "pi_3PiTHzKG6vc7r7YC0HNzNVOz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396803, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHzKG6vc7r7YC0HNzNVOz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHzKG6vc7r7YC0HNzNVOz.tail new file mode 100644 index 00000000..7bdf8486 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMobilePayConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHzKG6vc7r7YC0HNzNVOz.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHzKG6vc7r7YC0HNzNVOz\?client_secret=pi_3PiTHzKG6vc7r7YC0HNzNVOz_secret_e9nusPyjTMIwsKSqP68aj7qyR&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_geCLZBbqwEszRo +Content-Length: 1497 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcXeI2W93IxiIOWZ3Hka5KRg2Il22R" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTHyKG6vc7r7YCFcjah9Ui", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722396802, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PiTHzKG6vc7r7YC0HNzNVOz_secret_e9nusPyjTMIwsKSqP68aj7qyR", + "id" : "pi_3PiTHzKG6vc7r7YC0HNzNVOz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1722396803, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..17be9357 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=1VStq5ex35S%2Fn60QWBYPfnMVMxd1%2FW1LKLnXQt43s1Wp2itDIOo4wt%2B%2B5KMYQK2gLhYcO%2B0SOY1gjYqIUWEYlHYODntvyDO6C8b8x4trmCKPGe02IpRlGEsu4wLONOXImdEMCyo5kQ9AQlfWVK5fFoar20TXcIYq7ddJyA8xVGn7tIusb0cprSv4DnvKbG6l6tvdeIyLtUY91O%2FciRYNxU7k6F%2BSUMX7l%2F%2FPwjybIf4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ae37938ee2245ae2a7494573be5a20a8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTI0FY0qyl6XeW00w0f3UO","secret":"pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0001_get_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0001_get_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO.tail new file mode 100644 index 00000000..e766bcfb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0001_get_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI0FY0qyl6XeW00w0f3UO\?client_secret=pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ulwlq9GMF3RQWw +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko", + "id" : "pi_3PiTI0FY0qyl6XeW00w0f3UO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396804, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0002_post_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0002_post_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO_confirm.tail new file mode 100644 index 00000000..77d45546 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0002_post_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI0FY0qyl6XeW00w0f3UO\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZqWpL7AZH4cYq1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1733 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:25 GMT +original-request: req_ZqWpL7AZH4cYq1 +stripe-version: 2020-08-27 +idempotency-key: b73383f7-3b73-4e50-ac60-82b248ce1b5c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details%5Bemail%5D]=foo%40bar\.com&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=multibanco&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1723001605, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RWmNYWVhrYm5SZU5LTXlQamFrejJZdThURXNEd3Zy0100bU7j2WYW", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTI1FY0qyl6XeWb7AQf1dG", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396805, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko", + "id" : "pi_3PiTI0FY0qyl6XeW00w0f3UO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396804, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0003_get_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0003_get_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO.tail new file mode 100644 index 00000000..f943e6ed --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0003_get_v1_payment_intents_pi_3PiTI0FY0qyl6XeW00w0f3UO.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI0FY0qyl6XeW00w0f3UO\?client_secret=pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_P6XhBQEQVnqTGA +Content-Length: 1733 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1723001605, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RWmNYWVhrYm5SZU5LTXlQamFrejJZdThURXNEd3Zy0100bU7j2WYW", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTI1FY0qyl6XeWb7AQf1dG", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396805, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3PiTI0FY0qyl6XeW00w0f3UO_secret_hIivg67KjceIb3ILmpyL4vtko", + "id" : "pi_3PiTI0FY0qyl6XeW00w0f3UO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396804, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..130b3be3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WaK4fwSPThVsLc +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 465 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:26 GMT +original-request: req_WaK4fwSPThVsLc +stripe-version: 2020-08-27 +idempotency-key: 2a03a117-eef2-43c2-94e7-fb745e74c7f9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=multibanco + +{ + "object" : "payment_method", + "id" : "pm_1PiTI2FY0qyl6XeWdeMkG99y", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396806, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..b8b7325b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=eVBBfrRbUFhZDiewTjB0UOlBw%2FkdOrFyEP0v6w3QUGZ6tt5G0C5HVcZCSZRH%2FtjQYneW006ttHyMZYh%2BGFkZop0HiThWX1Oryy8vUMgweMJhq5QPUpntq0xDwkR0Qc7Cb5X7JAImrQ0QjeQd4BLBx1OqBX11BMj3NBW9kdtA7UwNDEpuzPXzusC4zr6fnmK1Frl5kBZhPw%2FWOAIB6BHFtG25vhS8E7F%2FJzqHTIZkO9I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1ed6fdb4563c768d5de3cde5f81cbce4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTI2FY0qyl6XeW1q37euAp","secret":"pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0006_get_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0006_get_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp.tail new file mode 100644 index 00000000..e499cfc1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0006_get_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI2FY0qyl6XeW1q37euAp\?client_secret=pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_puBdMaW5grVfQC +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6", + "id" : "pi_3PiTI2FY0qyl6XeW1q37euAp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396806, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0007_post_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0007_post_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp_confirm.tail new file mode 100644 index 00000000..228f6e5f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0007_post_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI2FY0qyl6XeW1q37euAp\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zhHQfbEZN74Gax +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1733 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:27 GMT +original-request: req_zhHQfbEZN74Gax +stripe-version: 2020-08-27 +idempotency-key: c4744b48-2a5a-4ecb-a375-aeb924fe28de +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6&expand\[0]=payment_method&payment_method=pm_1PiTI2FY0qyl6XeWdeMkG99y&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1723001607, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RWmNYREtYaGUyUGxoUVNaRUthUEdxeU9PbmNVY29Y01009aA3VVop", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTI2FY0qyl6XeWdeMkG99y", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396806, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6", + "id" : "pi_3PiTI2FY0qyl6XeW1q37euAp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396806, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0008_get_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0008_get_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp.tail new file mode 100644 index 00000000..fd6642c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0008_get_v1_payment_intents_pi_3PiTI2FY0qyl6XeW1q37euAp.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI2FY0qyl6XeW1q37euAp\?client_secret=pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hK5TjsTrCSoI7R +Content-Length: 1733 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1723001607, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RWmNYREtYaGUyUGxoUVNaRUthUEdxeU9PbmNVY29Y01009aA3VVop", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTI2FY0qyl6XeWdeMkG99y", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396806, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3PiTI2FY0qyl6XeW1q37euAp_secret_AAMzPbhV5mUEjepYM3n6SpTd6", + "id" : "pi_3PiTI2FY0qyl6XeW1q37euAp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396806, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..fad3cb3f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_14JO4dpmurJKDd +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 465 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:28 GMT +original-request: req_14JO4dpmurJKDd +stripe-version: 2020-08-27 +idempotency-key: 31090813-8930-4b5b-a8d5-5681ccac57f2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=multibanco + +{ + "object" : "payment_method", + "id" : "pm_1PiTI4FY0qyl6XeWwEqB7zRS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396808, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..bb1d7465 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=QKxI3kDtN4nLwCXXedURRyTxJ9jaE18Jz%2FlMNiAWOAtAPg%2BGd1jwIjz%2Bt8z7pdZ3TTfEI5t%2Fsph7xG%2BZFddvT24OiA%2BDraq5XephMG8R4t2k5TXXBUZsk6wNimK%2BNiF04WOe%2BW6u00%2BaOfG4kXmuTOIl7F7wR78mT0zcM%2B5u2rsBjqR9PmULbk835F%2F21SozfD4bN%2FIdloiaEIfScDQTsx%2FT4mCr1piIJBwA6P4xdf0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 83ec1ad51505ddcc986e37a38e173770 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTI4FY0qyl6XeW1QCvqch7","secret":"pi_3PiTI4FY0qyl6XeW1QCvqch7_secret_G66FswKwyQJ4z3jnB5iWZOFdn","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0011_get_v1_payment_intents_pi_3PiTI4FY0qyl6XeW1QCvqch7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0011_get_v1_payment_intents_pi_3PiTI4FY0qyl6XeW1QCvqch7.tail new file mode 100644 index 00000000..74e3a03b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0011_get_v1_payment_intents_pi_3PiTI4FY0qyl6XeW1QCvqch7.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI4FY0qyl6XeW1QCvqch7\?client_secret=pi_3PiTI4FY0qyl6XeW1QCvqch7_secret_G66FswKwyQJ4z3jnB5iWZOFdn&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GbJtA6hA45tQuy +Content-Length: 1733 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1723001608, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RWmNYcmc4Z080M0ZnQ1N2azM1TnJWcVQ0UGJ3cUxL0100BwXb7aGP", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTI4FY0qyl6XeWwEqB7zRS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396808, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3PiTI4FY0qyl6XeW1QCvqch7_secret_G66FswKwyQJ4z3jnB5iWZOFdn", + "id" : "pi_3PiTI4FY0qyl6XeW1QCvqch7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396808, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0012_get_v1_payment_intents_pi_3PiTI4FY0qyl6XeW1QCvqch7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0012_get_v1_payment_intents_pi_3PiTI4FY0qyl6XeW1QCvqch7.tail new file mode 100644 index 00000000..9d3ebeac --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testMultibancoConfirmFlows/0012_get_v1_payment_intents_pi_3PiTI4FY0qyl6XeW1QCvqch7.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI4FY0qyl6XeW1QCvqch7\?client_secret=pi_3PiTI4FY0qyl6XeW1QCvqch7_secret_G66FswKwyQJ4z3jnB5iWZOFdn&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aLa9uWfnQkvkpf +Content-Length: 1733 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1723001608, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RWmNYcmc4Z080M0ZnQ1N2azM1TnJWcVQ0UGJ3cUxL0100BwXb7aGP", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTI4FY0qyl6XeWwEqB7zRS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396808, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3PiTI4FY0qyl6XeW1QCvqch7_secret_G66FswKwyQJ4z3jnB5iWZOFdn", + "id" : "pi_3PiTI4FY0qyl6XeW1QCvqch7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1722396808, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..026057ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=dcYAeUkYbYQ34LLRuLUnJQdLnDk0hRLjPgErfwo46vPYgF9IfSJwIvpGlvc4a%2FdOhBEyhfIAUnJoHRdrObAQOsq3Ac9wWptIRUVnF1jMPU6hSC5vKtKEBJYmDfnt%2F52JRVkcibAzXWMjFjLp6MDN4WKE0rSyKik2Na9f3Ik0dmzrTbZQjLh2R%2FXtbLvk4alaVvOZK4lN9mpgq2zeXqDmxwg7NE7osQBIfHLOLoCnIIY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 26bde052a74ba03881d903a2de70e6f1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTI6HNG4o8pO5l1omLzZGE","secret":"pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0001_get_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0001_get_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE.tail new file mode 100644 index 00000000..5fc81c01 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0001_get_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI6HNG4o8pO5l1omLzZGE\?client_secret=pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UjOu8Ubymg2Fzn +Content-Length: 790 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA", + "id" : "pi_3PiTI6HNG4o8pO5l1omLzZGE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396810, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0002_post_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0002_post_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE_confirm.tail new file mode 100644 index 00000000..f00d45c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0002_post_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE_confirm.tail @@ -0,0 +1,94 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI6HNG4o8pO5l1omLzZGE\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_F4Op8oVXqKcukC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1602 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:31 GMT +original-request: req_F4Op8oVXqKcukC +stripe-version: 2020-08-27 +idempotency-key: 9b5df469-85aa-4162-ad38-4a438d4d5366 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=foo%40bar\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=oxxo&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RWmNYVFRZNHlvZEdOMVBlV2tRRU9GenZKeGtnUjdD0100N5VLb8AC", + "expires_after" : 1722664799 + } + }, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTI7HNG4o8pO5lFZ4Rh4vS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396811, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA", + "id" : "pi_3PiTI6HNG4o8pO5l1omLzZGE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396810, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0003_get_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0003_get_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE.tail new file mode 100644 index 00000000..a0a8a199 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0003_get_v1_payment_intents_pi_3PiTI6HNG4o8pO5l1omLzZGE.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI6HNG4o8pO5l1omLzZGE\?client_secret=pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0zDnZvTyUo7kAa +Content-Length: 1602 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RWmNYVFRZNHlvZEdOMVBlV2tRRU9GenZKeGtnUjdD0100N5VLb8AC", + "expires_after" : 1722664799 + } + }, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTI7HNG4o8pO5lFZ4Rh4vS", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396811, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_3PiTI6HNG4o8pO5l1omLzZGE_secret_l6a7pDvHcMhQZg6HuY1RtGrBA", + "id" : "pi_3PiTI6HNG4o8pO5l1omLzZGE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396810, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..9298ecd3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uXET4WM5BISlje +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 459 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:32 GMT +original-request: req_uXET4WM5BISlje +stripe-version: 2020-08-27 +idempotency-key: b904c919-56b8-454a-97dd-aa69591b8723 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&payment_user_agent=.*&type=oxxo + +{ + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTI8HNG4o8pO5lLWq7YBok", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396812, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..caaa8b1d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Hsbc%2BIo%2BJXnORnamCsdb64GBoCj7LGI3q3bAoxOveStpsSgaalP%2FZ3ZM6UWRX5nL2ZxAa%2F4X7jWXbP87Ze9zcKmHw%2FIo8talHI9OjO7thjmGUAqTyTdrtlXUTHPzEDz1q9TLRrQVOOax13JGgI45j6ni8yI2y0v2tMrV2qNJIDC89zLRp1qbe2MjFeN6btdLE2CvQLj2%2FFhb%2BDBzFh6NGSuMiZ1bJf1ig4L4FtBMVNE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 85383b3572674e3967eef10c6dde8a63 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:32 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTI8HNG4o8pO5l00QjdMZh","secret":"pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0006_get_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0006_get_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh.tail new file mode 100644 index 00000000..36811670 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0006_get_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI8HNG4o8pO5l00QjdMZh\?client_secret=pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_P9N5sew4Dl7DUA +Content-Length: 790 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7", + "id" : "pi_3PiTI8HNG4o8pO5l00QjdMZh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396812, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0007_post_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0007_post_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh_confirm.tail new file mode 100644 index 00000000..6aa0e9b2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0007_post_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh_confirm.tail @@ -0,0 +1,94 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI8HNG4o8pO5l00QjdMZh\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_iWqem195hoyFKs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1602 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:33 GMT +original-request: req_iWqem195hoyFKs +stripe-version: 2020-08-27 +idempotency-key: edb49680-7089-44e7-a5fb-a7807ca65b23 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7&expand\[0]=payment_method&payment_method=pm_1PiTI8HNG4o8pO5lLWq7YBok&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RWmNYNmtlNU15Y29KcHBTbDNMTVNZZkV2dHY3bXVL010068A9u5aG", + "expires_after" : 1722664799 + } + }, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTI8HNG4o8pO5lLWq7YBok", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396812, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7", + "id" : "pi_3PiTI8HNG4o8pO5l00QjdMZh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396812, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0008_get_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0008_get_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh.tail new file mode 100644 index 00000000..3cc4747b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0008_get_v1_payment_intents_pi_3PiTI8HNG4o8pO5l00QjdMZh.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTI8HNG4o8pO5l00QjdMZh\?client_secret=pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1wLjFgRlhlYqMA +Content-Length: 1602 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RWmNYNmtlNU15Y29KcHBTbDNMTVNZZkV2dHY3bXVL010068A9u5aG", + "expires_after" : 1722664799 + } + }, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTI8HNG4o8pO5lLWq7YBok", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396812, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_3PiTI8HNG4o8pO5l00QjdMZh_secret_LW4smvMvuPtrsevbUFSy3aTI7", + "id" : "pi_3PiTI8HNG4o8pO5l00QjdMZh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396812, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..985d75a1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VXIOxKSLSPlCNw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 459 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:34 GMT +original-request: req_VXIOxKSLSPlCNw +stripe-version: 2020-08-27 +idempotency-key: fe7c37a5-5aee-4471-bebc-223225aac154 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=foo%40bar\.com&billing_details\[name]=Jane%20Doe&payment_user_agent=.*&type=oxxo + +{ + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTIAHNG4o8pO5lPFTcINwn", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396814, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..df8c232b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=7Hq2PFKcp6oAUFpImP8cWNwLl6AmPnKpTKFWrTOJebq2F9GvtkujXgHfxBDV20Hr%2FuNlKlYA4RyD6RR2S2h8ueGyOpBZIp0iIHrYa4P0Tk5%2FO6yE1dtf6yWwUYeW7wIx%2BcgAbE%2F3LrQT5K%2FRcIYDOvXvPf27bPS2vI4%2Bh%2ButZtz2krB7yCeAN%2FkQgtXXBgSSKQtPshZpOzewPhke10rCWcw0%2FPgup3OhzNts8Oa%2BIWY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c8bf9fda86aec5f27ef8b91888e3fd2f;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIAHNG4o8pO5l0mGlaDkM","secret":"pi_3PiTIAHNG4o8pO5l0mGlaDkM_secret_WgkZwnzuoHJdXdPFkAXeQu18G","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0011_get_v1_payment_intents_pi_3PiTIAHNG4o8pO5l0mGlaDkM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0011_get_v1_payment_intents_pi_3PiTIAHNG4o8pO5l0mGlaDkM.tail new file mode 100644 index 00000000..f6b07b6f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0011_get_v1_payment_intents_pi_3PiTIAHNG4o8pO5l0mGlaDkM.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIAHNG4o8pO5l0mGlaDkM\?client_secret=pi_3PiTIAHNG4o8pO5l0mGlaDkM_secret_WgkZwnzuoHJdXdPFkAXeQu18G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_d3Z3K7mrvfN5rW +Content-Length: 1602 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RWmNYRmZtd1RMZU9yMTE0RlJhZkIyeHpwNjV5OUEx0100gALpfCla", + "expires_after" : 1722664799 + } + }, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTIAHNG4o8pO5lPFTcINwn", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396814, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_3PiTIAHNG4o8pO5l0mGlaDkM_secret_WgkZwnzuoHJdXdPFkAXeQu18G", + "id" : "pi_3PiTIAHNG4o8pO5l0mGlaDkM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396814, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0012_get_v1_payment_intents_pi_3PiTIAHNG4o8pO5l0mGlaDkM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0012_get_v1_payment_intents_pi_3PiTIAHNG4o8pO5l0mGlaDkM.tail new file mode 100644 index 00000000..baccb21c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testOXXOConfirmFlows/0012_get_v1_payment_intents_pi_3PiTIAHNG4o8pO5l0mGlaDkM.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIAHNG4o8pO5l0mGlaDkM\?client_secret=pi_3PiTIAHNG4o8pO5l0mGlaDkM_secret_WgkZwnzuoHJdXdPFkAXeQu18G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DnvzglMcLaYMov +Content-Length: 1602 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RWmNYRmZtd1RMZU9yMTE0RlJhZkIyeHpwNjV5OUEx0100gALpfCla", + "expires_after" : 1722664799 + } + }, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiTIAHNG4o8pO5lPFTcINwn", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396814, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_3PiTIAHNG4o8pO5l0mGlaDkM_secret_WgkZwnzuoHJdXdPFkAXeQu18G", + "id" : "pi_3PiTIAHNG4o8pO5l0mGlaDkM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1722396814, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..98964b63 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NhcaGvFIAjDlR7j%2FNFBtWQ6CiF2SKYyODWE5yXbvlInx7XPonrUYoWinVRWQqDH37AX3uH9qxWBvQ6fRihZnM9Lj8SBHxtbMy2vj5GFIazQ6btwh%2F%2BJk0ssnUzvu5iSGdq1kx8qLKpgPpUYALLs8XuAkix56UFxHfPPjXDqFt%2BCfQFJQGQLGUjQ9Sv9v0itKJo12ObgERrYv7a%2FcMFeQqqBnnqXMN1Vo8HqrO0w10eo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6d38c9f9da9394853132b78cf70f1efe +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIBAOnZToJom11KGbQC8i","secret":"pi_3PiTIBAOnZToJom11KGbQC8i_secret_Tc0XyAKGobsiq1IqDci2Gh7gm","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIBAOnZToJom11KGbQC8i.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIBAOnZToJom11KGbQC8i.tail new file mode 100644 index 00000000..d5c81b7d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIBAOnZToJom11KGbQC8i.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIBAOnZToJom11KGbQC8i\?client_secret=pi_3PiTIBAOnZToJom11KGbQC8i_secret_Tc0XyAKGobsiq1IqDci2Gh7gm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3eHjmeMV3TuJp2 +Content-Length: 792 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTIBAOnZToJom11KGbQC8i_secret_Tc0XyAKGobsiq1IqDci2Gh7gm", + "id" : "pi_3PiTIBAOnZToJom11KGbQC8i", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paynow" + ], + "setup_future_usage" : null, + "created" : 1722396815, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIBAOnZToJom11KGbQC8i_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIBAOnZToJom11KGbQC8i_confirm.tail new file mode 100644 index 00000000..e4572610 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIBAOnZToJom11KGbQC8i_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIBAOnZToJom11KGbQC8i\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DXnS8y6rmsJ0yV +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1950 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:37 GMT +original-request: req_DXnS8y6rmsJ0yV +stripe-version: 2020-08-27 +idempotency-key: a8fb4478-1826-4fdb-ae5c-bec550f2859c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIBAOnZToJom11KGbQC8i_secret_Tc0XyAKGobsiq1IqDci2Gh7gm&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=paynow&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "paynow_display_qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSDdvWE1BT25aVG9Kb20xLF9RWmNYTjJadm5HYnVXS3ZsamFLN2lzRGZHRWVnSTRD01006NisPutN.svg", + "data" : "https:\/\/stripe.com\/payment_methods\/test_payment?payment_attempt=payatt_3PiTIBAOnZToJom11GDWd995", + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/paynow\/instructions\/CCIaFwoVYWNjdF8xSDdvWE1BT25aVG9Kb20xKJDhprUGMgYAX74J-CY6L3dxJyOkMitbbLk0WzE_hpaQo2IX9evar_UofLla9VIMHaKI4vQ-wELjuv853nDv", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSDdvWE1BT25aVG9Kb20xLF9RWmNYTjJadm5HYnVXS3ZsamFLN2lzRGZHRWVnSTRD01006NisPutN.png" + }, + "type" : "paynow_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "paynow" : { + + }, + "id" : "pm_1PiTICAOnZToJom1nD01i0eb", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396816, + "allow_redisplay" : "unspecified", + "type" : "paynow", + "customer" : null + }, + "client_secret" : "pi_3PiTIBAOnZToJom11KGbQC8i_secret_Tc0XyAKGobsiq1IqDci2Gh7gm", + "id" : "pi_3PiTIBAOnZToJom11KGbQC8i", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paynow" + ], + "setup_future_usage" : null, + "created" : 1722396815, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..9f7f3f4c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0003_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XthmoixtBN19ih +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:37 GMT +original-request: req_XthmoixtBN19ih +stripe-version: 2020-08-27 +idempotency-key: eeaac9f3-82dc-4ff1-bdb3-bf61eac4ba59 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paynow + +{ + "object" : "payment_method", + "paynow" : { + + }, + "id" : "pm_1PiTIDAOnZToJom10gI11NmH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396817, + "allow_redisplay" : "unspecified", + "type" : "paynow", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..602f3117 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=JXc%2FvmNrEgJXd%2FtD1%2F63NOZjedFkSo%2FptUSsGK99Rm9aVXp9drCFfyJa1Wqp1M89Qav2KrDOJfEbyJ5KAD3p0bW4ChR7Oa1HQsxPWlHFoTj%2B4DwS4CFLvOfJlQz9ht9gIBnvqCM0t7yQ6aiWcdMr30nkNz44El86QSuqv12DiGyfGW5pNRQqA1cdP%2BRatexI25PC%2BP99BGhyA7Z%2BSBqKPhFrkJa7%2FySAU1Hl5A8l66U%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 92ffeb0f61940ab74d38cb3127656204 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIDAOnZToJom12JSu0YfY","secret":"pi_3PiTIDAOnZToJom12JSu0YfY_secret_mzeJZDDf8ZRLDArtuCTae4rof","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIDAOnZToJom12JSu0YfY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIDAOnZToJom12JSu0YfY.tail new file mode 100644 index 00000000..b670192f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIDAOnZToJom12JSu0YfY.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIDAOnZToJom12JSu0YfY\?client_secret=pi_3PiTIDAOnZToJom12JSu0YfY_secret_mzeJZDDf8ZRLDArtuCTae4rof&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WYz30Oi65DWXGq +Content-Length: 792 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTIDAOnZToJom12JSu0YfY_secret_mzeJZDDf8ZRLDArtuCTae4rof", + "id" : "pi_3PiTIDAOnZToJom12JSu0YfY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paynow" + ], + "setup_future_usage" : null, + "created" : 1722396817, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIDAOnZToJom12JSu0YfY_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIDAOnZToJom12JSu0YfY_confirm.tail new file mode 100644 index 00000000..cf48123d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIDAOnZToJom12JSu0YfY_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIDAOnZToJom12JSu0YfY\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1chZTzNI4YnTLF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1950 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:38 GMT +original-request: req_1chZTzNI4YnTLF +stripe-version: 2020-08-27 +idempotency-key: 8b5490e2-7d2b-452f-b316-51a2624354b7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIDAOnZToJom12JSu0YfY_secret_mzeJZDDf8ZRLDArtuCTae4rof&expand\[0]=payment_method&payment_method=pm_1PiTIDAOnZToJom10gI11NmH&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "paynow_display_qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSDdvWE1BT25aVG9Kb20xLF9RWmNYUjUzem4xbldGamI4TmZuSHVkdHVtMVhCeE5r0100AVPdKNjR.svg", + "data" : "https:\/\/stripe.com\/payment_methods\/test_payment?payment_attempt=payatt_3PiTIDAOnZToJom12o0SgPyb", + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/paynow\/instructions\/CCIaFwoVYWNjdF8xSDdvWE1BT25aVG9Kb20xKJLhprUGMgb5i9Lxmec6L3cGhegYCvnbNTvKcHBZE7j7yyP3Tv-FpDhG9a5ZrkuHMrcej27OAFv1a46cS2C4", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSDdvWE1BT25aVG9Kb20xLF9RWmNYUjUzem4xbldGamI4TmZuSHVkdHVtMVhCeE5r0100AVPdKNjR.png" + }, + "type" : "paynow_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "paynow" : { + + }, + "id" : "pm_1PiTIDAOnZToJom10gI11NmH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396817, + "allow_redisplay" : "unspecified", + "type" : "paynow", + "customer" : null + }, + "client_secret" : "pi_3PiTIDAOnZToJom12JSu0YfY_secret_mzeJZDDf8ZRLDArtuCTae4rof", + "id" : "pi_3PiTIDAOnZToJom12JSu0YfY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paynow" + ], + "setup_future_usage" : null, + "created" : 1722396817, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..3f728de1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0007_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5I2S3MnOuUyhLA +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:39 GMT +original-request: req_5I2S3MnOuUyhLA +stripe-version: 2020-08-27 +idempotency-key: 37ee0cb8-d6b8-4cfe-88f6-4ce22a2b148c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paynow + +{ + "object" : "payment_method", + "paynow" : { + + }, + "id" : "pm_1PiTIFAOnZToJom1S1llMVI3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396819, + "allow_redisplay" : "unspecified", + "type" : "paynow", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..bf1328c0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=E2dL6CT2pYy9JZvt793QZGuTBzhCKxoelKvpmgO6RQ%2FGYgWCli5N3KJvbqDuam0GlliMNhqZnei3Ovx8EzdY3Hwd26Lkggte1qhPFzpApRPKKsoIckOAwBk94C2OkYHFfP0Ki4QYTzF49DvVHQ%2BQ1XNg7C9gcXJW74WtHgow%2B1iLzVcshhRZCdKoGWenIomJsUDabKu2P2JosJIdLZoeZQ8eFgh%2Fl54u0Iypsi0C7XE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9435ae232a849c5c994e9852b61648ee +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:39 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIFAOnZToJom118jTKXAJ","secret":"pi_3PiTIFAOnZToJom118jTKXAJ_secret_PSLugp1oyX99iJYTRNlpRM0eM","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0009_get_v1_payment_intents_pi_3PiTIFAOnZToJom118jTKXAJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0009_get_v1_payment_intents_pi_3PiTIFAOnZToJom118jTKXAJ.tail new file mode 100644 index 00000000..229294d4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayNowConfirmFlows/0009_get_v1_payment_intents_pi_3PiTIFAOnZToJom118jTKXAJ.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIFAOnZToJom118jTKXAJ\?client_secret=pi_3PiTIFAOnZToJom118jTKXAJ_secret_PSLugp1oyX99iJYTRNlpRM0eM&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gfpqdN0fb21b2h +Content-Length: 1950 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "paynow_display_qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSDdvWE1BT25aVG9Kb20xLF9RWmNYTXAwVVlGU092c283OXZnSGNCOE5JOEM2bGNH01005ElVmDuE.svg", + "data" : "https:\/\/stripe.com\/payment_methods\/test_payment?payment_attempt=payatt_3PiTIFAOnZToJom11aV1rmq1", + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/paynow\/instructions\/CCIaFwoVYWNjdF8xSDdvWE1BT25aVG9Kb20xKJPhprUGMgYZK6jDUMk6L3dZ02iIQ94R6Nr0MNXu2p5BVtlxR5An3bx2z3RYdQ9QXNmiyRa0okDlY4iEPUZ5", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSDdvWE1BT25aVG9Kb20xLF9RWmNYTXAwVVlGU092c283OXZnSGNCOE5JOEM2bGNH01005ElVmDuE.png" + }, + "type" : "paynow_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "paynow" : { + + }, + "id" : "pm_1PiTIFAOnZToJom1S1llMVI3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396819, + "allow_redisplay" : "unspecified", + "type" : "paynow", + "customer" : null + }, + "client_secret" : "pi_3PiTIFAOnZToJom118jTKXAJ_secret_PSLugp1oyX99iJYTRNlpRM0eM", + "id" : "pi_3PiTIFAOnZToJom118jTKXAJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paynow" + ], + "setup_future_usage" : null, + "created" : 1722396819, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..a98ef18c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Tw9vMBtIL5nmQO5vIg8w41hNYZyuWDG98B4qpBSQl1frDumXe9Bn4XcUqoVcI6cMx%2BxyQOyrXYC4zrVDoXF%2F98rghPtWTtmak5SKqd77YpatZB78BlwO7aUQ2LxqD55OQh9WKILSV1egSI8hYUydCb1g3iGnUraM8%2B5OyunEmy2fpVT0XMfQlkaaEUt5otdQ65Qr8z3Ef%2BQkPJutL51gfMjEO%2F7ZpD%2FCbTAQcXbaNXA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6d03898cbd4e14c4b7ebe8ee9d07969d;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:40:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5IUgKG6vc7r7YC19zUtwex","secret":"pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0001_get_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0001_get_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex.tail new file mode 100644 index 00000000..0c63d326 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0001_get_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex.tail @@ -0,0 +1,60 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUgKG6vc7r7YC19zUtwex\?client_secret=pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IDoKdpWrEWaZrK +Content-Length: 792 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13", + "id" : "pi_3Q5IUgKG6vc7r7YC19zUtwex", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836850, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0002_post_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0002_post_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex_confirm.tail new file mode 100644 index 00000000..6073596b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0002_post_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUgKG6vc7r7YC19zUtwex\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_C7xdUTIybWaXF9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1621 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:52 GMT +original-request: req_C7xdUTIybWaXF9 +stripe-version: 2020-08-27 +idempotency-key: f9642ca8-279d-42ad-bd6f-3029b69ac795 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=paypal&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuuI9j1Aecimj3QEx9cCTXqLjE1iw?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUhKG6vc7r7YCpgLBHHZ0", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836851, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13", + "id" : "pi_3Q5IUgKG6vc7r7YC19zUtwex", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836850, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0003_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuuI9j1Aecimj3QEx9cCTXqLjE1iw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0003_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuuI9j1Aecimj3QEx9cCTXqLjE1iw.tail new file mode 100644 index 00000000..be721c95 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0003_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuuI9j1Aecimj3QEx9cCTXqLjE1iw.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuuI9j1Aecimj3QEx9cCTXqLjE1iw\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_payment?payment_attempt=payatt_3Q5IUgKG6vc7r7YC1bsTWd1m +Date: Wed, 02 Oct 2024 02:40:52 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0004_get_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0004_get_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex.tail new file mode 100644 index 00000000..758b71e0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0004_get_v1_payment_intents_pi_3Q5IUgKG6vc7r7YC19zUtwex.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUgKG6vc7r7YC19zUtwex\?client_secret=pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_d8isdg9GocfXA1 +Content-Length: 1621 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuuI9j1Aecimj3QEx9cCTXqLjE1iw?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUhKG6vc7r7YCpgLBHHZ0", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836851, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUgKG6vc7r7YC19zUtwex_secret_Te05ZQs0jXJVpS7VukTCU9J13", + "id" : "pi_3Q5IUgKG6vc7r7YC19zUtwex", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836850, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0005_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0005_post_v1_payment_methods.tail new file mode 100644 index 00000000..a3c1eb61 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0005_post_v1_payment_methods.tail @@ -0,0 +1,58 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_r8dBcVbbCWP5N8 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 518 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:53 GMT +original-request: req_r8dBcVbbCWP5N8 +stripe-version: 2020-08-27 +idempotency-key: df253f32-eef2-4c90-b5e2-d9058cca2620 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUjKG6vc7r7YCcNmkfy07", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836853, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..aa8f3aa7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=O72OjbcGRpt8vf9iQbklK2tASRcOyW2XPQYmKYK2U71OGaSxYLIaiBotYbrrVb22DH84Tdm21nZQYrAxstkc5OE%2FBOa2DfqlubUrf5QOpk3W9HBvHcWRgWnoaa6tanwjZV86WxBD2tfE8q1DKKfugUt36IVPhnKIeC4l8X5%2BbEn%2Fmko7WU6kCNzntVn7R%2FxdDE3ygZaISz7H1xQdiz9dFk7O8KjIddprl3quqey8LcM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e148dde99b84362ad04be30847c0b0c6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:40:53 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5IUjKG6vc7r7YC0YXqOUte","secret":"pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0007_get_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0007_get_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte.tail new file mode 100644 index 00000000..3e54edb4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0007_get_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte.tail @@ -0,0 +1,60 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUjKG6vc7r7YC0YXqOUte\?client_secret=pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8Bm20CtGYoXHrG +Content-Length: 792 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI", + "id" : "pi_3Q5IUjKG6vc7r7YC0YXqOUte", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836853, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0008_post_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0008_post_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte_confirm.tail new file mode 100644 index 00000000..071b84d9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0008_post_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUjKG6vc7r7YC0YXqOUte\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZTb6dIxipfeF1M +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1621 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:54 GMT +original-request: req_ZTb6dIxipfeF1M +stripe-version: 2020-08-27 +idempotency-key: 79874d73-85f9-4ca2-8f54-7d1973fa64e1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI&expand\[0]=payment_method&payment_method=pm_1Q5IUjKG6vc7r7YCcNmkfy07&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuLK1OR7XZdyhMKKr1majmzKnx3hb?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUjKG6vc7r7YCcNmkfy07", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836853, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI", + "id" : "pi_3Q5IUjKG6vc7r7YC0YXqOUte", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836853, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0009_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuLK1OR7XZdyhMKKr1majmzKnx3hb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0009_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuLK1OR7XZdyhMKKr1majmzKnx3hb.tail new file mode 100644 index 00000000..3173216d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0009_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuLK1OR7XZdyhMKKr1majmzKnx3hb.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuLK1OR7XZdyhMKKr1majmzKnx3hb\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_payment?payment_attempt=payatt_3Q5IUjKG6vc7r7YC00Z57QxJ +Date: Wed, 02 Oct 2024 02:40:55 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0010_get_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0010_get_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte.tail new file mode 100644 index 00000000..7bc1d691 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0010_get_v1_payment_intents_pi_3Q5IUjKG6vc7r7YC0YXqOUte.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUjKG6vc7r7YC0YXqOUte\?client_secret=pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bkZysxO6nH3kTR +Content-Length: 1621 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:55 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuLK1OR7XZdyhMKKr1majmzKnx3hb?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUjKG6vc7r7YCcNmkfy07", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836853, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUjKG6vc7r7YC0YXqOUte_secret_77PxXxbS8pe2OnjIIcKNXHwhI", + "id" : "pi_3Q5IUjKG6vc7r7YC0YXqOUte", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836853, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0011_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0011_post_v1_payment_methods.tail new file mode 100644 index 00000000..84621c2c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0011_post_v1_payment_methods.tail @@ -0,0 +1,58 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NECF9RIN9NduH4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 518 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:55 GMT +original-request: req_NECF9RIN9NduH4 +stripe-version: 2020-08-27 +idempotency-key: 366cd3c2-037e-4b7b-ae37-91f0ee69516d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUlKG6vc7r7YCjqf1hDbP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836855, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0012_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0012_post_create_payment_intent.tail new file mode 100644 index 00000000..93be8fdb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0012_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=71x3u%2Bf3JIsdwCbWz8Aq09XVxO6eySA0uaZJpGJ9EJEPFq2S2EoTKdD47%2Fwhm%2FG9ehJHkq6j6sxgVxP7Vf0sBGkxw49Z8CPJih1W9FYJt6a4S1i8t4y4yG0DCu7oIFBmZC2pijyL4FVjkq99iN7PsV5Xt9TNKFRMHNDkMCfuJxPTFrA1PGOd8D8XYe4gnwPuzXyo6Wb0WO8yXQTSiYRm8gsca9Gmns0HF2oXia%2BcW8Y%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 40d579a7d4a17ee9a6221325a171c8bc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:40:56 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5IUlKG6vc7r7YC0Q12K7HD","secret":"pi_3Q5IUlKG6vc7r7YC0Q12K7HD_secret_vFvC8lkkvDrMZ5Iegm7qVrBWO","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0013_get_v1_payment_intents_pi_3Q5IUlKG6vc7r7YC0Q12K7HD.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0013_get_v1_payment_intents_pi_3Q5IUlKG6vc7r7YC0Q12K7HD.tail new file mode 100644 index 00000000..e1500f0c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0013_get_v1_payment_intents_pi_3Q5IUlKG6vc7r7YC0Q12K7HD.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUlKG6vc7r7YC0Q12K7HD\?client_secret=pi_3Q5IUlKG6vc7r7YC0Q12K7HD_secret_vFvC8lkkvDrMZ5Iegm7qVrBWO&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Dse3iAf30OtTbk +Content-Length: 1615 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuGz900EUWEvWvRGQF52huZp2hNjC?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUlKG6vc7r7YCjqf1hDbP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836855, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUlKG6vc7r7YC0Q12K7HD_secret_vFvC8lkkvDrMZ5Iegm7qVrBWO", + "id" : "pi_3Q5IUlKG6vc7r7YC0Q12K7HD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836855, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0014_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuGz900EUWEvWvRGQF52huZp2hNjC.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0014_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuGz900EUWEvWvRGQF52huZp2hNjC.tail new file mode 100644 index 00000000..5ca639e6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0014_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuGz900EUWEvWvRGQF52huZp2hNjC.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuGz900EUWEvWvRGQF52huZp2hNjC\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_payment?payment_attempt=payatt_3Q5IUlKG6vc7r7YC0U8ZCNzU +Date: Wed, 02 Oct 2024 02:40:56 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0015_get_v1_payment_intents_pi_3Q5IUlKG6vc7r7YC0Q12K7HD.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0015_get_v1_payment_intents_pi_3Q5IUlKG6vc7r7YC0Q12K7HD.tail new file mode 100644 index 00000000..0bc50967 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0015_get_v1_payment_intents_pi_3Q5IUlKG6vc7r7YC0Q12K7HD.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUlKG6vc7r7YC0Q12K7HD\?client_secret=pi_3Q5IUlKG6vc7r7YC0Q12K7HD_secret_vFvC8lkkvDrMZ5Iegm7qVrBWO&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ATbdTv1lkdbGmt +Content-Length: 1615 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuGz900EUWEvWvRGQF52huZp2hNjC?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUlKG6vc7r7YCjqf1hDbP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836855, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUlKG6vc7r7YC0Q12K7HD_secret_vFvC8lkkvDrMZ5Iegm7qVrBWO", + "id" : "pi_3Q5IUlKG6vc7r7YC0Q12K7HD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1727836855, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0016_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0016_post_create_payment_intent.tail new file mode 100644 index 00000000..f8853882 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0016_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9Uymc0ggUpnYfwJZs%2FDv%2FFtq1H5C%2BiajfinNN%2BQsfXRJEg0ajaQ%2F834q800ouB6agDVcAHxejvvy2I%2Bf50Bn6H9mppg6zyvEiAi65mAo8OIIftIr%2F4flsd4z%2FhI2TnOQVMXmIn0aMN%2B1bvzJ%2BMwlx2zoEWjGM5FrxXYBlwe691Sn%2F69OGTqnrIf78CoDHH7tu6nWaCzZ6aLCP6MbXrGiSrVPO%2Ff%2BtnqU%2FoinKps5deI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 39165f90e531df7e341c0c5a990ce6cc +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:40:57 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5IUnKG6vc7r7YC0qZfBGlr","secret":"pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0017_get_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0017_get_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr.tail new file mode 100644 index 00000000..77d0bad1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0017_get_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr.tail @@ -0,0 +1,60 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUnKG6vc7r7YC0qZfBGlr\?client_secret=pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_S9ko5zOIuwJTvG +Content-Length: 801 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r", + "id" : "pi_3Q5IUnKG6vc7r7YC0qZfBGlr", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836857, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0018_post_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0018_post_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr_confirm.tail new file mode 100644 index 00000000..59ed6fdc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0018_post_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUnKG6vc7r7YC0qZfBGlr\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8vExxrx0D9VsIE +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1630 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:59 GMT +original-request: req_8vExxrx0D9VsIE +stripe-version: 2020-08-27 +idempotency-key: be30d39f-fd25-4f4c-9bfb-2fe88df33ac9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=paypal&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuotdZezcSpGIKxyszLoPH4sZDx93?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUoKG6vc7r7YCtFkMCH28", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836858, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r", + "id" : "pi_3Q5IUnKG6vc7r7YC0qZfBGlr", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836857, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0019_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuotdZezcSpGIKxyszLoPH4sZDx93.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0019_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuotdZezcSpGIKxyszLoPH4sZDx93.tail new file mode 100644 index 00000000..86fcdf5d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0019_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuotdZezcSpGIKxyszLoPH4sZDx93.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuotdZezcSpGIKxyszLoPH4sZDx93\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_payment?payment_attempt=payatt_3Q5IUnKG6vc7r7YC0hYzk72R +Date: Wed, 02 Oct 2024 02:40:59 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0020_get_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0020_get_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr.tail new file mode 100644 index 00000000..fd59cdd0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0020_get_v1_payment_intents_pi_3Q5IUnKG6vc7r7YC0qZfBGlr.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUnKG6vc7r7YC0qZfBGlr\?client_secret=pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Jvch2q6mcA3w6u +Content-Length: 1630 +Vary: Origin +Date: Wed, 02 Oct 2024 02:40:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuotdZezcSpGIKxyszLoPH4sZDx93?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUoKG6vc7r7YCtFkMCH28", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836858, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUnKG6vc7r7YC0qZfBGlr_secret_H5zcGQEVlOgckT2kXbD3TJp1r", + "id" : "pi_3Q5IUnKG6vc7r7YC0qZfBGlr", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836857, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0021_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0021_post_v1_payment_methods.tail new file mode 100644 index 00000000..38b970c9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0021_post_v1_payment_methods.tail @@ -0,0 +1,58 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DZ84xFMlBlUPPb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 518 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:00 GMT +original-request: req_DZ84xFMlBlUPPb +stripe-version: 2020-08-27 +idempotency-key: c2b407ca-3002-43a5-ad2f-913339a218a3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUqKG6vc7r7YCCqHc1EXP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836860, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0022_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0022_post_create_payment_intent.tail new file mode 100644 index 00000000..7be615ed --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0022_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=QZyuqtB7nnC0EDwod2P5jH%2F74x1l9ezTRsunXjZAWTND6S2V4AR7deXlgjvnzyG6Lv7jjGpeMdWB0OJeEiaic4z4Hz5WVEVapz6rL2e4LsUIUlvaKAuPoY5rxevlN60OLXu7S6Bumd222BYDhDh9M1L4E68aqV8h7Xr%2Bu7tAA2%2BD1UeTeBv2Jol1OAtbBhqdfiu1rNS90KaeUROe%2Fy6kj%2FQtsqsE8iWBN8JcWdnqs%2F0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6a1ed04f1f3f4cb1a8a8775b3a123e6d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:41:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5IUqKG6vc7r7YC1iekWf0h","secret":"pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0023_get_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0023_get_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h.tail new file mode 100644 index 00000000..4c651f85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0023_get_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h.tail @@ -0,0 +1,60 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUqKG6vc7r7YC1iekWf0h\?client_secret=pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4jQQhUg0xqI4Z2 +Content-Length: 801 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ", + "id" : "pi_3Q5IUqKG6vc7r7YC1iekWf0h", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836860, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0024_post_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0024_post_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h_confirm.tail new file mode 100644 index 00000000..a52f11c9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0024_post_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h_confirm.tail @@ -0,0 +1,96 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUqKG6vc7r7YC1iekWf0h\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5aEXV6tClfmNv7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1630 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:01 GMT +original-request: req_5aEXV6tClfmNv7 +stripe-version: 2020-08-27 +idempotency-key: 286e8a63-c13f-4a73-8550-8047a01b4ac7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1Q5IUqKG6vc7r7YCCqHc1EXP&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCudQwHJVi0z9Jc4ruYIruZUAI3RPX?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUqKG6vc7r7YCCqHc1EXP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836860, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ", + "id" : "pi_3Q5IUqKG6vc7r7YC1iekWf0h", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836860, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0025_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCudQwHJVi0z9Jc4ruYIruZUAI3RPX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0025_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCudQwHJVi0z9Jc4ruYIruZUAI3RPX.tail new file mode 100644 index 00000000..f79698b0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0025_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCudQwHJVi0z9Jc4ruYIruZUAI3RPX.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCudQwHJVi0z9Jc4ruYIruZUAI3RPX\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_payment?payment_attempt=payatt_3Q5IUqKG6vc7r7YC1NsMz3iB +Date: Wed, 02 Oct 2024 02:41:02 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0026_get_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0026_get_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h.tail new file mode 100644 index 00000000..26c3266b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0026_get_v1_payment_intents_pi_3Q5IUqKG6vc7r7YC1iekWf0h.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUqKG6vc7r7YC1iekWf0h\?client_secret=pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2TxLpyrqOmwkvt +Content-Length: 1630 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCudQwHJVi0z9Jc4ruYIruZUAI3RPX?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUqKG6vc7r7YCCqHc1EXP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836860, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUqKG6vc7r7YC1iekWf0h_secret_uixs4R28zkA4MNbSweNeUKquQ", + "id" : "pi_3Q5IUqKG6vc7r7YC1iekWf0h", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836860, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..14af2447 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0027_post_v1_payment_methods.tail @@ -0,0 +1,58 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kb2IYjtQpvktvv +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 518 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:02 GMT +original-request: req_kb2IYjtQpvktvv +stripe-version: 2020-08-27 +idempotency-key: 2c2f628c-f017-4702-a52b-f31488e07497 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUsKG6vc7r7YCiEKORh5E", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836862, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0028_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0028_post_create_payment_intent.tail new file mode 100644 index 00000000..11c146b1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0028_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=kf35jmB9q6CVIUOtRcAkUiRsRwpXxBk07YfikaYSeGqof4DiovU1RAzZk5hZQKjCwjz5F52%2FpnVhBaxeGOb4sxqeVqe554EDizpIR6BqtcSYnDlJ4zHih8pF0D0SugI2miogRPM2NIYPUEe116zmX0CCARo3FAqCL%2FiH%2BHWhwY7kVlrBC7JtaZJDfq53LxBlCohrJbOZhra2WQORPrulFSB%2B2yg4EvqETT6neRKeonM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3b76233fc4ce579d5a1c3b85d22ed968;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:41:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Q5IUtKG6vc7r7YC0aJC31x0","secret":"pi_3Q5IUtKG6vc7r7YC0aJC31x0_secret_viKCSAjiuPPYxOH50pxnEyElv","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0029_get_v1_payment_intents_pi_3Q5IUtKG6vc7r7YC0aJC31x0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0029_get_v1_payment_intents_pi_3Q5IUtKG6vc7r7YC0aJC31x0.tail new file mode 100644 index 00000000..6db81bf1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0029_get_v1_payment_intents_pi_3Q5IUtKG6vc7r7YC0aJC31x0.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUtKG6vc7r7YC0aJC31x0\?client_secret=pi_3Q5IUtKG6vc7r7YC0aJC31x0_secret_viKCSAjiuPPYxOH50pxnEyElv&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_u7DrfgV54gu9ta +Content-Length: 1624 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuImI4vxmyZ49BL8ZcJl70VKKvLqw?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUsKG6vc7r7YCiEKORh5E", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836862, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUtKG6vc7r7YC0aJC31x0_secret_viKCSAjiuPPYxOH50pxnEyElv", + "id" : "pi_3Q5IUtKG6vc7r7YC0aJC31x0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836863, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0030_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuImI4vxmyZ49BL8ZcJl70VKKvLqw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0030_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuImI4vxmyZ49BL8ZcJl70VKKvLqw.tail new file mode 100644 index 00000000..7ec22809 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0030_get_authorize_acct_1JtgfQKG6vc7r7YC_pa_nonce_QxCuImI4vxmyZ49BL8ZcJl70VKKvLqw.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuImI4vxmyZ49BL8ZcJl70VKKvLqw\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_payment?payment_attempt=payatt_3Q5IUtKG6vc7r7YC087rdj9A +Date: Wed, 02 Oct 2024 02:41:04 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0031_get_v1_payment_intents_pi_3Q5IUtKG6vc7r7YC0aJC31x0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0031_get_v1_payment_intents_pi_3Q5IUtKG6vc7r7YC0aJC31x0.tail new file mode 100644 index 00000000..a295483f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0031_get_v1_payment_intents_pi_3Q5IUtKG6vc7r7YC0aJC31x0.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Q5IUtKG6vc7r7YC0aJC31x0\?client_secret=pi_3Q5IUtKG6vc7r7YC0aJC31x0_secret_viKCSAjiuPPYxOH50pxnEyElv&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RcvUV5RWoGT4OF +Content-Length: 1624 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QxCuImI4vxmyZ49BL8ZcJl70VKKvLqw?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUsKG6vc7r7YCiEKORh5E", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836862, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "client_secret" : "pi_3Q5IUtKG6vc7r7YC0aJC31x0_secret_viKCSAjiuPPYxOH50pxnEyElv", + "id" : "pi_3Q5IUtKG6vc7r7YC0aJC31x0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : "off_session", + "created" : 1727836863, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0032_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0032_post_create_setup_intent.tail new file mode 100644 index 00000000..50620477 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0032_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FodRZNMpBIOg2ClaXhteIRJktz%2F8gDt87QkURy36QdRvD8BF9SJzoO4gos1NWMkXInsy4UZV9u3vdMN1mB1W8ODg8BilyvL3kOO%2BpYP4kAgOtDHmzwBd%2FNMqItFGkr2u7BbiGrwB227i0B8ZNuPXAnKp2GqtcFVmg5u5ukZaGyHy%2BpE4KSVceehJdGs6YeEEOapebk32nZhOv6CtKQBGgvskzA8xkurePsOeg0sJ8tI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: aab08c2ddf66d0f4e61a4b436461b595 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:41:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Q5IUvKG6vc7r7YCDhjcEaxP","secret":"seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0033_get_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0033_get_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP.tail new file mode 100644 index 00000000..02740a68 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0033_get_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP.tail @@ -0,0 +1,46 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUvKG6vc7r7YCDhjcEaxP\?client_secret=seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_h7dcWW6aW53mh2 +Content-Length: 535 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5IUvKG6vc7r7YCDhjcEaxP", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836865, + "client_secret" : "seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0034_post_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0034_post_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP_confirm.tail new file mode 100644 index 00000000..ede30444 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0034_post_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP_confirm.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUvKG6vc7r7YCDhjcEaxP\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rNshMP2MGwsimn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1364 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:05 GMT +original-request: req_rNshMP2MGwsimn +stripe-version: 2020-08-27 +idempotency-key: 7b43e592-ede3-42b0-b248-1f7ed0e287f6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=paypal&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1Q5IUvKG6vc7r7YCDhjcEaxP", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCu3UProhPQRyBWMelkUrfdFeSH3Ti?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUvKG6vc7r7YCrb8ZtRTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836865, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836865, + "client_secret" : "seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0035_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCu3UProhPQRyBWMelkUrfdFeSH3Ti.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0035_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCu3UProhPQRyBWMelkUrfdFeSH3Ti.tail new file mode 100644 index 00000000..39ab08af --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0035_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCu3UProhPQRyBWMelkUrfdFeSH3Ti.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCu3UProhPQRyBWMelkUrfdFeSH3Ti\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_setup?setup_attempt=setatt_1Q5IUvKG6vc7r7YC9n7YPNAz +Date: Wed, 02 Oct 2024 02:41:06 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0036_get_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0036_get_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP.tail new file mode 100644 index 00000000..6ddacaf1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0036_get_v1_setup_intents_seti_1Q5IUvKG6vc7r7YCDhjcEaxP.tail @@ -0,0 +1,78 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUvKG6vc7r7YCDhjcEaxP\?client_secret=seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_68tFqYVlTZQRr9 +Content-Length: 1364 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5IUvKG6vc7r7YCDhjcEaxP", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCu3UProhPQRyBWMelkUrfdFeSH3Ti?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUvKG6vc7r7YCrb8ZtRTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836865, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836865, + "client_secret" : "seti_1Q5IUvKG6vc7r7YCDhjcEaxP_secret_QxCuOqvHCRggECGk8FoDRiNCHqZ1tTq", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0037_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0037_post_v1_payment_methods.tail new file mode 100644 index 00000000..1bb82986 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0037_post_v1_payment_methods.tail @@ -0,0 +1,58 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zxoGsKQjwCfiUC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 518 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:06 GMT +original-request: req_zxoGsKQjwCfiUC +stripe-version: 2020-08-27 +idempotency-key: 30731b82-f04b-4123-8d49-319f2550852a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUwKG6vc7r7YCuAsos9TC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836866, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0038_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0038_post_create_setup_intent.tail new file mode 100644 index 00000000..16e0e07f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0038_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=rk2p%2F2Ic12JASbifC6RtJq9%2FgG7HytEVfhj35%2BQ6QxPYKCVJKs4FuJFyMncNnPR5ZyWo2TOu27OYpIlAiVpZnXKNOChBGjKRRvY6rfGYZosGbskk7cU8nSnGvvgf2QqsAQMBqvRGtxvELyj%2Fia4LCqB2rRcsy9nQ%2FoaiG9sI0P4oQD1kFBQMqgJlPiOG06AX1ktkFBPxxfXGdanXsGbqtnwmrMwUrP5S%2F%2Ffs9NLs8Yg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7b56af56e180f90fd4f86b0dc396f4a9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:41:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Q5IUxKG6vc7r7YCbMReanBa","secret":"seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0039_get_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0039_get_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa.tail new file mode 100644 index 00000000..86269922 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0039_get_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa.tail @@ -0,0 +1,46 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUxKG6vc7r7YCbMReanBa\?client_secret=seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_F4td8ASMcK0SQE +Content-Length: 535 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5IUxKG6vc7r7YCbMReanBa", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836867, + "client_secret" : "seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0040_post_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0040_post_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa_confirm.tail new file mode 100644 index 00000000..ce48a125 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0040_post_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa_confirm.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUxKG6vc7r7YCbMReanBa\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UhGRQbDh4Qp6q1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1364 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:08 GMT +original-request: req_UhGRQbDh4Qp6q1 +stripe-version: 2020-08-27 +idempotency-key: b9045ddd-53dd-4af0-bc91-51ec79df599b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1Q5IUwKG6vc7r7YCuAsos9TC&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1Q5IUxKG6vc7r7YCbMReanBa", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCuVFVOoxZKJlM8h5hk9SIt9cE7sVX?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUwKG6vc7r7YCuAsos9TC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836866, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836867, + "client_secret" : "seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0041_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCuVFVOoxZKJlM8h5hk9SIt9cE7sVX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0041_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCuVFVOoxZKJlM8h5hk9SIt9cE7sVX.tail new file mode 100644 index 00000000..0f379234 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0041_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCuVFVOoxZKJlM8h5hk9SIt9cE7sVX.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCuVFVOoxZKJlM8h5hk9SIt9cE7sVX\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_setup?setup_attempt=setatt_1Q5IUxKG6vc7r7YCXjrU9SrQ +Date: Wed, 02 Oct 2024 02:41:08 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0042_get_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0042_get_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa.tail new file mode 100644 index 00000000..05fc6b48 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0042_get_v1_setup_intents_seti_1Q5IUxKG6vc7r7YCbMReanBa.tail @@ -0,0 +1,78 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUxKG6vc7r7YCbMReanBa\?client_secret=seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_w1eZS9Ku9SdZv9 +Content-Length: 1364 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5IUxKG6vc7r7YCbMReanBa", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCuVFVOoxZKJlM8h5hk9SIt9cE7sVX?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUwKG6vc7r7YCuAsos9TC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836866, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836867, + "client_secret" : "seti_1Q5IUxKG6vc7r7YCbMReanBa_secret_QxCuxPhVGTPCShlQnvieNqZvj9AzaaS", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0043_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0043_post_v1_payment_methods.tail new file mode 100644 index 00000000..c98585f8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0043_post_v1_payment_methods.tail @@ -0,0 +1,58 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_H1hYvjpew7J1Rj +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 518 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:09 GMT +original-request: req_H1hYvjpew7J1Rj +stripe-version: 2020-08-27 +idempotency-key: 9c35f9d0-3b13-47e3-a69a-b28ebabe867c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUzKG6vc7r7YCd6Vrk1MM", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836869, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0044_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0044_post_create_setup_intent.tail new file mode 100644 index 00000000..df878161 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0044_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=J3irfP4GNIF0E1kcOJ3tB6MqEKN7oF8q9u%2F9RWbYXx8bbYTtHJXjj8yodKMTNYHsOQ1dCkQVbbFBTnOFAZl2TAq1mJBpDCh3VB0eL%2BAwSNVaiS%2FkOwh38k03Kch2PPrudRQV7rElCU%2F5OVEiPuDhWJggbSgfTOyVh5bLjpPPRud7MNdBq5WcuUb%2FK6ZiXlUgzqJsl4EVCQ56gLAXvsPVAE%2BV3PmdcKvd%2BbWT%2F4gZF9s%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3cb044c7292c72a00d146daa4aea927c +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 02 Oct 2024 02:41:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf","secret":"seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf_secret_QxCuu0asMUqiy7f8wMM3udYzq2agewS","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0045_get_v1_setup_intents_seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0045_get_v1_setup_intents_seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf.tail new file mode 100644 index 00000000..4c9dc64a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0045_get_v1_setup_intents_seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf.tail @@ -0,0 +1,78 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf\?client_secret=seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf_secret_QxCuu0asMUqiy7f8wMM3udYzq2agewS&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GjABNypspbrEkb +Content-Length: 1358 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCutEEDc9nai4PP83eWc9IfXgl2ZIj?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUzKG6vc7r7YCd6Vrk1MM", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836869, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836869, + "client_secret" : "seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf_secret_QxCuu0asMUqiy7f8wMM3udYzq2agewS", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0046_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCutEEDc9nai4PP83eWc9IfXgl2ZIj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0046_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCutEEDc9nai4PP83eWc9IfXgl2ZIj.tail new file mode 100644 index 00000000..bdf661cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0046_get_authorize_acct_1JtgfQKG6vc7r7YC_sa_nonce_QxCutEEDc9nai4PP83eWc9IfXgl2ZIj.tail @@ -0,0 +1,20 @@ +GET +https:\/\/pm-redirects\.stripe\.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCutEEDc9nai4PP83eWc9IfXgl2ZIj\?useWebAuthSession=true&followRedirectsInSDK=true$ +302 +text/plain +Content-Type: text/plain;charset=utf-8 +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=pmredirectgw-srv" +x-stripe-priority-routing-enabled: true +content-security-policy: report-uri /csp-report?p=%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';style-src 'unsafe-inline';frame-ancestors 'self';connect-src 'self';img-src 'self' https://b.stripecdn.com +x-wc: A +Server: nginx +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=pmredirectgw-srv"}],"include_subdomains":true} +Location: https://stripe.com/payment_methods/test_setup?setup_attempt=setatt_1Q5IUzKG6vc7r7YCHvljvgwA +Date: Wed, 02 Oct 2024 02:41:10 GMT +Content-Length: 0 +x-stripe-routing-context-priority-tier: livemode-critical +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +content-security-policy-report-only: report-uri /csp-report?p=%2F8cd07f3a%2F%2Fauthorize%2F%3Amerchant%2F%3Asecret_token;block-all-mixed-content;default-src 'none' 'report-sample';base-uri 'none';form-action 'none';frame-ancestors 'none';style-src 'self';script-src 'self';img-src 'self'; +x-stripe-inbound-proxy-type: envoy + diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0047_get_v1_setup_intents_seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0047_get_v1_setup_intents_seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf.tail new file mode 100644 index 00000000..20d3e2e5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPayPalConfirmFlows/0047_get_v1_setup_intents_seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf.tail @@ -0,0 +1,78 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf\?client_secret=seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf_secret_QxCuu0asMUqiy7f8wMM3udYzq2agewS&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nfJI3F9LAGxNup +Content-Length: 1358 +Vary: Origin +Date: Wed, 02 Oct 2024 02:41:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/sa_nonce_QxCutEEDc9nai4PP83eWc9IfXgl2ZIj?useWebAuthSession=true&followRedirectsInSDK=true" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "country" : null, + "payer_id" : null, + "payer_email" : null + }, + "id" : "pm_1Q5IUzKG6vc7r7YCd6Vrk1MM", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1727836869, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "paypal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1727836869, + "client_secret" : "seti_1Q5IUzKG6vc7r7YCkhZ1Ezkf_secret_QxCuu0asMUqiy7f8wMM3udYzq2agewS", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..bf694916 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=OTKJC6BVyH7RCWHhqHtMJkY%2FU7QkuMqvEDVkI9t0stEoTTks3u4BHlT%2F%2BkwgqPSagspxMvqidEK0PEnWRM0yxrtla%2Ffg16LvjQXp61S0zHNZkqTvGKKyI2JCGdeM2z8utKNq%2B84xVulS1%2Fw3F97vDwMswZfNS59QgjpFLGc12FdGbBEszF2Qe7hXhSB0kccCSS%2F73E2VW2RbMrmUEmdzIlFVvH1BPNh8XiqSEay2rfI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 38a848379cadc157d6648a91210a6c70 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:47 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIMBgCYKNuUnn1mwMKvMb","secret":"pi_3PiTIMBgCYKNuUnn1mwMKvMb_secret_dkq07NFX1Fk2MM0LbGH0fhAd5","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIMBgCYKNuUnn1mwMKvMb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIMBgCYKNuUnn1mwMKvMb.tail new file mode 100644 index 00000000..c0561dcd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIMBgCYKNuUnn1mwMKvMb.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIMBgCYKNuUnn1mwMKvMb\?client_secret=pi_3PiTIMBgCYKNuUnn1mwMKvMb_secret_dkq07NFX1Fk2MM0LbGH0fhAd5$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KTHOEXaAIXp86X +Content-Length: 795 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "thb", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTIMBgCYKNuUnn1mwMKvMb_secret_dkq07NFX1Fk2MM0LbGH0fhAd5", + "id" : "pi_3PiTIMBgCYKNuUnn1mwMKvMb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "promptpay" + ], + "setup_future_usage" : null, + "created" : 1722396826, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIMBgCYKNuUnn1mwMKvMb_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIMBgCYKNuUnn1mwMKvMb_confirm.tail new file mode 100644 index 00000000..f692fe69 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIMBgCYKNuUnn1mwMKvMb_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIMBgCYKNuUnn1mwMKvMb\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ATvIlK4U37oiVw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1977 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:48 GMT +original-request: req_ATvIlK4U37oiVw +stripe-version: 2020-08-27 +idempotency-key: 0b0379d2-3e55-4840-90ad-8c83d6ecf3b9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIMBgCYKNuUnn1mwMKvMb_secret_dkq07NFX1Fk2MM0LbGH0fhAd5&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details%5Bemail%5D]=foo%40bar\.com&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=promptpay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "thb", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "promptpay_display_qr_code", + "promptpay_display_qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xTnBFQVdCZ0NZS051VW5uLF9RWmNYbGl6ejc4T0NZM1ExNXNsR0JOcmZybzZ3SVRs0100NT6eZurH.svg", + "data" : "https:\/\/stripe.com\/payment_methods\/test_payment?payment_attempt=payatt_3PiTIMBgCYKNuUnn1UOLijgX", + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/promptpay\/instructions\/CBoaFwoVYWNjdF8xTnBFQVdCZ0NZS051VW5uKJvhprUGMgayAa_5Cu86L1OAbbUzLPmoSdtN34G_cj-04KFCbm6lyg1eFBpcTN97cW-K5hYVTSTvnhb5AA-w", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xTnBFQVdCZ0NZS051VW5uLF9RWmNYbGl6ejc4T0NZM1ExNXNsR0JOcmZybzZ3SVRs0100NT6eZurH.png" + } + }, + "payment_method" : { + "object" : "payment_method", + "promptpay" : { + + }, + "id" : "pm_1PiTINBgCYKNuUnnUHCIBgho", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396827, + "allow_redisplay" : "unspecified", + "type" : "promptpay", + "customer" : null + }, + "client_secret" : "pi_3PiTIMBgCYKNuUnn1mwMKvMb_secret_dkq07NFX1Fk2MM0LbGH0fhAd5", + "id" : "pi_3PiTIMBgCYKNuUnn1mwMKvMb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "promptpay" + ], + "setup_future_usage" : null, + "created" : 1722396826, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..3cf12913 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0003_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_oRPLMMAzsrG5jH +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 463 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:48 GMT +original-request: req_oRPLMMAzsrG5jH +stripe-version: 2020-08-27 +idempotency-key: 2ebb3755-0612-4abf-8074-9b82e9e859be +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=promptpay + +{ + "object" : "payment_method", + "promptpay" : { + + }, + "id" : "pm_1PiTIOBgCYKNuUnnPrchgOxD", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396828, + "allow_redisplay" : "unspecified", + "type" : "promptpay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..deb9b4c0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=JeEtUJ5C1VVHiHyEbdo9%2BYYIWVFRDXeleCHlAMqhmHMYA%2F6bDSqYx32GzmaDQ3vnO5clyDu8KF%2BYJwI4gCWdrdlzlWmNaVOnRmJuDN9pMZqAxX7%2FqCNjWS8sosWe44Hg5dSZ%2F7WuOKBVvMbRfRujwfx9TAOyu%2FfFhw%2Bm70%2FOHTMN01JZzsAYhMj9Di684XH88kZeAtRKbcHy3C%2BjMFgf%2FBY4tvH8rBIIzoTbzo%2BJF7A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a45e57a18b67e00f8976b82f3b44b839 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:48 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIOBgCYKNuUnn1Szo5dUi","secret":"pi_3PiTIOBgCYKNuUnn1Szo5dUi_secret_0H341XUw5cjH4rat3uBGHwtRD","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIOBgCYKNuUnn1Szo5dUi.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIOBgCYKNuUnn1Szo5dUi.tail new file mode 100644 index 00000000..2b4fe89c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIOBgCYKNuUnn1Szo5dUi.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIOBgCYKNuUnn1Szo5dUi\?client_secret=pi_3PiTIOBgCYKNuUnn1Szo5dUi_secret_0H341XUw5cjH4rat3uBGHwtRD&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_71BJEpJzNQccTo +Content-Length: 795 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "thb", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTIOBgCYKNuUnn1Szo5dUi_secret_0H341XUw5cjH4rat3uBGHwtRD", + "id" : "pi_3PiTIOBgCYKNuUnn1Szo5dUi", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "promptpay" + ], + "setup_future_usage" : null, + "created" : 1722396828, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIOBgCYKNuUnn1Szo5dUi_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIOBgCYKNuUnn1Szo5dUi_confirm.tail new file mode 100644 index 00000000..5c36bd07 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIOBgCYKNuUnn1Szo5dUi_confirm.tail @@ -0,0 +1,95 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIOBgCYKNuUnn1Szo5dUi\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7ZkgLT6krWQRF4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1977 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:49 GMT +original-request: req_7ZkgLT6krWQRF4 +stripe-version: 2020-08-27 +idempotency-key: 4edc1aa6-1e5c-484a-a4f6-e6355f2985fc +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIOBgCYKNuUnn1Szo5dUi_secret_0H341XUw5cjH4rat3uBGHwtRD&expand\[0]=payment_method&payment_method=pm_1PiTIOBgCYKNuUnnPrchgOxD&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "thb", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "promptpay_display_qr_code", + "promptpay_display_qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xTnBFQVdCZ0NZS051VW5uLF9RWmNYNEE1SzN6QmtDd2ZFZGlLS0p0NFYyb05jVGJR0100kRINnM5m.svg", + "data" : "https:\/\/stripe.com\/payment_methods\/test_payment?payment_attempt=payatt_3PiTIOBgCYKNuUnn1vn9qey2", + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/promptpay\/instructions\/CBoaFwoVYWNjdF8xTnBFQVdCZ0NZS051VW5uKJ3hprUGMgYbcU-ve5w6L1PULbxabnDk7QElJmsTjQQ6rtDqhi-aR_WUrQmsv3TTXlv2xLG403cmcn-9RplB", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xTnBFQVdCZ0NZS051VW5uLF9RWmNYNEE1SzN6QmtDd2ZFZGlLS0p0NFYyb05jVGJR0100kRINnM5m.png" + } + }, + "payment_method" : { + "object" : "payment_method", + "promptpay" : { + + }, + "id" : "pm_1PiTIOBgCYKNuUnnPrchgOxD", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396828, + "allow_redisplay" : "unspecified", + "type" : "promptpay", + "customer" : null + }, + "client_secret" : "pi_3PiTIOBgCYKNuUnn1Szo5dUi_secret_0H341XUw5cjH4rat3uBGHwtRD", + "id" : "pi_3PiTIOBgCYKNuUnn1Szo5dUi", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "promptpay" + ], + "setup_future_usage" : null, + "created" : 1722396828, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..384e9156 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0007_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_B6jMl7G3PgAMr6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 463 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:49 GMT +original-request: req_B6jMl7G3PgAMr6 +stripe-version: 2020-08-27 +idempotency-key: 2be71c58-8c44-4a8b-9411-e33ead4cf893 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details%5Bemail%5D=foo%40bar\.com&payment_user_agent=.*&type=promptpay + +{ + "object" : "payment_method", + "promptpay" : { + + }, + "id" : "pm_1PiTIPBgCYKNuUnnfRgeb6I8", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396829, + "allow_redisplay" : "unspecified", + "type" : "promptpay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..005f87ca --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Smle%2BmG07JpTXgz2eghpYEj%2BQzXF1G453t0Mo8eDRs7k6FNQ2RWXdYwwp%2F396g4mgeaMRDiQNW5Sl81FUCSjhB19ddTVy%2BPhO3z4rpEg0uojVCnQyoJGpihj0hzXa0PnPRAJZ3KChm2GIW%2BbBhsyq1o8EPEFKSHaozi6ECycm6wQcADJVcVEprxagabWlCxujztpbOVm%2F4jQi1ClsE6c60EDxC6D1prA%2FGRSdqa6j8A%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 108abd34eb0b68f96b38e351c5221282 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIQBgCYKNuUnn1vrQBvMl","secret":"pi_3PiTIQBgCYKNuUnn1vrQBvMl_secret_f0kVkN816mkB7c5DPfOeSSF7f","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0009_get_v1_payment_intents_pi_3PiTIQBgCYKNuUnn1vrQBvMl.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0009_get_v1_payment_intents_pi_3PiTIQBgCYKNuUnn1vrQBvMl.tail new file mode 100644 index 00000000..c59f2624 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testPromptPayConfirmFlows/0009_get_v1_payment_intents_pi_3PiTIQBgCYKNuUnn1vrQBvMl.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIQBgCYKNuUnn1vrQBvMl\?client_secret=pi_3PiTIQBgCYKNuUnn1vrQBvMl_secret_f0kVkN816mkB7c5DPfOeSSF7f&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bcWjJmhrAyX5XJ +Content-Length: 1977 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:50 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "thb", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "promptpay_display_qr_code", + "promptpay_display_qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xTnBFQVdCZ0NZS051VW5uLF9RWmNYVTVNcTVLUXJNY1Vsd1BWUEMxa1hka2w3dERP0100eJyhvm34.svg", + "data" : "https:\/\/stripe.com\/payment_methods\/test_payment?payment_attempt=payatt_3PiTIQBgCYKNuUnn1iQggVm1", + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/promptpay\/instructions\/CBoaFwoVYWNjdF8xTnBFQVdCZ0NZS051VW5uKJ7hprUGMgZMtm2TL2Y6L1NxzpXUhL8xu7WEy5qjtAcqanK7NAyc2cfVBSN39wOSt2gCQt4COs5q4jXaWnfA", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xTnBFQVdCZ0NZS051VW5uLF9RWmNYVTVNcTVLUXJNY1Vsd1BWUEMxa1hka2w3dERP0100eJyhvm34.png" + } + }, + "payment_method" : { + "object" : "payment_method", + "promptpay" : { + + }, + "id" : "pm_1PiTIPBgCYKNuUnnfRgeb6I8", + "billing_details" : { + "email" : "foo@bar.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396829, + "allow_redisplay" : "unspecified", + "type" : "promptpay", + "customer" : null + }, + "client_secret" : "pi_3PiTIQBgCYKNuUnn1vrQBvMl_secret_f0kVkN816mkB7c5DPfOeSSF7f", + "id" : "pi_3PiTIQBgCYKNuUnn1vrQBvMl", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "promptpay" + ], + "setup_future_usage" : null, + "created" : 1722396830, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..023bcf0b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=B6DU9uXtDDUoZurg%2BlkHVgnaTVdsT92RPZRSUATZI0YkuQa6TOar1%2F0mWTCfmJbKVA0zasSQYGmcTvii%2FG%2FdHlSmIV6bGcvWf5Gvqfwhhb%2FwGMXvZGINiYGygP252NjJ0yzQZrXHqAzNxTE0Efb2Ld%2F1Iv445JReEIcQ3pnmby60mD%2FCBzJnuOKZb4O7hmO90mtjcG81fJZLsEH%2Bn%2F%2FW1YOXezo1S%2BbfuRCWY0r8hJw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 23b3b53f3406919b5fe933b211bc6d23;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 18 Oct 2024 16:05:58 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QBIgcGoesj9fw9Q0zxa1THJ","secret":"pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0001_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0001_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..949946d3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0001_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,60 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5DmSzRXugFJB0K +Content-Length: 797 +Vary: Origin +Date: Fri, 18 Oct 2024 16:05:59 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0002_post_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0002_post_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ_confirm.tail new file mode 100644 index 00000000..daddcc9e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0002_post_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ_confirm.tail @@ -0,0 +1,94 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\/confirm$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_A1ZO6AFXmvgvLD +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:05:59 GMT +original-request: req_A1ZO6AFXmvgvLD +stripe-version: 2020-08-27 +idempotency-key: 1d3fdcf1-47b9-4b3a-9359-e83586e8549b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=revolut_pay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0003_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0003_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..91cbc4c1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0003_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qYurH13KcoNmIr +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0004_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0004_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..eb23a078 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0004_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JOuztf8kUrTgxq +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0005_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0005_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..1a92a2fb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0005_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_C8JH2QkIKyoG7a +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0006_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0006_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..4e02207b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0006_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pAEII2B0WXAsjy +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0007_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0007_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..860b50a9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0007_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BHEp2rRN9pWNr5 +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0008_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0008_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail new file mode 100644 index 00000000..1a5898cf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0008_get_v1_payment_intents_pi_3QBIgcGoesj9fw9Q0zxa1THJ.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgcGoesj9fw9Q0zxa1THJ\?client_secret=pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TnPLPg0iWowJ8e +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVEjtksoFK2QrKrU6Huy5a8UdS72Q" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIgdGoesj9fw9QqBsmNBGp", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267559, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ_secret_3Z3sxtGSY2SCCYTfCFLumlRjj", + "id" : "pi_3QBIgcGoesj9fw9Q0zxa1THJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267558, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..04baafb7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MoJFAowXKiBSXK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 458 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:16 GMT +original-request: req_MoJFAowXKiBSXK +stripe-version: 2020-08-27 +idempotency-key: c934c199-e834-4db9-8c2d-b1800af5c6a1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=revolut_pay + +{ + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..5fc1f954 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YnsdDztP7Rzh5G5J6DW4NYhdRas2AJkG3Ll4zs2k9ma1LLmgU0t%2B2ZSmBpFMGG84OYUi5v29uVeYwrsKaetlA5%2FoNvNcwr6DKQWZXQAHqNfgJlx02%2FD3A0ykRwOu7Gx3wdXT%2Fml1LR7sLlt%2F3l8PmE0mWPmQIV0lqGvHxVZs4xegKDp9ZCT5JLckc2C%2BtxCTXUI14ULxi2m27EUsX6TB70KC%2FblQnX8KZyUlbmks7nw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 013d9aa01b019b922aebdb373b9fed2a;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 18 Oct 2024 16:06:17 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QBIgvGoesj9fw9Q13kLIB73","secret":"pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0011_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0011_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..8e7aeecb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0011_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,60 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PBQClTBtxyiAJf +Content-Length: 797 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0012_post_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0012_post_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73_confirm.tail new file mode 100644 index 00000000..ab835eb6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0012_post_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73_confirm.tail @@ -0,0 +1,94 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\/confirm$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_txF4ySzCIG6V60 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:18 GMT +original-request: req_txF4ySzCIG6V60 +stripe-version: 2020-08-27 +idempotency-key: 08757984-c1a2-45f9-9656-e2e34433c845 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand\[0]=payment_method&payment_method=pm_1QBIguGoesj9fw9QVRzjT6P8&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0013_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0013_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..08b6bf32 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0013_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_e6ncvTWjEMv18i +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0014_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0014_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..1f25cf5b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0014_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TqbXYRL4bJZjs0 +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:21 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0015_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0015_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..aca87fcd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0015_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4AzOcDex37q9EN +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0016_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0016_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..363ba54c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0016_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4yjicR46oygTFF +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0017_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0017_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..b43093d3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0017_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_l4pZt4QoerZlsg +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0018_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0018_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail new file mode 100644 index 00000000..b8678c37 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0018_get_v1_payment_intents_pi_3QBIgvGoesj9fw9Q13kLIB73.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIgvGoesj9fw9Q13kLIB73\?client_secret=pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IXkxnnQ5mDpTCb +Content-Length: 1509 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PVuAIMWHhVkfBOiqr1RGyJ16Y8Rt8" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIguGoesj9fw9QVRzjT6P8", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267576, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIgvGoesj9fw9Q13kLIB73_secret_DAGXkKKCrvNjKC7g9ZzmkmU9o", + "id" : "pi_3QBIgvGoesj9fw9Q13kLIB73", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267577, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0019_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0019_post_v1_payment_methods.tail new file mode 100644 index 00000000..47515fbe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0019_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_K6ZXtQfj3z11bj +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 458 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:35 GMT +original-request: req_K6ZXtQfj3z11bj +stripe-version: 2020-08-27 +idempotency-key: 6dc8baa0-0bb9-4fdb-9779-3ffef64c94ec +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=revolut_pay + +{ + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0020_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0020_post_create_payment_intent.tail new file mode 100644 index 00000000..11fb5bec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0020_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=XV79G4x8j%2BfZ%2FFt4mmWJyjOpOrEJc2Vx4L7r2F%2FLGO2Us10jY%2FwqmRCc3WoBh35DQpcYGWBHGRyFsLJfiKnixgKZvOJEH%2FI9E3tQk5iqOmZlxrx9Z12EwjdBF8A5B8rjvXuVlIFmyj5Ks251SOXb6YTh2tdf5%2F%2BrjP%2BWXU2Yupr8Iau0p31NerMwF59sojhsuJUHMtDNbP9XXyvVRCZ7JPynn9IwgjldmkscNpZ%2FOpc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 12972f342db0b25241a18c278b73c412;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 18 Oct 2024 16:06:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QBIhDGoesj9fw9Q1KfczfG6","secret":"pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0021_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0021_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..867150b6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0021_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_e7jRUjlNXY399I +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0022_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0022_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..03790062 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0022_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aMI786LWo1iU4s +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0023_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0023_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..660f735d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0023_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jHVR4oS5qD8Mfp +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0024_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0024_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..9c6b2727 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0024_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1P5p6P06Vdjf5Y +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0025_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0025_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..aa796917 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0025_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RUtAbX5YE5WlNF +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0026_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0026_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..55a1b8f2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0026_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_F7fU6avRRu5kyc +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0027_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0027_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail new file mode 100644 index 00000000..17c5fd85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testRevolutPayConfirmFlows/0027_get_v1_payment_intents_pi_3QBIhDGoesj9fw9Q1KfczfG6.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QBIhDGoesj9fw9Q1KfczfG6\?client_secret=pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZJ4MpNR9Ha2l0I +Content-Length: 1503 +Vary: Origin +Date: Fri, 18 Oct 2024 16:06:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_R3PWj40BbaxxoLCgvSwwHPQEMSFYsEq" + } + }, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1QBIhDGoesj9fw9QP8yDnCGR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1729267595, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3QBIhDGoesj9fw9Q1KfczfG6_secret_V1vMh7JWEbMLuq59fBAxOYjRC", + "id" : "pi_3QBIhDGoesj9fw9Q1KfczfG6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1729267595, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..8896c895 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9MDdFJz6iPTWNpyDp%2FYXlN%2FR2aF%2Bu9bc2yOOr%2FUsoidEsFaxLa49h6NcfxtQm%2FqEIwwkxfma7zEGkGk9XQrc9FeFba6M3WYtNgWVFyJG8dD%2BwtfiTnSnenQO5F5VTqLxLj%2FFrSqqNUoLZKMZ0UcitOQQUF07OKu69PUPouXAcZUEyh1HkfrHSFg1BAn8r%2BkCRgNWV4yCbIA4BGao48LFUdrZuKFqwZJj4cnVnp6LwkM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f5793853328f10f9ee05eec0a8062ca4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIjFY0qyl6XeW1unrjkE5","secret":"pi_3PiTIjFY0qyl6XeW1unrjkE5_secret_od4DqhTQeIsytso5Y0qnRxgAD","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIjFY0qyl6XeW1unrjkE5.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIjFY0qyl6XeW1unrjkE5.tail new file mode 100644 index 00000000..1484add5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIjFY0qyl6XeW1unrjkE5.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIjFY0qyl6XeW1unrjkE5\?client_secret=pi_3PiTIjFY0qyl6XeW1unrjkE5_secret_od4DqhTQeIsytso5Y0qnRxgAD$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5gtTzimb4AypXR +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIjFY0qyl6XeW1unrjkE5_secret_od4DqhTQeIsytso5Y0qnRxgAD", + "id" : "pi_3PiTIjFY0qyl6XeW1unrjkE5", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396849, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIjFY0qyl6XeW1unrjkE5_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIjFY0qyl6XeW1unrjkE5_confirm.tail new file mode 100644 index 00000000..95fcef84 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIjFY0qyl6XeW1unrjkE5_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIjFY0qyl6XeW1unrjkE5\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7tw3coUtqr2O0S +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1623 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:10 GMT +original-request: req_7tw3coUtqr2O0S +stripe-version: 2020-08-27 +idempotency-key: 304aab90-e6cb-4502-a8f7-9263c204358c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIjFY0qyl6XeW1unrjkE5_secret_od4DqhTQeIsytso5Y0qnRxgAD&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=asdf&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=asdf&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=AL&payment_method_data\[billing_details%5Bemail%5D]=f%40z\.c&payment_method_data\[billing_details%5Bname%5D]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sepa_debit%5Biban%5D]=DE89370400440532013000&payment_method_data\[type]=sepa_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIkFY0qyl6XeWwGFSSL6P", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396850, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTIjFY0qyl6XeW1unrjkE5_secret_od4DqhTQeIsytso5Y0qnRxgAD", + "id" : "pi_3PiTIjFY0qyl6XeW1unrjkE5", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396849, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..0f72bdc1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0003_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5uUXpuySoxtBwU +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 681 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:11 GMT +original-request: req_5uUXpuySoxtBwU +stripe-version: 2020-08-27 +idempotency-key: 2c0b29f2-ba27-4d76-b1db-5e7337dc8d6c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details%5Bemail%5D=f%40z\.c&billing_details%5Bname%5D=Foo&payment_user_agent=.*&sepa_debit%5Biban%5D=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIlFY0qyl6XeW5fe1NR9m", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396851, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..ee83e51d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=n5uDhWUZwZrIlcp09YUWeJy5gEa16zfP4i2xFFYmJJMqR3AN07i%2F8GHnd48cA1QRfVMwKdYezu8nEt1UZkohG5doiWAwg%2BhVLhDai6hxmQaDs8vX4MEz83osntnvW2%2FUQdAkHr7wSA1S8UBb57XrLKU4TV1whxy2OOfUkcM91Vbbd0d5arg%2F2RDT0hWAvz4rJqBgRa1HY0tQZUqaXJF58F86XCFb7GxALv4MIIMopms%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3c9580c6242c29ab70202567ac07114e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIlFY0qyl6XeW1viHPW3U","secret":"pi_3PiTIlFY0qyl6XeW1viHPW3U_secret_IQrIcsoiV3gf8UKPXfBnnvp8O","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIlFY0qyl6XeW1viHPW3U.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIlFY0qyl6XeW1viHPW3U.tail new file mode 100644 index 00000000..7bd49b5c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0005_get_v1_payment_intents_pi_3PiTIlFY0qyl6XeW1viHPW3U.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIlFY0qyl6XeW1viHPW3U\?client_secret=pi_3PiTIlFY0qyl6XeW1viHPW3U_secret_IQrIcsoiV3gf8UKPXfBnnvp8O&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uFouC3NTFWcEZ2 +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIlFY0qyl6XeW1viHPW3U_secret_IQrIcsoiV3gf8UKPXfBnnvp8O", + "id" : "pi_3PiTIlFY0qyl6XeW1viHPW3U", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396851, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIlFY0qyl6XeW1viHPW3U_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIlFY0qyl6XeW1viHPW3U_confirm.tail new file mode 100644 index 00000000..12ca35df --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0006_post_v1_payment_intents_pi_3PiTIlFY0qyl6XeW1viHPW3U_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIlFY0qyl6XeW1viHPW3U\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_53Ji44VS38iMVw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1623 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:12 GMT +original-request: req_53Ji44VS38iMVw +stripe-version: 2020-08-27 +idempotency-key: c52e63b2-6c12-4ba8-9feb-fa856499a839 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIlFY0qyl6XeW1viHPW3U_secret_IQrIcsoiV3gf8UKPXfBnnvp8O&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTIlFY0qyl6XeW5fe1NR9m&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIlFY0qyl6XeW5fe1NR9m", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396851, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTIlFY0qyl6XeW1viHPW3U_secret_IQrIcsoiV3gf8UKPXfBnnvp8O", + "id" : "pi_3PiTIlFY0qyl6XeW1viHPW3U", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396851, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0007_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0007_post_v1_payment_methods.tail new file mode 100644 index 00000000..87823670 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0007_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VzGzoxThH7HdKh +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 681 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:13 GMT +original-request: req_VzGzoxThH7HdKh +stripe-version: 2020-08-27 +idempotency-key: 05f87f71-d4e8-4d86-ad55-189f7dccf314 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details%5Bemail%5D=f%40z\.c&billing_details%5Bname%5D=Foo&payment_user_agent=.*&sepa_debit%5Biban%5D=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTInFY0qyl6XeWIcfc6q9H", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396853, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..5051d17d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=eLBXQ7qRzXvQ0Xv3RMhIIDXbbWxW42p1uSrUhvbu24Qs93aYT%2FeKyFvU1c8pAw8mF6p1yDHtE17bRuw3vH3RxENLqB7XlpegbK6zAwF9veiIP4o1sUfuthlpx5Mw2aV%2Bzbn09adqy27sn4wMUu3vMLuergjNgXyk1waohXyB9cwEAjW7d1Y4YfYpihDpAYIcGFfxqe85WR%2BVmYc%2F09ioa8IhyblcgwOGTjGYcGcCq%2Fo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b42189dd52117e975a3c89dbdf7a821d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTInFY0qyl6XeW1hM8UL5q","secret":"pi_3PiTInFY0qyl6XeW1hM8UL5q_secret_nVuLqYXA5CmI3phfmrWQHrsbg","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0009_get_v1_payment_intents_pi_3PiTInFY0qyl6XeW1hM8UL5q.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0009_get_v1_payment_intents_pi_3PiTInFY0qyl6XeW1hM8UL5q.tail new file mode 100644 index 00000000..5015b898 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0009_get_v1_payment_intents_pi_3PiTInFY0qyl6XeW1hM8UL5q.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTInFY0qyl6XeW1hM8UL5q\?client_secret=pi_3PiTInFY0qyl6XeW1hM8UL5q_secret_nVuLqYXA5CmI3phfmrWQHrsbg&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8kCr1gQEzw9dTz +Content-Length: 1623 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTInFY0qyl6XeWIcfc6q9H", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396853, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTInFY0qyl6XeW1hM8UL5q_secret_nVuLqYXA5CmI3phfmrWQHrsbg", + "id" : "pi_3PiTInFY0qyl6XeW1hM8UL5q", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396853, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..7e1515bf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=oDdHCaxMYZt%2F6kcFbuOOALw7xLk4C7AM2zppsvtInbdNglt4zAACX%2BlXwM4%2FSG2%2BxubrgPlksCitpnVZPZiqz6GI0%2B9mwHW4GYO4AUTwFtHyFuEhhLy%2FtE9yr3pb433q00M0QwMq9zGH51FxwCJiyegeshXR5wba%2FDSa9xHgvjihYhWidnY1rDJf4GpOWbUGCqS8%2BAU12zHxglg0HpUhS3HpqphpeEg1TM9Y2RA%2BRkE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0bb0a6f4152da68fb3739c870ea9a49a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIoFY0qyl6XeW0vuG3nGU","secret":"pi_3PiTIoFY0qyl6XeW0vuG3nGU_secret_bce20aLhyUmGz83o2xbhSsxhz","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTIoFY0qyl6XeW0vuG3nGU.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTIoFY0qyl6XeW0vuG3nGU.tail new file mode 100644 index 00000000..c179ff03 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTIoFY0qyl6XeW0vuG3nGU.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIoFY0qyl6XeW0vuG3nGU\?client_secret=pi_3PiTIoFY0qyl6XeW0vuG3nGU_secret_bce20aLhyUmGz83o2xbhSsxhz$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_F9cpHhQX55etfb +Content-Length: 904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIoFY0qyl6XeW0vuG3nGU_secret_bce20aLhyUmGz83o2xbhSsxhz", + "id" : "pi_3PiTIoFY0qyl6XeW0vuG3nGU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396854, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0012_post_v1_payment_intents_pi_3PiTIoFY0qyl6XeW0vuG3nGU_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0012_post_v1_payment_intents_pi_3PiTIoFY0qyl6XeW0vuG3nGU_confirm.tail new file mode 100644 index 00000000..4a87e3ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0012_post_v1_payment_intents_pi_3PiTIoFY0qyl6XeW0vuG3nGU_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIoFY0qyl6XeW0vuG3nGU\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OGJjEWKoF0G7Ep +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1632 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:15 GMT +original-request: req_OGJjEWKoF0G7Ep +stripe-version: 2020-08-27 +idempotency-key: da5c6d3b-f88a-4769-b694-d002e3b6ce2f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIoFY0qyl6XeW0vuG3nGU_secret_bce20aLhyUmGz83o2xbhSsxhz&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=asdf&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=asdf&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=AL&payment_method_data\[billing_details%5Bemail%5D]=f%40z\.c&payment_method_data\[billing_details%5Bname%5D]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sepa_debit%5Biban%5D]=DE89370400440532013000&payment_method_data\[type]=sepa_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIpFY0qyl6XeWFOT0IXO1", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396855, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTIoFY0qyl6XeW0vuG3nGU_secret_bce20aLhyUmGz83o2xbhSsxhz", + "id" : "pi_3PiTIoFY0qyl6XeW0vuG3nGU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396854, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0013_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0013_post_v1_payment_methods.tail new file mode 100644 index 00000000..d3fa5003 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0013_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6ECavmlZ4e6dZA +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 681 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:16 GMT +original-request: req_6ECavmlZ4e6dZA +stripe-version: 2020-08-27 +idempotency-key: 74e98c43-1d52-47fe-88c8-58d782af929b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details%5Bemail%5D=f%40z\.c&billing_details%5Bname%5D=Foo&payment_user_agent=.*&sepa_debit%5Biban%5D=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIqFY0qyl6XeWtQ2ciyB0", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396856, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..20af95b1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=o3oZ6SUFYCm2fpTJClR%2Ba9Z1wThC80SpHvxjoMYOvbK2DY39Se1SFguhueB6Cx%2FmEc%2BHi8RIhj5xWwL94R2grAyMCTML7fQ85Fzr9oeK%2Fx04NjuBdRn%2F5rAV%2FXIdfLsGawOtvPz%2FEj7LdELDqjCt4e%2BQit0PEorVyTsizFRgf%2BR5W2XYvsLXxI08bCV4OU3JioNmQwzGrXj4v4mJqHfT8Eu%2B3NcGse1qnRBnhaWc63g%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 133376fecc2f15f206a1500bdefb0469 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIqFY0qyl6XeW1gH0KBo4","secret":"pi_3PiTIqFY0qyl6XeW1gH0KBo4_secret_oIHEgDgOOmM6Xs6TzJaeItIv8","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0015_get_v1_payment_intents_pi_3PiTIqFY0qyl6XeW1gH0KBo4.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0015_get_v1_payment_intents_pi_3PiTIqFY0qyl6XeW1gH0KBo4.tail new file mode 100644 index 00000000..a1da0f21 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0015_get_v1_payment_intents_pi_3PiTIqFY0qyl6XeW1gH0KBo4.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIqFY0qyl6XeW1gH0KBo4\?client_secret=pi_3PiTIqFY0qyl6XeW1gH0KBo4_secret_oIHEgDgOOmM6Xs6TzJaeItIv8&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dijYcyRQxIqntF +Content-Length: 904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIqFY0qyl6XeW1gH0KBo4_secret_oIHEgDgOOmM6Xs6TzJaeItIv8", + "id" : "pi_3PiTIqFY0qyl6XeW1gH0KBo4", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396856, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0016_post_v1_payment_intents_pi_3PiTIqFY0qyl6XeW1gH0KBo4_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0016_post_v1_payment_intents_pi_3PiTIqFY0qyl6XeW1gH0KBo4_confirm.tail new file mode 100644 index 00000000..9fed5b36 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0016_post_v1_payment_intents_pi_3PiTIqFY0qyl6XeW1gH0KBo4_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIqFY0qyl6XeW1gH0KBo4\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_k6HJWmlCKIhDVF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1632 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:17 GMT +original-request: req_k6HJWmlCKIhDVF +stripe-version: 2020-08-27 +idempotency-key: c9976493-fd0b-4e63-9ec6-c1b796983b94 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIqFY0qyl6XeW1gH0KBo4_secret_oIHEgDgOOmM6Xs6TzJaeItIv8&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTIqFY0qyl6XeWtQ2ciyB0&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIqFY0qyl6XeWtQ2ciyB0", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396856, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTIqFY0qyl6XeW1gH0KBo4_secret_oIHEgDgOOmM6Xs6TzJaeItIv8", + "id" : "pi_3PiTIqFY0qyl6XeW1gH0KBo4", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396856, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..cd84aca8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JgQAZrwHhYqYiS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 681 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:17 GMT +original-request: req_JgQAZrwHhYqYiS +stripe-version: 2020-08-27 +idempotency-key: cea8697b-c5b6-42c2-9d8d-1f860a48bd8e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details%5Bemail%5D=f%40z\.c&billing_details%5Bname%5D=Foo&payment_user_agent=.*&sepa_debit%5Biban%5D=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIrFY0qyl6XeWMsouH7Gn", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396857, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..595d2f1c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=WEbRe95p55LUQDyLqUEsYzWjfOPL5E4J1P41LPjOl3kXXxYgD%2Bg8Y5myjm76Q3E0NqUM4Kf7luwNFIbd9F%2BlCL1kiHyZGGsJ%2B2XNxRU23HFYxurWRAkR5CVivdEUgotwxBH%2Fj99D0huTl82HBp%2F0d011jt%2FA2ojhUBz5H7SQx8qRfxUls7UT9jWpt29WFkuJ91KQbGfHcpC6qIwpnxOnCAuAVxVEKkkTrJV90d0ZWxU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2385bc3d1ea1ea5644f7365e41d60cb6;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:18 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIsFY0qyl6XeW1C1tsKon","secret":"pi_3PiTIsFY0qyl6XeW1C1tsKon_secret_COziSqRiKRgBnfkdLcglL4YXs","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0019_get_v1_payment_intents_pi_3PiTIsFY0qyl6XeW1C1tsKon.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0019_get_v1_payment_intents_pi_3PiTIsFY0qyl6XeW1C1tsKon.tail new file mode 100644 index 00000000..ff34f8f2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0019_get_v1_payment_intents_pi_3PiTIsFY0qyl6XeW1C1tsKon.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIsFY0qyl6XeW1C1tsKon\?client_secret=pi_3PiTIsFY0qyl6XeW1C1tsKon_secret_COziSqRiKRgBnfkdLcglL4YXs&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CTgbkeX9U1aKSG +Content-Length: 1632 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIrFY0qyl6XeWMsouH7Gn", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396857, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiTIsFY0qyl6XeW1C1tsKon_secret_COziSqRiKRgBnfkdLcglL4YXs", + "id" : "pi_3PiTIsFY0qyl6XeW1C1tsKon", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396858, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0020_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0020_post_create_setup_intent.tail new file mode 100644 index 00000000..32d6fdd4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0020_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=wp7usEovOGaYqeJBHFzfUU4JyUL%2FPh%2FRmW6wxxr5RZ55nkrBgFWFJrmVuYIMTF3O7yGZIVVo8gDjI9CMYjKmJ%2FbKyv0Dv1uj4%2Fi%2BFMwlwIVW6V7UOQQNiAnqLix9oQ59X%2Bold9ncp1y7PMi7GcqdXVeN%2B0s9Kg8wCMY6UzttCbmtZHQbui9D853EGc3V4pQ02S%2FxlokbHyJGpEDj8%2BLFX8UzAnAtsXS%2FeFQPJoXq7vo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2fe88f2fb1f346f5c5eaa9d917595734 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTItFY0qyl6XeWxlhPwXUb","secret":"seti_1PiTItFY0qyl6XeWxlhPwXUb_secret_QZcXVjgZMhkel4kUeGpsf9oUmdgUDHu","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0021_get_v1_setup_intents_seti_1PiTItFY0qyl6XeWxlhPwXUb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0021_get_v1_setup_intents_seti_1PiTItFY0qyl6XeWxlhPwXUb.tail new file mode 100644 index 00000000..a129f4b7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0021_get_v1_setup_intents_seti_1PiTItFY0qyl6XeWxlhPwXUb.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTItFY0qyl6XeWxlhPwXUb\?client_secret=seti_1PiTItFY0qyl6XeWxlhPwXUb_secret_QZcXVjgZMhkel4kUeGpsf9oUmdgUDHu$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_POsDZTvVg3rkZe +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTItFY0qyl6XeWxlhPwXUb", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396859, + "client_secret" : "seti_1PiTItFY0qyl6XeWxlhPwXUb_secret_QZcXVjgZMhkel4kUeGpsf9oUmdgUDHu", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0022_post_v1_setup_intents_seti_1PiTItFY0qyl6XeWxlhPwXUb_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0022_post_v1_setup_intents_seti_1PiTItFY0qyl6XeWxlhPwXUb_confirm.tail new file mode 100644 index 00000000..4aa75a25 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0022_post_v1_setup_intents_seti_1PiTItFY0qyl6XeWxlhPwXUb_confirm.tail @@ -0,0 +1,81 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTItFY0qyl6XeWxlhPwXUb\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AtMvGhcPaUqhVT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1266 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:20 GMT +original-request: req_AtMvGhcPaUqhVT +stripe-version: 2020-08-27 +idempotency-key: 771cc279-d705-4ddf-8dd6-3f48e903f4cf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTItFY0qyl6XeWxlhPwXUb_secret_QZcXVjgZMhkel4kUeGpsf9oUmdgUDHu&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[address]\[city]=asdf&payment_method_data\[billing_details]\[address]\[country]=US&payment_method_data\[billing_details]\[address]\[line1]=asdf&payment_method_data\[billing_details]\[address]\[line2]=&payment_method_data\[billing_details]\[address]\[postal_code]=12345&payment_method_data\[billing_details]\[address]\[state]=AL&payment_method_data\[billing_details%5Bemail%5D]=f%40z\.c&payment_method_data\[billing_details%5Bname%5D]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sepa_debit%5Biban%5D]=DE89370400440532013000&payment_method_data\[type]=sepa_debit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTItFY0qyl6XeWxlhPwXUb", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTItFY0qyl6XeWmKk7ekMx", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396859, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396859, + "client_secret" : "seti_1PiTItFY0qyl6XeWxlhPwXUb_secret_QZcXVjgZMhkel4kUeGpsf9oUmdgUDHu", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0023_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0023_post_v1_payment_methods.tail new file mode 100644 index 00000000..c3f97710 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0023_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LuMpztxVBNgV42 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 681 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:20 GMT +original-request: req_LuMpztxVBNgV42 +stripe-version: 2020-08-27 +idempotency-key: ff825974-07d6-42ba-9506-d5102ee6071b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details%5Bemail%5D=f%40z\.c&billing_details%5Bname%5D=Foo&payment_user_agent=.*&sepa_debit%5Biban%5D=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIuFY0qyl6XeWBqoHME2n", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396860, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0024_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0024_post_create_setup_intent.tail new file mode 100644 index 00000000..11f39e1b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0024_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=8tELoPxJCz3MV2HRH6JZPMkDxWwrSyrucoRlzF2kBTRUs1S%2Fyu4nQbZUuChnzYdTvctTMRnOeHmdtG9mP9zTUtqQYxiM0Q3Ivrn%2BoAQWscktsc4ayvqKvHxsAMc%2B8IBx9cAprpaVcBBbpvnSnr%2Frc2TRa%2BXBQUgOg0OiaWxVZqQ5DtFDyMhZTQsqtCjHzHWO0tqOqKqBidFj1LC8Sn8CpzsOqlu9efouiywVJ62h0WU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 827010a8ca9a1445ef1cec6fbcc2d599 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:20 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTIuFY0qyl6XeWxQejK4vr","secret":"seti_1PiTIuFY0qyl6XeWxQejK4vr_secret_QZcYXK2uFmWm4TPf36Jq2yoRxFoeOOj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0025_get_v1_setup_intents_seti_1PiTIuFY0qyl6XeWxQejK4vr.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0025_get_v1_setup_intents_seti_1PiTIuFY0qyl6XeWxQejK4vr.tail new file mode 100644 index 00000000..22b6f975 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0025_get_v1_setup_intents_seti_1PiTIuFY0qyl6XeWxQejK4vr.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIuFY0qyl6XeWxQejK4vr\?client_secret=seti_1PiTIuFY0qyl6XeWxQejK4vr_secret_QZcYXK2uFmWm4TPf36Jq2yoRxFoeOOj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OqtvUDzasz3bBD +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:21 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTIuFY0qyl6XeWxQejK4vr", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396860, + "client_secret" : "seti_1PiTIuFY0qyl6XeWxQejK4vr_secret_QZcYXK2uFmWm4TPf36Jq2yoRxFoeOOj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0026_post_v1_setup_intents_seti_1PiTIuFY0qyl6XeWxQejK4vr_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0026_post_v1_setup_intents_seti_1PiTIuFY0qyl6XeWxQejK4vr_confirm.tail new file mode 100644 index 00000000..0a77ef4d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0026_post_v1_setup_intents_seti_1PiTIuFY0qyl6XeWxQejK4vr_confirm.tail @@ -0,0 +1,81 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIuFY0qyl6XeWxQejK4vr\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NF7T3SYbOatiK4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1266 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:21 GMT +original-request: req_NF7T3SYbOatiK4 +stripe-version: 2020-08-27 +idempotency-key: 1c3bf401-a105-4a7b-a0df-690593bddf29 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTIuFY0qyl6XeWxQejK4vr_secret_QZcYXK2uFmWm4TPf36Jq2yoRxFoeOOj&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTIuFY0qyl6XeWBqoHME2n&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTIuFY0qyl6XeWxQejK4vr", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIuFY0qyl6XeWBqoHME2n", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396860, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396860, + "client_secret" : "seti_1PiTIuFY0qyl6XeWxQejK4vr_secret_QZcYXK2uFmWm4TPf36Jq2yoRxFoeOOj", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0027_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0027_post_v1_payment_methods.tail new file mode 100644 index 00000000..a2a356a1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0027_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aGx06a2Zp0c4jL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 681 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:22 GMT +original-request: req_aGx06a2Zp0c4jL +stripe-version: 2020-08-27 +idempotency-key: 66881df6-8f05-4936-8548-6c3956bc585c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=asdf&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=asdf&billing_details\[address]\[line2]=&billing_details\[address]\[postal_code]=12345&billing_details\[address]\[state]=AL&billing_details%5Bemail%5D=f%40z\.c&billing_details%5Bname%5D=Foo&payment_user_agent=.*&sepa_debit%5Biban%5D=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIwFY0qyl6XeWgztgm89s", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396862, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0028_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0028_post_create_setup_intent.tail new file mode 100644 index 00000000..71d4d640 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0028_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Ma%2B8W8Fd6ZUz7RQddKhgRLix8ZoFQayrS%2B9a5PvYx%2FmfccIu8JZUwyvHUdZ1WjYosMOCumGSLLX4jT4L3CkC7%2Bljvsohm98qlv2GL83YItq2UCxcxmMXvXZOG5xzQbC2wkaqB875OcL%2BGYZtU9hZrBu2HwAvGDDkod9fyhjJz3%2F7g3DwltrsQn4JBsKj0JfXodd9fhPVxaXh9LzHxRTy3%2BFwvdIzIUCgAhnKkeFz4GE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 33dfd22032cbb3e532c511ed52e5a50d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:22 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTIwFY0qyl6XeWmaMZR3YH","secret":"seti_1PiTIwFY0qyl6XeWmaMZR3YH_secret_QZcYGBXTAbbpmEPkiwmImE4Cl1qT0h1","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0029_get_v1_setup_intents_seti_1PiTIwFY0qyl6XeWmaMZR3YH.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0029_get_v1_setup_intents_seti_1PiTIwFY0qyl6XeWmaMZR3YH.tail new file mode 100644 index 00000000..da746757 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSEPADebitConfirmFlows/0029_get_v1_setup_intents_seti_1PiTIwFY0qyl6XeWmaMZR3YH.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIwFY0qyl6XeWmaMZR3YH\?client_secret=seti_1PiTIwFY0qyl6XeWmaMZR3YH_secret_QZcYGBXTAbbpmEPkiwmImE4Cl1qT0h1&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_X4NYwvsVuuMi1I +Content-Length: 1266 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTIwFY0qyl6XeWmaMZR3YH", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiTIwFY0qyl6XeWgztgm89s", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1722396862, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396862, + "client_secret" : "seti_1PiTIwFY0qyl6XeWmaMZR3YH_secret_QZcYGBXTAbbpmEPkiwmImE4Cl1qT0h1", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..2e35f246 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9zFa7iFdV4iguqGHJFGbngVdwPGLbVa0B0mgllnL0YLy26WWEr6hhnNMj4bBs0Eh1q0ESXZXYpnnTGouVV8HMm%2B8U%2B5XzvCpEIEI80r%2Fw0KfE%2FrUJMnx6J7GsP%2Fw9Uqvp7SvElBta00lYa9mYC%2F2Ec%2BJMiZB7ePq28xMnpuzKuIXO4jETFhbwQEtaAoOauvGxUIMpHw6qtRNV1OTx4Xa2MVuQeuDpBXrL8jKgo1NoXU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cf67b31fb4b1991391b06a06803a238b;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 02 Aug 2024 00:23:44 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pj9HYIFbdis1OxT0MCooEOZ","secret":"pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0001_get_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0001_get_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ.tail new file mode 100644 index 00000000..e806830b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0001_get_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HYIFbdis1OxT0MCooEOZ\?client_secret=pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IadsMjF6eFdk0F +Content-Length: 794 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj", + "id" : "pi_3Pj9HYIFbdis1OxT0MCooEOZ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558224, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0002_post_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0002_post_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ_confirm.tail new file mode 100644 index 00000000..d9fb6d20 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0002_post_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HYIFbdis1OxT0MCooEOZ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Y18igA61PTW7K9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1500 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:45 GMT +original-request: req_Y18igA61PTW7K9 +stripe-version: 2020-08-27 +idempotency-key: 9f20dd25-810e-4e41-af9a-a8392cd12d82 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=satispay&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QaJv3GzmNgb6AcEkuXrGNwhP3z1WjR2" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj9HZIFbdis1OxToL0Awzkh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558225, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj", + "id" : "pi_3Pj9HYIFbdis1OxT0MCooEOZ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558224, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0003_get_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0003_get_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ.tail new file mode 100644 index 00000000..90a22379 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0003_get_v1_payment_intents_pi_3Pj9HYIFbdis1OxT0MCooEOZ.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HYIFbdis1OxT0MCooEOZ\?client_secret=pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_S3QhvabHuLQUWb +Content-Length: 1500 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QaJv3GzmNgb6AcEkuXrGNwhP3z1WjR2" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj9HZIFbdis1OxToL0Awzkh", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558225, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3Pj9HYIFbdis1OxT0MCooEOZ_secret_Z9KrInwKWxeTqcmh3ESWYjTIj", + "id" : "pi_3Pj9HYIFbdis1OxT0MCooEOZ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558224, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..078c0c87 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_M6gpu0YlhIIXOp +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 452 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:46 GMT +original-request: req_M6gpu0YlhIIXOp +stripe-version: 2020-08-27 +idempotency-key: cd5c2708-9794-40dc-8346-36bb36593a6f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=satispay + +{ + "object" : "payment_method", + "id" : "pm_1Pj9HaIFbdis1OxTMP4HLC2q", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558226, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..8abeaec1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=nJWamTfPL87CEMihsg%2FOE8mwGXnYSEh7LmsMO%2BtF2MCSMTLa%2BbCbNoCtK9Qar0neHqIhEM6wqluG5Iwi7%2BjIKWgz%2FZm8UqAhOjrz8DUp2%2B7iQwxdkF1fP1U0O1M8w0zaOWDHbUFCxK89ljfHB7AFYQukxVTfzIVPRuSIYVW7lRYEN%2B%2BUosPYhWth0vSOLHWW1G1G74gihSoLyD9stYfI5dPlHiE4haatRPHmwFapjAU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b82446918d356263ffa89abfc7cfe372 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 02 Aug 2024 00:23:46 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pj9HaIFbdis1OxT1285sniR","secret":"pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0006_get_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0006_get_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR.tail new file mode 100644 index 00000000..b0c28144 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0006_get_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HaIFbdis1OxT1285sniR\?client_secret=pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XMzv6i7cNreTw0 +Content-Length: 794 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3", + "id" : "pi_3Pj9HaIFbdis1OxT1285sniR", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558226, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0007_post_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0007_post_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR_confirm.tail new file mode 100644 index 00000000..5a7a1d2b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0007_post_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HaIFbdis1OxT1285sniR\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yHFeeVEHZiMdj6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1500 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:47 GMT +original-request: req_yHFeeVEHZiMdj6 +stripe-version: 2020-08-27 +idempotency-key: 1dba0dfe-881f-424d-8970-94d791d15fbf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3&expand\[0]=payment_method&payment_method=pm_1Pj9HaIFbdis1OxTMP4HLC2q&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QaJvX60pRp9KoznEVsYp6LDXvANqb7u" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj9HaIFbdis1OxTMP4HLC2q", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558226, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3", + "id" : "pi_3Pj9HaIFbdis1OxT1285sniR", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558226, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0008_get_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0008_get_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR.tail new file mode 100644 index 00000000..06603df1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0008_get_v1_payment_intents_pi_3Pj9HaIFbdis1OxT1285sniR.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HaIFbdis1OxT1285sniR\?client_secret=pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FY23CKfTxSAP9j +Content-Length: 1500 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QaJvX60pRp9KoznEVsYp6LDXvANqb7u" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj9HaIFbdis1OxTMP4HLC2q", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558226, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3Pj9HaIFbdis1OxT1285sniR_secret_lxxMSo1d904CWtpPOKSS42yt3", + "id" : "pi_3Pj9HaIFbdis1OxT1285sniR", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558226, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..097fda18 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pwEa5vyYk0Ipjr +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 452 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:48 GMT +original-request: req_pwEa5vyYk0Ipjr +stripe-version: 2020-08-27 +idempotency-key: 3612af55-861e-4da1-891b-207dd3c4d8bd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=satispay + +{ + "object" : "payment_method", + "id" : "pm_1Pj9HcIFbdis1OxTWcXDAFEZ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558228, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..75be16ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=XW0GfJFFMenQePWmK3mY6Wfifo8zwvi%2FrbnDTDhCjUFwq%2FJouaf0oFqhlKXveVgSVGMDwFQ6u35MowtyIaHQVKWU%2FDmzBhEKv1MbbiEl4X%2BQTX3ZsNe1kEmx3hdZh6sAx7jtyFZvTgU%2Fb%2ByEsPq6ux1GanXQYVgcJ2tNjfK3KByypPqEBvue%2BSX6Mi5iAmXsn1EyfpRPeQ3xbhUOZgk%2By3c6dZllDRQYHVGryx2X6Yw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1061673f644512ded2701dada0e1ebf1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Fri, 02 Aug 2024 00:23:49 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pj9HcIFbdis1OxT1bYtukzw","secret":"pi_3Pj9HcIFbdis1OxT1bYtukzw_secret_8fS7iVPp2hLnchfMZDKCwB7dr","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0011_get_v1_payment_intents_pi_3Pj9HcIFbdis1OxT1bYtukzw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0011_get_v1_payment_intents_pi_3Pj9HcIFbdis1OxT1bYtukzw.tail new file mode 100644 index 00000000..22603aa7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0011_get_v1_payment_intents_pi_3Pj9HcIFbdis1OxT1bYtukzw.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HcIFbdis1OxT1bYtukzw\?client_secret=pi_3Pj9HcIFbdis1OxT1bYtukzw_secret_8fS7iVPp2hLnchfMZDKCwB7dr&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dfWYW91DvbrExr +Content-Length: 1494 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QaJvkDTgWeAuNQ4n9nWzIE8fpop7fKp" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj9HcIFbdis1OxTWcXDAFEZ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558228, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3Pj9HcIFbdis1OxT1bYtukzw_secret_8fS7iVPp2hLnchfMZDKCwB7dr", + "id" : "pi_3Pj9HcIFbdis1OxT1bYtukzw", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558228, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0012_get_v1_payment_intents_pi_3Pj9HcIFbdis1OxT1bYtukzw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0012_get_v1_payment_intents_pi_3Pj9HcIFbdis1OxT1bYtukzw.tail new file mode 100644 index 00000000..61ad6e73 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSatispayConfirmFlows/0012_get_v1_payment_intents_pi_3Pj9HcIFbdis1OxT1bYtukzw.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pj9HcIFbdis1OxT1bYtukzw\?client_secret=pi_3Pj9HcIFbdis1OxT1bYtukzw_secret_8fS7iVPp2hLnchfMZDKCwB7dr&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HpRYmSNpcIrN31 +Content-Length: 1494 +Vary: Origin +Date: Fri, 02 Aug 2024 00:23:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QaJvkDTgWeAuNQ4n9nWzIE8fpop7fKp" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pj9HcIFbdis1OxTWcXDAFEZ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558228, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3Pj9HcIFbdis1OxT1bYtukzw_secret_8fS7iVPp2hLnchfMZDKCwB7dr", + "id" : "pi_3Pj9HcIFbdis1OxT1bYtukzw", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1722558228, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..d93b6f50 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=yPEVZXe1b0CHcvvOb4jzniznbFVArPNPxAfpJoJFC57SlH3X2IiXYc23yUGVfJh%2FZK90Jpxpxw2Yw5jqFmvfTGYS5pVCp2dyjZz77Hhmi1VzQJ8ihFW7Hxt4RkyRHGzgKgjA8tCFvDg6ukEIwZVCkcu6YLS8csYua5JIZrJBOypr8qtBJf%2BkAMTm3PuYM%2BUve8QvA19gLDkTtpaE%2BglQvXSnjFi%2FWnvmvHtKVuW2sa8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e626ddb1db651db5405742a3c856cf9a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:58 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIYFY0qyl6XeW0RqJBsZe","secret":"pi_3PiTIYFY0qyl6XeW0RqJBsZe_secret_LxpkKedOBg2kYzsJikICKqdV6","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0001_get_v1_payment_intents_pi_3PiTIYFY0qyl6XeW0RqJBsZe.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0001_get_v1_payment_intents_pi_3PiTIYFY0qyl6XeW0RqJBsZe.tail new file mode 100644 index 00000000..fd264bae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0001_get_v1_payment_intents_pi_3PiTIYFY0qyl6XeW0RqJBsZe.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIYFY0qyl6XeW0RqJBsZe\?client_secret=pi_3PiTIYFY0qyl6XeW0RqJBsZe_secret_LxpkKedOBg2kYzsJikICKqdV6$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tCIM3vcCTy7pbp +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIYFY0qyl6XeW0RqJBsZe_secret_LxpkKedOBg2kYzsJikICKqdV6", + "id" : "pi_3PiTIYFY0qyl6XeW0RqJBsZe", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396838, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0002_post_v1_payment_intents_pi_3PiTIYFY0qyl6XeW0RqJBsZe_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0002_post_v1_payment_intents_pi_3PiTIYFY0qyl6XeW0RqJBsZe_confirm.tail new file mode 100644 index 00000000..f93410be --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0002_post_v1_payment_intents_pi_3PiTIYFY0qyl6XeW0RqJBsZe_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIYFY0qyl6XeW0RqJBsZe\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mcEjEGvU23ezk3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1639 +Vary: Origin +Date: Wed, 31 Jul 2024 03:33:59 GMT +original-request: req_mcEjEGvU23ezk3 +stripe-version: 2020-08-27 +idempotency-key: 0a2c68f7-99dc-442b-b198-7d46c33921ed +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIYFY0qyl6XeW0RqJBsZe_secret_LxpkKedOBg2kYzsJikICKqdV6&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1NnBnhFY0qyl6XeW9ThDjAvw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "client_secret" : "pi_3PiTIYFY0qyl6XeW0RqJBsZe_secret_LxpkKedOBg2kYzsJikICKqdV6", + "id" : "pi_3PiTIYFY0qyl6XeW0RqJBsZe", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396838, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0003_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0003_post_create_payment_intent.tail new file mode 100644 index 00000000..d83f4f48 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0003_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=GE0K3iOwrE2w5Nh5qAC0W0Lw8abGLUtLSK3wnBpoycOIAx8bktBEUqf4wrSdViotRxPZxb7RC%2FZJthi%2FL7zgyiswkdauB6Oa3TYv694syBE8rU22kw%2F5ZkTGutWgEaYS0eXYaHWTb0ULMmLOevyhp6%2BOcX5tt3Z1dQ1QAI2h9uVXnBn4AwJNGtS9ltMn0OY3z%2F%2BXaapPROFLfT041vwnFun86ItmiTzLVUICnQnLH2I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b6e8c121c3b4243e29e8e968b0372512 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:33:59 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIZFY0qyl6XeW1H6Mig0H","secret":"pi_3PiTIZFY0qyl6XeW1H6Mig0H_secret_MtORyTj9Mufjft6LGq6emyiWZ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0004_get_v1_payment_intents_pi_3PiTIZFY0qyl6XeW1H6Mig0H.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0004_get_v1_payment_intents_pi_3PiTIZFY0qyl6XeW1H6Mig0H.tail new file mode 100644 index 00000000..20fa69c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0004_get_v1_payment_intents_pi_3PiTIZFY0qyl6XeW1H6Mig0H.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIZFY0qyl6XeW1H6Mig0H\?client_secret=pi_3PiTIZFY0qyl6XeW1H6Mig0H_secret_MtORyTj9Mufjft6LGq6emyiWZ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Axixy76UKzHR26 +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIZFY0qyl6XeW1H6Mig0H_secret_MtORyTj9Mufjft6LGq6emyiWZ", + "id" : "pi_3PiTIZFY0qyl6XeW1H6Mig0H", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396839, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0005_post_v1_payment_intents_pi_3PiTIZFY0qyl6XeW1H6Mig0H_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0005_post_v1_payment_intents_pi_3PiTIZFY0qyl6XeW1H6Mig0H_confirm.tail new file mode 100644 index 00000000..984fc488 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0005_post_v1_payment_intents_pi_3PiTIZFY0qyl6XeW1H6Mig0H_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIZFY0qyl6XeW1H6Mig0H\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Nin0SEbxfC1SI1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1639 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:00 GMT +original-request: req_Nin0SEbxfC1SI1 +stripe-version: 2020-08-27 +idempotency-key: 8dfd8bbb-94cb-4701-be06-b1e906360367 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIZFY0qyl6XeW1H6Mig0H_secret_MtORyTj9Mufjft6LGq6emyiWZ&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1NnBnhFY0qyl6XeW9ThDjAvw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "client_secret" : "pi_3PiTIZFY0qyl6XeW1H6Mig0H_secret_MtORyTj9Mufjft6LGq6emyiWZ", + "id" : "pi_3PiTIZFY0qyl6XeW1H6Mig0H", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396839, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..d9d79801 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=BlTe66L9F3TJDSlBnXfq1Klm9YJbjmJsIrTq9TY0EuCx2bY6jUGN7%2BnQ0G0BGa04GS7W1W3TUxDJ3uZQShjCTMjhGjXglpLieVovF3vanSsQCKY4WlPzSZKWHmXO4UgH9qE1H2hFR0eNlwDDMmErzMo3DDhXfpaY0Pp2QwKKBFJsCxfcyMGxZE1W1%2BxdbImEA00mbKdOO9fmGCyXUS1yNBEeQ95I4dULfC3ggovPYPI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c398c7629efa3faf3502d236488639f7 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIbFY0qyl6XeW1NQuTErM","secret":"pi_3PiTIbFY0qyl6XeW1NQuTErM_secret_IAueDEfc6dyAldXn7xH30ptIk","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0007_get_v1_payment_intents_pi_3PiTIbFY0qyl6XeW1NQuTErM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0007_get_v1_payment_intents_pi_3PiTIbFY0qyl6XeW1NQuTErM.tail new file mode 100644 index 00000000..5cecc39c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0007_get_v1_payment_intents_pi_3PiTIbFY0qyl6XeW1NQuTErM.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIbFY0qyl6XeW1NQuTErM\?client_secret=pi_3PiTIbFY0qyl6XeW1NQuTErM_secret_IAueDEfc6dyAldXn7xH30ptIk&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_evZQu4XdRmvnLA +Content-Length: 1639 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "client_secret" : "pi_3PiTIbFY0qyl6XeW1NQuTErM_secret_IAueDEfc6dyAldXn7xH30ptIk", + "id" : "pi_3PiTIbFY0qyl6XeW1NQuTErM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722396841, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..438214d1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=cxgwRKZISOtEv%2BWvCpdsYuU%2B9zScIXfvKYaxuxwGXrcwZ4v5%2BD06HsF4DrbPGQ3N9iL%2Bm8LI0N%2BdxaLl4ZNyQpkMLU9Ekpe9w8VOLBnYcA%2BVNWLKEwYiO%2BANFmIhUKogvUzVImla5Cnb%2FzRCW%2FNrFOHuMrK7TH6ckb2%2F8saZG0HkJgDTqdePHTtcypBl4hFW2mtnfTn831iMl6ci2CN19zsfNyuoXI7h4jbDNRBHvvM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 31861a0531d7ab0e759c12c71f23631d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:02 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIcFY0qyl6XeW0QGtaGZR","secret":"pi_3PiTIcFY0qyl6XeW0QGtaGZR_secret_lR7W2RpVQUQ3ISvFD3lajkhfM","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0009_get_v1_payment_intents_pi_3PiTIcFY0qyl6XeW0QGtaGZR.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0009_get_v1_payment_intents_pi_3PiTIcFY0qyl6XeW0QGtaGZR.tail new file mode 100644 index 00000000..5a7ea148 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0009_get_v1_payment_intents_pi_3PiTIcFY0qyl6XeW0QGtaGZR.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIcFY0qyl6XeW0QGtaGZR\?client_secret=pi_3PiTIcFY0qyl6XeW0QGtaGZR_secret_lR7W2RpVQUQ3ISvFD3lajkhfM$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NTIixQzrAaNKwi +Content-Length: 904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIcFY0qyl6XeW0QGtaGZR_secret_lR7W2RpVQUQ3ISvFD3lajkhfM", + "id" : "pi_3PiTIcFY0qyl6XeW0QGtaGZR", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396842, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0010_post_v1_payment_intents_pi_3PiTIcFY0qyl6XeW0QGtaGZR_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0010_post_v1_payment_intents_pi_3PiTIcFY0qyl6XeW0QGtaGZR_confirm.tail new file mode 100644 index 00000000..8c57a65f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0010_post_v1_payment_intents_pi_3PiTIcFY0qyl6XeW0QGtaGZR_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIcFY0qyl6XeW0QGtaGZR\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Wq4UCH03ewWuDt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1648 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:03 GMT +original-request: req_Wq4UCH03ewWuDt +stripe-version: 2020-08-27 +idempotency-key: 4fb0691c-dca6-4d7a-9c15-2332424cee85 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIcFY0qyl6XeW0QGtaGZR_secret_lR7W2RpVQUQ3ISvFD3lajkhfM&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1NnBnhFY0qyl6XeW9ThDjAvw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "client_secret" : "pi_3PiTIcFY0qyl6XeW0QGtaGZR_secret_lR7W2RpVQUQ3ISvFD3lajkhfM", + "id" : "pi_3PiTIcFY0qyl6XeW0QGtaGZR", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396842, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0011_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0011_post_create_payment_intent.tail new file mode 100644 index 00000000..2df959b8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0011_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=tN0F36VMnRZijsnOpTM9SxlP8Vnu%2BsIHBgiIxMDza6taEk08XC02txkDvJJCEDlqmamYKXrL7A1iE9jq3K7g6o8toV1CqYwZZ5E%2BLgGSxUOaAp0OTV%2FGB2LDdXtVV%2Fule4oL9kpasbEAsCcVKTpeKMOQaRxro7tc%2FHJIZRk6udfyv9cjuqe9n2dyZO1RKffMeVV3aXqU8hqZWuFyi8WdTfbQGTFBdO66FhgR2Egq8bY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b55850b52d79f3387a2fb2be43322d1a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:03 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIdFY0qyl6XeW1q5Waorp","secret":"pi_3PiTIdFY0qyl6XeW1q5Waorp_secret_iJ1E09itrrp0nT1PremUx9tA8","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0012_get_v1_payment_intents_pi_3PiTIdFY0qyl6XeW1q5Waorp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0012_get_v1_payment_intents_pi_3PiTIdFY0qyl6XeW1q5Waorp.tail new file mode 100644 index 00000000..bf57de2e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0012_get_v1_payment_intents_pi_3PiTIdFY0qyl6XeW1q5Waorp.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIdFY0qyl6XeW1q5Waorp\?client_secret=pi_3PiTIdFY0qyl6XeW1q5Waorp_secret_iJ1E09itrrp0nT1PremUx9tA8&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ENvXHB89stnAWv +Content-Length: 904 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIdFY0qyl6XeW1q5Waorp_secret_iJ1E09itrrp0nT1PremUx9tA8", + "id" : "pi_3PiTIdFY0qyl6XeW1q5Waorp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396843, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0013_post_v1_payment_intents_pi_3PiTIdFY0qyl6XeW1q5Waorp_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0013_post_v1_payment_intents_pi_3PiTIdFY0qyl6XeW1q5Waorp_confirm.tail new file mode 100644 index 00000000..54e6522c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0013_post_v1_payment_intents_pi_3PiTIdFY0qyl6XeW1q5Waorp_confirm.tail @@ -0,0 +1,100 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIdFY0qyl6XeW1q5Waorp\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_paVyNeXn7Dljbf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1648 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:04 GMT +original-request: req_paVyNeXn7Dljbf +stripe-version: 2020-08-27 +idempotency-key: 5474d0fd-3c7b-4839-8b49-8851f8bd1bf5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIdFY0qyl6XeW1q5Waorp_secret_iJ1E09itrrp0nT1PremUx9tA8&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1NnBnhFY0qyl6XeW9ThDjAvw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "client_secret" : "pi_3PiTIdFY0qyl6XeW1q5Waorp_secret_iJ1E09itrrp0nT1PremUx9tA8", + "id" : "pi_3PiTIdFY0qyl6XeW1q5Waorp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396843, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0014_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0014_post_create_payment_intent.tail new file mode 100644 index 00000000..698784eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0014_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FDJ5jXLKx1%2FB41J%2FVcmBGOplfqTsRPOkXBYuwnPa6P60ZTyTZ9ndPleN%2F%2B6KrH2QNCtNsyBJrT7tfsJZUojKCGbutrcY0zkWffFqqx85BJCdqvsCvbqzd%2BMplYKOSbyR0NxUR2z5Wl8Hi9Smb523llE1UnCgr2iINrjKQ83D5PvrJ8pxu1iNe46d%2B6X0%2FIqdEXNGiEYMeAGAsQ7X4Q7SnZ6VePlpPoPXBXxg3zpZrK4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9b166e2b669139d4ee227c214a5b5e13 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:05 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIfFY0qyl6XeW1XtzML5b","secret":"pi_3PiTIfFY0qyl6XeW1XtzML5b_secret_YJ8Oxbitpr9nqrr8CIwgG5hYI","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0015_get_v1_payment_intents_pi_3PiTIfFY0qyl6XeW1XtzML5b.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0015_get_v1_payment_intents_pi_3PiTIfFY0qyl6XeW1XtzML5b.tail new file mode 100644 index 00000000..399f7494 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0015_get_v1_payment_intents_pi_3PiTIfFY0qyl6XeW1XtzML5b.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIfFY0qyl6XeW1XtzML5b\?client_secret=pi_3PiTIfFY0qyl6XeW1XtzML5b_secret_YJ8Oxbitpr9nqrr8CIwgG5hYI&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_A6frzkP0tlh4nH +Content-Length: 1648 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "client_secret" : "pi_3PiTIfFY0qyl6XeW1XtzML5b_secret_YJ8Oxbitpr9nqrr8CIwgG5hYI", + "id" : "pi_3PiTIfFY0qyl6XeW1XtzML5b", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : "off_session", + "created" : 1722396845, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0016_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0016_post_create_setup_intent.tail new file mode 100644 index 00000000..97be8f8f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0016_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=wPr8AGp9Q%2FZ0F%2BtoTo2M2sWeBFVlaI%2BB707b0psCjazkJlFu3liAJRMtzWkdnanOhDsIJmGqtPgk6Axc3bD8GmnRHsvwJohVfupj4ZeD4uQ6StmWcV3qCD8jpLmG11jZTuXGdGlfezDgotEpi9dHEtqsxVs6r2sgeVdHHmImchR%2BR6MAk9VxJzwTL3iwVnBudnsK6mfKDS2cKegLqRDiCgEqmFx%2BhOSyM6%2BzU3s7mcs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ffbfae5fc38a412ca05392bbf9b466be;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:06 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTIgFY0qyl6XeWLPpUQMbG","secret":"seti_1PiTIgFY0qyl6XeWLPpUQMbG_secret_QZcXuFdsfEYY6NF9jCblw6tzPSRq7FR","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0017_get_v1_setup_intents_seti_1PiTIgFY0qyl6XeWLPpUQMbG.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0017_get_v1_setup_intents_seti_1PiTIgFY0qyl6XeWLPpUQMbG.tail new file mode 100644 index 00000000..c80c4833 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0017_get_v1_setup_intents_seti_1PiTIgFY0qyl6XeWLPpUQMbG.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIgFY0qyl6XeWLPpUQMbG\?client_secret=seti_1PiTIgFY0qyl6XeWLPpUQMbG_secret_QZcXuFdsfEYY6NF9jCblw6tzPSRq7FR$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ansphGMC4E59PD +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTIgFY0qyl6XeWLPpUQMbG", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396846, + "client_secret" : "seti_1PiTIgFY0qyl6XeWLPpUQMbG_secret_QZcXuFdsfEYY6NF9jCblw6tzPSRq7FR", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0018_post_v1_setup_intents_seti_1PiTIgFY0qyl6XeWLPpUQMbG_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0018_post_v1_setup_intents_seti_1PiTIgFY0qyl6XeWLPpUQMbG_confirm.tail new file mode 100644 index 00000000..d8256f70 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0018_post_v1_setup_intents_seti_1PiTIgFY0qyl6XeWLPpUQMbG_confirm.tail @@ -0,0 +1,81 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIgFY0qyl6XeWLPpUQMbG\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fEcpViUKul4WYR +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1282 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:07 GMT +original-request: req_fEcpViUKul4WYR +stripe-version: 2020-08-27 +idempotency-key: d24f1fd1-8bde-4e93-9199-e77d50056b70 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTIgFY0qyl6XeWLPpUQMbG_secret_QZcXuFdsfEYY6NF9jCblw6tzPSRq7FR&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1NnBnhFY0qyl6XeW9ThDjAvw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTIgFY0qyl6XeWLPpUQMbG", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396846, + "client_secret" : "seti_1PiTIgFY0qyl6XeWLPpUQMbG_secret_QZcXuFdsfEYY6NF9jCblw6tzPSRq7FR", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0019_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0019_post_create_setup_intent.tail new file mode 100644 index 00000000..139f32d3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0019_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=n4qNcrX0GAIKAL4jeaT88Dnkj5FtaQEMeFnB9FMQiCTxHhzWYNfUHMc973guLoMgQfIqwCjLLwGFYY9SnHeOd1FzzCrdaXt975JR%2FHj7yk%2Bq2x2L%2FH4Tf3juLdh7EWfuULt3L8mkQX3AO6kvjUHx1Gm75YlmZAbQMhIZq1EEzu0r2hcy5qX%2Fb%2B%2BO3OoxahnMlLW%2F3UoudpabkoV%2FJGgB4b0KbWOH6gW1zFPob9CXsLY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fdcf52955bfe75c04b799dd1d3e0755a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTIhFY0qyl6XeWxFheNl8F","secret":"seti_1PiTIhFY0qyl6XeWxFheNl8F_secret_QZcX048ALddSnfJ91t7WPOQUAvUFA9B","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0020_get_v1_setup_intents_seti_1PiTIhFY0qyl6XeWxFheNl8F.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0020_get_v1_setup_intents_seti_1PiTIhFY0qyl6XeWxFheNl8F.tail new file mode 100644 index 00000000..b18bb524 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0020_get_v1_setup_intents_seti_1PiTIhFY0qyl6XeWxFheNl8F.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIhFY0qyl6XeWxFheNl8F\?client_secret=seti_1PiTIhFY0qyl6XeWxFheNl8F_secret_QZcX048ALddSnfJ91t7WPOQUAvUFA9B&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7ECVVKe8Xny7U0 +Content-Length: 539 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTIhFY0qyl6XeWxFheNl8F", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396847, + "client_secret" : "seti_1PiTIhFY0qyl6XeWxFheNl8F_secret_QZcX048ALddSnfJ91t7WPOQUAvUFA9B", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0021_post_v1_setup_intents_seti_1PiTIhFY0qyl6XeWxFheNl8F_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0021_post_v1_setup_intents_seti_1PiTIhFY0qyl6XeWxFheNl8F_confirm.tail new file mode 100644 index 00000000..c3d8b879 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0021_post_v1_setup_intents_seti_1PiTIhFY0qyl6XeWxFheNl8F_confirm.tail @@ -0,0 +1,81 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIhFY0qyl6XeWxFheNl8F\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aLaK84tZnSNVJh +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1282 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:08 GMT +original-request: req_aLaK84tZnSNVJh +stripe-version: 2020-08-27 +idempotency-key: 0f2565ca-ce93-43be-a930-83416d65fce4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTIhFY0qyl6XeWxFheNl8F_secret_QZcX048ALddSnfJ91t7WPOQUAvUFA9B&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1NnBnhFY0qyl6XeW9ThDjAvw&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTIhFY0qyl6XeWxFheNl8F", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396847, + "client_secret" : "seti_1PiTIhFY0qyl6XeWxFheNl8F_secret_QZcX048ALddSnfJ91t7WPOQUAvUFA9B", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0022_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0022_post_create_setup_intent.tail new file mode 100644 index 00000000..8971dae6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0022_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Ksi6nLdCVGpc%2B5PXoPPbc7OrFAt5PDqXAdb6kUExmSMThXNHMBNan9VTuIJL%2F5V77wjYgm5leCrgi7T8d3TJFKnzss3zOGeDYaKHFPKRu78ShPuz9Wg4VMzlHUvfR350kAWlxPv1o2Fn69NB2T7TafwdnlhCFUFpvWTSMXkDk9gWRW8NJZ1bfUGYIm%2FswkpNVqGU64RdjY%2Bb%2FN6vyDEJdM9eTcqgCFSzJBTm2mhFBCI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f7cece83680ddcbb131cfe2db2c53245 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTIiFY0qyl6XeWiu7YW1U8","secret":"seti_1PiTIiFY0qyl6XeWiu7YW1U8_secret_QZcXlQNU5TpZnuwn73V2f2PSsj7LLFf","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0023_get_v1_setup_intents_seti_1PiTIiFY0qyl6XeWiu7YW1U8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0023_get_v1_setup_intents_seti_1PiTIiFY0qyl6XeWiu7YW1U8.tail new file mode 100644 index 00000000..6919d73f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSavedSEPA/0023_get_v1_setup_intents_seti_1PiTIiFY0qyl6XeWiu7YW1U8.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTIiFY0qyl6XeWiu7YW1U8\?client_secret=seti_1PiTIiFY0qyl6XeWiu7YW1U8_secret_QZcXlQNU5TpZnuwn73V2f2PSsj7LLFf&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QAc1odfIQH5VP7 +Content-Length: 1282 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTIiFY0qyl6XeWiu7YW1U8", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1NnBnhFY0qyl6XeW9ThDjAvw", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : "AL", + "country" : "US", + "line2" : "", + "city" : "asdf", + "line1" : "asdf", + "postal_code" : "12345" + } + }, + "livemode" : false, + "created" : 1693968545, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : "cus_OaMPphpKbeixCz" + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396848, + "client_secret" : "seti_1PiTIiFY0qyl6XeWiu7YW1U8_secret_QZcXlQNU5TpZnuwn73V2f2PSsj7LLFf", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..f7eb056d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ZiBwgY%2FZ5gRGinC9dHtR%2FEACS317LgOTlIKthp4l3r7nyawA0Nmkxd0%2Bkt7gE8gpEH8Wwp%2F5jxuwPRX7H8GpoISCJSIdB68ARcsbME1NWJi%2Fka4NvtrkwhTlMA98QqL%2FIrZMOiO4QpdT%2BlKIJEzodMhJSE06yPz1kPDJnZ%2FQzSwTriS48Yc%2BKbGxIKr6uDPwOkEqqMpATP0v6hoRvpMXTOIKTIm5lnSQOBEgctMk28E%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b04a2a61543b345c66a8e4eb2601e353 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIxFY0qyl6XeW1pYqZmqh","secret":"pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh.tail new file mode 100644 index 00000000..54b510cf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0001_get_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIxFY0qyl6XeW1pYqZmqh\?client_secret=pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GJK8FyZqAxLf7a +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK", + "id" : "pi_3PiTIxFY0qyl6XeW1pYqZmqh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396863, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh_confirm.tail new file mode 100644 index 00000000..cc87031d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0002_post_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIxFY0qyl6XeW1pYqZmqh\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bHzZlLXwSfYVE5 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1620 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:24 GMT +original-request: req_bHzZlLXwSfYVE5 +stripe-version: 2020-08-27 +idempotency-key: 59d7a038-ac53-44fd-93e4-65b10ee18e7d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sofort%5Bcountry%5D]=AT&payment_method_data\[type]=sofort&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYreAx00XocCT6oK3F3Nam5n8ls2M" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTIyFY0qyl6XeWAF5m3VQl", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396864, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK", + "id" : "pi_3PiTIxFY0qyl6XeW1pYqZmqh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396863, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0003_get_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0003_get_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh.tail new file mode 100644 index 00000000..b0fc73e3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0003_get_v1_payment_intents_pi_3PiTIxFY0qyl6XeW1pYqZmqh.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIxFY0qyl6XeW1pYqZmqh\?client_secret=pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kc4wQFKoYhUmLm +Content-Length: 1620 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYreAx00XocCT6oK3F3Nam5n8ls2M" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTIyFY0qyl6XeWAF5m3VQl", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396864, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTIxFY0qyl6XeW1pYqZmqh_secret_Lt6FMyUJ37KQtUT6jHGv7TUfK", + "id" : "pi_3PiTIxFY0qyl6XeW1pYqZmqh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396863, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..b1330c66 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KyBLhfaGbewv36 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 471 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:25 GMT +original-request: req_KyBLhfaGbewv36 +stripe-version: 2020-08-27 +idempotency-key: 006a8c0d-5074-483f-a2e6-1c64e4dade06 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&sofort%5Bcountry%5D=AT&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTIyFY0qyl6XeW22PoanXE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396865, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..07661454 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=WmZRIooxOjOgvCUVgaTXHLTmWGYgqyyDgP5ZG6%2FZUJ0%2F8C3%2FYkQxKjPzDdK%2FYAJGVe3msJbhVS527wyRXKEOSi7uUyJPJB3JJyA3Xgxcsvh5ynziOkfVEaxT8%2FE9jfQVuVrppft9cXSIVW3yHrhOvPmTFf4tb%2FMyHk1WOroUY9XtT0bgDys8ykM81%2BEevr941EAoBL8GGoo64btDso2FXuBXwiXZqM20rg7FLbX%2FzIk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e95371767f6410f5311b02232de30791 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTIzFY0qyl6XeW14bCK4tY","secret":"pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0006_get_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0006_get_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY.tail new file mode 100644 index 00000000..7db7acd3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0006_get_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIzFY0qyl6XeW14bCK4tY\?client_secret=pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ihUCdG0fkjoo9T +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4", + "id" : "pi_3PiTIzFY0qyl6XeW14bCK4tY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396865, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0007_post_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0007_post_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY_confirm.tail new file mode 100644 index 00000000..6277709a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0007_post_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIzFY0qyl6XeW14bCK4tY\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FPEBwQ7jNVmsd6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1620 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:26 GMT +original-request: req_FPEBwQ7jNVmsd6 +stripe-version: 2020-08-27 +idempotency-key: 36963d23-60a2-4ea6-9de7-c9b2cf45111a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTIyFY0qyl6XeW22PoanXE&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYjWVWbXDEuAcaLk8ApkhMQ2mACu4" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTIyFY0qyl6XeW22PoanXE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396865, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4", + "id" : "pi_3PiTIzFY0qyl6XeW14bCK4tY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396865, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0008_get_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0008_get_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY.tail new file mode 100644 index 00000000..0e816b0e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0008_get_v1_payment_intents_pi_3PiTIzFY0qyl6XeW14bCK4tY.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTIzFY0qyl6XeW14bCK4tY\?client_secret=pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_v8h5y6SiBtJgwR +Content-Length: 1620 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYjWVWbXDEuAcaLk8ApkhMQ2mACu4" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTIyFY0qyl6XeW22PoanXE", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396865, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTIzFY0qyl6XeW14bCK4tY_secret_rPA5avhXCW7VuDEBrmXZJuoR4", + "id" : "pi_3PiTIzFY0qyl6XeW14bCK4tY", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396865, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..2e61a81c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WaGuMhu21hvRJ4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 471 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:27 GMT +original-request: req_WaGuMhu21hvRJ4 +stripe-version: 2020-08-27 +idempotency-key: 6edd12ad-6c3a-4831-9014-c0568bc6bfd6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&sofort%5Bcountry%5D=AT&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ1FY0qyl6XeWdhPOWj1Z", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396867, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..a3d2cfcd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=i0JsaORhRs8w2HYg7m7qoz5ZiFl3Cv0ALoEfKFZkqKJzM3Iqk4DiOacnLmlL1pNwBNq2djZrTCo9RASt40kmc%2BwyVM7%2FmgZblkReEpsOJYMroO0nNoyHiOmbLoejBY8pf8v2Ao2kUEdNDniCtBD6YhmogxvK%2FVT3uji2DD3hXbHqRPkVQ%2BLlXluCM4JjTHLWwSBo%2BMzXulQtE%2BxMk7zLKt4m4ok5Fup48wa2vgc0%2FZQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8e65045172c8278a723cfa862d17ab9b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:27 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJ1FY0qyl6XeW1z2IbX7C","secret":"pi_3PiTJ1FY0qyl6XeW1z2IbX7C_secret_pjHqQofSHCHYCbgiPTGPjQUB1","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJ1FY0qyl6XeW1z2IbX7C.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJ1FY0qyl6XeW1z2IbX7C.tail new file mode 100644 index 00000000..b6d8c50f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJ1FY0qyl6XeW1z2IbX7C.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ1FY0qyl6XeW1z2IbX7C\?client_secret=pi_3PiTJ1FY0qyl6XeW1z2IbX7C_secret_pjHqQofSHCHYCbgiPTGPjQUB1&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Q0ztEXsKuv3wH7 +Content-Length: 1614 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcY2fkrA1izNkymbA17zrWdijluxvy" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ1FY0qyl6XeWdhPOWj1Z", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396867, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ1FY0qyl6XeW1z2IbX7C_secret_pjHqQofSHCHYCbgiPTGPjQUB1", + "id" : "pi_3PiTJ1FY0qyl6XeW1z2IbX7C", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396867, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0012_get_v1_payment_intents_pi_3PiTJ1FY0qyl6XeW1z2IbX7C.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0012_get_v1_payment_intents_pi_3PiTJ1FY0qyl6XeW1z2IbX7C.tail new file mode 100644 index 00000000..30999163 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0012_get_v1_payment_intents_pi_3PiTJ1FY0qyl6XeW1z2IbX7C.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ1FY0qyl6XeW1z2IbX7C\?client_secret=pi_3PiTJ1FY0qyl6XeW1z2IbX7C_secret_pjHqQofSHCHYCbgiPTGPjQUB1&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Mz3PpGLsFmgzIK +Content-Length: 1614 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcY2fkrA1izNkymbA17zrWdijluxvy" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ1FY0qyl6XeWdhPOWj1Z", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396867, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ1FY0qyl6XeW1z2IbX7C_secret_pjHqQofSHCHYCbgiPTGPjQUB1", + "id" : "pi_3PiTJ1FY0qyl6XeW1z2IbX7C", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : null, + "created" : 1722396867, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..a1c352b9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9jU%2FhEt6kEgMgmEEfsUpzwdS8WKk2Kao2LLWQw00NSKwn6kZKHSc6e2XIgdUGcftvX7hM4HUVmUa1cl46k9OLIlXoevKdo9zYBXochf0LOO7LxJn81HFO7ceJIm17sPpk8k79m9Co2xAw2WWzFNajWVoouIrspeM%2B0IA4G5qLbNx7siVgxiMlV1WVCd%2Fbgglcazp5vtpqR2benOGbrl5ErTL44eupX9yZYLlhQSnWTI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5b0b1f54d59e0f4daeb551704f1db9a2;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJ2FY0qyl6XeW09FXlvBW","secret":"pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW.tail new file mode 100644 index 00000000..391c08d0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ2FY0qyl6XeW09FXlvBW\?client_secret=pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xuaY6TqQZFsjeb +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf", + "id" : "pi_3PiTJ2FY0qyl6XeW09FXlvBW", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396868, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0015_post_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0015_post_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW_confirm.tail new file mode 100644 index 00000000..25bcec1f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0015_post_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ2FY0qyl6XeW09FXlvBW\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_J3etGTfZfOaY91 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1633 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:29 GMT +original-request: req_J3etGTfZfOaY91 +stripe-version: 2020-08-27 +idempotency-key: 1f387ff2-246f-4eec-bba2-4455f9ecdb3b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sofort%5Bcountry%5D]=AT&payment_method_data\[type]=sofort&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYZd8pRZc4I0jIjnYBkkZcTtjrQJW" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ3FY0qyl6XeWjCpFM9ps", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396869, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf", + "id" : "pi_3PiTJ2FY0qyl6XeW09FXlvBW", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396868, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW.tail new file mode 100644 index 00000000..ec32b643 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJ2FY0qyl6XeW09FXlvBW.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ2FY0qyl6XeW09FXlvBW\?client_secret=pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FYe2mwZHlA5W0q +Content-Length: 1633 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYZd8pRZc4I0jIjnYBkkZcTtjrQJW" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ3FY0qyl6XeWjCpFM9ps", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396869, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ2FY0qyl6XeW09FXlvBW_secret_d7Cuum3tTAFlU5Ma8KUR5dVZf", + "id" : "pi_3PiTJ2FY0qyl6XeW09FXlvBW", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396868, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..c7161156 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UibJRkI5TrWJY0 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 475 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:30 GMT +original-request: req_UibJRkI5TrWJY0 +stripe-version: 2020-08-27 +idempotency-key: 4bf93b32-a9e7-4799-ac83-81ce453afc58 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&sofort%5Bcountry%5D=AT&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ4FY0qyl6XeW788YZaSX", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396870, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..87717194 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=FGuBk26pfCxzkd9tbp7oWdjhU%2BJ1ZRQ%2F2LU0TYO3%2B2k7Lm5F8SgTIsNfY489nzjH%2BIwkRSoQdEnxrxqFJGV9VK2CWtK41uoq3sse7oxeC064pQjRiq1Gj2hWGgrk52Y6b7vQZVGOr34%2BYbZo%2Bm7YEL8sZSBOqVsq%2FRuX10WqimqU5jtipkWQGTItqfUcUq7lIprayMA0485hkWCIVNjlGzEUVXCGomczxCU8%2BYFLDJ8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7e2d8864526b3ff93d9ef7d6d32c0b4d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJ4FY0qyl6XeW07RzIcKP","secret":"pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0019_get_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0019_get_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP.tail new file mode 100644 index 00000000..30d206ef --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0019_get_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ4FY0qyl6XeW07RzIcKP\?client_secret=pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3jsotKOL2NDHVz +Content-Length: 900 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH", + "id" : "pi_3PiTJ4FY0qyl6XeW07RzIcKP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396870, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0020_post_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0020_post_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP_confirm.tail new file mode 100644 index 00000000..cbd99475 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0020_post_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ4FY0qyl6XeW07RzIcKP\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WuFWub1DnOCtVx +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1633 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:31 GMT +original-request: req_WuFWub1DnOCtVx +stripe-version: 2020-08-27 +idempotency-key: dd6b0c8f-ed05-4bee-9e6b-658a5cdd5663 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTJ4FY0qyl6XeW788YZaSX&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYAnV654Cyxm8OziuFT82uqCb9mR8" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ4FY0qyl6XeW788YZaSX", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396870, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH", + "id" : "pi_3PiTJ4FY0qyl6XeW07RzIcKP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396870, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP.tail new file mode 100644 index 00000000..3aeb4b2a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJ4FY0qyl6XeW07RzIcKP.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ4FY0qyl6XeW07RzIcKP\?client_secret=pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_obEcUt4EC9AuRt +Content-Length: 1633 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYAnV654Cyxm8OziuFT82uqCb9mR8" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ4FY0qyl6XeW788YZaSX", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396870, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ4FY0qyl6XeW07RzIcKP_secret_KTVwBWKjAF5FewoNgMVVpNlKH", + "id" : "pi_3PiTJ4FY0qyl6XeW07RzIcKP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396870, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0022_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0022_post_v1_payment_methods.tail new file mode 100644 index 00000000..d0a8896d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0022_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DSuuJ3PhcYOvt7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 475 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:32 GMT +original-request: req_DSuuJ3PhcYOvt7 +stripe-version: 2020-08-27 +idempotency-key: b486c930-ff7f-4fef-842a-bd5b43b1a3c0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&sofort%5Bcountry%5D=AT&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ6FY0qyl6XeWhb73xp76", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396872, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0023_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0023_post_create_payment_intent.tail new file mode 100644 index 00000000..cadb231c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0023_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=aAqMmSDf4XD2hRrNAAWu61WsvjxsmpNa6TsXHbaG2olQRxeExmIhtvHcM5fxCkudOEzaTtUWZ6zUfpAkbcT3nC9N5cRhkGWs60kuI1rURSQsn%2BbgnmoxmKpMvEzz%2F2Lc1dDLtLorLxn01SZwgPq1RjToauXxiVVs8qnqIIIRZ0hAbqqxeIRHsxym59B8w388DIO9k3wJWDkQhoOnoIf8VUPNOFtOLOlr9Z%2BTGZ9szc4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 541ee691a45a4c59601656caba0dc2a9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:32 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJ6FY0qyl6XeW07WjYW7e","secret":"pi_3PiTJ6FY0qyl6XeW07WjYW7e_secret_HoshWK3YawsPnZEYbPdAG4pdK","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJ6FY0qyl6XeW07WjYW7e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJ6FY0qyl6XeW07WjYW7e.tail new file mode 100644 index 00000000..094c793d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJ6FY0qyl6XeW07WjYW7e.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ6FY0qyl6XeW07WjYW7e\?client_secret=pi_3PiTJ6FY0qyl6XeW07WjYW7e_secret_HoshWK3YawsPnZEYbPdAG4pdK&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4Cz4cLM8iF0S4Y +Content-Length: 1627 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYy1bRCvxZGHTZu9HFXtppG1SS8UL" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ6FY0qyl6XeWhb73xp76", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396872, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ6FY0qyl6XeW07WjYW7e_secret_HoshWK3YawsPnZEYbPdAG4pdK", + "id" : "pi_3PiTJ6FY0qyl6XeW07WjYW7e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396872, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJ6FY0qyl6XeW07WjYW7e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJ6FY0qyl6XeW07WjYW7e.tail new file mode 100644 index 00000000..18b972db --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJ6FY0qyl6XeW07WjYW7e.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJ6FY0qyl6XeW07WjYW7e\?client_secret=pi_3PiTJ6FY0qyl6XeW07WjYW7e_secret_HoshWK3YawsPnZEYbPdAG4pdK&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6qH4nHUvFAx1lN +Content-Length: 1627 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYy1bRCvxZGHTZu9HFXtppG1SS8UL" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ6FY0qyl6XeWhb73xp76", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396872, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_3PiTJ6FY0qyl6XeW07WjYW7e_secret_HoshWK3YawsPnZEYbPdAG4pdK", + "id" : "pi_3PiTJ6FY0qyl6XeW07WjYW7e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sofort" + ], + "setup_future_usage" : "off_session", + "created" : 1722396872, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0026_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0026_post_create_setup_intent.tail new file mode 100644 index 00000000..90a7bc88 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0026_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=UyEh8%2F%2FvNV2%2FzZrMF8Emv7icayPBvOmh9Byado60ecnJ89JNotHvq5Q2U9CbWgPDbl5SnvNUqVoaR4RPLEwIPyqDu6x8jm9pABOuR7VWuWsYZGPCXzzMUsQ9uIqjewztmxN5YT94QOiwx58Dtw7ZUIfDmfexNnfhZg%2FDCcJot8ReciSbnUPGkYbjNXiBVcegc9LNn3NXuEB9%2F156Fqhn1%2F96tPj5tGTwYGTjbtkstrg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cbf9255e6d924b4956f4288613f1c68d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:34 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTJ7FY0qyl6XeW3knlHSRN","secret":"seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0027_get_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0027_get_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN.tail new file mode 100644 index 00000000..0249cee9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0027_get_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJ7FY0qyl6XeW3knlHSRN\?client_secret=seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9Bb5jKCrZpgVaT +Content-Length: 535 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:34 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTJ7FY0qyl6XeW3knlHSRN", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396873, + "client_secret" : "seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0028_post_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0028_post_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN_confirm.tail new file mode 100644 index 00000000..5bed5921 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0028_post_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJ7FY0qyl6XeW3knlHSRN\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8o2pMQAH8g6BML +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1268 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:34 GMT +original-request: req_8o2pMQAH8g6BML +stripe-version: 2020-08-27 +idempotency-key: 99b4989f-a422-4c00-b553-f0528ca73abe +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[payment_user_agent]=.*&payment_method_data\[sofort%5Bcountry%5D]=AT&payment_method_data\[type]=sofort&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTJ7FY0qyl6XeW3knlHSRN", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcYJ9JBzbsXHrC1DPpVb9WSjp1N4iZ" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ8FY0qyl6XeWEo48CiZu", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396874, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396873, + "client_secret" : "seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0029_get_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0029_get_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN.tail new file mode 100644 index 00000000..91854f18 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0029_get_v1_setup_intents_seti_1PiTJ7FY0qyl6XeW3knlHSRN.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJ7FY0qyl6XeW3knlHSRN\?client_secret=seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1R2gO944pH6wQF +Content-Length: 1268 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTJ7FY0qyl6XeW3knlHSRN", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcYJ9JBzbsXHrC1DPpVb9WSjp1N4iZ" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ8FY0qyl6XeWEo48CiZu", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396874, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396873, + "client_secret" : "seti_1PiTJ7FY0qyl6XeW3knlHSRN_secret_QZcY6zSafyB09oxNAl4eUkHC8Wnmjt2", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0030_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0030_post_v1_payment_methods.tail new file mode 100644 index 00000000..b140c34c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0030_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yetNpWMBN3zL0q +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 475 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:35 GMT +original-request: req_yetNpWMBN3zL0q +stripe-version: 2020-08-27 +idempotency-key: 3afe05f6-0bf7-4e02-aa6e-7f1e9fefe83f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&sofort%5Bcountry%5D=AT&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ9FY0qyl6XeWdboRi9Xp", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396875, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0031_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0031_post_create_setup_intent.tail new file mode 100644 index 00000000..8cba5e80 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0031_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YEsb3DaxNEfdmmboOHexhPOJc6QleYdhqL4DGWKo2Mxf7w8rztdCpjZc1IPBjSwje7fH%2FWINdYnXRQ8KblJaH92HM%2F2r18Zaj0CMQ51iGdZJwuNF5RfwAvGKcK4alKqlw9uSwad9eQmzmrhwHgYlyFHrSJSrj9qe9YVAWN4okqTrS4A4KBPOChDPsVy53H89gnWrXQ2HOJI8qMkjAu%2F14F4Q8jaWo4mFJBX8HrE%2B%2BRI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1cdb442edba64ad46633dd7bbb1bba47 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8","secret":"seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0032_get_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0032_get_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8.tail new file mode 100644 index 00000000..ba8056fe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0032_get_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8\?client_secret=seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_U9lHQB2GH0LKtY +Content-Length: 535 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396875, + "client_secret" : "seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0033_post_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0033_post_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_confirm.tail new file mode 100644 index 00000000..61b7485e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0033_post_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_l2nmHGEyNn6aAu +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1268 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:36 GMT +original-request: req_l2nmHGEyNn6aAu +stripe-version: 2020-08-27 +idempotency-key: a9e26e46-0cc0-46cd-a21b-843af139e151 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTJ9FY0qyl6XeWdboRi9Xp&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcYQH2j8cL1xBcBzPzMOh88AdJ49gx" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ9FY0qyl6XeWdboRi9Xp", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396875, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396875, + "client_secret" : "seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0034_get_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0034_get_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8.tail new file mode 100644 index 00000000..f0afd716 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0034_get_v1_setup_intents_seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8\?client_secret=seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MkELp4XXClROte +Content-Length: 1268 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcYQH2j8cL1xBcBzPzMOh88AdJ49gx" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJ9FY0qyl6XeWdboRi9Xp", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396875, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396875, + "client_secret" : "seti_1PiTJ9FY0qyl6XeWy6Hu3Sy8_secret_QZcYD20VQ29Dl5LBODTXZtylE1RVUSX", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0035_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0035_post_v1_payment_methods.tail new file mode 100644 index 00000000..0a4f66e9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0035_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mXCVRfLC2UI3kR +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 475 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:37 GMT +original-request: req_mXCVRfLC2UI3kR +stripe-version: 2020-08-27 +idempotency-key: ddaaa852-2224-414d-bd12-fe9e8251c036 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&payment_user_agent=.*&sofort%5Bcountry%5D=AT&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJBFY0qyl6XeWcfV5UsqA", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396877, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0036_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0036_post_create_setup_intent.tail new file mode 100644 index 00000000..b3996929 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0036_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=mLGnifdF8xTT4F%2BjOJDtGxAh%2FeTe%2F4pfMWKBls7ec4zx0vqSTCmwoj5I1zmO%2B0OBTNwzuvMzgGuCWepCT6AqRS4H8gda2etdtVIdieYZlazSsmX292p2Wg6UJGjd%2Fo7d6HgTkb%2FEzvlNVZI%2F%2BnZ5aPDjlCwK2p51UkOZe5bnvJFApmv5AFuw%2FGSwnR9QtgAAAafDXIKL9M%2FQQeg0p54kwFfx2HPkj4YugdtmtPizE8I%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3b96d5f7f01f568361a7724967a8826d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTJBFY0qyl6XeWsuSLNbVJ","secret":"seti_1PiTJBFY0qyl6XeWsuSLNbVJ_secret_QZcYYZhzZsSdjWuYmuiV5FQqzV9SONW","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0037_get_v1_setup_intents_seti_1PiTJBFY0qyl6XeWsuSLNbVJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0037_get_v1_setup_intents_seti_1PiTJBFY0qyl6XeWsuSLNbVJ.tail new file mode 100644 index 00000000..cfa05a33 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0037_get_v1_setup_intents_seti_1PiTJBFY0qyl6XeWsuSLNbVJ.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJBFY0qyl6XeWsuSLNbVJ\?client_secret=seti_1PiTJBFY0qyl6XeWsuSLNbVJ_secret_QZcYYZhzZsSdjWuYmuiV5FQqzV9SONW&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vhc5hVPcrS1rgP +Content-Length: 1262 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:37 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTJBFY0qyl6XeWsuSLNbVJ", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcY7gg0Tc618gdBv3UMyYOF4F8bqGW" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJBFY0qyl6XeWcfV5UsqA", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396877, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396877, + "client_secret" : "seti_1PiTJBFY0qyl6XeWsuSLNbVJ_secret_QZcYYZhzZsSdjWuYmuiV5FQqzV9SONW", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0038_get_v1_setup_intents_seti_1PiTJBFY0qyl6XeWsuSLNbVJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0038_get_v1_setup_intents_seti_1PiTJBFY0qyl6XeWsuSLNbVJ.tail new file mode 100644 index 00000000..c9b901ff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSofortConfirmFlows/0038_get_v1_setup_intents_seti_1PiTJBFY0qyl6XeWsuSLNbVJ.tail @@ -0,0 +1,75 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTJBFY0qyl6XeWsuSLNbVJ\?client_secret=seti_1PiTJBFY0qyl6XeWsuSLNbVJ_secret_QZcYYZhzZsSdjWuYmuiV5FQqzV9SONW&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5eJlHTwwDColQj +Content-Length: 1262 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTJBFY0qyl6XeWsuSLNbVJ", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcY7gg0Tc618gdBv3UMyYOF4F8bqGW" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "AT" + }, + "id" : "pm_1PiTJBFY0qyl6XeWcfV5UsqA", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396877, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396877, + "client_secret" : "seti_1PiTJBFY0qyl6XeWsuSLNbVJ_secret_QZcYYZhzZsSdjWuYmuiV5FQqzV9SONW", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..f1e968cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=HJfEhMKm9tx6gvo6mJq6Be95w9ka9i%2B9vHqzuyA8%2BUjgFeDIUKcD6K9%2F2UEeUDUjA2e8EiEKojCq3Z19dcUjrD%2BCmtbyc82nwIQwFCXThP%2FB40b2GxPZVvl%2F4CONHYJLBf8t6hlGK3SeBRmXPIKzCCX4j01DlahPc558Fi9OpP7nvBbtAHq%2Faj8y7RArGgrDqsKBNsxfzPA5AJCcJYuMYV%2BpJtJEK6KqBg0OXeXJ3Do%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e397422b8f99720c290a793bc4360d54 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:38 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJCFY0qyl6XeW02odRwT7","secret":"pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7.tail new file mode 100644 index 00000000..48bd21c5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJCFY0qyl6XeW02odRwT7\?client_secret=pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RJE1LjJnzqTka1 +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU", + "id" : "pi_3PiTJCFY0qyl6XeW02odRwT7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396878, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7_confirm.tail new file mode 100644 index 00000000..d0ae0b0b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJCFY0qyl6XeW02odRwT7\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3Zsh516yLQRykV +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1597 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:39 GMT +original-request: req_3Zsh516yLQRykV +stripe-version: 2020-08-27 +idempotency-key: 2ec6ac5b-68aa-48e4-ba76-c670bfce03b5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=sunbit&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYrueLVdMNBJUeOeN6yK63uVeEHvn" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJDFY0qyl6XeWgN1o1L05", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396879, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU", + "id" : "pi_3PiTJCFY0qyl6XeW02odRwT7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396878, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7.tail new file mode 100644 index 00000000..accf1568 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJCFY0qyl6XeW02odRwT7.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJCFY0qyl6XeW02odRwT7\?client_secret=pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_t1v5o3nfOiz4is +Content-Length: 1597 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYrueLVdMNBJUeOeN6yK63uVeEHvn" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJDFY0qyl6XeWgN1o1L05", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396879, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3PiTJCFY0qyl6XeW02odRwT7_secret_9jzP2jmgeQRjQ3Ov7cnJ77FfU", + "id" : "pi_3PiTJCFY0qyl6XeW02odRwT7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396878, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..9f81a54e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_P1eypfQSPXBUG3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:40 GMT +original-request: req_P1eypfQSPXBUG3 +stripe-version: 2020-08-27 +idempotency-key: 8c3fb1b3-542c-4f90-9baa-97130b0794f7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=sunbit + +{ + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJEFY0qyl6XeWVZ6lXloA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396880, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..b1a80c85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=KrBCq27zyJFo6BhnHvkq%2F5T7iB4mBiuoaVkJP%2FJB7iqdled8KNPUuGniZWgR8DOqpjAvVgCQJwNtLMUafdM498rBAXkDBnrC9HUY8Y0J2tRSojn0qqSfu2z6pseJ4T0YmmrST8CyyLls57NsFqhGwvE3wYEV9X7HhRl7Js%2BpS2uPbAFJtLq0EhsrF4daimHYpzEGhaMYKdFwGpN7JPnUYyzQVrIn2n8t7CVMpMqP2Mo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ee67a9e7dc45e78a1912a90f2a9fd7a5;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJEFY0qyl6XeW0HtZkX1e","secret":"pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e.tail new file mode 100644 index 00000000..08b2c0b3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJEFY0qyl6XeW0HtZkX1e\?client_secret=pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PTxAH8t5lnXuH2 +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE", + "id" : "pi_3PiTJEFY0qyl6XeW0HtZkX1e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396880, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0007_post_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0007_post_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e_confirm.tail new file mode 100644 index 00000000..789c8b84 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0007_post_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJEFY0qyl6XeW0HtZkX1e\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LBR9CzNNxCKOMt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1597 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:41 GMT +original-request: req_LBR9CzNNxCKOMt +stripe-version: 2020-08-27 +idempotency-key: 57cd3c9e-eaba-42ed-982c-f7b696923857 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE&expand\[0]=payment_method&payment_method=pm_1PiTJEFY0qyl6XeWVZ6lXloA&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcY4g9yjDmkj53F42V8C9IDpKTHVDu" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJEFY0qyl6XeWVZ6lXloA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396880, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE", + "id" : "pi_3PiTJEFY0qyl6XeW0HtZkX1e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396880, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e.tail new file mode 100644 index 00000000..ad644eae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJEFY0qyl6XeW0HtZkX1e.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJEFY0qyl6XeW0HtZkX1e\?client_secret=pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fVied4JAaRcX7R +Content-Length: 1597 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcY4g9yjDmkj53F42V8C9IDpKTHVDu" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJEFY0qyl6XeWVZ6lXloA", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396880, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3PiTJEFY0qyl6XeW0HtZkX1e_secret_25Cd9HVPrM5RUsfsw80h2L3gE", + "id" : "pi_3PiTJEFY0qyl6XeW0HtZkX1e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396880, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..a7c3971f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_YVLwC2klpVY67i +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:42 GMT +original-request: req_YVLwC2klpVY67i +stripe-version: 2020-08-27 +idempotency-key: ca3430b4-a02f-4c26-870d-423ab79a8193 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=sunbit + +{ + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJGFY0qyl6XeWKXuHXWEe", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396882, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..931795ec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Mg8GcdI6BoJH4ApxT7%2FcO%2FWLrcd%2FBnmaE%2FlfVqt6bj7JytrIuoF1bgunJHvHG1JwC3F3rY%2BJrkezLc9AzQ9Mq0FNOCpf%2F6y7qXx1zocOnm7%2Bv1tEs30ilnAkNGfFRt89T8ynrMzqal2b9x%2BOk89Xh6AxE8ih44LUOuVEJ4FxuCVZWQdYxqwjj%2FGMgbualOi03QtDSQFzqQANNtCa%2BBCz7UUTUbOqdE6K5ZNOvBNH3VY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 08e4d2d2820be52b65f9d890e7f4645a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJGFY0qyl6XeW0uOXbpda","secret":"pi_3PiTJGFY0qyl6XeW0uOXbpda_secret_T86VT1xZRhIdqLOgcogVwrsq8","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJGFY0qyl6XeW0uOXbpda.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJGFY0qyl6XeW0uOXbpda.tail new file mode 100644 index 00000000..cb3732db --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJGFY0qyl6XeW0uOXbpda.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJGFY0qyl6XeW0uOXbpda\?client_secret=pi_3PiTJGFY0qyl6XeW0uOXbpda_secret_T86VT1xZRhIdqLOgcogVwrsq8&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ITqChGMBVDn4it +Content-Length: 1591 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYXp0FlLi6cn56SCuosyscmDfeUYB" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJGFY0qyl6XeWKXuHXWEe", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396882, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3PiTJGFY0qyl6XeW0uOXbpda_secret_T86VT1xZRhIdqLOgcogVwrsq8", + "id" : "pi_3PiTJGFY0qyl6XeW0uOXbpda", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396882, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0012_get_v1_payment_intents_pi_3PiTJGFY0qyl6XeW0uOXbpda.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0012_get_v1_payment_intents_pi_3PiTJGFY0qyl6XeW0uOXbpda.tail new file mode 100644 index 00000000..2f105777 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSunbitConfirmFlows/0012_get_v1_payment_intents_pi_3PiTJGFY0qyl6XeW0uOXbpda.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJGFY0qyl6XeW0uOXbpda\?client_secret=pi_3PiTJGFY0qyl6XeW0uOXbpda_secret_T86VT1xZRhIdqLOgcogVwrsq8&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_N0J3SVR1YvEHci +Content-Length: 1591 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcYXp0FlLi6cn56SCuosyscmDfeUYB" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiTJGFY0qyl6XeWKXuHXWEe", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396882, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3PiTJGFY0qyl6XeW0uOXbpda_secret_T86VT1xZRhIdqLOgcogVwrsq8", + "id" : "pi_3PiTJGFY0qyl6XeW0uOXbpda", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1722396882, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..aba2ae46 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=e98OVamIVSyjN8%2Bn1AGbKW8jeshUcnIy%2BS%2BEWe2WQOG7Wegd49f2ioNcITvak5dRC0RVOhyjbofgt2T2iPlCArPmtWlarmb9an69jJQE9yZIgY%2BhfAFslFUxguydsTRTwdlayH91AcrwBAlZS2NbW5G6XoFeFaQgKNDrgbYNq8K4JWQ%2BersZ%2BOnqkDm%2B3XPTtRcTgd1HbzB1ELfWCqLq0pPPt0Q8eKEK2AXawP6qY4E%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4027fa58562f0a4bb9e7ef5d52f2efc2 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJHKG6vc7r7YC1JL1HSTL","secret":"pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..526a69ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FGxoPf9Jk5qyZh +Content-Length: 791 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL_confirm.tail new file mode 100644 index 00000000..81f27089 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Le8vFs2nHD8pc6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:44 GMT +original-request: req_Le8vFs2nHD8pc6 +stripe-version: 2020-08-27 +idempotency-key: ade64ec7-6395-4e8e-865a-8b06b77988ba +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=swish&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..8eb2bc55 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3Jqpw2TzU4dHt1 +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0004_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0004_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..eb3da023 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0004_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wrRB03dugaKAQs +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0005_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0005_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..fe849153 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0005_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_A2JzUut21t0oFd +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..d176e49e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AffUeKl2I6Rw7W +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0007_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0007_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..5dafbdea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0007_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8qHW4hLXRyIkCm +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:50 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail new file mode 100644 index 00000000..2e8571ad --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJHKG6vc7r7YC1JL1HSTL.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJHKG6vc7r7YC1JL1HSTL\?client_secret=pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_up8h056xmKYQ7u +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSjVwYmxiR2hTTVBBdEJTYm1Ydmh5WjJEUnpM0100UzjKdIb5.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKNThprUGMgYICoEI9606L0cbp5kXUJpcN6431duaN6Po6eofRWGqZ7qsBPyuZ9PT7E7gihBbCeu5HJpHC39y", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYEJOXUw61uiMaKWKkDpl2kcYmORU" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJIKG6vc7r7YCJUqgxR5h", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396884, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL_secret_up9BRo3PvuBZMxgZeE0yNXv84", + "id" : "pi_3PiTJHKG6vc7r7YC1JL1HSTL", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396883, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..e157b854 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sFQZduxeSHoWRK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 446 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:51 GMT +original-request: req_sFQZduxeSHoWRK +stripe-version: 2020-08-27 +idempotency-key: 4dd6cfc2-4280-4d07-9c67-e724bc047603 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=swish + +{ + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..56e8ae54 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=m4l%2Bj5pli7AkLnzZ4kOeVCBASlNxbE0BuDNL6UBLek2ypvBU5%2FEIUr0EW%2BOfp4x1lEoUMTj8XVSdFbzwGHon3OmLUPUg5i69cBsYAwfAjvdX1cB1HX%2BR6SLEtnS1yFyuoZmiD3%2BhxIJ7FTZjRr7vNEm3CTjFLwBww1eDX4yDzMo%2FYGgj315wuxts%2FyMQLXsEX3A5lzDff9eNN%2BW22B7KqBrlgcwLmWMaVJcz3%2FrYA5U%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5ea196407aacd25f9e325369acc20a6b;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:34:52 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJQKG6vc7r7YC1uSovsaM","secret":"pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..68922995 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_umOVNYlWHC4184 +Content-Length: 791 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0012_post_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0012_post_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM_confirm.tail new file mode 100644 index 00000000..5bae4963 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0012_post_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7mYNLSx0WFYzwY +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:53 GMT +original-request: req_7mYNLSx0WFYzwY +stripe-version: 2020-08-27 +idempotency-key: 6a681961-6bf1-472c-923a-d6688471a83b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand\[0]=payment_method&payment_method=pm_1PiTJPKG6vc7r7YCctibK49N&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0013_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0013_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..aeb01b1a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0013_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VBH7uUsCQLyPIv +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..44b5b6d1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_t08qB1nqutawyR +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:54 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0015_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0015_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..b173d3ec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0015_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_trLywHj0y4aSzE +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..7c40707a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_q7izaSY2usuzHS +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0017_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0017_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..8da4960a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0017_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hUgrN1iW8lQ65h +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:34:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0018_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0018_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail new file mode 100644 index 00000000..ec4bfd16 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0018_get_v1_payment_intents_pi_3PiTJQKG6vc7r7YC1uSovsaM.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJQKG6vc7r7YC1uSovsaM\?client_secret=pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wiwAhAwR4E98Fc +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:00 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZSUN0NTJsZEx1ZTdMMnVKTGpSTmp3b1FPbzZZ0100ppCLgNiu.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKN3hprUGMga9B4KYMbw6L0cRiul9cu5uCRWMHf0FJcLmFj7_ATz2iT0rW7rHFCVZwi17CKSrntT2xkbQegmr", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcYB02YgxYb2hN8lkaeQAAsq2JakkM" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJPKG6vc7r7YCctibK49N", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396891, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJQKG6vc7r7YC1uSovsaM_secret_3d57g5x46LqH1ZeZ7mYnSs6gu", + "id" : "pi_3PiTJQKG6vc7r7YC1uSovsaM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396892, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0019_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0019_post_v1_payment_methods.tail new file mode 100644 index 00000000..86997bb3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0019_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WakAMkGhwlntxC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 446 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:00 GMT +original-request: req_WakAMkGhwlntxC +stripe-version: 2020-08-27 +idempotency-key: 632b152b-6ca5-4d3a-bba9-9a4523925bce +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=swish + +{ + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0020_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0020_post_create_payment_intent.tail new file mode 100644 index 00000000..1fde9e30 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0020_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2FxQEiqr3jcK%2FaEOuPiwqpLRoSX7ifb32Fy245rxk700ini1cTMTksPY%2FAQ%2FP%2BfRRCTY%2FXTfodx5920B4oww1IE1IbVAB85ottMV1SjlJ74ACCwztms5GuMpFO8StXC46f3EHSAYpYZxCM6L4Ly%2BD3pU5YuuygWcHEI%2BTf9LS5iuYqkForCTRnkcXKa9aYX5xj1yzZ89PSPTDC%2Bny891nFnGsiJUqrXrKI1cZ%2BXym8m8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bfc3348fbd2eee448f8ea7011ba3b8e5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:35:01 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJYKG6vc7r7YC0mvx0JMX","secret":"pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..929e14e4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_fLm1BLZLs701F2 +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0022_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0022_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..3de6a45f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0022_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pgXwBzRoBEVmWn +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:01 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0023_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0023_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..81f7add6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0023_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xxF1rR8xMxxcLf +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..9e7e88dd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mgJzLSbrZgXoLX +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..50967ac7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_v6xOWcPOI1CdHD +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:05 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0026_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0026_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..5e552b8d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0026_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nb7DalfPFphFEl +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0027_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0027_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail new file mode 100644 index 00000000..9375c2a3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testSwishConfirmFlows/0027_get_v1_payment_intents_pi_3PiTJYKG6vc7r7YC0mvx0JMX.tail @@ -0,0 +1,94 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJYKG6vc7r7YC0mvx0JMX\?client_secret=pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_abkbyYMFSFo6Au +Content-Length: 2172 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "swish_handle_redirect_or_display_qr_code" : { + "qr_code" : { + "image_url_svg" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.svg?use_theme=true", + "data" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N", + "image_url_png" : "https:\/\/qr.stripe.com\/test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLF9RWmNZY3Roc2tiUHpYZWNYbXhwdGFua1BkeXpRWHRL0100zLDhYh5v.png" + }, + "hosted_instructions_url" : "https:\/\/payments.stripe.com\/swish\/instructions\/CC4aFwoVYWNjdF8xSnRnZlFLRzZ2YzdyN1lDKOThprUGMgagvFKTxv06L0cnc9kJgTLhoN1h8Rv1FcTSJd7Sq_0fmiYEorusWUsTwBh5DBEd3V2a_ULSOG82", + "mobile_auth_url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QZcY9LqGMyZ2RuYVqvJOSBDwRt91A6N" + }, + "type" : "swish_handle_redirect_or_display_qr_code" + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJYKG6vc7r7YCa7hzRISF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722396900, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX_secret_a4RBoM659OncrRZpBgGi7Dh8v", + "id" : "pi_3PiTJYKG6vc7r7YC0mvx0JMX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1722396900, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..a50fcce5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=PHnCzW2heSE8ZVF0kHpMCIb8FnT%2BNTOE8OFXMZgnQfuktZ0gP%2BpqCtw99y64wck77YeTlRJwU2Pt8kJH4%2F1mKnO90tbkdjcyOcI6vtFJWk6qZnb3CpVVsmnbz%2F0vlsDWEU7v%2BkN5NEYfdvEmN90n7smYBtNLVJj%2BKTbykQwPU7KfkXhsc8iry1WhI8HXF6gezVAB3AFIRlecGIePiHDFiSh1GxJWVqwJ5cLgt5K1oFw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f366f2bf01dce73101d869a032754d3d;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:35:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJgGoesj9fw9Q12D8WmHA","secret":"pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..060af9ad --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0001_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5aEfqevYyiq7qp +Content-Length: 791 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA_confirm.tail new file mode 100644 index 00000000..e1b5e4c1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0002_post_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ddOGDpue18gRjN +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:09 GMT +original-request: req_ddOGDpue18gRjN +stripe-version: 2020-08-27 +idempotency-key: f4fa2bc6-d8d4-4866-9693-0e8fdf549f45 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=twint&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..8226d8d4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0003_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2xgvyjjWplepS9 +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0004_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0004_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..cbe44e06 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0004_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cDarE3zmXzcdeb +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0005_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0005_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..440bcd37 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0005_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RW21fg7r96IpuB +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..f5ab0e4a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0006_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0mOlbUpxlTpwDN +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0007_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0007_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..6ce529cb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0007_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_W2PCYYiMK2ykRb +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail new file mode 100644 index 00000000..4461a529 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0008_get_v1_payment_intents_pi_3PiTJgGoesj9fw9Q12D8WmHA.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJgGoesj9fw9Q12D8WmHA\?client_secret=pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aXjKJfICxLUrhP +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcY12UBMPohTMJfyFEQYxD5uPA5kVz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJhGoesj9fw9QonuypDBt", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396909, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJgGoesj9fw9Q12D8WmHA_secret_oRfcLmQBPS9vcYoNTtwVG1IZ2", + "id" : "pi_3PiTJgGoesj9fw9Q12D8WmHA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396908, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..af7a9f49 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6J4IyRr2z8f0KK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 446 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:17 GMT +original-request: req_6J4IyRr2z8f0KK +stripe-version: 2020-08-27 +idempotency-key: 1fa477a6-fc38-4c39-a95e-53964f248df4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=twint + +{ + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..7d90bfe6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Y4sNNlmIBmChCt1jprsyjyxWkCbHM25e5m4gVGxKpO5t5Ux3y0ALpmgeyliHmzce5kiHLmO7UQLFw%2B6zM0YUrCK%2BlXxTCAAkP8vxymhInGQA95LP7P%2Feg44We1kBLO2WFpbuP44xJ5TRk%2FfKvoyO4kp1l%2ByHrIyRTrjJ92iOMOLXRNz5x%2Fn8EsQ1ydKiP7kPKP6jOVHIvleXj4iOR48rIN1fUypO6KNSUf4vfLuvNYs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 81b616b30a3807091b1037810aaf7f58 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:35:17 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJpGoesj9fw9Q1H4oM4xc","secret":"pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..17ddb720 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0011_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,59 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zaLMIK6al9rIxM +Content-Length: 791 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : null, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0012_post_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0012_post_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc_confirm.tail new file mode 100644 index 00000000..14024edd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0012_post_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc_confirm.tail @@ -0,0 +1,93 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yrFKmVarSiveVK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:18 GMT +original-request: req_yrFKmVarSiveVK +stripe-version: 2020-08-27 +idempotency-key: 203914cc-c083-45e5-a6fd-2ca86c4864ef +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand\[0]=payment_method&payment_method=pm_1PiTJpGoesj9fw9Ql5Z6HWKI&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0013_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0013_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..aeaa0172 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0013_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BXW6E9Fkx3tb4E +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..1be42eff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0014_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kA0WEF1gWRNJpx +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0015_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0015_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..bb4e1c2b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0015_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yUYEzmvi8tKT9p +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:21 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..e80cd19c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0016_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lv63NMxIaf1xQ8 +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0017_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0017_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..03d0aea2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0017_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uNkLMvqVWcw8Vt +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0018_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0018_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail new file mode 100644 index 00000000..1844e93d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0018_get_v1_payment_intents_pi_3PiTJpGoesj9fw9Q1H4oM4xc.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJpGoesj9fw9Q1H4oM4xc\?client_secret=pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FOQ2PiTsJcNQct +Content-Length: 1491 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcYVdYKWfap9jpvYMmqA2wadshX5lF" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJpGoesj9fw9Ql5Z6HWKI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396917, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc_secret_Lo3t0TojNyiiKe42lS1mscP3G", + "id" : "pi_3PiTJpGoesj9fw9Q1H4oM4xc", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396917, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0019_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0019_post_v1_payment_methods.tail new file mode 100644 index 00000000..ce667c18 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0019_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MgiYOfqSg1kHUG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 446 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:25 GMT +original-request: req_MgiYOfqSg1kHUG +stripe-version: 2020-08-27 +idempotency-key: c9f5fdfa-a7c9-4525-8683-7b8377ce1836 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=twint + +{ + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0020_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0020_post_create_payment_intent.tail new file mode 100644 index 00000000..e5599588 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0020_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=TCpFc%2B1NGh8bU%2FyHrLCprJpsDeb2UjMby7Q9D0xhQ%2BiraWKRReRsQYQsp64tV9lP0KLVX8dvxlrgdbe%2FC%2FUZIGrLChBq4GX0YgmbhRhdfGvEtBxSzyLkP26H%2BLk1BNTLtvjid6MBtUUp2SVphBNlXHKQjAYyf0yvCHuVrx7p67u2%2FQAIsG4F0MAfyDpQh7O1qeBM0UcCxHu7eeL1g67Eo6F0lWVpay8BqVt0JSfcUm4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2656eeff5230d5b2e0337635807da781;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:35:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTJxGoesj9fw9Q0idzhAO7","secret":"pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..b018a06a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0021_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EjzJlZ9sHSzLi8 +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0022_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0022_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..db450de1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0022_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TLB9nB4WOKkMmb +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0023_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0023_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..8003164b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0023_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yN5cRHx91gMrs6 +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..0b526d87 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0024_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wUSVhyH6GsF8ew +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..052559e7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0025_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_b0pqS1DmcggGwi +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0026_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0026_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..3c839bea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0026_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_l8u7EdFmNcqOfD +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0027_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0027_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail new file mode 100644 index 00000000..b27c59aa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testTwintConfirmFlows/0027_get_v1_payment_intents_pi_3PiTJxGoesj9fw9Q0idzhAO7.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTJxGoesj9fw9Q0idzhAO7\?client_secret=pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aHjw6FFqefh3JT +Content-Length: 1485 +Vary: Origin +Date: Wed, 31 Jul 2024 03:35:33 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "chf", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1KmkHbGoesj9fw9Q\/pa_nonce_QZcZmIo4mi6bESwu3EPwLpgPzm0bFSz" + } + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiTJxGoesj9fw9Q644kwhnT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "twint" : { + + }, + "created" : 1722396925, + "allow_redisplay" : "unspecified", + "type" : "twint", + "customer" : null + }, + "client_secret" : "pi_3PiTJxGoesj9fw9Q0idzhAO7_secret_PoJp30eA8936veEOn8TQAR3W0", + "id" : "pi_3PiTJxGoesj9fw9Q0idzhAO7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "twint" + ], + "setup_future_usage" : null, + "created" : 1722396925, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..e902a07d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=w8PkCs9InDL1PB9FbPTrNJ2YVhEuX0dD%2B5r0g5c%2BE1l%2FBFMvVfAiWXAB04bXsosVhGxBGJqd5SRDY9Lv9aOhtS5LqS%2Bg0QdbPw42iHtfgWDgpDpxAFkQf724BDZ2Inp53ZMed%2B8EeNjnLZBWCGZ3DC4BxCYDKZg7Mmv27ee%2BIpkjqIocXiwmkcOG1hlFBs7zJPKX%2B5CitbJ3gH3G1FYW6RL3L0brNGYqxrsDuypQy70%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: adfa57f789e1c8ac9f36bb0c8295804c +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:35 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHDFY0qyl6XeW14CLJ6C8","secret":"pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8.tail new file mode 100644 index 00000000..8ea60ca8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0001_get_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHDFY0qyl6XeW14CLJ6C8\?client_secret=pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lC8ReZEdUUITBD +Content-Length: 890 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm", + "id" : "pi_3PiTHDFY0qyl6XeW14CLJ6C8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396755, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8_confirm.tail new file mode 100644 index 00000000..779b12d0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0002_post_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8_confirm.tail @@ -0,0 +1,99 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHDFY0qyl6XeW14CLJ6C8\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_V195QlfnxJz8eG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1646 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:36 GMT +original-request: req_V195QlfnxJz8eG +stripe-version: 2020-08-27 +idempotency-key: 24f7f8b8-9e6a-4229-a2fc-35ec46b8174a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[ideal%5Bbank%5D]=abn_amro&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=ideal&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWj43Jn5khoiEuZscUnQo9hHmw1Di" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHEFY0qyl6XeWrpRSN8n5", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396756, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm", + "id" : "pi_3PiTHDFY0qyl6XeW14CLJ6C8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396755, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8.tail new file mode 100644 index 00000000..c696f237 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0003_get_v1_payment_intents_pi_3PiTHDFY0qyl6XeW14CLJ6C8.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHDFY0qyl6XeW14CLJ6C8\?client_secret=pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zWIDVTubehWgrz +Content-Length: 1646 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:36 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWj43Jn5khoiEuZscUnQo9hHmw1Di" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHEFY0qyl6XeWrpRSN8n5", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396756, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHDFY0qyl6XeW14CLJ6C8_secret_VbMNyEzLwGxeIeaQGPiG79rDm", + "id" : "pi_3PiTHDFY0qyl6XeW14CLJ6C8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396755, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0004_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0004_post_v1_payment_methods.tail new file mode 100644 index 00000000..79b977e4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0004_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zYu0eIFXGhXhLm +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 496 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:37 GMT +original-request: req_zYu0eIFXGhXhLm +stripe-version: 2020-08-27 +idempotency-key: 2be20e74-c2a7-4541-8b43-7191ab96060e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Foo&ideal%5Bbank%5D=abn_amro&payment_user_agent=.*&type=ideal + +{ + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHFFY0qyl6XeWIbT9XsBc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396757, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0005_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0005_post_create_payment_intent.tail new file mode 100644 index 00000000..366f11e8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0005_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=DZaYZjW07IRSvFPB48tVFfo292zOrCSATPD6TzxolT9kwmPTxm%2B3QtVJtEGGvdZF2xB9v9%2FA3A7LM%2FtDmySdbNFt91%2BGMUjIxPWfHfdCKpYHkShVMuLHSBTuiCEPB9cKmVbB1Seg6bD58higELBlD7JEC1enYHJzDbZBOWWGKGUyy%2Bk28Voy2guTuR0rHMbbWhE2fCEDBRJPHoZ%2BiH6Ywb7lv4v%2FfzoWC25swgQbras%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b8e9a522dc7ce60d1273876e9da21f53 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHFFY0qyl6XeW0ZB08CTk","secret":"pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk.tail new file mode 100644 index 00000000..a36b817d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0006_get_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHFFY0qyl6XeW0ZB08CTk\?client_secret=pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Y34uKsKt1qoKo5 +Content-Length: 890 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:38 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x", + "id" : "pi_3PiTHFFY0qyl6XeW0ZB08CTk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396757, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk_confirm.tail new file mode 100644 index 00000000..90f5d165 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0007_post_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk_confirm.tail @@ -0,0 +1,99 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHFFY0qyl6XeW0ZB08CTk\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vDOuV9w9ETFcUf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1646 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:38 GMT +original-request: req_vDOuV9w9ETFcUf +stripe-version: 2020-08-27 +idempotency-key: 3191c47d-03f7-4608-886f-ddbb019dad46 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTHFFY0qyl6XeWIbT9XsBc&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWXYCWeSE8X1P2gk5oB8aViH5mWPw" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHFFY0qyl6XeWIbT9XsBc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396757, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x", + "id" : "pi_3PiTHFFY0qyl6XeW0ZB08CTk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396757, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk.tail new file mode 100644 index 00000000..444f5cf6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0008_get_v1_payment_intents_pi_3PiTHFFY0qyl6XeW0ZB08CTk.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHFFY0qyl6XeW0ZB08CTk\?client_secret=pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rc8FS6fXTVhzYP +Content-Length: 1646 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:39 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWXYCWeSE8X1P2gk5oB8aViH5mWPw" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHFFY0qyl6XeWIbT9XsBc", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396757, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHFFY0qyl6XeW0ZB08CTk_secret_G4NBq8ffnGM3aIkH90u8ot25x", + "id" : "pi_3PiTHFFY0qyl6XeW0ZB08CTk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396757, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0009_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0009_post_v1_payment_methods.tail new file mode 100644 index 00000000..a20d6687 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0009_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CGo2aKcEFmRJGO +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 496 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:39 GMT +original-request: req_CGo2aKcEFmRJGO +stripe-version: 2020-08-27 +idempotency-key: e256fd81-a849-469a-b0be-538687168b88 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Foo&ideal%5Bbank%5D=abn_amro&payment_user_agent=.*&type=ideal + +{ + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHHFY0qyl6XeWtLMy8hhC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396759, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0010_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0010_post_create_payment_intent.tail new file mode 100644 index 00000000..e9687bac --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0010_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=hBy53tSg8BkJeq8obMdeqHTXbntS8Z4L0xXMCwgR6Ibevw3%2FA6UxLtOCi%2FlAWaGMXhSjRIn5%2BUacXbsZfg1PiqU8Qi5ceadAUa4Fvw1zUIsyAqUfdc77ld5HmUp6qYw4or9QLtbkEYhYh4JQcbB5TEq%2FmzlB2vFQ9bF7Q4nlI%2FF4%2B9g9nNN%2FnEdlRlZcnIUc2urFRXyRAN4FKdCjkno5fA5cYnyAe8QuFLdUEvv9mWc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: bc1a890f46596d83643c5bf33b10408a;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHHFY0qyl6XeW1b0SliHE","secret":"pi_3PiTHHFY0qyl6XeW1b0SliHE_secret_TKTE8bOtqwO0yAekh3lUZJJ1N","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHHFY0qyl6XeW1b0SliHE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHHFY0qyl6XeW1b0SliHE.tail new file mode 100644 index 00000000..226ec541 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0011_get_v1_payment_intents_pi_3PiTHHFY0qyl6XeW1b0SliHE.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHHFY0qyl6XeW1b0SliHE\?client_secret=pi_3PiTHHFY0qyl6XeW1b0SliHE_secret_TKTE8bOtqwO0yAekh3lUZJJ1N&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UIXFAxkhZqFXeJ +Content-Length: 1640 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWAsexqQn1Nwkzgbv0PcDZNnogKYB" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHHFY0qyl6XeWtLMy8hhC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396759, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHHFY0qyl6XeW1b0SliHE_secret_TKTE8bOtqwO0yAekh3lUZJJ1N", + "id" : "pi_3PiTHHFY0qyl6XeW1b0SliHE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396759, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHHFY0qyl6XeW1b0SliHE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHHFY0qyl6XeW1b0SliHE.tail new file mode 100644 index 00000000..841ed98b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0012_get_v1_payment_intents_pi_3PiTHHFY0qyl6XeW1b0SliHE.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHHFY0qyl6XeW1b0SliHE\?client_secret=pi_3PiTHHFY0qyl6XeW1b0SliHE_secret_TKTE8bOtqwO0yAekh3lUZJJ1N&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gcFiLpcpZX7Wgj +Content-Length: 1640 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWAsexqQn1Nwkzgbv0PcDZNnogKYB" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHHFY0qyl6XeWtLMy8hhC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396759, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHHFY0qyl6XeW1b0SliHE_secret_TKTE8bOtqwO0yAekh3lUZJJ1N", + "id" : "pi_3PiTHHFY0qyl6XeW1b0SliHE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : null, + "created" : 1722396759, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0013_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0013_post_create_payment_intent.tail new file mode 100644 index 00000000..4a519c0c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0013_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=DBhvnEDyQIO1835tnBJKvL%2BvJDbDnOwKxR9AKytX2oLdAV3vkoPwLWAQuNyLEm6HGbpIuDSYBaTQDoObVs1HenGVuSWIE8W1rcfp4xZRmKLmn0luKw9SzhYGX5MgO0QSR5Q6OuenF3zSW%2BICQKAaq7ZxhRSBOmiYdTRMsDreSuQznnVD2hikmnMDe1Snj4k2ZWXUbG29L732hq7kn62k1SiRwNZ0xVw8I7YAC6hyWsI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c37283496536745629e0776ce6130aab +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:41 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHJFY0qyl6XeW1R1AS68e","secret":"pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0014_get_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0014_get_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e.tail new file mode 100644 index 00000000..9bb81fd7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0014_get_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHJFY0qyl6XeW1R1AS68e\?client_secret=pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lO0NDMU06HZYFL +Content-Length: 899 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb", + "id" : "pi_3PiTHJFY0qyl6XeW1R1AS68e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396761, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0015_post_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0015_post_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e_confirm.tail new file mode 100644 index 00000000..c8897b41 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0015_post_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e_confirm.tail @@ -0,0 +1,99 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHJFY0qyl6XeW1R1AS68e\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tVruNi8EcMq9W8 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1658 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:42 GMT +original-request: req_tVruNi8EcMq9W8 +stripe-version: 2020-08-27 +idempotency-key: 60a6f087-99ec-4893-a053-7415deb34138 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[ideal%5Bbank%5D]=abn_amro&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=ideal&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWusHqD4sfpLGFfjV4uOcWRLo2KE5" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHJFY0qyl6XeWmYoieong", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396761, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb", + "id" : "pi_3PiTHJFY0qyl6XeW1R1AS68e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396761, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0016_get_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0016_get_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e.tail new file mode 100644 index 00000000..2086e4ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0016_get_v1_payment_intents_pi_3PiTHJFY0qyl6XeW1R1AS68e.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHJFY0qyl6XeW1R1AS68e\?client_secret=pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hdTrU8ljAp0Mt4 +Content-Length: 1658 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:42 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWusHqD4sfpLGFfjV4uOcWRLo2KE5" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHJFY0qyl6XeWmYoieong", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396761, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHJFY0qyl6XeW1R1AS68e_secret_sR2FMyypFXq8GwbdoDLJo4Chb", + "id" : "pi_3PiTHJFY0qyl6XeW1R1AS68e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396761, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0017_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0017_post_v1_payment_methods.tail new file mode 100644 index 00000000..dede8c63 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0017_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9VIRxZseMDf1ch +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 499 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:43 GMT +original-request: req_9VIRxZseMDf1ch +stripe-version: 2020-08-27 +idempotency-key: 4940b399-0c92-4b73-86b5-bfe8ee40adbf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&ideal%5Bbank%5D=abn_amro&payment_user_agent=.*&type=ideal + +{ + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHLFY0qyl6XeWFIEYMxCp", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396763, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0018_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0018_post_create_payment_intent.tail new file mode 100644 index 00000000..ef3ff964 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0018_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=CXa8wyoa%2BPQxdC3M4hcHAsDnnms%2FyDX9ewXawr5vriRlIYYtP%2FHc%2B8572DoqLtkEkOh%2F8hRxzWU780IQ1JU5Gwuv%2Fq7jf5HSzC4bp4otQZia2RP1sG2qhOI4TIsQTkGzsLi%2FG3202ZUgtr8G1EYNWGs%2FgU1yUnLVV1BtXg1XLRvUdeV170Z%2FiLJF9siQETauYnV%2FWAynTH9ku042c%2FXpEo8K1YUxYpv57l587gCTFMU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: cf50a73efaeb1a2902b843d4f17053b4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHLFY0qyl6XeW1i6upz8S","secret":"pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0019_get_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0019_get_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S.tail new file mode 100644 index 00000000..3a833fe6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0019_get_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHLFY0qyl6XeW1i6upz8S\?client_secret=pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AEvPFEhNZu0Jxx +Content-Length: 899 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I", + "id" : "pi_3PiTHLFY0qyl6XeW1i6upz8S", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396763, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0020_post_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0020_post_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S_confirm.tail new file mode 100644 index 00000000..23d604ca --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0020_post_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S_confirm.tail @@ -0,0 +1,99 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHLFY0qyl6XeW1i6upz8S\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yuHmmoNJsAmcDk +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1658 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:44 GMT +original-request: req_yuHmmoNJsAmcDk +stripe-version: 2020-08-27 +idempotency-key: 7ad559e1-1e1d-43c2-8026-632efd8fcfea +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTHLFY0qyl6XeWFIEYMxCp&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcW4uEy1gMPJIOS0TUaV4tMNZn8PaD" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHLFY0qyl6XeWFIEYMxCp", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396763, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I", + "id" : "pi_3PiTHLFY0qyl6XeW1i6upz8S", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396763, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0021_get_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0021_get_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S.tail new file mode 100644 index 00000000..096742a0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0021_get_v1_payment_intents_pi_3PiTHLFY0qyl6XeW1i6upz8S.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHLFY0qyl6XeW1i6upz8S\?client_secret=pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_o9ohV5UGUtv4gs +Content-Length: 1658 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcW4uEy1gMPJIOS0TUaV4tMNZn8PaD" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHLFY0qyl6XeWFIEYMxCp", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396763, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHLFY0qyl6XeW1i6upz8S_secret_qdG8i5W845eD2qjL4PwOde24I", + "id" : "pi_3PiTHLFY0qyl6XeW1i6upz8S", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396763, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0022_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0022_post_v1_payment_methods.tail new file mode 100644 index 00000000..1ab04d0e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0022_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FT0PxcmI4FdG5s +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 499 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:45 GMT +original-request: req_FT0PxcmI4FdG5s +stripe-version: 2020-08-27 +idempotency-key: d029f7a6-a3de-41b7-ad05-1201609dac20 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&ideal%5Bbank%5D=abn_amro&payment_user_agent=.*&type=ideal + +{ + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHNFY0qyl6XeWWROnGV8d", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396765, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0023_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0023_post_create_payment_intent.tail new file mode 100644 index 00000000..290d3a13 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0023_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YFnlWSXUHLU5%2Fq1jkEW%2FLaCfHBZ8ciIWj6YkZjGFXTIRmghBmyxlaqZhZESbUGEtW0UWZAf%2BtWoTGmbXH8wUrrhkFVjyHeyfffzgaeGEhGpEpjfpBRHaCsxGJY8TROfSi5UnJ%2F2zsuZUVXleK9goNKwy2z%2FfAKUMeC0RQnbxb7DJ001bG3OP3AX2snLEvy0hp1CPrxNvjs6QNLbREWfFgKyxHyjKF%2BD8%2FfrT7XZMqsI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: c75ec7480f4f4982dca2d877540979e3 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:45 GMT +x-robots-tag: noindex, nofollow +Content-Length: 139 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiTHNFY0qyl6XeW0ry3hAKO","secret":"pi_3PiTHNFY0qyl6XeW0ry3hAKO_secret_48FiEl93guWoMEolMDud2RdxQ","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0024_get_v1_payment_intents_pi_3PiTHNFY0qyl6XeW0ry3hAKO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0024_get_v1_payment_intents_pi_3PiTHNFY0qyl6XeW0ry3hAKO.tail new file mode 100644 index 00000000..c834c3a0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0024_get_v1_payment_intents_pi_3PiTHNFY0qyl6XeW0ry3hAKO.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHNFY0qyl6XeW0ry3hAKO\?client_secret=pi_3PiTHNFY0qyl6XeW0ry3hAKO_secret_48FiEl93guWoMEolMDud2RdxQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_blVejKRL4TOxaj +Content-Length: 1652 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWrmJEkamk5M3aS6y51H63zX4PlSI" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHNFY0qyl6XeWWROnGV8d", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396765, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHNFY0qyl6XeW0ry3hAKO_secret_48FiEl93guWoMEolMDud2RdxQ", + "id" : "pi_3PiTHNFY0qyl6XeW0ry3hAKO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396765, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0025_get_v1_payment_intents_pi_3PiTHNFY0qyl6XeW0ry3hAKO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0025_get_v1_payment_intents_pi_3PiTHNFY0qyl6XeW0ry3hAKO.tail new file mode 100644 index 00000000..a9736412 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0025_get_v1_payment_intents_pi_3PiTHNFY0qyl6XeW0ry3hAKO.tail @@ -0,0 +1,95 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiTHNFY0qyl6XeW0ry3hAKO\?client_secret=pi_3PiTHNFY0qyl6XeW0ry3hAKO_secret_48FiEl93guWoMEolMDud2RdxQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jNoO0ZARcU3XlP +Content-Length: 1652 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QZcWrmJEkamk5M3aS6y51H63zX4PlSI" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHNFY0qyl6XeWWROnGV8d", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396765, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "client_secret" : "pi_3PiTHNFY0qyl6XeW0ry3hAKO_secret_48FiEl93guWoMEolMDud2RdxQ", + "id" : "pi_3PiTHNFY0qyl6XeW0ry3hAKO", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal" + ], + "setup_future_usage" : "off_session", + "created" : 1722396765, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0026_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0026_post_create_setup_intent.tail new file mode 100644 index 00000000..951c642f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0026_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=GHeZz85nJGLLld%2F2yRAFvR06uqkv9d1d82yUgoCJrUAbmP3ZOYoZ7vlu8ptqOHdGrWxvOwdQ6d3GHVmtw6iw%2FXpD1GfFX49aR1HBLnhv2n7q9xynkvobaxKzlSlLjv9FM8SJy81%2B9hZC1UO1VJXB4zS3flgixcpNGP6DMXEc2DCt22V%2BD7i%2BeH01fPGrjm0w379vEObkyf1sOHQn6qfsZ7fzyVilBJJ8H7hWnwXe6RI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9fc1df383d2282d5845f897edd2d4cb1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:46 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTHOFY0qyl6XeWs8SFGyfb","secret":"seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0027_get_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0027_get_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb.tail new file mode 100644 index 00000000..6237c15a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0027_get_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHOFY0qyl6XeWs8SFGyfb\?client_secret=seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UBxflmacyCHfOe +Content-Length: 534 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHOFY0qyl6XeWs8SFGyfb", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396766, + "client_secret" : "seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0028_post_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0028_post_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb_confirm.tail new file mode 100644 index 00000000..322f82b3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0028_post_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb_confirm.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHOFY0qyl6XeWs8SFGyfb\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nJc7xRgQvPYU1Z +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1293 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:47 GMT +original-request: req_nJc7xRgQvPYU1Z +stripe-version: 2020-08-27 +idempotency-key: 694b4511-ae8e-44a9-b3a8-5c761f6ac9de +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=f%40z\.c&payment_method_data\[billing_details]\[name]=Foo&payment_method_data\[ideal%5Bbank%5D]=abn_amro&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=ideal&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTHOFY0qyl6XeWs8SFGyfb", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWQaw86g3e4AlUl8M75gR8170Z0On" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHPFY0qyl6XeWJLrl3tkk", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396767, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396766, + "client_secret" : "seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0029_get_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0029_get_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb.tail new file mode 100644 index 00000000..12cc123d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0029_get_v1_setup_intents_seti_1PiTHOFY0qyl6XeWs8SFGyfb.tail @@ -0,0 +1,76 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHOFY0qyl6XeWs8SFGyfb\?client_secret=seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UCWvCblbAORVjD +Content-Length: 1293 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHOFY0qyl6XeWs8SFGyfb", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWQaw86g3e4AlUl8M75gR8170Z0On" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHPFY0qyl6XeWJLrl3tkk", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396767, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396766, + "client_secret" : "seti_1PiTHOFY0qyl6XeWs8SFGyfb_secret_QZcWI2RFPfcuLv44H5CEJqA9vsVuMMC", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0030_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0030_post_v1_payment_methods.tail new file mode 100644 index 00000000..1f3878a1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0030_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ttZGP5xUFaIf0W +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 499 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:48 GMT +original-request: req_ttZGP5xUFaIf0W +stripe-version: 2020-08-27 +idempotency-key: 6fe99306-1cee-415a-aa84-afefc7a27a45 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&ideal%5Bbank%5D=abn_amro&payment_user_agent=.*&type=ideal + +{ + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHQFY0qyl6XeWp3l6XJwd", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396768, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0031_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0031_post_create_setup_intent.tail new file mode 100644 index 00000000..8e86a3f8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0031_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YLb0tEVKsBPmJ%2BbKXKWARgcrGgT9swy580gie7%2BM8CFJr4UqhnLPZ0KY85nkXA9zT2vJ40oD%2Bdu8E6rKC9V3Pa2QRh7jiTWDMcObuIx%2BUZTKUyNYSHAA%2BCR3uoHyo8GKraFngsWj26dXWfr3ZG4PSA81U5c4bWdi3pEQHTGyjcNmdxFRJ5ehSb0c9oH7cGcRid52hPuG5%2FCX0CgffYPZNV6cYUzIt61J401BZpq0tmU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 622d3932d7dfc82622975eb776171027 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:48 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTHQFY0qyl6XeWFjQpkQ40","secret":"seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0032_get_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0032_get_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40.tail new file mode 100644 index 00000000..50714de4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0032_get_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHQFY0qyl6XeWFjQpkQ40\?client_secret=seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_QC3xaZsV9VNHuj +Content-Length: 534 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHQFY0qyl6XeWFjQpkQ40", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396768, + "client_secret" : "seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0033_post_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0033_post_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40_confirm.tail new file mode 100644 index 00000000..4f9bbd85 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0033_post_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40_confirm.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHQFY0qyl6XeWFjQpkQ40\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2wAsC5KZ1e6ZEp +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1293 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:49 GMT +original-request: req_2wAsC5KZ1e6ZEp +stripe-version: 2020-08-27 +idempotency-key: 0b288fa1-df37-4fab-bf24-7adf6ae719c4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method=pm_1PiTHQFY0qyl6XeWp3l6XJwd&return_url=https%3A\/\/foo\.com&use_stripe_sdk=true + +{ + "id" : "seti_1PiTHQFY0qyl6XeWFjQpkQ40", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWVPS2SwCC2eEoJxCRmIkkbkfSJju" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHQFY0qyl6XeWp3l6XJwd", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396768, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396768, + "client_secret" : "seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0034_get_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0034_get_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40.tail new file mode 100644 index 00000000..79d1c0fc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0034_get_v1_setup_intents_seti_1PiTHQFY0qyl6XeWFjQpkQ40.tail @@ -0,0 +1,76 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHQFY0qyl6XeWFjQpkQ40\?client_secret=seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hCMmTcSlAMWCRc +Content-Length: 1293 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHQFY0qyl6XeWFjQpkQ40", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "https:\/\/foo.com", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWVPS2SwCC2eEoJxCRmIkkbkfSJju" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHQFY0qyl6XeWp3l6XJwd", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396768, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396768, + "client_secret" : "seti_1PiTHQFY0qyl6XeWFjQpkQ40_secret_QZcWxb7AAjgeDR0Af1lVcoR9dftt9us", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0035_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0035_post_v1_payment_methods.tail new file mode 100644 index 00000000..bbf2dca2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0035_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EOUMYJY43vwg96 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 499 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:50 GMT +original-request: req_EOUMYJY43vwg96 +stripe-version: 2020-08-27 +idempotency-key: dfb79dd9-8934-420c-854e-193b80d97528 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=f%40z\.c&billing_details\[name]=Foo&ideal%5Bbank%5D=abn_amro&payment_user_agent=.*&type=ideal + +{ + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHSFY0qyl6XeWi8G3e9GC", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396770, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0036_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0036_post_create_setup_intent.tail new file mode 100644 index 00000000..09e96424 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0036_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=a5QYRlDwnY8T2XWSvWut9JuQ1m%2BsB0lJB5bCsZRVAqlcz0X8rtBq05%2F9mUH1NJRaDXYmdkpuGYyAbCpIv4dhbUCh71pk6EUM9wAxiACafKpNDuBor3RT0i0hpRzg62hKb64pZHznKx%2F4%2FjsFw0VyjEpkaI64sPiB2a6C9cL7wr16bwpgvGygMeMOgPEBjbDLacN%2B5uAKd7pHAmSF8apT%2BvEtBl1nwxysKExgBYXkd%2Bc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 7b57e80ab37366640c4dea764c4cf44b;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 03:32:50 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiTHSFY0qyl6XeWUxtVchWu","secret":"seti_1PiTHSFY0qyl6XeWUxtVchWu_secret_QZcW3Lext8IgUEoAGQ8MwHUTATxQFzF","status":"requires_action"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0037_get_v1_setup_intents_seti_1PiTHSFY0qyl6XeWUxtVchWu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0037_get_v1_setup_intents_seti_1PiTHSFY0qyl6XeWUxtVchWu.tail new file mode 100644 index 00000000..5a3642b8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0037_get_v1_setup_intents_seti_1PiTHSFY0qyl6XeWUxtVchWu.tail @@ -0,0 +1,76 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHSFY0qyl6XeWUxtVchWu\?client_secret=seti_1PiTHSFY0qyl6XeWUxtVchWu_secret_QZcW3Lext8IgUEoAGQ8MwHUTATxQFzF&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7wbXGLJZdG54zF +Content-Length: 1287 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHSFY0qyl6XeWUxtVchWu", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWPpiS4tglL43iJep68Vc6tRo35S8" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHSFY0qyl6XeWi8G3e9GC", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396770, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396770, + "client_secret" : "seti_1PiTHSFY0qyl6XeWUxtVchWu_secret_QZcW3Lext8IgUEoAGQ8MwHUTATxQFzF", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0038_get_v1_setup_intents_seti_1PiTHSFY0qyl6XeWUxtVchWu.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0038_get_v1_setup_intents_seti_1PiTHSFY0qyl6XeWUxtVchWu.tail new file mode 100644 index 00000000..10148222 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLPMConfirmFlowTests/testiDEALConfirmFlows/0038_get_v1_setup_intents_seti_1PiTHSFY0qyl6XeWUxtVchWu.tail @@ -0,0 +1,76 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiTHSFY0qyl6XeWUxtVchWu\?client_secret=seti_1PiTHSFY0qyl6XeWUxtVchWu_secret_QZcW3Lext8IgUEoAGQ8MwHUTATxQFzF&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_W56ocCbPXNeeey +Content-Length: 1287 +Vary: Origin +Date: Wed, 31 Jul 2024 03:32:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiTHSFY0qyl6XeWUxtVchWu", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "foo:\/\/bar", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/sa_nonce_QZcWPpiS4tglL43iJep68Vc6tRo35S8" + } + }, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "ideal" : { + "bic" : "ABNANL2A", + "bank" : "abn_amro" + }, + "id" : "pm_1PiTHSFY0qyl6XeWi8G3e9GC", + "billing_details" : { + "email" : "f@z.c", + "phone" : null, + "name" : "Foo", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722396770, + "allow_redisplay" : "unspecified", + "type" : "ideal", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722396770, + "client_secret" : "seti_1PiTHSFY0qyl6XeWUxtVchWu_secret_QZcW3Lext8IgUEoAGQ8MwHUTATxQFzF", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testLoadPerformance/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testLoadPerformance/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..459d93fc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testLoadPerformance/0000_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1050&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_PSE13a3ixAJeXp +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1TyygySr5EQ", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "73c64612-a333-42d7-94c6-0ba7dddd5832", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "amazon_pay", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..9bf4c270 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0000_get_v1_elements_sessions.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=FOO&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +400 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_CgjZ6eiuhhVWn6 +Content-Length: 1031 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "error" : { + "param" : "deferred_intent[currency]", + "message" : "Invalid currency: foo. Stripe currently supports these currencies: usd, aed, afn, all, amd, ang, aoa, ars, aud, awg, azn, bam, bbd, bdt, bgn, bhd, bif, bmd, bnd, bob, brl, bsd, bwp, byn, bzd, cad, cdf, chf, clp, cny, cop, crc, cve, czk, djf, dkk, dop, dzd, egp, etb, eur, fjd, fkp, gbp, gel, gip, gmd, gnf, gtq, gyd, hkd, hnl, hrk, htg, huf, idr, ils, inr, isk, jmd, jod, jpy, kes, kgs, khr, kmf, krw, kwd, kyd, kzt, lak, lbp, lkr, lrd, lsl, mad, mdl, mga, mkd, mmk, mnt, mop, mur, mvr, mwk, mxn, myr, mzn, nad, ngn, nio, nok, npr, nzd, omr, pab, pen, pgk, php, pkr, pln, pyg, qar, ron, rsd, rub, rwf, sar, sbd, scr, sek, sgd, shp, sle, sos, srd, std, szl, thb, tjs, tnd, top, try, ttd, twd, tzs, uah, ugx, uyu, uzs, vnd, vuv, wst, xaf, xcd, xof, xpf, yer, zar, zmw, usdc, btn, ghs, eek, lvl, svc, vef, ltl, sll, mro", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_CgjZ6eiuhhVWn6?t=1729100064" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..eeb77b81 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0001_get_v1_elements_sessions.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bon_behalf_of%5D=foo&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +400 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_aM56S0czNxIkKg +Content-Length: 349 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "error" : { + "code" : "resource_missing", + "message" : "No such on_behalf_of: 'foo'", + "param" : "deferred_intent[on_behalf_of]", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_aM56S0czNxIkKg?t=1729100064", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..228ba894 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentFails/0002_get_v1_elements_sessions.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bpayment_method_types%5D%5B1%5D=foo&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +400 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_4j6CeGVi0fogAz +Content-Length: 301 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "error" : { + "param" : "deferred_intent.payment_method_types", + "message" : "The payment method type `foo` is not supported when creating a SetupIntent.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_4j6CeGVi0fogAz?t=1729100064" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0000_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0000_get_v1_elements_sessions.tail new file mode 100644 index 00000000..d2a98770 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0000_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_GzH5ssmover5vO +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1Pcrngze1tc", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "d347e645-4d46-4478-8bf5-556db115e5bf", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "google_pay", + "amazon_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..725012ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0001_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_MJqJdSSz2NWLky +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1l2YhY7nHvu", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "385853b5-3f88-48e2-ae84-1a86ad9d5cdf", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..3ed4e488 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0002_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_HyK1AQKPSPIVJY +Content-Length: 14471 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "amazon_pay", + "cashapp", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1jCNFlZ1nBL", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "50353176-dac2-437f-aca5-ac1371ac15c1", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "amazon_pay", + "cashapp", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "amazon_pay", + "cashapp", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0003_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0003_get_v1_elements_sessions.tail new file mode 100644 index 00000000..67867814 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0003_get_v1_elements_sessions.tail @@ -0,0 +1,321 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bcurrency%5D=USD&deferred_intent%5Bmode%5D=setup&deferred_intent%5Bpayment_method_types%5D%5B0%5D=card&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_An3o9zkQN8Edso +Content-Length: 13002 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1SCAdY1V8KD", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "3b88839f-34e6-49a6-a43c-a97004fbed03", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0004_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0004_get_v1_elements_sessions.tail new file mode 100644 index 00000000..ae82fe7f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDeferredIntentSucceeds/0004_get_v1_elements_sessions.tail @@ -0,0 +1,368 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bmode%5D=setup&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_myos7Xvw2Fae33 +Content-Length: 14470 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:26 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_19j8VkY2org", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "2b83335d-46dc-46af-b617-bf0ec582d13f", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "amazon_pay", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..f6eaaec3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=UJr8DT9oHKxsBN22L2TFhSBsfZm14ntqz9WnGZbqHpr1DsIAs0qlsQFyV8RqpUvleLIzybkVxUxB840phZtkIAplnMclWDlMWVoH7bcRU5NhwQtUclGI3qPRkifa9O7OBZmMnGVcssOn0snX2YwopkRohwKMy3CXxi5UNIFaoa6LDUZadHEd3gseazSDf8wGcEnbClFdGEOY4rfrkVbC2YA3OINao1z3%2FKppP4sVyT4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b2e6eee15e87d4ee48855d4c4f098f53;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLGhvdkExd3NBSGVadDE3NDhna2hSQTNOdVNTa2FRWTE_00cm1tH4Lm","customer":"cus_OtOGvD0ZVacBoj"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0001_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0001_get_v1_payment_methods.tail new file mode 100644 index 00000000..e37d6f8e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0001_get_v1_payment_methods.tail @@ -0,0 +1,135 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&type=card$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PbZqL53QTGQdvN +Content-Length: 2583 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1O5bTlIq2LmpyICoB8eZH4BJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Apple Pay", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "Oyster Point", + "line1" : "1", + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698357158, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + }, + { + "object" : "payment_method", + "id" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Not Apple Pay", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 4, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698431542, + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..b5ca5e20 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0002_get_v1_elements_sessions.tail @@ -0,0 +1,333 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=JPY&deferred_intent%5Bmode%5D=payment&key=pk_test_51NpIYRIq2LmpyICoBLPaTxfWFW4I34pnWuBjKXf8CgOlVih7Ni6oDfPRHGTzBEnpsrHiPvqP2UyydilqY66BWp8N00mQCJ1PU5&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_Tt6a7D0B9WVICa +Content-Length: 13282 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "link", + "konbini" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : true, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : false, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : true, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1wPxHJfv6xe", + "account_id" : "acct_1NpIYRIq2LmpyICo", + "merchant_id" : "acct_1NpIYRIq2LmpyICo", + "merchant_currency" : "jpy", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : true + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "JP", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "9edf73f6-5f76-4113-ada8-f7d7c51b44b6", + "experiment_metadata" : { + "seed" : "23368c6f2627fa144d05c85f5dab7b791803b78dbe1b0022f12af00d455a96d0" + }, + "experiment_assignments" : { + "default_values_prefill_holdback" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control", + "link_popup_webview_option_ios" : "control", + "elements_merchant_ui_api_srv" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : true, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : { + "bank_account_permissions" : [ + + ], + "bank_account_verification_method" : null + }, + "link_consumer_incentive" : null, + "link_default_opt_in" : "FULL", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + "CARD" + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "includes_link_in_payment_method_types" + ] + }, + "link_supported_payment_methods" : [ + "CARD" + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : true, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : true, + "link_payment_element_enable_webauthn_login" : true, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : true, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : true, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : "LINK_PAYMENT_METHOD", + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "link", + "konbini" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1NpIYRIq2LmpyICo\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "Mobile Japan Test Merchant", + "ordered_payment_method_types_and_wallets" : [ + "card", + "link", + "apple_pay", + "konbini", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0003_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0003_get_v1_payment_methods.tail new file mode 100644 index 00000000..9914e423 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0003_get_v1_payment_methods.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=us_bank_account$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HaKyaHYnFVO1xg +Content-Length: 89 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail new file mode 100644 index 00000000..f7efde71 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/customers\/cus_OtOGvD0ZVacBoj\?$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fcustomers%2F%3Acustomer; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=accounts-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=accounts-bapi-srv"}],"include_subdomains":true} +request-id: req_FCIUBHQLoIZjBK +Content-Length: 215 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "customer", + "id" : "cus_OtOGvD0ZVacBoj", + "livemode" : false, + "created" : 1698357141, + "email" : null, + "default_source" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "description" : null, + "shipping" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..106d0d07 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0005_get_v1_payment_methods.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=sepa_debit$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_o6BK5TpR1E1urY +Content-Length: 89 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0006_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0006_get_v1_payment_methods.tail new file mode 100644 index 00000000..8560488c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadDoesNotFilterSavedApplePayCardsWhenDisabled/0006_get_v1_payment_methods.tail @@ -0,0 +1,135 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=card$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LFrLmFuEOuzabe +Content-Length: 2583 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1O5bTlIq2LmpyICoB8eZH4BJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Apple Pay", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "Oyster Point", + "line1" : "1", + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698357158, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + }, + { + "object" : "payment_method", + "id" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Not Apple Pay", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 4, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698431542, + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..48fe5573 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=mGo3nof4GlqEcAADYuHA2QPYcD1Myt%2FgPmxZzQ6pwIvkuHW6Zo%2FjKqkhWsz4vcpNSaQgotlmNSyC3q9HyDK8pcP8EXuLkzkvkEt8w%2Fr9Tqdz0yWAaSQsn0nJeXllyabe76q7kJYB5RlfVEMtQR89F4gXgnQh87%2F6PeP3UamjadBaoNd4ogr%2FwiRM9gp%2FnNXbsk9M5v0CtGdlVrboXswCSKrBf%2FTfIqVBY%2BgGUORF0SM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 34ae114f63811378a970058f7c3b0d42;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 10 Oct 2024 14:40:39 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLEV3NTVIQnJaZEpHSVQ3ZnpZYjVBa3JNcWlaSkxCT1E_00Axfn9W7S","customer":"cus_OtOGvD0ZVacBoj"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0001_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0001_get_v1_payment_methods.tail new file mode 100644 index 00000000..5027d14f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0001_get_v1_payment_methods.tail @@ -0,0 +1,135 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9yi73qj9p9lIKZ +Content-Length: 2583 +Vary: Origin +Date: Thu, 10 Oct 2024 14:40:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1O5bTlIq2LmpyICoB8eZH4BJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Apple Pay", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "Oyster Point", + "line1" : "1", + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698357158, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + }, + { + "object" : "payment_method", + "id" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Not Apple Pay", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 4, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698431542, + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..1a9f93d0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0002_get_v1_elements_sessions.tail @@ -0,0 +1,332 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=JPY&deferred_intent%5Bmode%5D=payment&key=pk_test_51NpIYRIq2LmpyICoBLPaTxfWFW4I34pnWuBjKXf8CgOlVih7Ni6oDfPRHGTzBEnpsrHiPvqP2UyydilqY66BWp8N00mQCJ1PU5&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_3kCqmKIPCJMySG +Content-Length: 13217 +Vary: Origin +Date: Thu, 10 Oct 2024 14:40:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "link", + "konbini" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : true, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : false, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : true, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1L3AWkCPaVX", + "account_id" : "acct_1NpIYRIq2LmpyICo", + "merchant_id" : "acct_1NpIYRIq2LmpyICo", + "merchant_currency" : "jpy", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : true + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "JP", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "a1cfedea-781c-4277-b295-449dbd243aeb", + "experiment_metadata" : { + "seed" : "530e80543638e9c96ec5901d23cd77ca9e374988f0f8b03a5a98369a01a977b9" + }, + "experiment_assignments" : { + "default_values_prefill_holdback" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control", + "link_popup_webview_option_ios" : "control", + "elements_merchant_ui_api_srv" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : true, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : { + "bank_account_permissions" : [ + + ], + "bank_account_verification_method" : null + }, + "link_consumer_incentive" : null, + "link_default_opt_in" : "FULL", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + "CARD" + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "includes_link_in_payment_method_types" + ] + }, + "link_supported_payment_methods" : [ + "CARD" + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : true, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : true, + "link_payment_element_enable_webauthn_login" : true, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : true, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : true, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : "LINK_PAYMENT_METHOD", + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "link", + "konbini" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1NpIYRIq2LmpyICo\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "Mobile Japan Test Merchant", + "ordered_payment_method_types_and_wallets" : [ + "card", + "link", + "apple_pay", + "konbini", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0003_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0003_get_v1_payment_methods.tail new file mode 100644 index 00000000..cf81d36c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0003_get_v1_payment_methods.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=us_bank_account$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DcF5tazfLJhXtd +Content-Length: 89 +Vary: Origin +Date: Thu, 10 Oct 2024 14:40:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail new file mode 100644 index 00000000..962d0a40 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/customers\/cus_OtOGvD0ZVacBoj\?$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fcustomers%2F%3Acustomer; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=accounts-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=accounts-bapi-srv"}],"include_subdomains":true} +request-id: req_twSniONksApMan +Content-Length: 215 +Vary: Origin +Date: Thu, 10 Oct 2024 14:40:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "customer", + "id" : "cus_OtOGvD0ZVacBoj", + "livemode" : false, + "created" : 1698357141, + "email" : null, + "default_source" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "description" : null, + "shipping" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..4ea1d251 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0005_get_v1_payment_methods.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=sepa_debit$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_anZsixpVwqH7GP +Content-Length: 89 +Vary: Origin +Date: Thu, 10 Oct 2024 14:40:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0006_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0006_get_v1_payment_methods.tail new file mode 100644 index 00000000..13116de3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersCardBrandAcceptance/0006_get_v1_payment_methods.tail @@ -0,0 +1,135 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dPTAWPgMPqXk1f +Content-Length: 2583 +Vary: Origin +Date: Thu, 10 Oct 2024 14:40:41 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1O5bTlIq2LmpyICoB8eZH4BJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Apple Pay", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "Oyster Point", + "line1" : "1", + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698357158, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + }, + { + "object" : "payment_method", + "id" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Not Apple Pay", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 4, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698431542, + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..f6eaaec3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=UJr8DT9oHKxsBN22L2TFhSBsfZm14ntqz9WnGZbqHpr1DsIAs0qlsQFyV8RqpUvleLIzybkVxUxB840phZtkIAplnMclWDlMWVoH7bcRU5NhwQtUclGI3qPRkifa9O7OBZmMnGVcssOn0snX2YwopkRohwKMy3CXxi5UNIFaoa6LDUZadHEd3gseazSDf8wGcEnbClFdGEOY4rfrkVbC2YA3OINao1z3%2FKppP4sVyT4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b2e6eee15e87d4ee48855d4c4f098f53;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xTnBJWVJJcTJMbXB5SUNvLGhvdkExd3NBSGVadDE3NDhna2hSQTNOdVNTa2FRWTE_00cm1tH4Lm","customer":"cus_OtOGvD0ZVacBoj"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0001_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0001_get_v1_payment_methods.tail new file mode 100644 index 00000000..e37d6f8e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0001_get_v1_payment_methods.tail @@ -0,0 +1,135 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&type=card$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PbZqL53QTGQdvN +Content-Length: 2583 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1O5bTlIq2LmpyICoB8eZH4BJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Apple Pay", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "Oyster Point", + "line1" : "1", + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698357158, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + }, + { + "object" : "payment_method", + "id" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Not Apple Pay", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 4, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698431542, + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0002_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0002_get_v1_elements_sessions.tail new file mode 100644 index 00000000..b5ca5e20 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0002_get_v1_elements_sessions.tail @@ -0,0 +1,333 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?deferred_intent%5Bamount%5D=1000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=JPY&deferred_intent%5Bmode%5D=payment&key=pk_test_51NpIYRIq2LmpyICoBLPaTxfWFW4I34pnWuBjKXf8CgOlVih7Ni6oDfPRHGTzBEnpsrHiPvqP2UyydilqY66BWp8N00mQCJ1PU5&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_Tt6a7D0B9WVICa +Content-Length: 13282 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "link", + "konbini" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : true, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : false, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : true, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1wPxHJfv6xe", + "account_id" : "acct_1NpIYRIq2LmpyICo", + "merchant_id" : "acct_1NpIYRIq2LmpyICo", + "merchant_currency" : "jpy", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : true + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "JP", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "9edf73f6-5f76-4113-ada8-f7d7c51b44b6", + "experiment_metadata" : { + "seed" : "23368c6f2627fa144d05c85f5dab7b791803b78dbe1b0022f12af00d455a96d0" + }, + "experiment_assignments" : { + "default_values_prefill_holdback" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control", + "link_popup_webview_option_ios" : "control", + "elements_merchant_ui_api_srv" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : true, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : { + "bank_account_permissions" : [ + + ], + "bank_account_verification_method" : null + }, + "link_consumer_incentive" : null, + "link_default_opt_in" : "FULL", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + "CARD" + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "includes_link_in_payment_method_types" + ] + }, + "link_supported_payment_methods" : [ + "CARD" + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : true, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : true, + "link_payment_element_enable_webauthn_login" : true, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : true, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : true, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : "LINK_PAYMENT_METHOD", + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "link", + "konbini" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1NpIYRIq2LmpyICo\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "Mobile Japan Test Merchant", + "ordered_payment_method_types_and_wallets" : [ + "card", + "link", + "apple_pay", + "konbini", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0003_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0003_get_v1_payment_methods.tail new file mode 100644 index 00000000..9914e423 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0003_get_v1_payment_methods.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=us_bank_account$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HaKyaHYnFVO1xg +Content-Length: 89 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail new file mode 100644 index 00000000..f7efde71 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0004_get_v1_customers_cus_OtOGvD0ZVacBoj.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/customers\/cus_OtOGvD0ZVacBoj\?$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fcustomers%2F%3Acustomer; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=accounts-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=accounts-bapi-srv"}],"include_subdomains":true} +request-id: req_FCIUBHQLoIZjBK +Content-Length: 215 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "customer", + "id" : "cus_OtOGvD0ZVacBoj", + "livemode" : false, + "created" : 1698357141, + "email" : null, + "default_source" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "description" : null, + "shipping" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0005_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0005_get_v1_payment_methods.tail new file mode 100644 index 00000000..106d0d07 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0005_get_v1_payment_methods.tail @@ -0,0 +1,35 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=sepa_debit$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_o6BK5TpR1E1urY +Content-Length: 89 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:27 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0006_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0006_get_v1_payment_methods.tail new file mode 100644 index 00000000..8560488c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadFiltersSavedApplePayCards/0006_get_v1_payment_methods.tail @@ -0,0 +1,135 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_OtOGvD0ZVacBoj&limit=100&type=card$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LFrLmFuEOuzabe +Content-Length: 2583 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1O5bTlIq2LmpyICoB8eZH4BJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Apple Pay", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "Oyster Point", + "line1" : "1", + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698357158, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + }, + { + "object" : "payment_method", + "id" : "card_1O5upWIq2LmpyICo9tQmU9xY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Not Apple Pay", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "H5ytsQoN2pwNyAbE", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 4, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1698431542, + "type" : "card", + "customer" : "cus_OtOGvD0ZVacBoj" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithExternalPaymentMethods/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithExternalPaymentMethods/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..ff41062d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithExternalPaymentMethods/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ttwEBvwDsnvWR3%2FzFUymb6GTB58leHUeunSFDLVmCo7PzjCfF20bBm5RuBwfWn2e4cYFz%2F88zgDsowuPzlCW3T%2B3fbuuI2o7dj%2B8DNdeSYAHETfvzGOkR2yDl2qOCqlqr%2BgPbScQ%2BFIFj%2BExMTFAhZBuDGnZB5971rzuHp4SDuTN4avJSQIPD8kVxlNeAZ6IbraOk3Milgt22loQ9aNgmibKqt3OXM03mZrwiflRGe8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 2148c4d4a2cc1aa3290f703f54328c66 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAb7AFY0qyl6XeW1zo94MY9","secret":"pi_3QAb7AFY0qyl6XeW1zo94MY9_secret_sQ0ty6Ft8hiKEujcxG1iH7HRx","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithExternalPaymentMethods/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithExternalPaymentMethods/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..ff2edc2a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithExternalPaymentMethods/0001_get_v1_elements_sessions.tail @@ -0,0 +1,600 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=pi_3QAb7AFY0qyl6XeW1zo94MY9_secret_sQ0ty6Ft8hiKEujcxG1iH7HRx&expand%5B0%5D=payment_method_preference\.payment_intent\.payment_method&external_payment_methods%5B0%5D=external_paypal&locale=en-US&type=payment_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_4noL90DWdX9a7o +Content-Length: 20703 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:28 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3QAb7AFY0qyl6XeW1zo94MY9_secret_sQ0ty6Ft8hiKEujcxG1iH7HRx", + "id" : "pi_3QAb7AFY0qyl6XeW1zo94MY9", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "setup_future_usage" : null, + "created" : 1729100068, + "description" : null + }, + "ordered_payment_method_types" : [ + "card", + "sofort", + "bancontact", + "ideal" + ], + "type" : "payment_intent" + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_0gVoIkkJc61", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : [ + { + "light_image_url" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-paypal@3x-5227ab4fca3d36846bd6622f495cdf4b.png", + "label" : "PayPal", + "dark_image_url" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-paypal_dark@3x-26040e151c8f87187da2f997791fcc31.png", + "type" : "external_paypal" + } + ], + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "181a5ad3-dbe1-4b3b-99e4-9805ef39701b", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact@3x-5b31be92d86c437286200810aeaa49ce.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact-c6d62da104212dacefee6ea12a070237.svg" + }, + "type" : "bancontact", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "items" : [ + { + "display_text" : "ABN Amro", + "api_value" : "abn_amro" + }, + { + "display_text" : "ASN Bank", + "api_value" : "asn_bank" + }, + { + "display_text" : "bunq B.V.", + "api_value" : "bunq" + }, + { + "display_text" : "ING Bank", + "api_value" : "ing" + }, + { + "display_text" : "Knab", + "api_value" : "knab" + }, + { + "display_text" : "N26", + "api_value" : "n26" + }, + { + "display_text" : "Nationale-Nederlanden", + "api_value" : "nn" + }, + { + "display_text" : "Rabobank", + "api_value" : "rabobank" + }, + { + "display_text" : "RegioBank", + "api_value" : "regiobank" + }, + { + "display_text" : "Revolut", + "api_value" : "revolut" + }, + { + "display_text" : "SNS Bank", + "api_value" : "sns_bank" + }, + { + "display_text" : "Triodos Bank", + "api_value" : "triodos_bank" + }, + { + "display_text" : "Van Lanschot", + "api_value" : "van_lanschot" + }, + { + "display_text" : "Yoursafe", + "api_value" : "yoursafe" + } + ], + "type" : "selector", + "translation_id" : "upe.labels.ideal.bank", + "api_path" : { + "v1" : "ideal[bank]" + } + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal@3x-fc5387c2139f55b84777c646208df7ee.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal-608d5ba5730f82c25f122960ccaa9836.svg" + }, + "type" : "ideal", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : true, + "fields" : [ + { + "for" : "name", + "type" : "placeholder" + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "type" : "country", + "api_path" : { + "v1" : "sofort[country]" + }, + "allowed_country_codes" : [ + "AT", + "BE", + "DE", + "ES", + "NL" + ] + }, + { + "for" : "billing_address_without_country", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "dark_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark@3x-1b34ec3a7503b3e20be0a069f8046f29.png", + "dark_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark-ded76aa1c92357f1b78dab85ed5b6347.svg", + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort@3x-8842e382bdf77386d547ff0ef56880c1.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort-c12d29b37f06a2d05dfb22ea9736104b.svg" + }, + "type" : "sofort", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + }, + "processing" : { + "type" : "finished" + } + } + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "sofort", + "bancontact", + "ideal" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay", + "sofort", + "bancontact", + "ideal" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithInvalidExternalPaymentMethods/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithInvalidExternalPaymentMethods/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..0ce368db --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithInvalidExternalPaymentMethods/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=d7ANRi%2B2W%2BZOg3eO7hMAkmh3MB7sHSYSNWPkRBS6tVKTJSY2IngRqOOygE7%2BRLJyxoEmjpGKQY4yOQKZIoP3k6x9dbOd9JXk8iQfDnxvngaV9%2FeMgw2nrpjvNygbe5UXiWJsrxG8TkIXr8AzmNqZjlY7Jxm8OiCpMhTg56xz257pKCwm5Fuc7skJIa8kUh9Uo6eqJ%2FxdQms8GoQsSvaxmVVIPjOtgkPJcPQRKia3q9g%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 76abeb7b08e42274dc5554f7fba66818 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAb7BFY0qyl6XeW1g9IKM2P","secret":"pi_3QAb7BFY0qyl6XeW1g9IKM2P_secret_CGKImSg8jSBmYUIHgqt52EZKL","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithInvalidExternalPaymentMethods/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithInvalidExternalPaymentMethods/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..1a0bd751 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithInvalidExternalPaymentMethods/0001_get_v1_elements_sessions.tail @@ -0,0 +1,595 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=pi_3QAb7BFY0qyl6XeW1g9IKM2P_secret_CGKImSg8jSBmYUIHgqt52EZKL&expand%5B0%5D=payment_method_preference\.payment_intent\.payment_method&external_payment_methods%5B0%5D=external_invalid_value&locale=en-US&type=payment_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_mQPqHy1PMhxW6O +Content-Length: 20343 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3QAb7BFY0qyl6XeW1g9IKM2P_secret_CGKImSg8jSBmYUIHgqt52EZKL", + "id" : "pi_3QAb7BFY0qyl6XeW1g9IKM2P", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "setup_future_usage" : null, + "created" : 1729100069, + "description" : null + }, + "ordered_payment_method_types" : [ + "card", + "sofort", + "bancontact", + "ideal" + ], + "type" : "payment_intent" + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_0ykwuCMveNt", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : [ + + ], + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "bab42a1c-d61c-4d91-90ea-01dc48dd2b6b", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact@3x-5b31be92d86c437286200810aeaa49ce.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact-c6d62da104212dacefee6ea12a070237.svg" + }, + "type" : "bancontact", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "items" : [ + { + "display_text" : "ABN Amro", + "api_value" : "abn_amro" + }, + { + "display_text" : "ASN Bank", + "api_value" : "asn_bank" + }, + { + "display_text" : "bunq B.V.", + "api_value" : "bunq" + }, + { + "display_text" : "ING Bank", + "api_value" : "ing" + }, + { + "display_text" : "Knab", + "api_value" : "knab" + }, + { + "display_text" : "N26", + "api_value" : "n26" + }, + { + "display_text" : "Nationale-Nederlanden", + "api_value" : "nn" + }, + { + "display_text" : "Rabobank", + "api_value" : "rabobank" + }, + { + "display_text" : "RegioBank", + "api_value" : "regiobank" + }, + { + "display_text" : "Revolut", + "api_value" : "revolut" + }, + { + "display_text" : "SNS Bank", + "api_value" : "sns_bank" + }, + { + "display_text" : "Triodos Bank", + "api_value" : "triodos_bank" + }, + { + "display_text" : "Van Lanschot", + "api_value" : "van_lanschot" + }, + { + "display_text" : "Yoursafe", + "api_value" : "yoursafe" + } + ], + "type" : "selector", + "translation_id" : "upe.labels.ideal.bank", + "api_path" : { + "v1" : "ideal[bank]" + } + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal@3x-fc5387c2139f55b84777c646208df7ee.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal-608d5ba5730f82c25f122960ccaa9836.svg" + }, + "type" : "ideal", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : true, + "fields" : [ + { + "for" : "name", + "type" : "placeholder" + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "type" : "country", + "api_path" : { + "v1" : "sofort[country]" + }, + "allowed_country_codes" : [ + "AT", + "BE", + "DE", + "ES", + "NL" + ] + }, + { + "for" : "billing_address_without_country", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "dark_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark@3x-1b34ec3a7503b3e20be0a069f8046f29.png", + "dark_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark-ded76aa1c92357f1b78dab85ed5b6347.svg", + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort@3x-8842e382bdf77386d547ff0ef56880c1.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort-c12d29b37f06a2d05dfb22ea9736104b.svg" + }, + "type" : "sofort", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + }, + "processing" : { + "type" : "finished" + } + } + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "sofort", + "bancontact", + "ideal" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay", + "sofort", + "bancontact", + "ideal" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..a970b919 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=FWUPvspc%2Ft8Q83QJcOvdKD1wDWstz6bMbeqy3Ac8xfauxEg3%2Bm5ug3Tsc%2Bq8c2Vl%2FLRE20WZ%2Fs68EsHKQ2u2lypoZP0tuNvs60nkjdkSYi6Hwn5uQ1Tdos9uzJsu2JIz6r%2B%2BPnIgG5Q%2FawMtzNDmMibpW%2FeqdnULdiGk1AiB6fPS2xmUQ4TyzbWfsjtzW99LE86JYhXBZMAK%2F6y104w1Elsn2YsBY%2BkCOBaPBWa9GS4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: decb11384ed632c6d3271f8bd2917bba +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QAb7CFY0qyl6XeW1Ecb8vhU","secret":"pi_3QAb7CFY0qyl6XeW1Ecb8vhU_secret_A2DtXDoYAcVYpe7tXWgUuV0zW","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithPaymentIntent/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithPaymentIntent/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..1c745b95 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithPaymentIntent/0001_get_v1_elements_sessions.tail @@ -0,0 +1,593 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=pi_3QAb7CFY0qyl6XeW1Ecb8vhU_secret_A2DtXDoYAcVYpe7tXWgUuV0zW&expand%5B0%5D=payment_method_preference\.payment_intent\.payment_method&locale=en-US&type=payment_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_bUCYaZV0VI9jyD +Content-Length: 20345 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:30 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5050, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 5050, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3QAb7CFY0qyl6XeW1Ecb8vhU_secret_A2DtXDoYAcVYpe7tXWgUuV0zW", + "id" : "pi_3QAb7CFY0qyl6XeW1Ecb8vhU", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "setup_future_usage" : null, + "created" : 1729100070, + "description" : null + }, + "ordered_payment_method_types" : [ + "card", + "sofort", + "bancontact", + "ideal" + ], + "type" : "payment_intent" + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_05pJGmsjZ37", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "b5556915-f78f-4531-942b-6e595022907c", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact@3x-5b31be92d86c437286200810aeaa49ce.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact-c6d62da104212dacefee6ea12a070237.svg" + }, + "type" : "bancontact", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "items" : [ + { + "display_text" : "ABN Amro", + "api_value" : "abn_amro" + }, + { + "display_text" : "ASN Bank", + "api_value" : "asn_bank" + }, + { + "display_text" : "bunq B.V.", + "api_value" : "bunq" + }, + { + "display_text" : "ING Bank", + "api_value" : "ing" + }, + { + "display_text" : "Knab", + "api_value" : "knab" + }, + { + "display_text" : "N26", + "api_value" : "n26" + }, + { + "display_text" : "Nationale-Nederlanden", + "api_value" : "nn" + }, + { + "display_text" : "Rabobank", + "api_value" : "rabobank" + }, + { + "display_text" : "RegioBank", + "api_value" : "regiobank" + }, + { + "display_text" : "Revolut", + "api_value" : "revolut" + }, + { + "display_text" : "SNS Bank", + "api_value" : "sns_bank" + }, + { + "display_text" : "Triodos Bank", + "api_value" : "triodos_bank" + }, + { + "display_text" : "Van Lanschot", + "api_value" : "van_lanschot" + }, + { + "display_text" : "Yoursafe", + "api_value" : "yoursafe" + } + ], + "type" : "selector", + "translation_id" : "upe.labels.ideal.bank", + "api_path" : { + "v1" : "ideal[bank]" + } + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal@3x-fc5387c2139f55b84777c646208df7ee.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal-608d5ba5730f82c25f122960ccaa9836.svg" + }, + "type" : "ideal", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : true, + "fields" : [ + { + "for" : "name", + "type" : "placeholder" + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "type" : "country", + "api_path" : { + "v1" : "sofort[country]" + }, + "allowed_country_codes" : [ + "AT", + "BE", + "DE", + "ES", + "NL" + ] + }, + { + "for" : "billing_address_without_country", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "dark_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark@3x-1b34ec3a7503b3e20be0a069f8046f29.png", + "dark_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark-ded76aa1c92357f1b78dab85ed5b6347.svg", + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort@3x-8842e382bdf77386d547ff0ef56880c1.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort-c12d29b37f06a2d05dfb22ea9736104b.svg" + }, + "type" : "sofort", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + }, + "processing" : { + "type" : "finished" + } + } + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "sofort", + "bancontact", + "ideal" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay", + "sofort", + "bancontact", + "ideal" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..9dde05c4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=VHQVLhGFq8i99PTBt3jTRo0tDm4ARkv3kdURBtB%2BGcDJZ6epv7M7vvqasrDdLkWXlzxy74mRTUoq1x9CveTwRJ17f6UIVMoJiX81lASnzEpGUKYug%2FryPu4Zq0vSkAWTCsoXrNgE0UrwNuPZ2IOn%2FM1PU2bM7hEZ8jVpQr3Lb%2FXq2%2FK5FA5UzHXVWkmWcYEr1qrFa8hWqQ5TOfMvLJ3Qm3sj8ezkdy3WwO1kG%2FfgZyY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4c877e8d28b7c540d76d70fc4c60782a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:31 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1QAb7DFY0qyl6XeWrlxYQA5I","secret":"seti_1QAb7DFY0qyl6XeWrlxYQA5I_secret_R2gUgnXj4w3TFUfDkaI9KerWI8LF9Fh","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntent/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntent/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..0b742fc7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntent/0001_get_v1_elements_sessions.tail @@ -0,0 +1,574 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=seti_1QAb7DFY0qyl6XeWrlxYQA5I_secret_R2gUgnXj4w3TFUfDkaI9KerWI8LF9Fh&expand%5B0%5D=payment_method_preference\.setup_intent\.payment_method&locale=en-US&type=setup_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_E9RgmKsPwYdG9N +Content-Length: 19917 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:32 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "setup_intent" : { + "id" : "seti_1QAb7DFY0qyl6XeWrlxYQA5I", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "ideal", + "card", + "bancontact", + "sofort" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1729100071, + "client_secret" : "seti_1QAb7DFY0qyl6XeWrlxYQA5I_secret_R2gUgnXj4w3TFUfDkaI9KerWI8LF9Fh", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" + }, + "type" : "setup_intent", + "ordered_payment_method_types" : [ + "card", + "sofort", + "bancontact", + "ideal" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_08OknHWmZZ0", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "2cf07b64-c5cb-46f8-83c8-4653439fc5a9", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact@3x-5b31be92d86c437286200810aeaa49ce.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-bancontact-c6d62da104212dacefee6ea12a070237.svg" + }, + "type" : "bancontact", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + { + "type" : "name", + "api_path" : { + "v1" : "billing_details[name]" + } + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "items" : [ + { + "display_text" : "ABN Amro", + "api_value" : "abn_amro" + }, + { + "display_text" : "ASN Bank", + "api_value" : "asn_bank" + }, + { + "display_text" : "bunq B.V.", + "api_value" : "bunq" + }, + { + "display_text" : "ING Bank", + "api_value" : "ing" + }, + { + "display_text" : "Knab", + "api_value" : "knab" + }, + { + "display_text" : "N26", + "api_value" : "n26" + }, + { + "display_text" : "Nationale-Nederlanden", + "api_value" : "nn" + }, + { + "display_text" : "Rabobank", + "api_value" : "rabobank" + }, + { + "display_text" : "RegioBank", + "api_value" : "regiobank" + }, + { + "display_text" : "Revolut", + "api_value" : "revolut" + }, + { + "display_text" : "SNS Bank", + "api_value" : "sns_bank" + }, + { + "display_text" : "Triodos Bank", + "api_value" : "triodos_bank" + }, + { + "display_text" : "Van Lanschot", + "api_value" : "van_lanschot" + }, + { + "display_text" : "Yoursafe", + "api_value" : "yoursafe" + } + ], + "type" : "selector", + "translation_id" : "upe.labels.ideal.bank", + "api_path" : { + "v1" : "ideal[bank]" + } + }, + { + "for" : "billing_address", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal@3x-fc5387c2139f55b84777c646208df7ee.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-ideal-608d5ba5730f82c25f122960ccaa9836.svg" + }, + "type" : "ideal", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : true, + "fields" : [ + { + "for" : "name", + "type" : "placeholder" + }, + { + "for" : "email", + "type" : "placeholder" + }, + { + "for" : "phone", + "type" : "placeholder" + }, + { + "type" : "country", + "api_path" : { + "v1" : "sofort[country]" + }, + "allowed_country_codes" : [ + "AT", + "BE", + "DE", + "ES", + "NL" + ] + }, + { + "for" : "billing_address_without_country", + "type" : "placeholder" + }, + { + "for" : "sepa_mandate", + "type" : "placeholder" + } + ], + "selector_icon" : { + "dark_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark@3x-1b34ec3a7503b3e20be0a069f8046f29.png", + "dark_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort_dark-ded76aa1c92357f1b78dab85ed5b6347.svg", + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort@3x-8842e382bdf77386d547ff0ef56880c1.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-sofort-c12d29b37f06a2d05dfb22ea9736104b.svg" + }, + "type" : "sofort", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + }, + "processing" : { + "type" : "finished" + } + } + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "sofort", + "bancontact", + "ideal" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay", + "sofort", + "bancontact", + "ideal" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntentAttachedPaymentMethod/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntentAttachedPaymentMethod/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..43107568 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntentAttachedPaymentMethod/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=71Tk8VCBZ3tx0hCncrJ1eVXyeFBF1Po5%2Fpo%2BKkgtTIsjNzS%2FDwy219G8UAQfNkGW2N9zrFCtKUvXLSEZh3GSMvpuk6z1Z1%2BJYr4E9Ny4HmuZljKqfkdDxosj3nQt619dmF2UxISmOfEW%2BKALTJE6KbSnSyUDUaJjBzc32EBkmS%2FKCCvhAd%2FZrT%2FPenkjGIhacgdOWV242GS41OEx%2BbgqAxwF%2Fnmytl52e17GO3paZ7c%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 467dfcb903a94dacfbbd6245acf6f42a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 16 Oct 2024 17:34:31 GMT +x-robots-tag: noindex, nofollow +Content-Length: 155 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1QAb7DFY0qyl6XeWzZOdSzrX","secret":"seti_1QAb7DFY0qyl6XeWzZOdSzrX_secret_R2gUjsRM2ZFgTNkJ4Fr4WHY8tJnSYne","status":"requires_confirmation"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntentAttachedPaymentMethod/0001_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntentAttachedPaymentMethod/0001_get_v1_elements_sessions.tail new file mode 100644 index 00000000..6ce39ffc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/PaymentSheetLoaderTest/testPaymentSheetLoadWithSetupIntentAttachedPaymentMethod/0001_get_v1_elements_sessions.tail @@ -0,0 +1,386 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_secret=seti_1QAb7DFY0qyl6XeWzZOdSzrX_secret_R2gUjsRM2ZFgTNkJ4Fr4WHY8tJnSYne&expand%5B0%5D=payment_method_preference\.setup_intent\.payment_method&locale=en-US&type=setup_intent$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_Sw3RwIw5NA0q9a +Content-Length: 14827 +Vary: Origin +Date: Wed, 16 Oct 2024 17:34:31 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "setup_intent" : { + "id" : "seti_1QAb7DFY0qyl6XeWzZOdSzrX", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1QAb7DFY0qyl6XeWMj9d4Jak", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 10, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1729100071, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1729100071, + "client_secret" : "seti_1QAb7DFY0qyl6XeWzZOdSzrX_secret_R2gUjsRM2ZFgTNkJ4Fr4WHY8tJnSYne", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_confirmation" + }, + "type" : "setup_intent", + "ordered_payment_method_types" : [ + "card" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : true, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : true, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "elements_disable_link_global_holdback_lookup" : false, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "payment_element_link_modal_preload_killswitch" : false, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "elements_enable_cb_apple_pay_for_connect_platforms" : true, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_link_global_holdback_rollout" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_link_in_ece_personalization" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "disable_payment_element_if_required_billing_config" : false, + "link_set_active_field_true" : true, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_0qcnbOpPRTO", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : true + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "346a098c-0c61-4149-a74b-ce7bae7a2893", + "experiment_metadata" : { + "seed" : "f141acec06c73601adc1c212d38a020b85a1cdd62a3e46835373aeaf676a9090" + }, + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_consumer_incentive" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_supported_payment_methods" : [ + + ], + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : null, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "google_pay" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0000_post_v1_tokens.tail new file mode 100644 index 00000000..3200bbbd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_H0qDz4cRTzG7kg +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:08 GMT +original-request: req_H0qDz4cRTzG7kg +stripe-version: 2020-08-27 +idempotency-key: e46aa1dc-cc09-44df-8331-294d740f4537 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRySFY0qyl6XeW3HUywJ6W", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRySFY0qyl6XeWCNcnQSs3", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391748, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..d17d1083 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HNbkw87qbktsNL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:08 GMT +original-request: req_HNbkw87qbktsNL +stripe-version: 2020-08-27 +idempotency-key: 0c1f67df-f933-4e2f-aaa9-2a260e91baf5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRySFY0qyl6XeW9za6ffvm", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391748, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0002_get_v1_payment_intents_pi_bad.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0002_get_v1_payment_intents_pi_bad.tail new file mode 100644 index 00000000..83112a2d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadPaymentIntentClientSecretErrors/0002_get_v1_payment_intents_pi_bad.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_bad\?client_secret=pi_bad_secret_1234$ +404 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vbyy8E9zdrUT5D +Content-Length: 331 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "resource_missing", + "message" : "No such payment_intent: 'pi_bad'", + "param" : "intent", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_vbyy8E9zdrUT5D?t=1722391748", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0000_post_v1_tokens.tail new file mode 100644 index 00000000..4ca3e529 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_W7pxFlJ9Foy8BQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:09 GMT +original-request: req_W7pxFlJ9Foy8BQ +stripe-version: 2020-08-27 +idempotency-key: a414040d-63ec-4e67-9007-cbcd39785983 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyTFY0qyl6XeWoKudMJla", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyTFY0qyl6XeWR1qgeyno", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391749, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..7cb83774 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PkmJVo5vydS14C +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:09 GMT +original-request: req_PkmJVo5vydS14C +stripe-version: 2020-08-27 +idempotency-key: 0e74e242-b018-4615-a13e-4b20f11e96bf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyTFY0qyl6XeWMoy1OJcR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391749, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0002_get_v1_setup_intents_seti_bad.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0002_get_v1_setup_intents_seti_bad.tail new file mode 100644 index 00000000..085fcb90 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testBadSetupIntentClientSecretErrors/0002_get_v1_setup_intents_seti_bad.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_bad\?client_secret=seti_bad_secret_1234$ +404 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_UwTlRVAcrUV64w +Content-Length: 330 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "resource_missing", + "message" : "No such setupintent: 'seti_bad'", + "param" : "intent", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_UwTlRVAcrUV64w?t=1722391749", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0000_post_v1_tokens.tail new file mode 100644 index 00000000..a00de86c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xYLQSeW8GAeTKI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:10 GMT +original-request: req_xYLQSeW8GAeTKI +stripe-version: 2020-08-27 +idempotency-key: ee2d22a0-856c-4c60-b5fb-191015f6205a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyUFY0qyl6XeWoYfzmimY", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyTFY0qyl6XeWwXqJS6a4", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391750, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..e289616d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PAAKIcplPdcmMM +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:10 GMT +original-request: req_PAAKIcplPdcmMM +stripe-version: 2020-08-27 +idempotency-key: 8a61c62b-d79c-42d7-ad9a-0a13b9c128dd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyUFY0qyl6XeWDEMFemtF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391750, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..e3b3b319 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=X2%2BHtam5cC0G%2FqozqBB%2BGyhROQ3XzXKfuH4yGjSadTIuICOV2%2FkCyzJ1M8sY%2FHRP3c4C%2B40vLKP%2FbbZqlcHjfx6548GLLhAGi03357nUy7kc7OtVshwOEPcTY0GnNlgCa7%2BePBSlj8Ouwx5AmXWWOSGTx6hw05yTKwt7ZtgONOMgBXQ6FrQz6wmJtCjN%2BwiKTpG9EQynhF37sNgjLhFP8wPRBBLNk7yRmwl1Ym6mTwo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b89387271325f6539ac9780a1fed2723;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRyUFY0qyl6XeW1tWilBBA","secret":"pi_3PiRyUFY0qyl6XeW1tWilBBA_secret_mOhNDc3tzh1QTf2z2sffnxR7u","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0003_get_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0003_get_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA.tail new file mode 100644 index 00000000..c7355f05 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0003_get_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyUFY0qyl6XeW1tWilBBA\?client_secret=pi_3PiRyUFY0qyl6XeW1tWilBBA_secret_mOhNDc3tzh1QTf2z2sffnxR7u$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AaZSfYRGRAdydS +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiRyUFY0qyl6XeW1tWilBBA_secret_mOhNDc3tzh1QTf2z2sffnxR7u", + "id" : "pi_3PiRyUFY0qyl6XeW1tWilBBA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391750, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0004_post_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0004_post_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA_confirm.tail new file mode 100644 index 00000000..e11616cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0004_post_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA_confirm.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyUFY0qyl6XeW1tWilBBA\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RM8tST5XlXwKEn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1152 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:12 GMT +original-request: req_RM8tST5XlXwKEn +stripe-version: 2020-08-27 +idempotency-key: 959db098-0ccf-429a-8053-86fef87473ab +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "", + "country" : "", + "line2" : null, + "city" : "", + "line1" : "510 Townsend St", + "postal_code" : "" + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRyUFY0qyl6XeWDEMFemtF", + "client_secret" : "pi_3PiRyUFY0qyl6XeW1tWilBBA_secret_mOhNDc3tzh1QTf2z2sffnxR7u", + "id" : "pi_3PiRyUFY0qyl6XeW1tWilBBA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391750, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0005_get_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0005_get_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA.tail new file mode 100644 index 00000000..bf078b23 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterPaymentIntentConfirmsStillSucceeds/0005_get_v1_payment_intents_pi_3PiRyUFY0qyl6XeW1tWilBBA.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyUFY0qyl6XeW1tWilBBA\?client_secret=pi_3PiRyUFY0qyl6XeW1tWilBBA_secret_mOhNDc3tzh1QTf2z2sffnxR7u$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jwTCGz5NqnZnNa +Content-Length: 1152 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "", + "country" : "", + "line2" : null, + "city" : "", + "line1" : "510 Townsend St", + "postal_code" : "" + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRyUFY0qyl6XeWDEMFemtF", + "client_secret" : "pi_3PiRyUFY0qyl6XeW1tWilBBA_secret_mOhNDc3tzh1QTf2z2sffnxR7u", + "id" : "pi_3PiRyUFY0qyl6XeW1tWilBBA", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391750, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0000_post_v1_tokens.tail new file mode 100644 index 00000000..4b21bbd6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gEHsds7dEGVYW3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:12 GMT +original-request: req_gEHsds7dEGVYW3 +stripe-version: 2020-08-27 +idempotency-key: cf5c9dde-44a6-413a-8805-5c1b1a52519e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyWFY0qyl6XeWNiUqIMi3", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyWFY0qyl6XeWXc4ecxdb", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391752, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..cad5cb1f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EqxdlZpY9InRhZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:13 GMT +original-request: req_EqxdlZpY9InRhZ +stripe-version: 2020-08-27 +idempotency-key: 44204024-e6e2-4e16-981a-b97290823231 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyXFY0qyl6XeWSwczX9vT", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391753, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0002_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0002_post_create_setup_intent.tail new file mode 100644 index 00000000..5315ccdc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0002_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=0Cr2UjMjGtFsv7HQ5NG63zMxeNnl2%2BrRbnM0dHbGtgszYEdWZ7XuAU%2FG8lYTRkKx1FDyxxO4XJaW3ar2UBBZn3IbAqevsSujo1mbaJ2nK8yWlfmxLkcXUU7fmaj8dawJVdPDFAvrHMbHLdGB5hvqIo%2BFZX6j8t6hPUUskVtbbOrTiC2Pgbuod9Nz3KjS7yUM55ixbwTAY%2B6HUG5lvdk5Rg9EVt8UAnYTlkkSjwJ7Ro8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 950675e0005f42d2e3aab5641213ae35 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:13 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiRyXFY0qyl6XeWGpoZl2L6","secret":"seti_1PiRyXFY0qyl6XeWGpoZl2L6_secret_QZbAbDgXmUnpkqK8wuXhjrRlVzCX7XE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0003_get_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0003_get_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6.tail new file mode 100644 index 00000000..4b5f2dfa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0003_get_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRyXFY0qyl6XeWGpoZl2L6\?client_secret=seti_1PiRyXFY0qyl6XeWGpoZl2L6_secret_QZbAbDgXmUnpkqK8wuXhjrRlVzCX7XE$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4KOE2huCEE9zJE +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRyXFY0qyl6XeWGpoZl2L6", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391753, + "client_secret" : "seti_1PiRyXFY0qyl6XeWGpoZl2L6_secret_QZbAbDgXmUnpkqK8wuXhjrRlVzCX7XE", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0004_post_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0004_post_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6_confirm.tail new file mode 100644 index 00000000..95a66dcf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0004_post_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6_confirm.tail @@ -0,0 +1,48 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRyXFY0qyl6XeWGpoZl2L6\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_eWVShBd8uoAUsk +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:14 GMT +original-request: req_eWVShBd8uoAUsk +stripe-version: 2020-08-27 +idempotency-key: c1da7313-6c6a-4a6c-b10d-4d187e654d6d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRyXFY0qyl6XeWGpoZl2L6", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiRyXFY0qyl6XeWSwczX9vT", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391753, + "client_secret" : "seti_1PiRyXFY0qyl6XeWGpoZl2L6_secret_QZbAbDgXmUnpkqK8wuXhjrRlVzCX7XE", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0005_get_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0005_get_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6.tail new file mode 100644 index 00000000..3168e4e9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelAfterSetupIntentConfirmsStillSucceeds/0005_get_v1_setup_intents_seti_1PiRyXFY0qyl6XeWGpoZl2L6.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRyXFY0qyl6XeWGpoZl2L6\?client_secret=seti_1PiRyXFY0qyl6XeWGpoZl2L6_secret_QZbAbDgXmUnpkqK8wuXhjrRlVzCX7XE$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dnithv4PGFWE7Y +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRyXFY0qyl6XeWGpoZl2L6", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiRyXFY0qyl6XeWSwczX9vT", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391753, + "client_secret" : "seti_1PiRyXFY0qyl6XeWGpoZl2L6_secret_QZbAbDgXmUnpkqK8wuXhjrRlVzCX7XE", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelBeforeIntentConfirmsCancels/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelBeforeIntentConfirmsCancels/0000_post_v1_tokens.tail new file mode 100644 index 00000000..7afe7e1d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelBeforeIntentConfirmsCancels/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2Lzzc7Ldjl1t5h +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:15 GMT +original-request: req_2Lzzc7Ldjl1t5h +stripe-version: 2020-08-27 +idempotency-key: 18016a47-b017-4cff-8a96-ae39e43416c6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyZFY0qyl6XeWt6tpq4qJ", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyZFY0qyl6XeWqtyFlV5j", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391755, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelBeforeIntentConfirmsCancels/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelBeforeIntentConfirmsCancels/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..12087368 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCancelBeforeIntentConfirmsCancels/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mcyhqcAVou1A4l +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:15 GMT +original-request: req_mcyhqcAVou1A4l +stripe-version: 2020-08-27 +idempotency-key: 9fccc92d-a330-4c9b-85ed-08f3cfdd0f4b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyZFY0qyl6XeWpMn5ynTZ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391755, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0000_post_v1_tokens.tail new file mode 100644 index 00000000..b141a2e7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_s77EwdL6ywUi5g +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:16 GMT +original-request: req_s77EwdL6ywUi5g +stripe-version: 2020-08-27 +idempotency-key: 603c034f-7c75-4667-bc19-90b40cd5aa34 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyZFY0qyl6XeWh2f4bUhI", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyZFY0qyl6XeWqogJHRv7", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391755, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..9c7d81a6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_VbCwOtmA8IvOAz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:16 GMT +original-request: req_VbCwOtmA8IvOAz +stripe-version: 2020-08-27 +idempotency-key: 65f5ca2e-0d1a-4350-ae47-547cabb1f7c3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyaFY0qyl6XeWpQfJ9ATK", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391756, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..848fd7e0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ceDc4GFMX4pY1j2fd7uZbzJI75NoJEbbhGrF1KLajG9mSgE69i9J2ZlC8fTSwiBi9%2FFpmaLsWpRny%2FIYnBH7OyN%2B%2BuB9Vv3sa2mEmnjCa2PZMYg3H88YL3e5pya93lredxN9ySGhKOnEq0nYSGkVinZD19R2eh%2FY0sUXKUvGStDTD1bV3nDVjQDF8l2O9EhrFamwivtv1nmt2mQCJMbVzVRYiLcvjLZAlmyRCCWu6gg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 99eb35993282f338037bbd9b48ed49c0 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRyaFY0qyl6XeW1FHXpHhE","secret":"pi_3PiRyaFY0qyl6XeW1FHXpHhE_secret_nTrJ4dtYem3pOAKoRvImIiWxf","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0003_get_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0003_get_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE.tail new file mode 100644 index 00000000..f4a82e1c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0003_get_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyaFY0qyl6XeW1FHXpHhE\?client_secret=pi_3PiRyaFY0qyl6XeW1FHXpHhE_secret_nTrJ4dtYem3pOAKoRvImIiWxf$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dnqlX7E22lr9mv +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:16 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiRyaFY0qyl6XeW1FHXpHhE_secret_nTrJ4dtYem3pOAKoRvImIiWxf", + "id" : "pi_3PiRyaFY0qyl6XeW1FHXpHhE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391756, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0004_post_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0004_post_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE_confirm.tail new file mode 100644 index 00000000..4e40c2c3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0004_post_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE_confirm.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyaFY0qyl6XeW1FHXpHhE\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vw7KiUJzyw0GMz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1152 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:18 GMT +original-request: req_vw7KiUJzyw0GMz +stripe-version: 2020-08-27 +idempotency-key: b842184e-c2b6-4384-83d3-a03dcf336bbb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "", + "country" : "", + "line2" : null, + "city" : "", + "line1" : "510 Townsend St", + "postal_code" : "" + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRyaFY0qyl6XeWpQfJ9ATK", + "client_secret" : "pi_3PiRyaFY0qyl6XeW1FHXpHhE_secret_nTrJ4dtYem3pOAKoRvImIiWxf", + "id" : "pi_3PiRyaFY0qyl6XeW1FHXpHhE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391756, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0005_get_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0005_get_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE.tail new file mode 100644 index 00000000..0b3ea2ee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntent/0005_get_v1_payment_intents_pi_3PiRyaFY0qyl6XeW1FHXpHhE.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyaFY0qyl6XeW1FHXpHhE\?client_secret=pi_3PiRyaFY0qyl6XeW1FHXpHhE_secret_nTrJ4dtYem3pOAKoRvImIiWxf$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rU7QdvCiKUp6XK +Content-Length: 1152 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "", + "country" : "", + "line2" : null, + "city" : "", + "line1" : "510 Townsend St", + "postal_code" : "" + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRyaFY0qyl6XeWpQfJ9ATK", + "client_secret" : "pi_3PiRyaFY0qyl6XeW1FHXpHhE_secret_nTrJ4dtYem3pOAKoRvImIiWxf", + "id" : "pi_3PiRyaFY0qyl6XeW1FHXpHhE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391756, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0000_post_v1_tokens.tail new file mode 100644 index 00000000..d7f81f50 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aup7TksQOKtYss +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:18 GMT +original-request: req_aup7TksQOKtYss +stripe-version: 2020-08-27 +idempotency-key: c5b08783-7569-4df1-9b06-1ab440f236af +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRycFY0qyl6XeW117AJAnq", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRycFY0qyl6XeWNAVjgBb9", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391758, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..c5d5b896 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3pkX4xFr0Qf4Q8 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:19 GMT +original-request: req_3pkX4xFr0Qf4Q8 +stripe-version: 2020-08-27 +idempotency-key: 41e15ede-1683-4b37-b0f1-0bc109284582 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRycFY0qyl6XeWgVv5ARuR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391758, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..66f08e7d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YMmXaG3oyWZivNkq63pmAMhCkpqVLUwA3rmFnkUrmtT7var%2F08W0bunJeK3FEm7YiTRSsWsO2CubGil80dbXpN9m9EHOSQK2gBT98iUwmmmdcixXhUqCiusBP4PS3ehbTL7CY5sCNA%2FinW5vCnrjGa2HrVOPgZ6OGOrnIV0X5O4z3Hue1Ve91yiTyNceZdHonE%2FA20cC58UksBom1xO0PWKiDwLMBXhF7T2Khsehxsc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 746720e4a9787704512ddf4ffa5a40d9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:19 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRydFY0qyl6XeW11cbTNpX","secret":"pi_3PiRydFY0qyl6XeW11cbTNpX_secret_WbsgjQroWo23va9EDvlbpnrza","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0003_get_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0003_get_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX.tail new file mode 100644 index 00000000..47efebbb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0003_get_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRydFY0qyl6XeW11cbTNpX\?client_secret=pi_3PiRydFY0qyl6XeW11cbTNpX_secret_WbsgjQroWo23va9EDvlbpnrza$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HHSe1hfZjzrZ0H +Content-Length: 884 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "manual", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiRydFY0qyl6XeW11cbTNpX_secret_WbsgjQroWo23va9EDvlbpnrza", + "id" : "pi_3PiRydFY0qyl6XeW11cbTNpX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391759, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0004_post_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0004_post_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX_confirm.tail new file mode 100644 index 00000000..48841e75 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0004_post_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX_confirm.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRydFY0qyl6XeW11cbTNpX\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bdjg33lMQwjLkl +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1156 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:20 GMT +original-request: req_bdjg33lMQwjLkl +stripe-version: 2020-08-27 +idempotency-key: c6f5a47e-bbbe-43fd-8163-b4b3517593ba +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "manual", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "", + "country" : "", + "line2" : null, + "city" : "", + "line1" : "510 Townsend St", + "postal_code" : "" + } + }, + "status" : "requires_capture", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRycFY0qyl6XeWgVv5ARuR", + "client_secret" : "pi_3PiRydFY0qyl6XeW11cbTNpX_secret_WbsgjQroWo23va9EDvlbpnrza", + "id" : "pi_3PiRydFY0qyl6XeW11cbTNpX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391759, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0005_get_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0005_get_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX.tail new file mode 100644 index 00000000..1d7399f4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesAutomaticConfirmationPaymentIntentManualCapture/0005_get_v1_payment_intents_pi_3PiRydFY0qyl6XeW11cbTNpX.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRydFY0qyl6XeW11cbTNpX\?client_secret=pi_3PiRydFY0qyl6XeW11cbTNpX_secret_WbsgjQroWo23va9EDvlbpnrza$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pYafYkHQMIA5i2 +Content-Length: 1156 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "manual", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "", + "country" : "", + "line2" : null, + "city" : "", + "line1" : "510 Townsend St", + "postal_code" : "" + } + }, + "status" : "requires_capture", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRycFY0qyl6XeWgVv5ARuR", + "client_secret" : "pi_3PiRydFY0qyl6XeW11cbTNpX_secret_WbsgjQroWo23va9EDvlbpnrza", + "id" : "pi_3PiRydFY0qyl6XeW11cbTNpX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391759, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0000_post_v1_tokens.tail new file mode 100644 index 00000000..18900b74 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_byKIH19HQ6pdvI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:21 GMT +original-request: req_byKIH19HQ6pdvI +stripe-version: 2020-08-27 +idempotency-key: 282c0ba2-b8e8-4ac8-80ac-101264dbbdbb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyfFY0qyl6XeWhFWqWoOw", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyfFY0qyl6XeWogZAgt8Z", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391761, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..d41090b6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_om26uuvScWgk0T +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:21 GMT +original-request: req_om26uuvScWgk0T +stripe-version: 2020-08-27 +idempotency-key: 79ddce46-881e-43ea-b7ef-9827f2baa880 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyfFY0qyl6XeWBELGvRex", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391761, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..99c253d5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6qIDLaZha8lQ0PLHrAB7Xkq9JkfS8yipK6t4G9tfwaS8avo9E7I6%2F9zQhwuIlA6sprfOxz60UTr7wj0gSH6%2Fo5rpz2SrUhIjxJksTa2zjSibjo8U57aZYN0FMghWXf7F%2FcmRQ30RRX0dkbvTxx2KsUhQT8Yd2mYpe%2FePhxBaHhJJEdQhNY%2F0%2FovIWF1m4wVD4aGmIQiJCIPkOA7LE6hnbaB3ofQfbpC7HC8%2FQdTfkhU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: effdafa41f112e243cea0751fed0078f;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:22 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRyfFY0qyl6XeW14TKwmwB","secret":"pi_3PiRyfFY0qyl6XeW14TKwmwB_secret_fetzTuGAyLlaEoO8nnrkP0ilH","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0003_get_v1_payment_intents_pi_3PiRyfFY0qyl6XeW14TKwmwB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0003_get_v1_payment_intents_pi_3PiRyfFY0qyl6XeW14TKwmwB.tail new file mode 100644 index 00000000..b6feb301 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0003_get_v1_payment_intents_pi_3PiRyfFY0qyl6XeW14TKwmwB.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyfFY0qyl6XeW14TKwmwB\?client_secret=pi_3PiRyfFY0qyl6XeW14TKwmwB_secret_fetzTuGAyLlaEoO8nnrkP0ilH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DnovrXpEP5wGC2 +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:22 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRyfFY0qyl6XeWBELGvRex", + "client_secret" : "pi_3PiRyfFY0qyl6XeW14TKwmwB_secret_fetzTuGAyLlaEoO8nnrkP0ilH", + "id" : "pi_3PiRyfFY0qyl6XeW14TKwmwB", + "confirmation_method" : "manual", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391761, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0004_get_v1_payment_intents_pi_3PiRyfFY0qyl6XeW14TKwmwB.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0004_get_v1_payment_intents_pi_3PiRyfFY0qyl6XeW14TKwmwB.tail new file mode 100644 index 00000000..5d2b0615 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesManualConfirmationPaymentIntent/0004_get_v1_payment_intents_pi_3PiRyfFY0qyl6XeW14TKwmwB.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRyfFY0qyl6XeW14TKwmwB\?client_secret=pi_3PiRyfFY0qyl6XeW14TKwmwB_secret_fetzTuGAyLlaEoO8nnrkP0ilH$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GIcYlDeTmK5zly +Content-Length: 895 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:23 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiRyfFY0qyl6XeWBELGvRex", + "client_secret" : "pi_3PiRyfFY0qyl6XeW14TKwmwB_secret_fetzTuGAyLlaEoO8nnrkP0ilH", + "id" : "pi_3PiRyfFY0qyl6XeW14TKwmwB", + "confirmation_method" : "manual", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391761, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0000_post_v1_tokens.tail new file mode 100644 index 00000000..ba720400 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0000_post_v1_tokens.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JHc1aI1eBN9b2l +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 880 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:23 GMT +original-request: req_JHc1aI1eBN9b2l +stripe-version: 2020-08-27 +idempotency-key: 0d18c72c-c429-45b3-ba11-0f92a06618ac +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pk_token=&pk_token_instrument_name=Simulated%20Instrument&pk_token_payment_network=PKPaymentNetwork%28_rawValue%3A%20AmEx%29&pk_token_transaction_id=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyhFY0qyl6XeWoOZIk67k", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : "4242", + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : null, + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyhFY0qyl6XeWLmOPfDk4", + "tokenization_method" : "apple_pay", + "address_country" : null, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + } + }, + "exp_year" : 2025 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391763, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..43f8d109 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0001_post_v1_payment_methods.tail @@ -0,0 +1,82 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zQ72mi5O3VTdV5 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1050 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:23 GMT +original-request: req_zQ72mi5O3VTdV5 +stripe-version: 2020-08-27 +idempotency-key: bcd580df-a848-41d4-a4e6-111e86082508 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiRyhFY0qyl6XeWXnLBbuMG", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : { + "type" : "apple_pay", + "apple_pay" : { + "type" : "apple_pay" + }, + "dynamic_last4" : "4242" + }, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391763, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0002_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0002_post_create_setup_intent.tail new file mode 100644 index 00000000..cf26a871 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0002_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=2tL9%2F6uPRGpzkzL0wdgUQCBzvpxpYFzEYIkttc61CaS3YK8Sds8yefl3l5vGC7Ungg%2FKTXDea5XyRfRX0D2uIj1znQ4rvs2WcVf9x9kgt%2Faf1G6mFCl%2By66HVB2tHXjilejuV8HZXVj5oosOsuKGcWH4ESP2ePoA%2Bqr89DfkpeHJSubXdOn07fl9wQ%2FX1V5Z94e%2BYuxJIVqBV873yBgRr%2BFN0t5k2T%2Bsi6JufPTWpvw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 122062d99fb6471476777f22e8ea69c9 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiRyiFY0qyl6XeWO3Bxrlh3","secret":"seti_1PiRyiFY0qyl6XeWO3Bxrlh3_secret_QZbB2no0Ricaydg1hDci09uOoM577Xz","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0003_get_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0003_get_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3.tail new file mode 100644 index 00000000..0fc08965 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0003_get_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRyiFY0qyl6XeWO3Bxrlh3\?client_secret=seti_1PiRyiFY0qyl6XeWO3Bxrlh3_secret_QZbB2no0Ricaydg1hDci09uOoM577Xz$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SSXxyEu9mr2SfW +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:24 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRyiFY0qyl6XeWO3Bxrlh3", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391764, + "client_secret" : "seti_1PiRyiFY0qyl6XeWO3Bxrlh3_secret_QZbB2no0Ricaydg1hDci09uOoM577Xz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0004_post_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0004_post_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3_confirm.tail new file mode 100644 index 00000000..1ac3d41b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0004_post_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3_confirm.tail @@ -0,0 +1,48 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRyiFY0qyl6XeWO3Bxrlh3\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BXAO1enAmNBPCn +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:25 GMT +original-request: req_BXAO1enAmNBPCn +stripe-version: 2020-08-27 +idempotency-key: 8216af46-5d1f-41f6-816f-c6101b3555bf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRyiFY0qyl6XeWO3Bxrlh3", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiRyhFY0qyl6XeWXnLBbuMG", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391764, + "client_secret" : "seti_1PiRyiFY0qyl6XeWO3Bxrlh3_secret_QZbB2no0Ricaydg1hDci09uOoM577Xz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0005_get_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0005_get_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3.tail new file mode 100644 index 00000000..0ced9655 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayContextFunctionalTest/testCompletesSetupIntent/0005_get_v1_setup_intents_seti_1PiRyiFY0qyl6XeWO3Bxrlh3.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRyiFY0qyl6XeWO3Bxrlh3\?client_secret=seti_1PiRyiFY0qyl6XeWO3Bxrlh3_secret_QZbB2no0Ricaydg1hDci09uOoM577Xz$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_5c44A121OIFero +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRyiFY0qyl6XeWO3Bxrlh3", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiRyhFY0qyl6XeWXnLBbuMG", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391764, + "client_secret" : "seti_1PiRyiFY0qyl6XeWO3Bxrlh3_secret_QZbB2no0Ricaydg1hDci09uOoM577Xz", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateSourceWithPayment/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateSourceWithPayment/0000_post_v1_tokens.tail new file mode 100644 index 00000000..63316045 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateSourceWithPayment/0000_post_v1_tokens.tail @@ -0,0 +1,37 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vPnxmSAQJcQNqE +Content-Length: 321 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:25 GMT +original-request: req_vPnxmSAQJcQNqE +stripe-version: 2020-08-27 +idempotency-key: 20ace4a6-b5ea-4105-9d28-2f7da2a6ca02 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[name]=Test%20Testerson&guid=.*&muid=.*&payment_user_agent=.*&pk_token=%7B%22version%22%3A%22EC_v1%22%2C%22data%22%3A%22lF8RBjPvhc2GuhjEh7qFNijDJjxD\/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6%2BzeBx8OocvalHZ5ba\/WA\/tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY%2ButmdORhxyH4nUw2wuEY4lAE7\/GtEU\/RNDhaKx\/m93l0oLlk84qD1ynTA5JP3gjkdX%2BRK23iCAZDScXCcCU0OnYlJV8sDyf3%2B8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2%2BDNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3%2ByrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E%2BLlBdXiREmlz4u1EA%3D%22%2C%22signature%22%3A%22MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O\/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22\/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUI\/JJxE%2BT5O8n5sT2KGw\/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB\/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB\/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw%2BSoyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6%2BW5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS%2B\/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou\/ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT\/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk%2BTvJ%2BbE9ihsP6K7\/S5LMA8GA1UdEwEB\/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH\/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr\/0F%2B3ZD3VNoo6%2B8ZyBXkK3ifiY95tZn5jVQQ2PnenC\/gIwMi3VRCGwowV3bF3zODuQZ\/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf\/iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR%2BL6OVBH6iTPIFMBNPcq6gAAAAAAAA%3D%22%2C%22header%22%3A%7B%22transactionId%22%3A%22a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9%22%2C%22ephemeralPublicKey%22%3A%22MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV\/P7CQztw28IvYozvKvjBPQ%3D%3D%22%2C%22publicKeyHash%22%3A%22yRcyn7njT6JL3AY9nmg0KD\/xm\/ch7gW1sGl2OuEucZY%3D%22%7D%7D&pk_token_instrument_name=Master%20Charge&sid=.* + +{ + "error" : { + "message" : "The certificate used to sign your request is invalid. For help troubleshooting, please visit https:\/\/stripe.com\/docs\/apple-pay\/apps#troubleshooting.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_vPnxmSAQJcQNqE?t=1722391765" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPayment/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPayment/0000_post_v1_tokens.tail new file mode 100644 index 00000000..91891566 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPayment/0000_post_v1_tokens.tail @@ -0,0 +1,37 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SbnCibMJbhWWz0 +Content-Length: 321 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:25 GMT +original-request: req_SbnCibMJbhWWz0 +stripe-version: 2020-08-27 +idempotency-key: 2922949d-9991-47d4-b0ea-ee0f548e0f71 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[name]=Test%20Testerson&guid=.*&muid=.*&payment_user_agent=.*&pk_token=%7B%22version%22%3A%22EC_v1%22%2C%22data%22%3A%22lF8RBjPvhc2GuhjEh7qFNijDJjxD\/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6%2BzeBx8OocvalHZ5ba\/WA\/tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY%2ButmdORhxyH4nUw2wuEY4lAE7\/GtEU\/RNDhaKx\/m93l0oLlk84qD1ynTA5JP3gjkdX%2BRK23iCAZDScXCcCU0OnYlJV8sDyf3%2B8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2%2BDNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3%2ByrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E%2BLlBdXiREmlz4u1EA%3D%22%2C%22signature%22%3A%22MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O\/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22\/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUI\/JJxE%2BT5O8n5sT2KGw\/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB\/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB\/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw%2BSoyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6%2BW5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS%2B\/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou\/ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT\/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk%2BTvJ%2BbE9ihsP6K7\/S5LMA8GA1UdEwEB\/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH\/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr\/0F%2B3ZD3VNoo6%2B8ZyBXkK3ifiY95tZn5jVQQ2PnenC\/gIwMi3VRCGwowV3bF3zODuQZ\/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf\/iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR%2BL6OVBH6iTPIFMBNPcq6gAAAAAAAA%3D%22%2C%22header%22%3A%7B%22transactionId%22%3A%22a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9%22%2C%22ephemeralPublicKey%22%3A%22MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV\/P7CQztw28IvYozvKvjBPQ%3D%3D%22%2C%22publicKeyHash%22%3A%22yRcyn7njT6JL3AY9nmg0KD\/xm\/ch7gW1sGl2OuEucZY%3D%22%7D%7D&pk_token_instrument_name=Master%20Charge&sid=.* + +{ + "error" : { + "message" : "The certificate used to sign your request is invalid. For help troubleshooting, please visit https:\/\/stripe.com\/docs\/apple-pay\/apps#troubleshooting.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_SbnCibMJbhWWz0?t=1722391765" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPaymentClassic/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPaymentClassic/0000_post_v1_tokens.tail new file mode 100644 index 00000000..fdfadaf4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPApplePayFunctionalTest/testCreateTokenWithPaymentClassic/0000_post_v1_tokens.tail @@ -0,0 +1,37 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bceshlBf1NuStQ +Content-Length: 321 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:26 GMT +original-request: req_bceshlBf1NuStQ +stripe-version: 2020-08-27 +idempotency-key: 02ffb4c0-ceb8-44ca-96db-208945f060c0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[name]=Test%20Testerson&guid=.*&muid=.*&payment_user_agent=.*&pk_token=%7B%22version%22%3A%22EC_v1%22%2C%22data%22%3A%22lF8RBjPvhc2GuhjEh7qFNijDJjxD\/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6%2BzeBx8OocvalHZ5ba\/WA\/tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY%2ButmdORhxyH4nUw2wuEY4lAE7\/GtEU\/RNDhaKx\/m93l0oLlk84qD1ynTA5JP3gjkdX%2BRK23iCAZDScXCcCU0OnYlJV8sDyf3%2B8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2%2BDNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3%2ByrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E%2BLlBdXiREmlz4u1EA%3D%22%2C%22signature%22%3A%22MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O\/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22\/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUI\/JJxE%2BT5O8n5sT2KGw\/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB\/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB\/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw%2BSoyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6%2BW5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS%2B\/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou\/ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT\/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk%2BTvJ%2BbE9ihsP6K7\/S5LMA8GA1UdEwEB\/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH\/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr\/0F%2B3ZD3VNoo6%2B8ZyBXkK3ifiY95tZn5jVQQ2PnenC\/gIwMi3VRCGwowV3bF3zODuQZ\/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf\/iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR%2BL6OVBH6iTPIFMBNPcq6gAAAAAAAA%3D%22%2C%22header%22%3A%7B%22transactionId%22%3A%22a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9%22%2C%22ephemeralPublicKey%22%3A%22MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV\/P7CQztw28IvYozvKvjBPQ%3D%3D%22%2C%22publicKeyHash%22%3A%22yRcyn7njT6JL3AY9nmg0KD\/xm\/ch7gW1sGl2OuEucZY%3D%22%7D%7D&pk_token_instrument_name=Master%20Charge&sid=.* + +{ + "error" : { + "message" : "The certificate used to sign your request is invalid. For help troubleshooting, please visit https:\/\/stripe.com\/docs\/apple-pay\/apps#troubleshooting.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_bceshlBf1NuStQ?t=1722391765" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPBankAccountFunctionalTest/testCreateAndRetreiveBankAccountToken/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPBankAccountFunctionalTest/testCreateAndRetreiveBankAccountToken/0000_post_v1_tokens.tail new file mode 100644 index 00000000..73b69712 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPBankAccountFunctionalTest/testCreateAndRetreiveBankAccountToken/0000_post_v1_tokens.tail @@ -0,0 +1,53 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3L1cAonXXthoxa +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 568 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:26 GMT +original-request: req_3L1cAonXXthoxa +stripe-version: 2020-08-27 +idempotency-key: 46b800e0-31b3-48c2-80e6-5e9681d2d4ca +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: bank_account\[account_holder_name]=Jimmy%20bob&bank_account\[account_holder_type]=company&bank_account\[account_number]=000123456789&bank_account\[country]=US&bank_account\[routing_number]=110000000&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "btok_1PiRykFY0qyl6XeWKFxflQEK", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391766, + "used" : false, + "type" : "bank_account", + "bank_account" : { + "id" : "ba_1PiRykFY0qyl6XeWpKMcU7W1", + "account_holder_type" : "company", + "last4" : "6789", + "bank_name" : "STRIPE TEST BANK", + "account_type" : null, + "status" : "new", + "account_holder_name" : "Jimmy bob", + "routing_number" : "110000000", + "object" : "bank_account", + "country" : "US", + "currency" : "usd", + "name" : "Jimmy bob" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPBankAccountFunctionalTest/testInvalidKey/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPBankAccountFunctionalTest/testInvalidKey/0000_post_v1_tokens.tail new file mode 100644 index 00000000..ff58aaba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPBankAccountFunctionalTest/testInvalidKey/0000_post_v1_tokens.tail @@ -0,0 +1,25 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 31 Jul 2024 02:09:26 GMT +access-control-allow-credentials: true +Content-Length: 122 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Bearer realm="Stripe" +X-Stripe-Mock-Request: bank_account\[account_holder_type]=individual&bank_account\[account_number]=000123456789&bank_account\[country]=US&bank_account\[routing_number]=110000000&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "error" : { + "message" : "Invalid API Key provided: not_a_va********asdf", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCardTokenCreationWithExpiredCard/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCardTokenCreationWithExpiredCard/0000_post_v1_tokens.tail new file mode 100644 index 00000000..4ec0809d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCardTokenCreationWithExpiredCard/0000_post_v1_tokens.tail @@ -0,0 +1,41 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_52YRKOgULTBrEf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 335 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:40 GMT +original-request: req_52YRKOgULTBrEf +stripe-version: 2020-08-27 +idempotency-key: 1891d26b-069c-41f4-a1ad-abe4ec265bb1 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[exp_month]=6&card\[exp_year]=2013&card\[number]=4242%204242%204242%204242&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "error" : { + "code" : "invalid_expiry_year", + "message" : "Your card's expiration year is invalid.", + "param" : "exp_year", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_52YRKOgULTBrEf?t=1722391779", + "type" : "card_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/invalid-expiry-year" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCardTokenCreationWithInvalidParams/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCardTokenCreationWithInvalidParams/0000_post_v1_tokens.tail new file mode 100644 index 00000000..c64441ab --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCardTokenCreationWithInvalidParams/0000_post_v1_tokens.tail @@ -0,0 +1,41 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aEw2W8epHkJL6B +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 318 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:40 GMT +original-request: req_aEw2W8epHkJL6B +stripe-version: 2020-08-27 +idempotency-key: 34c6f977-0bc3-48f0-8a28-3487519799c7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[exp_month]=6&card\[exp_year]=2024&card\[number]=4242%204242%204242%204241&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "error" : { + "code" : "incorrect_number", + "message" : "Your card number is incorrect.", + "param" : "number", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_aEw2W8epHkJL6B?t=1722391780", + "type" : "card_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/incorrect-number" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCreateCVCUpdateToken/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCreateCVCUpdateToken/0000_post_v1_tokens.tail new file mode 100644 index 00000000..ab965a92 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCreateCVCUpdateToken/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zvEL7or82BD7gf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 186 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:40 GMT +original-request: req_zvEL7or82BD7gf +stripe-version: 2020-08-27 +idempotency-key: d99188fd-0b51-4dd1-af74-055c72e00dfb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: cvc_update\[cvc]=1234&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "cvctok_1PiRyyFY0qyl6XeWMGs8h9pk", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391780, + "used" : false, + "type" : "cvc_update" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCreateCardToken/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCreateCardToken/0000_post_v1_tokens.tail new file mode 100644 index 00000000..7bd21c11 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testCreateCardToken/0000_post_v1_tokens.tail @@ -0,0 +1,66 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TO88a0QYLGTclN +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 852 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:40 GMT +original-request: req_TO88a0QYLGTclN +stripe-version: 2020-08-27 +idempotency-key: b49454f0-bbfd-465f-9d20-c0543bfefba6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[address_city]=New%20York&card\[address_country]=USA&card\[address_line1]=123%20Fake%20Street&card\[address_line2]=Apartment%204&card\[address_state]=NY&card\[address_zip]=10002&card\[currency]=usd&card\[exp_month]=6&card\[exp_year]=2050&card\[number]=4242%204242%204242%204242&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiRyyFY0qyl6XeW5aAjnkfi", + "card" : { + "address_line1_check" : "unchecked", + "dynamic_last4" : null, + "last4" : "4242", + "address_line2" : "Apartment 4", + "address_city" : "New York", + "address_zip_check" : "unchecked", + "address_zip" : "10002", + "country" : "US", + "object" : "card", + "address_line1" : "123 Fake Street", + "address_state" : "NY", + "brand" : "Visa", + "currency" : "usd", + "cvc_check" : null, + "exp_month" : 6, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiRyyFY0qyl6XeW2QkyXkwp", + "tokenization_method" : null, + "address_country" : "USA", + "wallet" : null, + "exp_year" : 2050 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391780, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testInvalidCVC/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testInvalidCVC/0000_post_v1_tokens.tail new file mode 100644 index 00000000..f42083cb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testInvalidCVC/0000_post_v1_tokens.tail @@ -0,0 +1,41 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_y7W3xzvY8wvmxd +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 312 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:41 GMT +original-request: req_y7W3xzvY8wvmxd +stripe-version: 2020-08-27 +idempotency-key: 982aed5f-b2e9-472a-83d1-6c3d92b7e011 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: cvc_update\[cvc]=1&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "error" : { + "code" : "invalid_cvc", + "message" : "Your card's security code is invalid.", + "param" : "cvc", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_y7W3xzvY8wvmxd?t=1722391781", + "type" : "card_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/invalid-cvc" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testInvalidKey/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testInvalidKey/0000_post_v1_tokens.tail new file mode 100644 index 00000000..32a701d2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPCardFunctionalTest/testInvalidKey/0000_post_v1_tokens.tail @@ -0,0 +1,25 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 31 Jul 2024 02:09:41 GMT +access-control-allow-credentials: true +Content-Length: 122 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Bearer realm="Stripe" +X-Stripe-Mock-Request: card\[exp_month]=6&card\[exp_year]=2050&card\[number]=4242%204242%204242%204242&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "error" : { + "message" : "Invalid API Key provided: not_a_va********asdf", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testCreateFileForDisputeEvidence/0000_post_v1_files.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testCreateFileForDisputeEvidence/0000_post_v1_files.tail new file mode 100644 index 00000000..f3c01533 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testCreateFileForDisputeEvidence/0000_post_v1_files.tail @@ -0,0 +1,38 @@ +POST +https:\/\/uploads\.stripe\.com\/v1\/files$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ffiles; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=grpc-upload-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: livemode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=grpc-upload-srv"}],"include_subdomains":true} +request-id: req_Mw6l9G1Pz2t1sh +Content-Length: 310 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:46 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "file", + "id" : "file_1PiRz3FY0qyl6XeWAym9ewMf", + "expires_at" : 1745719785, + "purpose" : "dispute_evidence", + "size" : 160, + "created" : 1722391785, + "filename" : "image.jpg", + "title" : null, + "type" : "jpg", + "url" : "https:\/\/files.stripe.com\/v1\/files\/file_1PiRz3FY0qyl6XeWAym9ewMf\/contents" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testCreateFileForIdentityDocument/0000_post_v1_files.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testCreateFileForIdentityDocument/0000_post_v1_files.tail new file mode 100644 index 00000000..539db230 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testCreateFileForIdentityDocument/0000_post_v1_files.tail @@ -0,0 +1,38 @@ +POST +https:\/\/uploads\.stripe\.com\/v1\/files$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ffiles; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=grpc-upload-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: livemode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=grpc-upload-srv"}],"include_subdomains":true} +request-id: req_PUumYJmPDqd9Yk +Content-Length: 235 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:47 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "file", + "id" : "file_1PiRz5FY0qyl6XeWzgAh8tvM", + "expires_at" : null, + "purpose" : "identity_document", + "size" : 160, + "created" : 1722391787, + "filename" : "image.jpg", + "title" : null, + "type" : "jpg", + "url" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testInvalidKey/0000_post_v1_files.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testInvalidKey/0000_post_v1_files.tail new file mode 100644 index 00000000..7f2c26b0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPFileFunctionalTest/testInvalidKey/0000_post_v1_files.tail @@ -0,0 +1,24 @@ +POST +https:\/\/uploads\.stripe\.com\/v1\/files$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 31 Jul 2024 02:09:48 GMT +access-control-allow-credentials: true +Content-Length: 122 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Bearer realm="Stripe" + +{ + "error" : { + "message" : "Invalid API Key provided: not_a_va********asdf", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPIIFunctionalTest/testCreatePersonallyIdentifiableInformationToken/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPIIFunctionalTest/testCreatePersonallyIdentifiableInformationToken/0000_post_v1_tokens.tail new file mode 100644 index 00000000..e18ebf1f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPIIFunctionalTest/testCreatePersonallyIdentifiableInformationToken/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_T4dbwDPlR9zugc +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 176 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:48 GMT +original-request: req_T4dbwDPlR9zugc +stripe-version: 2020-08-27 +idempotency-key: 4dd5bbad-00dc-44e8-bc1e-e96f3cc66249 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pii\[personal_id_number]=0123456789&sid=.* + +{ + "object" : "token", + "id" : "pii_1PiRz6FY0qyl6XeWTtAc4XpC", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391788, + "used" : false, + "type" : "pii" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPIIFunctionalTest/testSSNLast4Token/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPIIFunctionalTest/testSSNLast4Token/0000_post_v1_tokens.tail new file mode 100644 index 00000000..87816983 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPIIFunctionalTest/testSSNLast4Token/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dBtJM1cArplvmw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 176 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:49 GMT +original-request: req_dBtJM1cArplvmw +stripe-version: 2020-08-27 +idempotency-key: e9a36cfa-b09a-41d1-a3e0-9f90452ff7ce +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pii\[ssn_last_4]=1234&sid=.* + +{ + "object" : "token", + "id" : "pii_1PiRz6FY0qyl6XeWbSWEYyDO", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391788, + "used" : false, + "type" : "pii" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..51f9170a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_3VGR5oKeh2W9KJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:51 GMT +original-request: req_3VGR5oKeh2W9KJ +stripe-version: 2020-08-27 +idempotency-key: f5bf259c-38b3-4af1-9340-f89475eea21c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiRz9FY0qyl6XeWhns1qWNr", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391791, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..09260877 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=3nKZswXrkQBJzu5GqpO2tW0%2BSunLOqKmtmPKBkGlRaCHlAdGT%2FpHWnjR1o7K1lsjX5Pc9D0OAja9sVSjdsv%2BjqgQK5JNLxE3yGwC4ZTC8Ce9N8k4hm0zMwGP3qP%2FVNM3FO8WNXoyl25iim8xg1ynrSUGV5fefSQRHT34FM38yLfRB0gVquXE7i9JaNm6cTA527l5YECikgTfTmKh3xVVIrGitKyPBCARecucSD81%2Bt4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 643e05f47e74eeb2b7ca416794d4c286 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:53 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRzAFY0qyl6XeW0U7X5Y2j","secret":"pi_3PiRzAFY0qyl6XeW0U7X5Y2j_secret_kliJfdkiYpV5PJPWrRdR0wEWp","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0002_get_v1_payment_intents_pi_3PiRzAFY0qyl6XeW0U7X5Y2j.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0002_get_v1_payment_intents_pi_3PiRzAFY0qyl6XeW0U7X5Y2j.tail new file mode 100644 index 00000000..c9a83817 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardpaymentintentserversideconfirmation/0002_get_v1_payment_intents_pi_3PiRzAFY0qyl6XeW0U7X5Y2j.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRzAFY0qyl6XeW0U7X5Y2j\?client_secret=pi_3PiRzAFY0qyl6XeW0U7X5Y2j_secret_kliJfdkiYpV5PJPWrRdR0wEWp&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SygsSjwkgUQQ3G +Content-Length: 1892 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiRz9FY0qyl6XeWhns1qWNr", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391791, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_3PiRzAFY0qyl6XeW0U7X5Y2j_secret_kliJfdkiYpV5PJPWrRdR0wEWp", + "id" : "pi_3PiRzAFY0qyl6XeW0U7X5Y2j", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391792, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..782bff0e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SlqdaPZrWxb7aS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:53 GMT +original-request: req_SlqdaPZrWxb7aS +stripe-version: 2020-08-27 +idempotency-key: 2a74ff8a-0d21-4c88-8471-c3719a45e257 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiRzBFY0qyl6XeWjGqqJXi3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391793, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..995f6947 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=L%2FoMlwl%2FND90SUieVsD77vAglpbO%2FxwWj7%2BylOL7OdwO4Ul8D3pAUcJ9jRs%2Bmm6w9jcAFrasQAymffgn6chKEWktJ%2B7LOonQTh25uC7FaEQIUc9l%2F1DhmnhpsPSrM5MFD08bG6QOc9gp4MkNL8SFZ8Uz6pZBH69OhL8b8rDB1h95Wg3EbD3Eu6xsJj5xsztl7VYgl%2F89Hyn%2BbpPafFDZjDOc2eL7293gXLAWrOJkJWQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6795e724ebfbc2b33676fd79f37c1f59 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:54 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiRzBFY0qyl6XeW1yPMQw10","secret":"seti_1PiRzBFY0qyl6XeW1yPMQw10_secret_QZbBiDZJ3GOqGzh4A6Y0Qx7ZxFalJSQ","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0002_get_v1_setup_intents_seti_1PiRzBFY0qyl6XeW1yPMQw10.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0002_get_v1_setup_intents_seti_1PiRzBFY0qyl6XeW1yPMQw10.tail new file mode 100644 index 00000000..ce8b70f0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testcardsetupintentserversideconfirmation/0002_get_v1_setup_intents_seti_1PiRzBFY0qyl6XeW1yPMQw10.tail @@ -0,0 +1,91 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRzBFY0qyl6XeW1yPMQw10\?client_secret=seti_1PiRzBFY0qyl6XeW1yPMQw10_secret_QZbBiDZJ3GOqGzh4A6Y0Qx7ZxFalJSQ&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Wvj4kchN9yHYSC +Content-Length: 1538 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:55 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRzBFY0qyl6XeW1yPMQw10", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiRzBFY0qyl6XeWjGqqJXi3", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391793, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391793, + "client_secret" : "seti_1PiRzBFY0qyl6XeW1yPMQw10_secret_QZbBiDZJ3GOqGzh4A6Y0Qx7ZxFalJSQ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmpaymentintentsavedpmsendsanalytic/0000_post_v1_payment_intents_pi_3P20wFFY0qyl6XeW0dSOQ6W7_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmpaymentintentsavedpmsendsanalytic/0000_post_v1_payment_intents_pi_3P20wFFY0qyl6XeW0dSOQ6W7_confirm.tail new file mode 100644 index 00000000..8a0d580c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmpaymentintentsavedpmsendsanalytic/0000_post_v1_payment_intents_pi_3P20wFFY0qyl6XeW0dSOQ6W7_confirm.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3P20wFFY0qyl6XeW0dSOQ6W7\/confirm$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yeaIfIqyozYY0R +Content-Length: 1606 +Vary: Origin +Date: Mon, 05 Aug 2024 23:34:01 GMT +original-request: req_yeaIfIqyozYY0R +stripe-version: 2020-08-27 +idempotency-key: ae26a620-5e8b-4fd2-a3f8-879f7eee2a77 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3P20wFFY0qyl6XeW0dSOQ6W7_secret_9V8GkrCOt1MEW8SBmAaGnmT6A&expand\[0]=payment_method&payment_method=pm_123&use_stripe_sdk=true + +{ + "error" : { + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3P20wFFY0qyl6XeW0dSOQ6W7_secret_9V8GkrCOt1MEW8SBmAaGnmT6A", + "id" : "pi_3P20wFFY0qyl6XeW0dSOQ6W7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1712278047, + "description" : null + }, + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_yeaIfIqyozYY0R?t=1722900841", + "code" : "resource_missing", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing", + "message" : "No such PaymentMethod: 'pm_123'; It's possible this PaymentMethod exists on one of your connected accounts, in which case you should retry this request on that connected account. Learn more at https:\/\/stripe.com\/docs\/connect\/authentication", + "param" : "payment_method", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmpaymentintentsendsanalytic/0000_post_v1_payment_intents_pi_3P20wFFY0qyl6XeW0dSOQ6W7_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmpaymentintentsendsanalytic/0000_post_v1_payment_intents_pi_3P20wFFY0qyl6XeW0dSOQ6W7_confirm.tail new file mode 100644 index 00000000..fdedd77e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmpaymentintentsendsanalytic/0000_post_v1_payment_intents_pi_3P20wFFY0qyl6XeW0dSOQ6W7_confirm.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3P20wFFY0qyl6XeW0dSOQ6W7\/confirm$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_NuUUUD6xYgHIrE +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1604 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:55 GMT +original-request: req_NuUUUD6xYgHIrE +stripe-version: 2020-08-27 +idempotency-key: 94884d95-0547-4080-a4d5-63ec562679d5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3P20wFFY0qyl6XeW0dSOQ6W7_secret_9V8GkrCOt1MEW8SBmAaGnmT6A&expand\[0]=payment_method&use_stripe_sdk=true + +{ + "error" : { + "code" : "payment_intent_unexpected_state", + "message" : "You cannot confirm this PaymentIntent because it's missing a payment method. You can either update the PaymentIntent with a payment method and then confirm it again, or confirm it again directly with a payment method or ConfirmationToken.", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_NuUUUD6xYgHIrE?t=1722391795", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3P20wFFY0qyl6XeW0dSOQ6W7_secret_9V8GkrCOt1MEW8SBmAaGnmT6A", + "id" : "pi_3P20wFFY0qyl6XeW0dSOQ6W7", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1712278047, + "description" : null + }, + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/payment-intent-unexpected-state" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmsetupintentsavedpmsendsanalytic/0000_post_v1_setup_intents_seti_1P1xLBFY0qyl6XeWc7c2LrMK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmsetupintentsavedpmsendsanalytic/0000_post_v1_setup_intents_seti_1P1xLBFY0qyl6XeWc7c2LrMK_confirm.tail new file mode 100644 index 00000000..727c2494 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmsetupintentsavedpmsendsanalytic/0000_post_v1_setup_intents_seti_1P1xLBFY0qyl6XeWc7c2LrMK_confirm.tail @@ -0,0 +1,59 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1P1xLBFY0qyl6XeWc7c2LrMK\/confirm$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_X0IW5Oejg4l8Hy +Content-Length: 1190 +Vary: Origin +Date: Mon, 05 Aug 2024 23:34:39 GMT +original-request: req_X0IW5Oejg4l8Hy +stripe-version: 2020-08-27 +idempotency-key: 068adc5a-4ff4-4bb0-b9ce-4a0861f2829e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1P1xLBFY0qyl6XeWc7c2LrMK_secret_PrgithiYFFPH0NVGP1BK7Oy9OU3mrDT&expand\[0]=payment_method&payment_method=pm_123&use_stripe_sdk=true + +{ + "error" : { + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_X0IW5Oejg4l8Hy?t=1722900879", + "code" : "resource_missing", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing", + "message" : "No such PaymentMethod: 'pm_123'; It's possible this PaymentMethod exists on one of your connected accounts, in which case you should retry this request on that connected account. Learn more at https:\/\/stripe.com\/docs\/connect\/authentication", + "param" : "payment_method", + "type" : "invalid_request_error", + "setup_intent" : { + "id" : "seti_1P1xLBFY0qyl6XeWc7c2LrMK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1P1xLBFY0qyl6XeW7OPeQ8jp", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1712264217, + "client_secret" : "seti_1P1xLBFY0qyl6XeWc7c2LrMK_secret_PrgithiYFFPH0NVGP1BK7Oy9OU3mrDT", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmsetupintentsendsanalytic/0000_post_v1_setup_intents_seti_1P1xLBFY0qyl6XeWc7c2LrMK_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmsetupintentsendsanalytic/0000_post_v1_setup_intents_seti_1P1xLBFY0qyl6XeWc7c2LrMK_confirm.tail new file mode 100644 index 00000000..69bfce52 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testconfirmsetupintentsendsanalytic/0000_post_v1_setup_intents_seti_1P1xLBFY0qyl6XeWc7c2LrMK_confirm.tail @@ -0,0 +1,59 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1P1xLBFY0qyl6XeWc7c2LrMK\/confirm$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_00hlv2mWTbx9Q8 +Content-Length: 1014 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:55 GMT +original-request: req_00hlv2mWTbx9Q8 +stripe-version: 2020-08-27 +idempotency-key: c57090c5-ef12-4990-81b6-e855e7d42068 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1P1xLBFY0qyl6XeWc7c2LrMK_secret_PrgithiYFFPH0NVGP1BK7Oy9OU3mrDT&expand\[0]=payment_method&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&use_stripe_sdk=true + +{ + "error" : { + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_00hlv2mWTbx9Q8?t=1722391795", + "code" : "parameter_missing", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/parameter-missing", + "message" : "Missing required param: payment_method_data[card].", + "param" : "payment_method_data[card]", + "type" : "invalid_request_error", + "setup_intent" : { + "id" : "seti_1P1xLBFY0qyl6XeWc7c2LrMK", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1P1xLBFY0qyl6XeW7OPeQ8jp", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1712264217, + "client_secret" : "seti_1P1xLBFY0qyl6XeWc7c2LrMK_secret_PrgithiYFFPH0NVGP1BK7Oy9OU3mrDT", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testhandlenextactionpaymentintentsendsanalytic/0000_get_v1_payment_intents_pi_3P232pFY0qyl6XeW0FFRtE0A.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testhandlenextactionpaymentintentsendsanalytic/0000_get_v1_payment_intents_pi_3P232pFY0qyl6XeW0FFRtE0A.tail new file mode 100644 index 00000000..758a2672 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testhandlenextactionpaymentintentsendsanalytic/0000_get_v1_payment_intents_pi_3P232pFY0qyl6XeW0FFRtE0A.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3P232pFY0qyl6XeW0FFRtE0A\?client_secret=pi_3P232pFY0qyl6XeW0FFRtE0A_secret_foo&expand%5B0%5D=payment_method$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pFUjDhimraufYM +Content-Length: 432 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:55 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "payment_intent_invalid_parameter", + "message" : "The client_secret provided does not match the client_secret associated with the PaymentIntent.", + "param" : "client_secret", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_pFUjDhimraufYM?t=1722391795", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/payment-intent-invalid-parameter" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testhandlenextactionsetupintentsendsanalytic/0000_get_v1_setup_intents_seti_3P232pFY0qyl6XeW0FFRtE0A.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testhandlenextactionsetupintentsendsanalytic/0000_get_v1_setup_intents_seti_3P232pFY0qyl6XeW0FFRtE0A.tail new file mode 100644 index 00000000..5ee4e098 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testhandlenextactionsetupintentsendsanalytic/0000_get_v1_setup_intents_seti_3P232pFY0qyl6XeW0FFRtE0A.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_3P232pFY0qyl6XeW0FFRtE0A\?client_secret=seti_3P232pFY0qyl6XeW0FFRtE0A_secret_foo&expand%5B0%5D=payment_method$ +404 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zEE4Z40WjemYpy +Content-Length: 351 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:56 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "resource_missing", + "message" : "No such setupintent: 'seti_3P232pFY0qyl6XeW0FFRtE0A'", + "param" : "intent", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_zEE4Z40WjemYpy?t=1722391796", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..b2de9587 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0000_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nBBbuhm3aJXhjl +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 702 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:56 GMT +original-request: req_nBBbuhm3aJXhjl +stripe-version: 2020-08-27 +idempotency-key: 02ef16ba-387a-4bba-837f-524b32b0d1b2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=test%40example\.com&billing_details\[name]=SEPA%20Test%20Customer&payment_user_agent=.*&sepa_debit\[iban]=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiRzEFY0qyl6XeWCmonClBz", + "billing_details" : { + "email" : "test@example.com", + "phone" : null, + "name" : "SEPA Test Customer", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391796, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..c77d4e5e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ASKKapoZoFGIhix8yJPJF9T1VEkFJhT0ccoFPlaeBOCsTkhBD6%2BB0tpZh1UR47iVeW%2BJllCrENQVQ3IOlfIHd8Zu7KGxvB0yh4JeVVbE5DR6mlImTe3YjbP1biY57sRzasVz3mTi2qNRQkE7wcRum2u%2BsJhVJkZxBCwz3%2FDtn3yR9iz%2BQLIKmuBobWo2yBzrw5vLQW3EjDHXPZGt%2ByxVOCUpGNog0Zr%2BOOgZuxUCpAs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3592220868588a8214ec20319304742a;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:57 GMT +x-robots-tag: noindex, nofollow +Content-Length: 134 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRzEFY0qyl6XeW1uNVnv0e","secret":"pi_3PiRzEFY0qyl6XeW1uNVnv0e_secret_TUL4WFeHTTVnSg43KzsDxS6Le","status":"processing"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0002_get_v1_payment_intents_pi_3PiRzEFY0qyl6XeW1uNVnv0e.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0002_get_v1_payment_intents_pi_3PiRzEFY0qyl6XeW1uNVnv0e.tail new file mode 100644 index 00000000..c911e646 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitpaymentintentserversideconfirmation/0002_get_v1_payment_intents_pi_3PiRzEFY0qyl6XeW1uNVnv0e.tail @@ -0,0 +1,96 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiRzEFY0qyl6XeW1uNVnv0e\?client_secret=pi_3PiRzEFY0qyl6XeW1uNVnv0e_secret_TUL4WFeHTTVnSg43KzsDxS6Le&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SVpCjWI44fPHg6 +Content-Length: 1642 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:57 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiRzEFY0qyl6XeWCmonClBz", + "billing_details" : { + "email" : "test@example.com", + "phone" : null, + "name" : "SEPA Test Customer", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391796, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "client_secret" : "pi_3PiRzEFY0qyl6XeW1uNVnv0e_secret_TUL4WFeHTTVnSg43KzsDxS6Le", + "id" : "pi_3PiRzEFY0qyl6XeW1uNVnv0e", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sepa_debit" + ], + "setup_future_usage" : null, + "created" : 1722391796, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..fd662971 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0000_post_v1_payment_methods.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_XFSkdEQja3O4ff +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 702 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:57 GMT +original-request: req_XFSkdEQja3O4ff +stripe-version: 2020-08-27 +idempotency-key: 1f9678b7-9f2b-4eca-9993-ac6b9d6bceba +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=test%40example\.com&billing_details\[name]=SEPA%20Test%20Customer&payment_user_agent=.*&sepa_debit\[iban]=DE89370400440532013000&type=sepa_debit + +{ + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiRzFFY0qyl6XeWOuO05SrH", + "billing_details" : { + "email" : "test@example.com", + "phone" : null, + "name" : "SEPA Test Customer", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391797, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0001_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0001_post_create_setup_intent.tail new file mode 100644 index 00000000..195fd706 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0001_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=qLlcu9bJFVg18bKhqDPFJw40UJISZZJZ2wapPV8WbZ9qurk1MoQ4GYdmZDZukQYqJ%2FWaMkTwL0ETToqBg%2FR%2BAmiGbtr0Fe4TuMw2ZyNrNpmMZIPtdzLrl2rshdd6sDnlzIf0BU5%2FpWJAsoOiDyJQGXZntI3G5g76a%2FdXq3oBQDQbIbhuPvFOB3kS9E%2FPdhZLW5UwXV113deLs6utzIMsRuSVEFxVwavFUV4a1pvLnXk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 072feca73f7348a6000943952483172e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:09:58 GMT +x-robots-tag: noindex, nofollow +Content-Length: 143 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiRzGFY0qyl6XeWnnWjBj6k","secret":"seti_1PiRzGFY0qyl6XeWnnWjBj6k_secret_QZbBGcLBHCsi1vr8I864BC9MHF1dXnq","status":"succeeded"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0002_get_v1_setup_intents_seti_1PiRzGFY0qyl6XeWnnWjBj6k.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0002_get_v1_setup_intents_seti_1PiRzGFY0qyl6XeWnnWjBj6k.tail new file mode 100644 index 00000000..77ac6042 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerFunctionalSwiftTest/testsepadebitsetupintentserversideconfirmation/0002_get_v1_setup_intents_seti_1PiRzGFY0qyl6XeWnnWjBj6k.tail @@ -0,0 +1,77 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiRzGFY0qyl6XeWnnWjBj6k\?client_secret=seti_1PiRzGFY0qyl6XeWnnWjBj6k_secret_QZbBGcLBHCsi1vr8I864BC9MHF1dXnq&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_npsU9BhzAcsUjX +Content-Length: 1287 +Vary: Origin +Date: Wed, 31 Jul 2024 02:09:58 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiRzGFY0qyl6XeWnnWjBj6k", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "sepa_debit" : { + "fingerprint" : "vifs0Ho7vwRn1Miu", + "country" : "DE", + "last4" : "3000", + "bank_code" : "37040044", + "generated_from" : { + "setup_attempt" : null, + "charge" : null + }, + "branch_code" : "" + }, + "id" : "pm_1PiRzFFY0qyl6XeWOuO05SrH", + "billing_details" : { + "email" : "test@example.com", + "phone" : null, + "name" : "SEPA Test Customer", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391797, + "allow_redisplay" : "unspecified", + "type" : "sepa_debit", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "sepa_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391798, + "client_secret" : "seti_1PiRzGFY0qyl6XeWnnWjBj6k_secret_QZbBGcLBHCsi1vr8I864BC9MHF1dXnq", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerStubbedTests/testCanPresentErrorsAreReported/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerStubbedTests/testCanPresentErrorsAreReported/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..d311163b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentHandlerStubbedTests/testCanPresentErrorsAreReported/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=5dDAVLHBOGdOBI1tUXjJhG2owRsy8yZtqxzUOQ%2BUyVHfTCsb8Fs8gHZVgyq%2F%2Bmz1y41s6IqP7rncz%2F7q8%2FJBTRzVTu6kNyswYWbR309ZDxTqBDwnWzZIYKMr3HASvuAMZ2i%2Bfn0VBlOLgsZ9TRNZgB%2FnDF8u56Oo7EFQBZy6QJWiKB5dhQSTc8bCi8793jsfOBuM%2FT0%2BOTP4%2B0lQ7UgFet5Bm5pIl6ijsSVorjZhvlc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8c0188b82621254f75d46f68f3a354d8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:10:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiRzIFY0qyl6XeW0YVmVvOF","secret":"pi_3PiRzIFY0qyl6XeW0YVmVvOF_secret_9ChQ5U8bxeoYSDngSbfok6bf8","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAUBECSDebitPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAUBECSDebitPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..be27ce5b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAUBECSDebitPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=zt0GM3tsQUiBibtQquC2qD0XMd2JgIEU2CvV0EzqOQCmu9L6SpJBWy9iGqNuFHUIlcXcuULaWD38Sg0UXj37dGAyfpgS7XVzocKFp%2BvqLyMTHeTMY9yzpJU4lY5U8pxciUHnnTMW9K00j%2FzJWRhr74udlx0fOWYgfZT9V%2B%2BijsOSu8Jcc1%2Fn7gM8sDWgg9i3wppQyeRP5V2wyYveFZWbtCm%2Fxh4Yezh1tBIUSxOGiEo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5449b0fbcbc83a718ac414c374772327 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv618F7QokQdxBy0L8ravYC","secret":"pi_3Pv618F7QokQdxBy0L8ravYC_secret_vfYZeavcsAiwEVdJIiTiqyNtt","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAUBECSDebitPaymentIntent/0001_post_v1_payment_intents_pi_3Pv618F7QokQdxBy0L8ravYC_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAUBECSDebitPaymentIntent/0001_post_v1_payment_intents_pi_3Pv618F7QokQdxBy0L8ravYC_confirm.tail new file mode 100644 index 00000000..55d70435 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAUBECSDebitPaymentIntent/0001_post_v1_payment_intents_pi_3Pv618F7QokQdxBy0L8ravYC_confirm.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv618F7QokQdxBy0L8ravYC\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dT3L6DV08KNIVq +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 811 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:11 GMT +original-request: req_dT3L6DV08KNIVq +stripe-version: 2020-08-27 +idempotency-key: 3cb9acee-ff8e-4759-832d-5d329f37e02f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv618F7QokQdxBy0L8ravYC_secret_vfYZeavcsAiwEVdJIiTiqyNtt&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[au_becs_debit]\[account_number]=000123456&payment_method_data\[au_becs_debit]\[bsb_number]=000000&payment_method_data\[billing_details]\[email]=jrosen%40example\.com&payment_method_data\[billing_details]\[name]=Jenny%20Rosen&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=au_becs_debit + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "processing", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : "pm_1Pv618F7QokQdxByl4pXS8T5", + "client_secret" : "pi_3Pv618F7QokQdxBy0L8ravYC_secret_vfYZeavcsAiwEVdJIiTiqyNtt", + "id" : "pi_3Pv618F7QokQdxBy0L8ravYC", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1725405610, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAlipayPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAlipayPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..d81cf462 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAlipayPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NS0jWONU%2FaT80cdH81D6JlqLtxzDdo%2B%2FbtXhzFMhAqFkwLsMds4FsQ7mnOqxrCUx%2Bp6c3ucHyTBH%2FAz0HveMQ7ONShyKDlLebhE5Sqh2NaaYTxZ5X4WHTW%2BCV5Jc5JdJNoo5bPukONcKG06J9jPD1x6ZiBVqX7Lp4MNp2oFWGLn0WW4%2FjCya2%2BXF6TxOZaiW80xZ13wuVwig%2Bk2AnNxpJCCOrGhQ0H4gWzgWWci5QqE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 12c35fa6d90a3d8576196a34eaa40ebd +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv616FY0qyl6XeW0ERcy4kq","secret":"pi_3Pv616FY0qyl6XeW0ERcy4kq_secret_0qYH0oQeuqSqFUreCsXHY76eE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAlipayPaymentIntent/0001_post_v1_payment_intents_pi_3Pv616FY0qyl6XeW0ERcy4kq_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAlipayPaymentIntent/0001_post_v1_payment_intents_pi_3Pv616FY0qyl6XeW0ERcy4kq_confirm.tail new file mode 100644 index 00000000..564336bb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmAlipayPaymentIntent/0001_post_v1_payment_intents_pi_3Pv616FY0qyl6XeW0ERcy4kq_confirm.tail @@ -0,0 +1,76 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv616FY0qyl6XeW0ERcy4kq\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dGAXEMktnTV3UT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1219 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:09 GMT +original-request: req_dGAXEMktnTV3UT +stripe-version: 2020-08-27 +idempotency-key: a4dff928-8d6b-41ff-8dfd-e1ee71b0edc9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv616FY0qyl6XeW0ERcy4kq_secret_0qYH0oQeuqSqFUreCsXHY76eE&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=alipay&payment_method_options\[alipay]\[app_bundle_id]=com\.apple\.dt\.xctest\.tool&payment_method_options\[alipay]\[app_version_key]=.*&return_url=foo%3A\/\/bar + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 2000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "alipay_handle_redirect", + "alipay_handle_redirect" : { + "native_url" : null, + "native_data" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1Pv617FY0qyl6XeWIaxjeZkC?client_secret=src_client_secret_ht9zWZYbmTghOeVpvj1G5gc4", + "return_url" : "foo:\/\/bar" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv617FY0qyl6XeWNWVOf3MN", + "client_secret" : "pi_3Pv616FY0qyl6XeW0ERcy4kq_secret_0qYH0oQeuqSqFUreCsXHY76eE", + "id" : "pi_3Pv616FY0qyl6XeW0ERcy4kq", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alipay" + ], + "setup_future_usage" : null, + "created" : 1725405608, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCanceledPaymentIntentFails/0000_post_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCanceledPaymentIntentFails/0000_post_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn_confirm.tail new file mode 100644 index 00000000..6c737984 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCanceledPaymentIntentFails/0000_post_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn_confirm.tail @@ -0,0 +1,79 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GGCGfFY0qyl6XeWbSAsh2hn\/confirm$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nEvpPtev41erkd +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1616 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:11 GMT +original-request: req_nEvpPtev41erkd +stripe-version: 2020-08-27 +idempotency-key: cf5ec971-68f5-4640-b913-0ab31016ee81 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe&source_data\[card]\[cvc]=123&source_data\[card]\[exp_month]=7&source_data\[card]\[exp_year]=2029&source_data\[card]\[number]=4000%200000%200000%203220&source_data\[guid]=.*&source_data\[muid]=.*&source_data\[payment_user_agent]=.*&source_data\[sid]=.*&source_data\[type]=card + +{ + "error" : { + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : 1582671568, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "canceled", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe", + "id" : "pi_1GGCGfFY0qyl6XeWbSAsh2hn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1582671165, + "description" : null + }, + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_nEvpPtev41erkd?t=1725405611", + "code" : "payment_intent_unexpected_state", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/payment-intent-unexpected-state", + "message" : "This PaymentIntent's source could not be updated because it has a status of canceled. You may only update the source of a PaymentIntent with one of the following statuses: requires_payment_method, requires_confirmation, requires_action.", + "param" : "source", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithInvalidNetworkParam/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithInvalidNetworkParam/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..a993dc4b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithInvalidNetworkParam/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fwtO2eH%2BaBcuJG8rJ7jSaj8k5D9lKpgHU4ctYgkCwwaf4dirrJbB0axLjOcCDDHTNOTQba4UKR9gh7z3UKBa3hxXTjqtT09QtnvX5wKKX5wzWlnm0auBOpRN8dpUXeE7tcWm1QSFsxRqD7e166VSYYUaTjlwXEfw2dMKQWcbHfcQPzEXG%2BgQ%2Fhgaix%2BljT7ugHTsPxMY3xv4abnXKRROb81aw%2FDMzXcNlsjsirrFVbA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fe0a4e6d3028b86cd86e20979033a590 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv619FY0qyl6XeW1W0j73U9","secret":"pi_3Pv619FY0qyl6XeW1W0j73U9_secret_WA1fIcRN851PjVp48ROSwmwQA","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithInvalidNetworkParam/0001_post_v1_payment_intents_pi_3Pv619FY0qyl6XeW1W0j73U9_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithInvalidNetworkParam/0001_post_v1_payment_intents_pi_3Pv619FY0qyl6XeW1W0j73U9_confirm.tail new file mode 100644 index 00000000..4f258c75 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithInvalidNetworkParam/0001_post_v1_payment_intents_pi_3Pv619FY0qyl6XeW1W0j73U9_confirm.tail @@ -0,0 +1,76 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv619FY0qyl6XeW1W0j73U9\/confirm$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_M8yuNK10CR07Rm +Content-Length: 1464 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:12 GMT +original-request: req_M8yuNK10CR07Rm +stripe-version: 2020-08-27 +idempotency-key: f51a92dd-30fe-4d39-91af-b123bf7550de +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv619FY0qyl6XeW1W0j73U9_secret_WA1fIcRN851PjVp48ROSwmwQA&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[exp_month]=7&payment_method_data\[card]\[exp_year]=2029&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[network]=fake_network + +{ + "error" : { + "param" : "payment_method_options[card][network]", + "message" : "Invalid payment_method_options[card][network]: must be one of amex, cartes_bancaires, diners, discover, eftpos_au, girocard, interac, jcb, mastercard, unionpay, visa, or unknown", + "payment_intent" : { + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Pv619FY0qyl6XeW1W0j73U9_secret_WA1fIcRN851PjVp48ROSwmwQA", + "id" : "pi_3Pv619FY0qyl6XeW1W0j73U9", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1725405611, + "description" : null + }, + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_M8yuNK10CR07Rm?t=1725405612" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithNetworkParam/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithNetworkParam/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..4b0b011d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithNetworkParam/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Gv%2FN2IqJ0xhFoPwM5JxyNhwHQ1bl9OGSzx1B%2BvYAMyof3anjtY34B5wYhG7uAThv0S%2BXls2%2BJDWiiHyAA32p9rKRyBKDd%2BbtfZq1U97qBiBo6ZMzjVCF4YHPNkwuzoGbvLfgwuvgc6F1n3nYWJ7vcC6wWQzx8EwyCx4K9WjFwPYHhYFdyDwLa1XWtRm7x2g88mPzfAz3teHs1Ml3W7z2l%2BOGRCfevuHsXKlWpt9y3a8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: aa56768e2d71835bb1e0482c19f7322b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61AFY0qyl6XeW1x6qFbMV","secret":"pi_3Pv61AFY0qyl6XeW1x6qFbMV_secret_BPfo2TO30MEYld0FU1GoUDmCl","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithNetworkParam/0001_post_v1_payment_intents_pi_3Pv61AFY0qyl6XeW1x6qFbMV_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithNetworkParam/0001_post_v1_payment_intents_pi_3Pv61AFY0qyl6XeW1x6qFbMV_confirm.tail new file mode 100644 index 00000000..cf3b8136 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithNetworkParam/0001_post_v1_payment_intents_pi_3Pv61AFY0qyl6XeW1x6qFbMV_confirm.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61AFY0qyl6XeW1x6qFbMV\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gL9i5a8BlRo3Pz +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 898 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:13 GMT +original-request: req_gL9i5a8BlRo3Pz +stripe-version: 2020-08-27 +idempotency-key: b859b4cf-a522-4d3d-93c7-d50bc83dc97f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61AFY0qyl6XeW1x6qFbMV_secret_BPfo2TO30MEYld0FU1GoUDmCl&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=7&payment_method_data\[card]\[exp_year]=2029&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&payment_method_options\[card]\[network]=visa + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61AFY0qyl6XeWnuP3slis", + "client_secret" : "pi_3Pv61AFY0qyl6XeW1x6qFbMV_secret_BPfo2TO30MEYld0FU1GoUDmCl", + "id" : "pi_3Pv61AFY0qyl6XeW1x6qFbMV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1725405612, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithoutNetworkParam/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithoutNetworkParam/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..726dca2c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithoutNetworkParam/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NPvOFLYezIN8NCtgsZFG%2BKDsCpn1DiVS1CSGW37FAY8ZGJV84jccoBQs5WmdtDz%2BjrI%2FcCvn3V3w%2FGn8EmSfS52a2EqZGhUTBgCVxn345VUi%2BocAgtvQUYnSytNzINQMLuhLAADO%2FJbAtbn61UMLRI%2BjFcVaZAAuZGN1TFpXDer542SQdya%2FVaPGIIJ6HQHouKi%2F%2BZKTETV2W1hzHpG4pe1oMekXv%2FHJTF6c0chCYFM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d7970901b579d3b066b0963e344b23f8 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:14 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61CFY0qyl6XeW1Vck7DU6","secret":"pi_3Pv61CFY0qyl6XeW1Vck7DU6_secret_bdwyVV2cqSrdW2RAQqeEJ99bv","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithoutNetworkParam/0001_post_v1_payment_intents_pi_3Pv61CFY0qyl6XeW1Vck7DU6_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithoutNetworkParam/0001_post_v1_payment_intents_pi_3Pv61CFY0qyl6XeW1Vck7DU6_confirm.tail new file mode 100644 index 00000000..e75c7109 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmCardWithoutNetworkParam/0001_post_v1_payment_intents_pi_3Pv61CFY0qyl6XeW1Vck7DU6_confirm.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61CFY0qyl6XeW1Vck7DU6\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Ax3dL2XfSC02Em +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 898 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:15 GMT +original-request: req_Ax3dL2XfSC02Em +stripe-version: 2020-08-27 +idempotency-key: e35db1e0-f124-4808-8239-80649d16dbe3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61CFY0qyl6XeW1Vck7DU6_secret_bdwyVV2cqSrdW2RAQqeEJ99bv&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=7&payment_method_data\[card]\[exp_year]=2029&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61CFY0qyl6XeW2f6zMsVv", + "client_secret" : "pi_3Pv61CFY0qyl6XeW1Vck7DU6_secret_bdwyVV2cqSrdW2RAQqeEJ99bv", + "id" : "pi_3Pv61CFY0qyl6XeW1Vck7DU6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1725405614, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..6eef01bf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=NtM%2F3%2FtaDIRfbgfQs%2BdTzE7c%2BvOEY5ygMWJFCj6Vlk8GZevRJVeRBk2Rldyo4CY9p3YJVAtMTARgyz7Eqmo6MrKYO0JLOjyel5rDsuXSUS8WOD9OmyAoVombwmxHaqGnLzTXZjL9JzxIyKiNMwoNAAWTRuCA7VxthUG5yDPtX3%2FithI650yzxYqh0PHShhPTCG3XkfJDfMY%2FbyPtWhdcSna6J%2BkL8AJZE%2BnWTFFyMNU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: de53c5e8ec535280d20a7092d48fc6e6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:15 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61DFY0qyl6XeW1XG2LmxJ","secret":"pi_3Pv61DFY0qyl6XeW1XG2LmxJ_secret_Nk71mi2u2DUEG1hxl1BrdaVD1","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds/0001_post_v1_payment_intents_pi_3Pv61DFY0qyl6XeW1XG2LmxJ_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds/0001_post_v1_payment_intents_pi_3Pv61DFY0qyl6XeW1XG2LmxJ_confirm.tail new file mode 100644 index 00000000..4ed1fe49 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardPaymentMethodSucceeds/0001_post_v1_payment_intents_pi_3Pv61DFY0qyl6XeW1XG2LmxJ_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61DFY0qyl6XeW1XG2LmxJ\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CAE3OEMArtrsat +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1343 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:16 GMT +original-request: req_CAE3OEMArtrsat +stripe-version: 2020-08-27 +idempotency-key: 7c7a91e2-8cb8-4c03-8788-366f1d911c44 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61DFY0qyl6XeW1XG2LmxJ_secret_Nk71mi2u2DUEG1hxl1BrdaVD1&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=7&payment_method_data\[card]\[exp_year]=2029&payment_method_data\[card]\[number]=4000000000003220&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&return_url=example-app-scheme%3A\/\/authorized + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/hooks.stripe.com\/3d_secure_2\/hosted?merchant=acct_1G6m1pFY0qyl6XeW&payment_intent=pi_3Pv61DFY0qyl6XeW1XG2LmxJ&payment_intent_client_secret=pi_3Pv61DFY0qyl6XeW1XG2LmxJ_secret_Nk71mi2u2DUEG1hxl1BrdaVD1&publishable_key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&source=payatt_3Pv61DFY0qyl6XeW11AXrU9f" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61EFY0qyl6XeWLqz1sPHY", + "client_secret" : "pi_3Pv61DFY0qyl6XeW1XG2LmxJ_secret_Nk71mi2u2DUEG1hxl1BrdaVD1", + "id" : "pi_3Pv61DFY0qyl6XeW1XG2LmxJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1725405615, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardSucceeds/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardSucceeds/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..411067f7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardSucceeds/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=fUL0m%2BDucD%2B82HSmWzWJxLWV%2B16Uw992qtrUJJnkgiilkNp1OV%2B1y%2F%2BxGaNVLSGFsGVWX60v7PZkO0CRjUp%2ByCH0pTMaxZfjLz%2FTEh2%2BjSZnRVmUG4tBlx3UEOKZJjtaGTKfPeyFGfeKL2JTFI2b2V9GP6lSmPmNK2N%2BPGMym%2B2mxR9kx0S4AP6fNh71SdlZO05OpMD%2F2R6yEQifnoGbTTpq3XyHO8LCDwg%2B0RiznQQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ee7a955fedc1a5fe532af1b81e8e02fe;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:17 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61FFY0qyl6XeW0SlUb1Sf","secret":"pi_3Pv61FFY0qyl6XeW0SlUb1Sf_secret_O9O4dvoznF6ztAo5toDx96GhQ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardSucceeds/0001_post_v1_payment_intents_pi_3Pv61FFY0qyl6XeW0SlUb1Sf_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardSucceeds/0001_post_v1_payment_intents_pi_3Pv61FFY0qyl6XeW0SlUb1Sf_confirm.tail new file mode 100644 index 00000000..661b5503 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWith3DSCardSucceeds/0001_post_v1_payment_intents_pi_3Pv61FFY0qyl6XeW0SlUb1Sf_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61FFY0qyl6XeW0SlUb1Sf\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gaFD53XdacQCBp +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1341 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:18 GMT +original-request: req_gaFD53XdacQCBp +stripe-version: 2020-08-27 +idempotency-key: 98437280-3349-4344-929d-b76232c1f301 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61FFY0qyl6XeW0SlUb1Sf_secret_O9O4dvoznF6ztAo5toDx96GhQ&return_url=example-app-scheme%3A\/\/authorized&source_data\[card]\[cvc]=123&source_data\[card]\[exp_month]=7&source_data\[card]\[exp_year]=2029&source_data\[card]\[number]=4000%200000%200000%203220&source_data\[guid]=.*&source_data\[muid]=.*&source_data\[payment_user_agent]=.*&source_data\[sid]=.*&source_data\[type]=card + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : "src_1Pv61FFY0qyl6XeWH60rBxmv", + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/hooks.stripe.com\/3d_secure_2\/hosted?merchant=acct_1G6m1pFY0qyl6XeW&payment_intent=pi_3Pv61FFY0qyl6XeW0SlUb1Sf&payment_intent_client_secret=pi_3Pv61FFY0qyl6XeW0SlUb1Sf_secret_O9O4dvoznF6ztAo5toDx96GhQ&publishable_key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&source=src_1Pv61GFY0qyl6XeWQtrMK3dM" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Pv61FFY0qyl6XeW0SlUb1Sf_secret_O9O4dvoznF6ztAo5toDx96GhQ", + "id" : "pi_3Pv61FFY0qyl6XeW0SlUb1Sf", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1725405617, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAffirm/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAffirm/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..145a8173 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAffirm/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=JtB%2BPT6RKyN7aQ%2FihMFVJ4VnvKEaVy9Kz%2FAjopBG%2FuVWmnT%2Fh82O%2BnxMUalmlWnm%2BClR4NdIb4RMHZFgPcrYfStdEZ8ToefMLbaUpugemqN9Tl52JW08Lr9grecbxt%2BWxErrzz3D5Lps1qwexqhi5KmFd0VvtJvJqhhzB3R1%2FMINijmRt3IaE8xhkI1EadyzNe0YTCDU8U2l9xYgP9Z1INfLVj3wJYzfbvIysHzHFUE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e44e85bd54ce38b81919398a2e366295 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:18 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61GFY0qyl6XeW0tFjVwat","secret":"pi_3Pv61GFY0qyl6XeW0tFjVwat_secret_QQbd0CoITVdv4qunC9kfua5TF","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAffirm/0001_post_v1_payment_intents_pi_3Pv61GFY0qyl6XeW0tFjVwat_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAffirm/0001_post_v1_payment_intents_pi_3Pv61GFY0qyl6XeW0tFjVwat_confirm.tail new file mode 100644 index 00000000..b0e743bc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAffirm/0001_post_v1_payment_intents_pi_3Pv61GFY0qyl6XeW0tFjVwat_confirm.tail @@ -0,0 +1,87 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61GFY0qyl6XeW0tFjVwat\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_FyI2vHocjJd6uB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1423 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:20 GMT +original-request: req_FyI2vHocjJd6uB +stripe-version: 2020-08-27 +idempotency-key: 23985d4d-b082-4f5c-a9a5-14ffa525bd5f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61GFY0qyl6XeW0tFjVwat_secret_QQbd0CoITVdv4qunC9kfua5TF&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=affirm&return_url=example-app-scheme%3A\/\/unused&shipping\[address]\[city]=San%20Francisco&shipping\[address]\[country]=US&shipping\[address]\[line1]=123%20Main%20St&shipping\[address]\[line2]=Apt%202&shipping\[address]\[postal_code]=94106&shipping\[address]\[state]=CA&shipping\[name]=Jane%20Doe + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "Jane Doe", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "Apt 2", + "city" : "San Francisco", + "line1" : "123 Main St", + "postal_code" : "94106" + } + }, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 6000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/affirm-hooks.stripe.com\/affirm\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QmfLMO4puhvjs5kpFXE0jawp9vsh1Al\/redirect" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61HFY0qyl6XeW2wZPs6pp", + "client_secret" : "pi_3Pv61GFY0qyl6XeW0tFjVwat_secret_QQbd0CoITVdv4qunC9kfua5TF", + "id" : "pi_3Pv61GFY0qyl6XeW0tFjVwat", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "affirm" + ], + "setup_future_usage" : null, + "created" : 1725405618, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAlma/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAlma/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..335a98ae --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAlma/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=nXqK8LlZ1YWSnPS6c3h3vFAFxddOKMruJR77sZP7RdPmmoONxgCPnBOe5xMwWdinK7p7WXpnmgJqLH91EZgruu8V2G8XlScUB6gweIX%2BwLafOPvmEus3NlqQ1504Ol7e%2B8MHZzAIznFaLECIiHXJbwl4Nx%2BHl0LXx1Ov1Ua%2FpeBrPvsib8TbvAZrWemmfFxDsDAshi22%2Bo97qNTGG7UN4GayZ0kQ2TU0cqv2uC4kmw4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: f5924c72b9422284a8d2aa308538f61a +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:21 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61IKG6vc7r7YC1r9aTyiP","secret":"pi_3Pv61IKG6vc7r7YC1r9aTyiP_secret_7ghgDT4Ni7VpySb9aoz3PUaJY","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAlma/0001_post_v1_payment_intents_pi_3Pv61IKG6vc7r7YC1r9aTyiP_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAlma/0001_post_v1_payment_intents_pi_3Pv61IKG6vc7r7YC1r9aTyiP_confirm.tail new file mode 100644 index 00000000..eca0f749 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAlma/0001_post_v1_payment_intents_pi_3Pv61IKG6vc7r7YC1r9aTyiP_confirm.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61IKG6vc7r7YC1r9aTyiP\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kZJvLtI1Duvbu6 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1041 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:21 GMT +original-request: req_kZJvLtI1Duvbu6 +stripe-version: 2020-08-27 +idempotency-key: 9f8a3a13-9943-4efd-beaf-020166932b00 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61IKG6vc7r7YC1r9aTyiP_secret_7ghgDT4Ni7VpySb9aoz3PUaJY&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=alma&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QmfMHFVDJqvDX4QKowMVY6vPApkyUb5" + } + }, + "payment_method" : "pm_1Pv61JKG6vc7r7YC2LR8LqaA", + "client_secret" : "pi_3Pv61IKG6vc7r7YC1r9aTyiP_secret_7ghgDT4Ni7VpySb9aoz3PUaJY", + "id" : "pi_3Pv61IKG6vc7r7YC1r9aTyiP", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "alma" + ], + "setup_future_usage" : null, + "created" : 1725405620, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAmazonPay/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAmazonPay/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..05edb485 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAmazonPay/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=aMShqMhEZ8IV5HNw2jwNUTnSHDQjNHL8dGQ%2Ba1UilX3ekng7cwKDYIq1Yta0MloGM%2BJQYT0eOMeLcZWNb00298Wncm2JsHQgw3kLo%2B%2FYkX1ld9mjAwKvmimyV0yPGMjt4qdmcvGDowyeZZscponYo2WUAwK9Vikr84iKUTXeyGys9wKFqxFO4QKL9XQeRQ6SAXnayBLC99CeXFligBqxOEfGsn1rcXk9K5NFQUUeYAE%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4149f8ea99aaca785bf70bd461a92fe0 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:21 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61JFY0qyl6XeW0AffwufX","secret":"pi_3Pv61JFY0qyl6XeW0AffwufX_secret_zd9U1YNK43252Z3r7D1j5wGOE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAmazonPay/0001_post_v1_payment_intents_pi_3Pv61JFY0qyl6XeW0AffwufX_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAmazonPay/0001_post_v1_payment_intents_pi_3Pv61JFY0qyl6XeW0AffwufX_confirm.tail new file mode 100644 index 00000000..46bb0186 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithAmazonPay/0001_post_v1_payment_intents_pi_3Pv61JFY0qyl6XeW0AffwufX_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61JFY0qyl6XeW0AffwufX\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_1WAumyfkYqE2ww +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1146 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:22 GMT +original-request: req_1WAumyfkYqE2ww +stripe-version: 2020-08-27 +idempotency-key: b371af15-75a4-45f1-9d57-5c0aefad53b3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61JFY0qyl6XeW0AffwufX_secret_zd9U1YNK43252Z3r7D1j5wGOE&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=amazon_pay&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 6000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QmfM7i4lJAG2b9Pn5M02M9Ob2ElSUhu" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61KFY0qyl6XeWItaFKarj", + "client_secret" : "pi_3Pv61JFY0qyl6XeW0AffwufX_secret_zd9U1YNK43252Z3r7D1j5wGOE", + "id" : "pi_3Pv61JFY0qyl6XeW0AffwufX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1725405621, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBLIK/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBLIK/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..bfc85a79 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBLIK/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=c06%2BF1drRDVMglx4I6eXrAp9d6dkbOKmWqMYyLYqIjt1q8BIfwNCXwR47VxA5eugBg8Ug7idybfIV6kFIMDWUcfXk2F%2FmLASBOoLHSLC%2BYsl3ZMnGOqy2trsM%2FGpK9%2F6c2UgvVT5Hq03sqW4ToupbPJTX7oJHEuxoVhZIsm0zymXmP4GqnMAEeTPiYutgesQZlrns92S50O1xXinzxFDeSfi79bBrQh6DIQIGPou3CY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ad461c9ee3451bc5270c8b67fbc227ad +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61NArGMi59tL40M68bKvk","secret":"pi_3Pv61NArGMi59tL40M68bKvk_secret_NFSSGUOhOKrOqsRGVtSztITwD","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBLIK/0001_post_v1_payment_intents_pi_3Pv61NArGMi59tL40M68bKvk_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBLIK/0001_post_v1_payment_intents_pi_3Pv61NArGMi59tL40M68bKvk_confirm.tail new file mode 100644 index 00000000..4411347c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBLIK/0001_post_v1_payment_intents_pi_3Pv61NArGMi59tL40M68bKvk_confirm.tail @@ -0,0 +1,65 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61NArGMi59tL40M68bKvk\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cnx1LTHTp2yYns +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 837 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:26 GMT +original-request: req_cnx1LTHTp2yYns +stripe-version: 2020-08-27 +idempotency-key: 43da722c-8baa-4de7-aae0-fd0a1e0aefc5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61NArGMi59tL40M68bKvk_secret_NFSSGUOhOKrOqsRGVtSztITwD&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=blik&payment_method_options\[blik]\[code]=123456&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "pln", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "blik_authorize" + }, + "payment_method" : "pm_1Pv61NArGMi59tL4bZQ7mFh5", + "client_secret" : "pi_3Pv61NArGMi59tL40M68bKvk_secret_NFSSGUOhOKrOqsRGVtSztITwD", + "id" : "pi_3Pv61NArGMi59tL40M68bKvk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "blik" + ], + "setup_future_usage" : null, + "created" : 1725405625, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBancontact/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBancontact/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..e16b6445 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBancontact/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=ZqR6F4lUGrVnbCBH5wlth4f%2BnFhciJVtD792oPOXO78gne5Glkwxi8C4iGM5n6OQr7on9st7IZnyyCkapJQcdB0Fk9U2iW6xbFtvoZPcUNc45r1D8aTmySYlWg6%2FOrGGJ3jADwnjvijhFAar%2BEeDZIVJu1WbRsizTt7pwYQPMKW3cfdG%2BsL195%2BOa5O%2BtWsIVD1zClzajCsUrvZnIZPeQuRO84pjnsFuwoObz%2FWP%2BgA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 847a405e4b0b3048d476ab2fece49bd4 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61LFY0qyl6XeW0HQ7pNyM","secret":"pi_3Pv61LFY0qyl6XeW0HQ7pNyM_secret_FpcM4fxislq4dnA4WOIiwOqdb","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBancontact/0001_post_v1_payment_intents_pi_3Pv61LFY0qyl6XeW0HQ7pNyM_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBancontact/0001_post_v1_payment_intents_pi_3Pv61LFY0qyl6XeW0HQ7pNyM_confirm.tail new file mode 100644 index 00000000..b541a1fc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBancontact/0001_post_v1_payment_intents_pi_3Pv61LFY0qyl6XeW0HQ7pNyM_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61LFY0qyl6XeW0HQ7pNyM\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IOEe2ZhQJ0DKtX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1148 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:23 GMT +original-request: req_IOEe2ZhQJ0DKtX +stripe-version: 2020-08-27 +idempotency-key: 776e1432-5ef4-46d8-9b35-3d8ad6389ce0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61LFY0qyl6XeW0HQ7pNyM_secret_FpcM4fxislq4dnA4WOIiwOqdb&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=bancontact&return_url=example-app-scheme%3A\/\/authorized + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QmfMK7i0PC7oB56ewOu0vney5Oy3KN9" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61LFY0qyl6XeWpgroSG3y", + "client_secret" : "pi_3Pv61LFY0qyl6XeW0HQ7pNyM_secret_FpcM4fxislq4dnA4WOIiwOqdb", + "id" : "pi_3Pv61LFY0qyl6XeW0HQ7pNyM", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1725405623, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBillie/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBillie/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..b8007bd9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBillie/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Dh38f%2BcSloVn2UgDPMcMPaXiAE2Rsy1TQnXIjCuNlOnV8TNnWCZdgrCbkxvuIyfV4vI4OchxOVJ5VBtR6Em9mWp25BpUVjqURCST%2B%2B8%2FSd%2BiDb0qw6zdBqwtiPTc%2BwDWQ5PpbTSaO2nu%2BXFfgBzy%2F4NxFkJgNdIWeoVy9mLMoGHC%2FRm0HziCNO7LCowcK2PQY9WGocCtKNlPe1RrC1ivlJ84DdICqtEIJCFEJPHhKXI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0c7bc56a3409df60b8f83558716a3768 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:24 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61MAlz2yHYCNZ0CAh6Ru8","secret":"pi_3Pv61MAlz2yHYCNZ0CAh6Ru8_secret_Y42VyRVYwLjdxr3bxRv6Lu5SL","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBillie/0001_post_v1_payment_intents_pi_3Pv61MAlz2yHYCNZ0CAh6Ru8_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBillie/0001_post_v1_payment_intents_pi_3Pv61MAlz2yHYCNZ0CAh6Ru8_confirm.tail new file mode 100644 index 00000000..c49ccbf2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithBillie/0001_post_v1_payment_intents_pi_3Pv61MAlz2yHYCNZ0CAh6Ru8_confirm.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61MAlz2yHYCNZ0CAh6Ru8\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Vm9G0Nb1fyr87D +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1043 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:24 GMT +original-request: req_Vm9G0Nb1fyr87D +stripe-version: 2020-08-27 +idempotency-key: 886e0c42-d50b-4333-806f-91f53a655934 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61MAlz2yHYCNZ0CAh6Ru8_secret_Y42VyRVYwLjdxr3bxRv6Lu5SL&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=billie&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnNaAlz2yHYCNZ\/pa_nonce_QmfMqJMWWKvYp9FjsKUdIUtNcmnKOjs" + } + }, + "payment_method" : "pm_1Pv61MAlz2yHYCNZ2fEwboor", + "client_secret" : "pi_3Pv61MAlz2yHYCNZ0CAh6Ru8_secret_Y42VyRVYwLjdxr3bxRv6Lu5SL", + "id" : "pi_3Pv61MAlz2yHYCNZ0CAh6Ru8", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "billie" + ], + "setup_future_usage" : null, + "created" : 1725405624, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithCrypto/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithCrypto/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..6c5bf346 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithCrypto/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=CedNxsigcEwUINzVWfdh0TKipI4aE2UMxz8iI0EeMMuHm72SkuYGPG0I9vKCQUVkN9jm633NmpDLEZvObjC9O0CbCFucYWARdlQiZOZzjzCm4PQicqJ05Btn903Inlo%2F1TVwXoFtybgpft%2FYw9Pe6zPNXxnHGidGJVnJ6ZDBNtnFFaijW2%2FvfaFWNa3uM%2FWs07%2FGmQPREtosRF1jnIWKUE%2BKvfv9Kj3hd%2FxRLzro59U%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fc39f7625804b2320c560d367044f15f;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 21 Nov 2024 22:17:31 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3QNigpFY0qyl6XeW0hHlAmk2","secret":"pi_3QNigpFY0qyl6XeW0hHlAmk2_secret_And197hbBohPSmyW8s9VJi86h","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithCrypto/0001_post_v1_payment_intents_pi_3QNigpFY0qyl6XeW0hHlAmk2_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithCrypto/0001_post_v1_payment_intents_pi_3QNigpFY0qyl6XeW0hHlAmk2_confirm.tail new file mode 100644 index 00000000..53ee170e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithCrypto/0001_post_v1_payment_intents_pi_3QNigpFY0qyl6XeW0hHlAmk2_confirm.tail @@ -0,0 +1,75 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3QNigpFY0qyl6XeW0hHlAmk2\/confirm$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true} +request-id: req_6M9r3Cb82vtM93 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1140 +Vary: Origin +Date: Thu, 21 Nov 2024 22:17:36 GMT +original-request: req_6M9r3Cb82vtM93 +stripe-version: 2020-08-27 +idempotency-key: 0ffb131d-6a63-4664-9734-751337347ea6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3QNigpFY0qyl6XeW0hHlAmk2_secret_And197hbBohPSmyW8s9VJi86h&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=crypto&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_RGFB8PslpY1GVcOrt4qAPHK88TTrJL8" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1QNigpFY0qyl6XeWDpW2ZD5Y", + "client_secret" : "pi_3QNigpFY0qyl6XeW0hHlAmk2_secret_And197hbBohPSmyW8s9VJi86h", + "id" : "pi_3QNigpFY0qyl6XeW0hHlAmk2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "crypto" + ], + "setup_future_usage" : null, + "created" : 1732227451, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithEPS/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithEPS/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..d13888bc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithEPS/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=yfzZ0O9wPnmEGPyL09yGe3p9gdFHLC8dj%2Fpd%2F6sZfU%2B3zV6h71GNgCPgVwVU%2F2uLGvzAONMLYemfegqAUH0x0ccdPR%2BQseXOCCvml2j9qBIOyhOY%2BZBBZ5s1SK9uZOT94U4z3Ljx8qCvdi5bmznty7KlPqvc5U58MOCfwH0enQeexWITcDwbnE4pOPvZIWSvlmq4KzXNE1h7KDHM6EhyJ%2FUAYRbAQt9OofC0shV2YJs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0b91926abcb8f650683a47ef6951269b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:26 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61OFY0qyl6XeW1T1VgtLz","secret":"pi_3Pv61OFY0qyl6XeW1T1VgtLz_secret_T7DLZeI4aNNl4FvL9ArBbgEKj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithEPS/0001_post_v1_payment_intents_pi_3Pv61OFY0qyl6XeW1T1VgtLz_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithEPS/0001_post_v1_payment_intents_pi_3Pv61OFY0qyl6XeW1T1VgtLz_confirm.tail new file mode 100644 index 00000000..d26066fc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithEPS/0001_post_v1_payment_intents_pi_3Pv61OFY0qyl6XeW1T1VgtLz_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61OFY0qyl6XeW1T1VgtLz\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hnwnFgNacVPajV +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1141 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:27 GMT +original-request: req_hnwnFgNacVPajV +stripe-version: 2020-08-27 +idempotency-key: 6edd2754-cbdd-47d9-82d1-f781ab7aeed6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61OFY0qyl6XeW1T1VgtLz_secret_T7DLZeI4aNNl4FvL9ArBbgEKj&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Jenny%20Rosen&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=eps&return_url=example-app-scheme%3A\/\/authorized + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QmfMLm7hGej44Q46D4TusRomPscmvFV" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61OFY0qyl6XeW85ekzkP9", + "client_secret" : "pi_3Pv61OFY0qyl6XeW1T1VgtLz_secret_T7DLZeI4aNNl4FvL9ArBbgEKj", + "id" : "pi_3Pv61OFY0qyl6XeW1T1VgtLz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "eps" + ], + "setup_future_usage" : null, + "created" : 1725405626, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithGrabPay/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithGrabPay/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..0a194ad3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithGrabPay/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=4Zqmf3UCEi6nnK0k3fsFIAGbyvXYMstQWdzBntT%2BSDP9fZRRedG0Z2fofDzX8DCARHe4ka2J%2FxR7Xoi%2F3ie3IFPcdWSfjc2n7c0ncBQHgTHIi3gag73wNUOwUFkyUImefZFqeqWDgQkbxJszIAZl9jCR%2FlUtwbiNsIj62hbAxXGHVqBInItFmWJFTaT7TzawEUy6P7RkNe5BvOS2SN3VqBbuWfsUZhrAcXEzPi%2BuuH8%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 265d8acf2dc9667f79e4a53ec8b5eb43;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:27 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61PAOnZToJom10WlJglWX","secret":"pi_3Pv61PAOnZToJom10WlJglWX_secret_TmMO1sJ4shtOPFGSTeMDTFWKm","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithGrabPay/0001_post_v1_payment_intents_pi_3Pv61PAOnZToJom10WlJglWX_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithGrabPay/0001_post_v1_payment_intents_pi_3Pv61PAOnZToJom10WlJglWX_confirm.tail new file mode 100644 index 00000000..62e335fb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithGrabPay/0001_post_v1_payment_intents_pi_3Pv61PAOnZToJom10WlJglWX_confirm.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61PAOnZToJom10WlJglWX\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xVK2AUIrn338VZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1047 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:27 GMT +original-request: req_xVK2AUIrn338VZ +stripe-version: 2020-08-27 +idempotency-key: 65c80dfc-e14d-4a35-827c-5c23a4598711 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61PAOnZToJom10WlJglWX_secret_TmMO1sJ4shtOPFGSTeMDTFWKm&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Jenny%20Rosen&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=grabpay&return_url=example-app-scheme%3A\/\/authorized + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "sgd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1H7oXMAOnZToJom1\/pa_nonce_QmfMWpZ5HvtEhFllXqDG11LgE33YDp5" + } + }, + "payment_method" : "pm_1Pv61PAOnZToJom1vPAv7Heq", + "client_secret" : "pi_3Pv61PAOnZToJom10WlJglWX_secret_TmMO1sJ4shtOPFGSTeMDTFWKm", + "id" : "pi_3Pv61PAOnZToJom10WlJglWX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "grabpay" + ], + "setup_future_usage" : null, + "created" : 1725405627, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMobilePay/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMobilePay/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..15faaf74 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMobilePay/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=00%2BDlgcULGJilsHUsxAtayWI4fo9IdYh6yKZ362Wh8i9oK5CP%2Fi0F5pBZSGR0MKwmAePdikA2tvmP0ECIzEz8QvaOlVAl%2Fn8BaGOPvCNVAfczS%2FGiEKIlybt0IE1ASZAHe263x5ix%2Fi4qky33VaMQtg%2FTi1FfMr%2FLyJRA9El8KHOzzE7ZFvWRlxh3RubWBwYL2k18Yy10kzmiiY9EV1JVnxl11KZz6FQ2drkZ2BenKI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 003b0e23de035acb68a361ba1f645661 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:28 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61QKG6vc7r7YC0kWsmOci","secret":"pi_3Pv61QKG6vc7r7YC0kWsmOci_secret_fLlu0KKTNGHmFsKI27NpS0Opk","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMobilePay/0001_post_v1_payment_intents_pi_3Pv61QKG6vc7r7YC0kWsmOci_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMobilePay/0001_post_v1_payment_intents_pi_3Pv61QKG6vc7r7YC0kWsmOci_confirm.tail new file mode 100644 index 00000000..0b10e46b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMobilePay/0001_post_v1_payment_intents_pi_3Pv61QKG6vc7r7YC0kWsmOci_confirm.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61QKG6vc7r7YC0kWsmOci\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6Eps90TatIizOP +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1046 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:28 GMT +original-request: req_6Eps90TatIizOP +stripe-version: 2020-08-27 +idempotency-key: 354d4edf-f784-41b0-b309-5d9543a9e869 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61QKG6vc7r7YC0kWsmOci_secret_fLlu0KKTNGHmFsKI27NpS0Opk&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=mobilepay&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "dkk", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1JtgfQKG6vc7r7YC\/pa_nonce_QmfMcYh0wOusVFbEQZFb63fQ7vdFOm6" + } + }, + "payment_method" : "pm_1Pv61QKG6vc7r7YC6bHaINAQ", + "client_secret" : "pi_3Pv61QKG6vc7r7YC0kWsmOci_secret_fLlu0KKTNGHmFsKI27NpS0Opk", + "id" : "pi_3Pv61QKG6vc7r7YC0kWsmOci", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1725405628, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMultibanco/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMultibanco/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..e668d606 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMultibanco/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=3g9K48KjeaYRQPSkTy2bqhQg3rWzMjPaIu4boTKagaj%2Blk84dnHqbOUlyThC6plhs1F6KgWAisdXNE6n1ThZeaC3zplENehiy9Za%2F6hxpkm8jgqhiauHbYUxK5EEI1zSar01RU2o7qo%2BXMoFSN4kcSQrJXMYgJtlapEPLx2sRS7aQo6BpcUoOgIh9mptN6ewnZq8JTSFvj11%2FyrcLkytOgSb7y63wRrVrRC6%2Bzhetmg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8653977e099faa8b905e361501635b8e +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:29 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61RFY0qyl6XeW0eIToC3n","secret":"pi_3Pv61RFY0qyl6XeW0eIToC3n_secret_Kr457FwNjXjTJ8JSYGXkj7pkj","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMultibanco/0001_post_v1_payment_intents_pi_3Pv61RFY0qyl6XeW0eIToC3n_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMultibanco/0001_post_v1_payment_intents_pi_3Pv61RFY0qyl6XeW0eIToC3n_confirm.tail new file mode 100644 index 00000000..c97ec7b9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithMultibanco/0001_post_v1_payment_intents_pi_3Pv61RFY0qyl6XeW0eIToC3n_confirm.tail @@ -0,0 +1,76 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61RFY0qyl6XeW0eIToC3n\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DbeGvtyFiR0pKQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1253 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:30 GMT +original-request: req_DbeGvtyFiR0pKQ +stripe-version: 2020-08-27 +idempotency-key: 71b90e87-5c24-4420-86e7-5cdb73b7c82a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61RFY0qyl6XeW0eIToC3n_secret_Kr457FwNjXjTJ8JSYGXkj7pkj&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=tester%40example\.com&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=multibanco&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 6000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "multibanco_display_details", + "multibanco_display_details" : { + "expires_at" : 1726010429, + "reference" : "123456789", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/multibanco\/voucher\/test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLF9RbWZNWkMzbG5DSEZ1bkN2NjJqVVRwQ3JhZU0wWHpK0100WyYJ5MKv", + "entity" : "12345" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61RFY0qyl6XeWQruYViyo", + "client_secret" : "pi_3Pv61RFY0qyl6XeW0eIToC3n_secret_Kr457FwNjXjTJ8JSYGXkj7pkj", + "id" : "pi_3Pv61RFY0qyl6XeW0eIToC3n", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1725405629, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithOXXO/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithOXXO/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..59f970ad --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithOXXO/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=qHGC79RG7SXFrfFFeGnhDE2xBNyxEUlYOG5dguK1yl604mbS5e08RnAcybb7evJVCFRNb6qYS1BVgS%2BmCFHGtVFv6zRbxGG5VJ0XppeFNv7L%2FGjvabqVwR9LLul4qxId%2BylTrbpcrAaO8j%2BJ%2B5yP034JYXUuWHigc3yEWaswWuJu4%2FES2XVnSBGSMcaDZg8%2FrKPWBjX0wVKN4s%2BZr9mXicHemiL45Kb1af3AQvbqRug%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8e4c81e4c9c071bf5cec87ab8c451940 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:30 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61SHNG4o8pO5l0x76K9dn","secret":"pi_3Pv61SHNG4o8pO5l0x76K9dn_secret_Js0E28wYnvV3EDzybzpbEgNIo","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithOXXO/0001_post_v1_payment_intents_pi_3Pv61SHNG4o8pO5l0x76K9dn_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithOXXO/0001_post_v1_payment_intents_pi_3Pv61SHNG4o8pO5l0x76K9dn_confirm.tail new file mode 100644 index 00000000..872a2475 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithOXXO/0001_post_v1_payment_intents_pi_3Pv61SHNG4o8pO5l0x76K9dn_confirm.tail @@ -0,0 +1,70 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61SHNG4o8pO5l0x76K9dn\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LnwvrAyIXIUmxd +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1128 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:31 GMT +original-request: req_LnwvrAyIXIUmxd +stripe-version: 2020-08-27 +idempotency-key: f07d23ae-30ca-4ae9-a532-70de81a9cd79 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61SHNG4o8pO5l0x76K9dn_secret_Js0E28wYnvV3EDzybzpbEgNIo&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=email%40email\.com&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=oxxo + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "oxxo_display_details", + "oxxo_display_details" : { + "number" : "12345678901234657890123456789012", + "hosted_voucher_url" : "https:\/\/payments.stripe.com\/oxxo\/voucher\/test_YWNjdF8xR3ZBWTVITkc0bzhwTzVsLF9RbWZNdXpFa29pb1FPbDVQZzl4Sm14RDNvQlRhRTBJ0100B9w4Jy49", + "expires_after" : 1725688799 + } + }, + "payment_method" : "pm_1Pv61SHNG4o8pO5lpuitmKPr", + "client_secret" : "pi_3Pv61SHNG4o8pO5l0x76K9dn_secret_Js0E28wYnvV3EDzybzpbEgNIo", + "id" : "pi_3Pv61SHNG4o8pO5l0x76K9dn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1725405630, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPayPal/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPayPal/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..73e477c6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPayPal/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=dnOJFaOhL9mj1929FzNCxy34YaD4uNPZu%2FBe5OPuXov4egIbldhwLFxplN7drlb6xVBnUZwrXYF4QHGeFXqK7DCoEDjSU6tZ5vLHmorZkUGzOyIUrfxp2WNyL1880B9zssR8MR3D0BHkU6i7VgLtV5NM8kB3vVOZR3I5RoXUYelvFBeh1YcgBegnKZUvIPvGZBvhTrmp7SrZbyZ52O2DauphbYWP%2FvWAg4MvTuXZFlU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 26a369daab70d89f6681f44e556b8b9b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:31 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61TArGMi59tL40FdmF02x","secret":"pi_3Pv61TArGMi59tL40FdmF02x_secret_F15IAeOC9sW2ENSup4AterUq8","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPayPal/0001_post_v1_payment_intents_pi_3Pv61TArGMi59tL40FdmF02x_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPayPal/0001_post_v1_payment_intents_pi_3Pv61TArGMi59tL40FdmF02x_confirm.tail new file mode 100644 index 00000000..cdae4e9f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPayPal/0001_post_v1_payment_intents_pi_3Pv61TArGMi59tL40FdmF02x_confirm.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61TArGMi59tL40FdmF02x\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_IX2xfLdTqb78cd +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1074 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:31 GMT +original-request: req_IX2xfLdTqb78cd +stripe-version: 2020-08-27 +idempotency-key: db81f370-f93d-46f8-84c0-c8868da6ccca +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61TArGMi59tL40FdmF02x_secret_F15IAeOC9sW2ENSup4AterUq8&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[name]=Jane%20Doe&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=paypal&return_url=example-app-scheme%3A\/\/authorized + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1Pv61TArGMi59tL41k2z6PQM?client_secret=src_client_secret_IbuOAcJGvuLou9a72eWXxvSO" + } + }, + "payment_method" : "pm_1Pv61TArGMi59tL48q2WuWof", + "client_secret" : "pi_3Pv61TArGMi59tL40FdmF02x_secret_F15IAeOC9sW2ENSup4AterUq8", + "id" : "pi_3Pv61TArGMi59tL40FdmF02x", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1725405631, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPrzelewy24/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPrzelewy24/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..9d7729a3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPrzelewy24/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=2wypNT3xDtjO%2F1jjTYcnR1Hh7bVT4DZLNWj8r7LuQR089WRWUrW7wTbksTcJgsRyJHf6%2Fa2uAfVD3DIT%2FFBXhjbZStieIUM4bO2ylnFUr4Rd0A7Q0T1Acr%2BPz6tfEGw%2Fr%2FXpqFoMNYa0V%2FAC1mJQb95WtFTyUnjy9WYOE85Ry2rqSSnl47V%2F7po253NdSZ3l1qlsAUi8xsRZ6wlVpmgAYWB8bAsc2WgGzD%2F0gWLAFgI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e67b96a709234e4eaeeafa9c529c6be5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:32 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61UFY0qyl6XeW01w5AZqX","secret":"pi_3Pv61UFY0qyl6XeW01w5AZqX_secret_VDUXGbT0HlPS9SrHrqUxv2loz","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPrzelewy24/0001_post_v1_payment_intents_pi_3Pv61UFY0qyl6XeW01w5AZqX_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPrzelewy24/0001_post_v1_payment_intents_pi_3Pv61UFY0qyl6XeW01w5AZqX_confirm.tail new file mode 100644 index 00000000..3e456454 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithPrzelewy24/0001_post_v1_payment_intents_pi_3Pv61UFY0qyl6XeW01w5AZqX_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61UFY0qyl6XeW01w5AZqX\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TwQCaxTyYLz6nF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1141 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:32 GMT +original-request: req_TwQCaxTyYLz6nF +stripe-version: 2020-08-27 +idempotency-key: 945a84a5-ac2d-4885-bcb0-a95faf666811 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61UFY0qyl6XeW01w5AZqX_secret_VDUXGbT0HlPS9SrHrqUxv2loz&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=email%40email\.com&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=p24&return_url=example-app-scheme%3A\/\/authorized + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QmfMpfCmUZ3Rfobut8U26DW54mJ2A1F" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61UFY0qyl6XeWdyPzhwRY", + "client_secret" : "pi_3Pv61UFY0qyl6XeW01w5AZqX_secret_VDUXGbT0HlPS9SrHrqUxv2loz", + "id" : "pi_3Pv61UFY0qyl6XeW01w5AZqX", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "p24" + ], + "setup_future_usage" : null, + "created" : 1725405632, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSatispay/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSatispay/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..9403eecc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSatispay/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=YjJaEKTLP9iO2v1um4bObTC8mYabQ7UiVMmbU8%2B1HLvuIY8NLEURG7wNmSHExBp6z8LHY1zHWSfyMHwPBPu07pkYUZl1mwiHQb7rD638dMoFkzFIVN0s4bgnZKA0nkWisd4ccSSOu1K8r8LZaUdJcq5tho4%2BRo1GCLaUSeX2eT3M8Ui%2BH8oBJ2Yz0je0mlDA42oxAkVTPLmcmhed6r0irKiZ%2BPgFbhJq3DGGpsxY3Oo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 6e24aa1d62e3f62217b70a650dc52cbf +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:33 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61VIFbdis1OxT1lYj3EQb","secret":"pi_3Pv61VIFbdis1OxT1lYj3EQb_secret_taXC9alqlxIaGIlUEPhq9Rqo6","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSatispay/0001_post_v1_payment_intents_pi_3Pv61VIFbdis1OxT1lYj3EQb_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSatispay/0001_post_v1_payment_intents_pi_3Pv61VIFbdis1OxT1lYj3EQb_confirm.tail new file mode 100644 index 00000000..c135339a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSatispay/0001_post_v1_payment_intents_pi_3Pv61VIFbdis1OxT1lYj3EQb_confirm.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61VIFbdis1OxT1lYj3EQb\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kZnG2sLT8mbb2R +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1045 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:33 GMT +original-request: req_kZnG2sLT8mbb2R +stripe-version: 2020-08-27 +idempotency-key: 8de3d177-0f1d-4b7a-871e-0c02f9eefd4d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61VIFbdis1OxT1lYj3EQb_secret_taXC9alqlxIaGIlUEPhq9Rqo6&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=satispay&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1PSnETIFbdis1OxT\/pa_nonce_QmfMIWWqXeoj7FuMa1qqLv4I5lSyxNK" + } + }, + "payment_method" : "pm_1Pv61VIFbdis1OxTZAdUMAYu", + "client_secret" : "pi_3Pv61VIFbdis1OxT1lYj3EQb_secret_taXC9alqlxIaGIlUEPhq9Rqo6", + "id" : "pi_3Pv61VIFbdis1OxT1lYj3EQb", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "satispay" + ], + "setup_future_usage" : null, + "created" : 1725405633, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithShippingDetailsSucceeds/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithShippingDetailsSucceeds/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..c34d1a91 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithShippingDetailsSucceeds/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=1Cmxt1UdTlqAXCdXiZmLRScdOLhfKc05nKnOPyry9ZiiblMBM0UsuZki3rYLdAYwjIlVbFdQ1VD1D%2Bst%2F9Do%2BQia4W6iyOYepCN2d8muAVqQyZiVwBHUKmEWn67VaKvjcsjI2vXAfg040TId2G4WPeeMwCPFKj1XdH2yHGbsJlOm5ns90m8JW2e1gi6L%2BqEzFl%2FhBMyaQFssegbhjwwd1oPTI3DXXN4Hn3uZoBFaCJs%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 88014c4eef35ceb853e16f5a2c0c0017 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:34 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61WFY0qyl6XeW0KKcRBfd","secret":"pi_3Pv61WFY0qyl6XeW0KKcRBfd_secret_KDhGmUbGaMLtBOznEureeYlAT","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithShippingDetailsSucceeds/0001_post_v1_payment_intents_pi_3Pv61WFY0qyl6XeW0KKcRBfd_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithShippingDetailsSucceeds/0001_post_v1_payment_intents_pi_3Pv61WFY0qyl6XeW0KKcRBfd_confirm.tail new file mode 100644 index 00000000..c52f56ca --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithShippingDetailsSucceeds/0001_post_v1_payment_intents_pi_3Pv61WFY0qyl6XeW0KKcRBfd_confirm.tail @@ -0,0 +1,81 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61WFY0qyl6XeW0KKcRBfd\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mt3ixhvoYaH1pA +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1184 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:35 GMT +original-request: req_mt3ixhvoYaH1pA +stripe-version: 2020-08-27 +idempotency-key: 5f02251e-497b-4d85-878e-0c04c6d174ff +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61WFY0qyl6XeW0KKcRBfd_secret_KDhGmUbGaMLtBOznEureeYlAT&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=7&payment_method_data\[card]\[exp_year]=2029&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card&shipping\[address]\[city]=San%20Francisco&shipping\[address]\[country]=US&shipping\[address]\[line1]=123%20Main%20St&shipping\[address]\[line2]=Apt%202&shipping\[address]\[postal_code]=94106&shipping\[address]\[state]=CA&shipping\[carrier]=UPS&shipping\[name]=Jane&shipping\[phone]=555-555-5555&shipping\[tracking_number]=123abc + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : "123abc", + "phone" : "555-555-5555", + "carrier" : "UPS", + "name" : "Jane", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "Apt 2", + "city" : "San Francisco", + "line1" : "123 Main St", + "postal_code" : "94106" + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61WFY0qyl6XeWxxD21FkB", + "client_secret" : "pi_3Pv61WFY0qyl6XeW0KKcRBfd_secret_KDhGmUbGaMLtBOznEureeYlAT", + "id" : "pi_3Pv61WFY0qyl6XeW0KKcRBfd", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1725405634, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSunbit/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSunbit/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..d5392863 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSunbit/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Q0qhiPnu2KR00HM2%2FR4oB%2B7gepeN9%2BCGk011C2m6ABmw8hgcKybjCl9DTLjiF0zNsM29tCu6fLh4xuE%2BU9li3UMd0WTGPEsYkTFYkIOiF17cTdorQWj02cCEPfbTDBQWVcBw3P0HaNLBz%2BrTxwLt%2FRjGx%2FxiOWJvv2zOpKB03%2FK4hkIxp5yjkuf81gLp4vRDKpAy5ntuMKSCkXOHGlUUKZPbA02zzxUwoLYvOs55tvU%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 8383d617007946f1bac69fc2ccb18583;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61ZFY0qyl6XeW05ezvGnB","secret":"pi_3Pv61ZFY0qyl6XeW05ezvGnB_secret_07c9DamCYOe9zCZBILus8gNWR","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSunbit/0001_post_v1_payment_intents_pi_3Pv61ZFY0qyl6XeW05ezvGnB_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSunbit/0001_post_v1_payment_intents_pi_3Pv61ZFY0qyl6XeW05ezvGnB_confirm.tail new file mode 100644 index 00000000..3a2935fd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithSunbit/0001_post_v1_payment_intents_pi_3Pv61ZFY0qyl6XeW05ezvGnB_confirm.tail @@ -0,0 +1,74 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61ZFY0qyl6XeW05ezvGnB\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_L6jkOfdLZt1CLC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1142 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:38 GMT +original-request: req_L6jkOfdLZt1CLC +stripe-version: 2020-08-27 +idempotency-key: 2386a210-faa7-415d-b554-8dc6aa6d7aa4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61ZFY0qyl6XeW05ezvGnB_secret_07c9DamCYOe9zCZBILus8gNWR&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=sunbit&return_url=example-app-scheme%3A\/\/unused + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_action", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 6000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/unused", + "url" : "https:\/\/pm-redirects.stripe.com\/authorize\/acct_1G6m1pFY0qyl6XeW\/pa_nonce_QmfMw31OWQHW1TjPycCclG2t1BAHB6E" + } + }, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1Pv61ZFY0qyl6XeWh4OS7AhE", + "client_secret" : "pi_3Pv61ZFY0qyl6XeW05ezvGnB_secret_07c9DamCYOe9zCZBILus8gNWR", + "id" : "pi_3Pv61ZFY0qyl6XeW05ezvGnB", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1725405637, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..b14c331f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=TrQALRSyymh48Ku2PKw2GhEbmv2jstpQ1tKZpEZWS6DqhN4gdtk86TfDm3Nbm5UZhUJ1%2F0ADkDseKEfoH5S53Qd8OeFp7t8OFby0dtxioLmxLL8KUG06hFjrLii2mC3JsjitBvEyXjon8qmWGbpvSiads1vol34yHv8tC%2Fn%2BMkv1MES46yKLxXkI774bC2Y20nJQV0%2BfkSjAS0Gl7d43tRZgGHJAfImTCAlHjgVnRJQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 82fbf73c5175ca802612547615c6f334 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:38 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61aFY0qyl6XeW1I19NfO6","secret":"pi_3Pv61aFY0qyl6XeW1I19NfO6_secret_ULbMWpGXKKrcgZGp0cSiOXl9E","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0001_post_v1_payment_intents_pi_3Pv61aFY0qyl6XeW1I19NfO6_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0001_post_v1_payment_intents_pi_3Pv61aFY0qyl6XeW1I19NfO6_confirm.tail new file mode 100644 index 00000000..a82fba5a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0001_post_v1_payment_intents_pi_3Pv61aFY0qyl6XeW1I19NfO6_confirm.tail @@ -0,0 +1,117 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61aFY0qyl6XeW1I19NfO6\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jYBEdbjlUd2p0p +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2279 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:39 GMT +original-request: req_jYBEdbjlUd2p0p +stripe-version: 2020-08-27 +idempotency-key: 097bbf07-ae36-4572-983d-f3a0c9912ed5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61aFY0qyl6XeW1I19NfO6_secret_ULbMWpGXKKrcgZGp0cSiOXl9E&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=tester%40example\.com&payment_method_data\[billing_details]\[name]=iOS%20CI%20Tester&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=us_bank_account&payment_method_data\[us_bank_account]\[account_holder_type]=individual&payment_method_data\[us_bank_account]\[account_number]=000123456789&payment_method_data\[us_bank_account]\[account_type]=checking&payment_method_data\[us_bank_account]\[routing_number]=110000000 + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "verify_with_microdeposits", + "verify_with_microdeposits" : { + "microdeposit_type" : "descriptor_code", + "arrival_date" : 1725433200, + "hosted_verification_url" : "https:\/\/payments.stripe.com\/microdeposit\/pacs_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHBhX25vbmNlX1FtZk13M3cwZ1JUOGIxNnFaUnZWbnBXQXl5cTJ0d1Y0000VCOkpoiI" + } + }, + "status" : "requires_action", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pv61aFY0qyl6XeWYXhCJ6FL", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1725405638, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null + }, + "client_secret" : "pi_3Pv61aFY0qyl6XeW1I19NfO6_secret_ULbMWpGXKKrcgZGp0cSiOXl9E", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3Pv61aFY0qyl6XeW1I19NfO6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1725405638, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0002_post_v1_payment_intents_pi_3Pv61aFY0qyl6XeW1I19NfO6_verify_microdeposits.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0002_post_v1_payment_intents_pi_3Pv61aFY0qyl6XeW1I19NfO6_verify_microdeposits.tail new file mode 100644 index 00000000..532c21cd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithAmounts/0002_post_v1_payment_intents_pi_3Pv61aFY0qyl6XeW1I19NfO6_verify_microdeposits.tail @@ -0,0 +1,73 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61aFY0qyl6XeW1I19NfO6\/verify_microdeposits$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fverify_microdeposits; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=lpm-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=lpm-bapi-srv"}],"include_subdomains":true} +request-id: req_wq6LsxF09bV4VM +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1019 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:40 GMT +original-request: req_wq6LsxF09bV4VM +stripe-version: 2020-08-27 +idempotency-key: 6ea49917-8522-4d29-9d6a-0dd5805f6a64 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amounts\[0]=32&amounts\[1]=45&client_secret=pi_3Pv61aFY0qyl6XeW1I19NfO6_secret_ULbMWpGXKKrcgZGp0cSiOXl9E + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "processing", + "payment_method" : "pm_1Pv61aFY0qyl6XeWYXhCJ6FL", + "client_secret" : "pi_3Pv61aFY0qyl6XeW1I19NfO6_secret_ULbMWpGXKKrcgZGp0cSiOXl9E", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3Pv61aFY0qyl6XeW1I19NfO6", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1725405638, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..fd2a776c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=m80SuttFktKw6ERLKHH7M4ynIVkMXTuWr%2FrM8twSQuzZYQYToVa%2BHx3Ro8hSwZHgKNSXaE8EhnbWcms7SK83Wqf7IWhpveh7Mz63MNrVmGyZfIv%2BAB%2Fw0DhKRRd5VJqMcNBRdcdI5OlhgKUwm2DX0VvZyXKsmK1D9%2F88CGdZlHvDrqVqkQwx3V2ggg4sWtJU%2FqImusx%2B0dsxcPUQyEsnlIJd7qk6nCW4xje%2BLOMRt2E%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 9ac85b187db446d5f4b2bb33c84c241f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:40 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61cFY0qyl6XeW0qMvSdB9","secret":"pi_3Pv61cFY0qyl6XeW0qMvSdB9_secret_NQhZjAqSGw2K4VihWhBrnBqI0","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0001_post_v1_payment_intents_pi_3Pv61cFY0qyl6XeW0qMvSdB9_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0001_post_v1_payment_intents_pi_3Pv61cFY0qyl6XeW0qMvSdB9_confirm.tail new file mode 100644 index 00000000..3a4752a5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0001_post_v1_payment_intents_pi_3Pv61cFY0qyl6XeW0qMvSdB9_confirm.tail @@ -0,0 +1,117 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61cFY0qyl6XeW0qMvSdB9\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_xaN96E7PBYKoGL +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2279 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:41 GMT +original-request: req_xaN96E7PBYKoGL +stripe-version: 2020-08-27 +idempotency-key: c04f7b33-9ff1-4396-8305-e3f93d5e0a27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61cFY0qyl6XeW0qMvSdB9_secret_NQhZjAqSGw2K4VihWhBrnBqI0&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=tester%40example\.com&payment_method_data\[billing_details]\[name]=iOS%20CI%20Tester&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=us_bank_account&payment_method_data\[us_bank_account]\[account_holder_type]=individual&payment_method_data\[us_bank_account]\[account_number]=000123456789&payment_method_data\[us_bank_account]\[account_type]=checking&payment_method_data\[us_bank_account]\[routing_number]=110000000 + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "verify_with_microdeposits", + "verify_with_microdeposits" : { + "microdeposit_type" : "descriptor_code", + "arrival_date" : 1725433200, + "hosted_verification_url" : "https:\/\/payments.stripe.com\/microdeposit\/pacs_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHBhX25vbmNlX1FtZk1QNTlsejYybmhrM3lnNGd0VkhnWnBMd3J6SzU00008zqG8hbl" + } + }, + "status" : "requires_action", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pv61cFY0qyl6XeWpveQzX4U", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1725405640, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null + }, + "client_secret" : "pi_3Pv61cFY0qyl6XeW0qMvSdB9_secret_NQhZjAqSGw2K4VihWhBrnBqI0", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3Pv61cFY0qyl6XeW0qMvSdB9", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1725405640, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0002_post_v1_payment_intents_pi_3Pv61cFY0qyl6XeW0qMvSdB9_verify_microdeposits.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0002_post_v1_payment_intents_pi_3Pv61cFY0qyl6XeW0qMvSdB9_verify_microdeposits.tail new file mode 100644 index 00000000..c212336e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmPaymentIntentWithUSBankAccountverifyWithDescriptorCode/0002_post_v1_payment_intents_pi_3Pv61cFY0qyl6XeW0qMvSdB9_verify_microdeposits.tail @@ -0,0 +1,73 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61cFY0qyl6XeW0qMvSdB9\/verify_microdeposits$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fverify_microdeposits; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=lpm-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=lpm-bapi-srv"}],"include_subdomains":true} +request-id: req_iDnX26lU5nDTz1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1019 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:42 GMT +original-request: req_iDnX26lU5nDTz1 +stripe-version: 2020-08-27 +idempotency-key: 8f0502be-da9f-4ec4-99ad-2797c15f9daa +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61cFY0qyl6XeW0qMvSdB9_secret_NQhZjAqSGw2K4VihWhBrnBqI0&descriptor_code=SM11AA + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "processing", + "payment_method" : "pm_1Pv61cFY0qyl6XeWpveQzX4U", + "client_secret" : "pi_3Pv61cFY0qyl6XeW0qMvSdB9_secret_NQhZjAqSGw2K4VihWhBrnBqI0", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3Pv61cFY0qyl6XeW0qMvSdB9", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1725405640, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmUSBankAccountWithPaymentMethodOptions/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmUSBankAccountWithPaymentMethodOptions/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..171a46d3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmUSBankAccountWithPaymentMethodOptions/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=5nN9BpkTD2NNwspQtltcYQ7yZPrtcW0ZfR7auyd80gbgT%2FAd%2F1N2dcx%2BWifO60SuFrhGcermaQR8YQ8jxZlNeaW5HN9oRy7o940bAclBYcMxLFrQVDWf%2FVzMW%2FDF4O197AIeM02L%2FeoIRFmydM44vpzbXhEMJE9FMWreGHZeEnhQIiqP%2BjD%2F5My%2Fjf6kovHZRJLVcd5yTuK%2FBy6EZ%2FB3T0%2FlkYsM3RVgrlt8f4BykPI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 209889aeb4eaf15d81ea7ed33178800d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61eFY0qyl6XeW0SVbleB1","secret":"pi_3Pv61eFY0qyl6XeW0SVbleB1_secret_T2xSFbr73RvzcdTcZvJkDCsIU","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmUSBankAccountWithPaymentMethodOptions/0001_post_v1_payment_intents_pi_3Pv61eFY0qyl6XeW0SVbleB1_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmUSBankAccountWithPaymentMethodOptions/0001_post_v1_payment_intents_pi_3Pv61eFY0qyl6XeW0SVbleB1_confirm.tail new file mode 100644 index 00000000..4b9978eb --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testConfirmUSBankAccountWithPaymentMethodOptions/0001_post_v1_payment_intents_pi_3Pv61eFY0qyl6XeW0SVbleB1_confirm.tail @@ -0,0 +1,118 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Pv61eFY0qyl6XeW0SVbleB1\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_pxQhvVWVwOQqaQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 2322 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:43 GMT +original-request: req_pxQhvVWVwOQqaQ +stripe-version: 2020-08-27 +idempotency-key: 28b8ca21-305f-43fb-91af-4a619ca24b38 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3Pv61eFY0qyl6XeW0SVbleB1_secret_T2xSFbr73RvzcdTcZvJkDCsIU&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=tester%40example\.com&payment_method_data\[billing_details]\[name]=iOS%20CI%20Tester&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=us_bank_account&payment_method_data\[us_bank_account]\[account_holder_type]=individual&payment_method_data\[us_bank_account]\[account_number]=000123456789&payment_method_data\[us_bank_account]\[account_type]=checking&payment_method_data\[us_bank_account]\[routing_number]=110000000&payment_method_options\[us_bank_account]\[setup_future_usage]=off_session + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "setup_future_usage" : "off_session", + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : { + "type" : "verify_with_microdeposits", + "verify_with_microdeposits" : { + "microdeposit_type" : "descriptor_code", + "arrival_date" : 1725433200, + "hosted_verification_url" : "https:\/\/payments.stripe.com\/microdeposit\/pacs_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHBhX25vbmNlX1FtZk01T0ZuTkRxUDZjRUlTQ25nTTBlTFIwejliaFg0000hsvV1BtI" + } + }, + "status" : "requires_action", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Pv61eFY0qyl6XeW0vPlUVEE", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1725405642, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null + }, + "client_secret" : "pi_3Pv61eFY0qyl6XeW0SVbleB1_secret_T2xSFbr73RvzcdTcZvJkDCsIU", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3Pv61eFY0qyl6XeW0SVbleB1", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1725405642, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testCreatePaymentIntentWithInvalidCurrency/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testCreatePaymentIntentWithInvalidCurrency/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..f573a257 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testCreatePaymentIntentWithInvalidCurrency/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +402 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=HkxVi17e4y8nzB%2BpWU6bA38HHfRijiDXVn943reA2yvS0XWMt6qpc4CeVCe%2BXFo%2F7451wueWyvz51WOekax5MFyj6fb7f4YuNRpGEWeKTE%2FHwkZ269%2F%2BFQUnEEYuqmcX%2B8VWWYj9sDh40H6pg0zWfjLe1QYCHuUu%2B%2F7oSuqq0wHBLmUaCFRJqQ7ni1aR3QpWgJHDe5Ip%2B2fdTD2JqOh%2BaXOUWSTRAo5vg7BI1X7KklQ%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 49927ba182012f9afc05842342fb7f16 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 133 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +Error creating PaymentIntent: The currency provided (usd) is invalid. Payments with bancontact support the following currencies: eur. \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testCreatePaymentIntentWithTestingServer/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testCreatePaymentIntentWithTestingServer/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..266bc700 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testCreatePaymentIntentWithTestingServer/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=EINCTSNMQ9pCEhK81KZasJiCO9Rc5pQQqtSNqo2z42%2BTVug1PRkN8CkOwJToRthEEw6PPuBTh5HsJP1H7VjkBlNgfjjOyGSGToTy2bCssU3mx%2FFzaNF5NNPYNO9nZ7N4Qej4UfkZ%2BDqzGfvsrFxOv%2BD9%2BY6vAY4A1mfM1SJwWJ%2Bqgbucu5kj%2BTYiAbXTPT3c1vkd3SZ9Du1HjckWGtaOMDr9x%2B4cQeT%2F0UEcS9RK3Kk%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 859af4ede34f971b4715ec21d00e972d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Tue, 03 Sep 2024 23:20:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3Pv61fFY0qyl6XeW1IaJyJv8","secret":"pi_3Pv61fFY0qyl6XeW1IaJyJv8_secret_3rDeRwQFYusz2ppnmWTAPWW2B","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrieveMismatchedPublishableKey/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrieveMismatchedPublishableKey/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail new file mode 100644 index 00000000..e145171f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrieveMismatchedPublishableKey/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GGCGfFY0qyl6XeWbSAsh2hn\?client_secret=pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe$ +404 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_V72ZvsSTVl5JZI +Content-Length: 352 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "resource_missing", + "message" : "No such payment_intent: 'pi_1GGCGfFY0qyl6XeWbSAsh2hn'", + "param" : "intent", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_V72ZvsSTVl5JZI?t=1725405643", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/resource-missing" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrievePreviousCreatedPaymentIntent/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrievePreviousCreatedPaymentIntent/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail new file mode 100644 index 00000000..3e4d05f8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrievePreviousCreatedPaymentIntent/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GGCGfFY0qyl6XeWbSAsh2hn\?client_secret=pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KL1yon2epUB0IA +Content-Length: 878 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : 1582671568, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "canceled", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_jbhwsI0DGWhKreJs3CCrluUGe", + "id" : "pi_1GGCGfFY0qyl6XeWbSAsh2hn", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1582671165, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrieveWithWrongSecret/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrieveWithWrongSecret/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail new file mode 100644 index 00000000..398bbb7a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentIntentFunctionalTest/testRetrieveWithWrongSecret/0000_get_v1_payment_intents_pi_1GGCGfFY0qyl6XeWbSAsh2hn.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GGCGfFY0qyl6XeWbSAsh2hn\?client_secret=pi_1GGCGfFY0qyl6XeWbSAsh2hn_secret_bad-secret$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WJbxlfwTfKi5rF +Content-Length: 432 +Vary: Origin +Date: Tue, 03 Sep 2024 23:20:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "payment_intent_invalid_parameter", + "message" : "The client_secret provided does not match the client_secret associated with the PaymentIntent.", + "param" : "client_secret", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_WJbxlfwTfKi5rF?t=1725405644", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/payment-intent-invalid-parameter" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitParamsTests/testCreateAUBECSPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitParamsTests/testCreateAUBECSPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..aae8eb1d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitParamsTests/testCreateAUBECSPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Kt86PosOZ4Iq0y +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 577 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:48 GMT +original-request: req_Kt86PosOZ4Iq0y +stripe-version: 2020-08-27 +idempotency-key: e7543fe9-4347-4a38-961a-87216a35d7bc +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&au_becs_debit\[account_number]=000123456&au_becs_debit\[bsb_number]=000000&billing_details\[email]=jrosen%40example\.com&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&payment_user_agent=.*&type=au_becs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiS04F7QokQdxBy5yVdmMer", + "billing_details" : { + "email" : "jrosen@example.com", + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1722391848, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GaRLjF7QokQdxByYgFPQEi0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GaRLjF7QokQdxByYgFPQEi0.tail new file mode 100644 index 00000000..64ab919c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GaRLjF7QokQdxByYgFPQEi0.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GaRLjF7QokQdxByYgFPQEi0\?client_secret=pi_1GaRLjF7QokQdxByYgFPQEi0_secret_z76otRQH2jjOIEQYsA9vxhuKn&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_gJMB7NDywaX0kr +Content-Length: 1422 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_confirmation", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1GaRJkF7QokQdxByXiFDFLiA", + "billing_details" : { + "email" : "jrosen@example.com", + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1587495576, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_1GaRLjF7QokQdxByYgFPQEi0_secret_z76otRQH2jjOIEQYsA9vxhuKn", + "id" : "pi_1GaRLjF7QokQdxByYgFPQEi0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1587495699, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitTests/testFailWithoutRequired/0000_get_v1_payment_intents_pi_1GaRLjF7QokQdxByYgFPQEi0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitTests/testFailWithoutRequired/0000_get_v1_payment_intents_pi_1GaRLjF7QokQdxByYgFPQEi0.tail new file mode 100644 index 00000000..d112e83d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAUBECSDebitTests/testFailWithoutRequired/0000_get_v1_payment_intents_pi_1GaRLjF7QokQdxByYgFPQEi0.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GaRLjF7QokQdxByYgFPQEi0\?client_secret=pi_1GaRLjF7QokQdxByYgFPQEi0_secret_z76otRQH2jjOIEQYsA9vxhuKn&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZhmW251It6bOiK +Content-Length: 1422 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:48 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_confirmation", + "object" : "payment_intent", + "currency" : "aud", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1GaRJkF7QokQdxByXiFDFLiA", + "billing_details" : { + "email" : "jrosen@example.com", + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "au_becs_debit" : { + "bsb_number" : "000000", + "fingerprint" : "Ywo370ZoKyYRnGCA", + "last4" : "3456" + }, + "livemode" : false, + "created" : 1587495576, + "allow_redisplay" : "unspecified", + "type" : "au_becs_debit", + "customer" : null + }, + "client_secret" : "pi_1GaRLjF7QokQdxByYgFPQEi0_secret_z76otRQH2jjOIEQYsA9vxhuKn", + "id" : "pi_1GaRLjF7QokQdxByYgFPQEi0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "au_becs_debit" + ], + "setup_future_usage" : null, + "created" : 1587495699, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAffirmParamsTests/testCreateAffirmPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAffirmParamsTests/testCreateAffirmPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..63534b8e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAffirmParamsTests/testCreateAffirmPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LZp7tlDN62r1RQ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:49 GMT +original-request: req_LZp7tlDN62r1RQ +stripe-version: 2020-08-27 +idempotency-key: 3c84cf50-85ca-453d-8126-a9b63e53ed5d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=affirm + +{ + "object" : "payment_method", + "affirm" : { + + }, + "id" : "pm_1PiS05FY0qyl6XeWNQXiswct", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391849, + "allow_redisplay" : "unspecified", + "type" : "affirm", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAffirmTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3KUFbTFY0qyl6XeW1oDBbiQk.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAffirmTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3KUFbTFY0qyl6XeW1oDBbiQk.tail new file mode 100644 index 00000000..f9865771 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAffirmTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3KUFbTFY0qyl6XeW1oDBbiQk.tail @@ -0,0 +1,101 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3KUFbTFY0qyl6XeW1oDBbiQk\?client_secret=pi_3KUFbTFY0qyl6XeW1oDBbiQk_secret_8kdpLx37oa5WMrI2xoXThCK9s&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KbiSGILNisFh1s +Content-Length: 1651 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : { + "tracking_number" : null, + "phone" : null, + "carrier" : null, + "name" : "TestName", + "address" : { + "state" : "NY", + "country" : "US", + "line2" : "#3B", + "city" : "New York", + "line1" : "55 John St", + "postal_code" : "10002" + } + }, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 6099, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "affirm" : { + + }, + "id" : "pm_1KUFbUFY0qyl6XeW4Q7VNCTj", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1645125924, + "allow_redisplay" : "unspecified", + "type" : "affirm", + "customer" : null + }, + "client_secret" : "pi_3KUFbTFY0qyl6XeW1oDBbiQk_secret_8kdpLx37oa5WMrI2xoXThCK9s", + "id" : "pi_3KUFbTFY0qyl6XeW1oDBbiQk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "affirm" + ], + "setup_future_usage" : null, + "created" : 1645125923, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAfterpayClearpayParamsTest/testCreateAfterpayClearpayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAfterpayClearpayParamsTest/testCreateAfterpayClearpayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..d538d708 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAfterpayClearpayParamsTest/testCreateAfterpayClearpayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_CqpnaRYFv8hTfj +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 512 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:49 GMT +original-request: req_CqpnaRYFv8hTfj +stripe-version: 2020-08-27 +idempotency-key: 58ef9f6a-29cb-4219-9e20-59d6091948c4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=510%20Townsend%20St\.&billing_details\[address]\[postal_code]=94102&billing_details\[email]=jrosen%40example\.com&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&payment_user_agent=.*&type=afterpay_clearpay + +{ + "object" : "payment_method", + "id" : "pm_1PiS05FY0qyl6XeWOm7DjmUL", + "billing_details" : { + "email" : "jrosen@example.com", + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "510 Townsend St.", + "postal_code" : "94102" + } + }, + "livemode" : false, + "afterpay_clearpay" : { + + }, + "created" : 1722391849, + "allow_redisplay" : "unspecified", + "type" : "afterpay_clearpay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAfterpayClearpayTest/testCorrectParsing/0000_get_v1_payment_intents_pi_1HbSAfFY0qyl6XeWRnlezJ7K.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAfterpayClearpayTest/testCorrectParsing/0000_get_v1_payment_intents_pi_1HbSAfFY0qyl6XeWRnlezJ7K.tail new file mode 100644 index 00000000..c3884f2d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAfterpayClearpayTest/testCorrectParsing/0000_get_v1_payment_intents_pi_1HbSAfFY0qyl6XeWRnlezJ7K.tail @@ -0,0 +1,88 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1HbSAfFY0qyl6XeWRnlezJ7K\?client_secret=pi_1HbSAfFY0qyl6XeWRnlezJ7K_secret_t6Ju9Z0hxOvslawK34uC1Wm2b&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8ZE7YANR6sEZ2h +Content-Length: 1429 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:49 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_confirmation", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1HbSADFY0qyl6XeWYhb3ahvi", + "billing_details" : { + "email" : "email@email.com", + "phone" : null, + "name" : "Name", + "address" : { + "state" : null, + "country" : "US", + "line2" : null, + "city" : null, + "line1" : "line1", + "postal_code" : "94102" + } + }, + "livemode" : false, + "afterpay_clearpay" : { + + }, + "created" : 1602513494, + "allow_redisplay" : "unspecified", + "type" : "afterpay_clearpay", + "customer" : null + }, + "client_secret" : "pi_1HbSAfFY0qyl6XeWRnlezJ7K_secret_t6Ju9Z0hxOvslawK34uC1Wm2b", + "id" : "pi_1HbSAfFY0qyl6XeWRnlezJ7K", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "afterpay_clearpay" + ], + "setup_future_usage" : null, + "created" : 1602513521, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAlmaParamsTests/testCreateAlmaPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAlmaParamsTests/testCreateAlmaPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..f1985625 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAlmaParamsTests/testCreateAlmaPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BBgDbus6P1iu1d +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 444 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:50 GMT +original-request: req_BBgDbus6P1iu1d +stripe-version: 2020-08-27 +idempotency-key: 6f25ec8f-a421-40fc-bdad-b9ba305742f5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alma + +{ + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiS06FY0qyl6XeWPEbGTZo0", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391850, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAlmaTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Oz1AfKG6vc7r7YC0VaP6KiE.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAlmaTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Oz1AfKG6vc7r7YC0VaP6KiE.tail new file mode 100644 index 00000000..cbb73890 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAlmaTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Oz1AfKG6vc7r7YC0VaP6KiE.tail @@ -0,0 +1,93 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Oz1AfKG6vc7r7YC0VaP6KiE\?client_secret=pi_3Oz1AfKG6vc7r7YC0VaP6KiE_secret_SxVptpJ5PaAceAYCGetQh8FVv&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_APgN2h0jH5NM1u +Content-Length: 1414 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:50 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1Oz1acKG6vc7r7YC9mVUvHGJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1711565566, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null + }, + "client_secret" : "pi_3Oz1AfKG6vc7r7YC0VaP6KiE_secret_SxVptpJ5PaAceAYCGetQh8FVv", + "id" : "pi_3Oz1AfKG6vc7r7YC0VaP6KiE", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "ideal", + "sepa_debit", + "bancontact", + "sofort", + "eps", + "giropay", + "p24", + "afterpay_clearpay", + "mobilepay", + "alma" + ], + "setup_future_usage" : null, + "created" : 1711563957, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAmazonPayParamsTests/testCreateAmazonPayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAmazonPayParamsTests/testCreateAmazonPayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..ddfdd4a2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAmazonPayParamsTests/testCreateAmazonPayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6OoPTIlOIFvtnI +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 456 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:50 GMT +original-request: req_6OoPTIlOIFvtnI +stripe-version: 2020-08-27 +idempotency-key: de2cd0a0-109c-4e50-823c-d8f39f62f31c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=amazon_pay + +{ + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiS06FY0qyl6XeWUTLGgQIY", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391850, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAmazonPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3OmQQ0FY0qyl6XeW0H4X6eI0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAmazonPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3OmQQ0FY0qyl6XeW0H4X6eI0.tail new file mode 100644 index 00000000..e76c4c84 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodAmazonPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3OmQQ0FY0qyl6XeW0H4X6eI0.tail @@ -0,0 +1,98 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3OmQQ0FY0qyl6XeW0H4X6eI0\?client_secret=pi_3OmQQ0FY0qyl6XeW0H4X6eI0_secret_BerPIzUf8vFy1KXG53iYvX2Zb&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HQDsz6kuCzVEQz +Content-Length: 1572 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:50 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 5099, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1OmQQ5FY0qyl6XeWImlt1idC", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1708562749, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null + }, + "client_secret" : "pi_3OmQQ0FY0qyl6XeW0H4X6eI0_secret_BerPIzUf8vFy1KXG53iYvX2Zb", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3OmQQ0FY0qyl6XeW0H4X6eI0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "afterpay_clearpay", + "klarna", + "us_bank_account", + "affirm", + "amazon_pay" + ], + "setup_future_usage" : null, + "created" : 1708562744, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBancontactParamsTests/testCreateBancontactPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBancontactParamsTests/testCreateBancontactPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..35b28508 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBancontactParamsTests/testCreateBancontactPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2SXy93GK2dcYtN +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 462 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:51 GMT +original-request: req_2SXy93GK2dcYtN +stripe-version: 2020-08-27 +idempotency-key: 47431fdc-4b33-44b5-9838-c544a2b96f86 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jane%20Doe&metadata\[test_key]=test_value&payment_user_agent=.*&type=bancontact + +{ + "object" : "payment_method", + "id" : "pm_1PiS07FY0qyl6XeWsmGrjgGB", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391851, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBancontactTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GdPnbFY0qyl6XeW8Ezvxe87.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBancontactTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GdPnbFY0qyl6XeW8Ezvxe87.tail new file mode 100644 index 00000000..7b1dcfd1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBancontactTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GdPnbFY0qyl6XeW8Ezvxe87.tail @@ -0,0 +1,88 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GdPnbFY0qyl6XeW8Ezvxe87\?client_secret=pi_1GdPnbFY0qyl6XeW8Ezvxe87_secret_Fxi2EZBQ0nInHumvvezcTRWF4&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_g6gCul4VO7FuYd +Content-Length: 1393 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_confirmation", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1GdPfEFY0qyl6XeWHlzgah2m", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1588204204, + "allow_redisplay" : "unspecified", + "type" : "bancontact", + "customer" : null, + "bancontact" : { + + } + }, + "client_secret" : "pi_1GdPnbFY0qyl6XeW8Ezvxe87_secret_Fxi2EZBQ0nInHumvvezcTRWF4", + "id" : "pi_1GdPnbFY0qyl6XeW8Ezvxe87", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "bancontact" + ], + "setup_future_usage" : null, + "created" : 1588204723, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBillieTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBillieTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail new file mode 100644 index 00000000..2941d293 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBillieTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail @@ -0,0 +1,84 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3NqgBBGoesj9fw9Q1TkY7iBp\?client_secret=pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EsLy5vEbyBufVT +Content-Length: 1306 +Vary: Origin +Date: Fri, 19 Jul 2024 22:36:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 299, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1NqgBGGoesj9fw9QGikN8q6H", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1694800310, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null + }, + "client_secret" : "pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6", + "id" : "pi_3NqgBBGoesj9fw9Q1TkY7iBp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "billie" + ], + "setup_future_usage" : null, + "created" : 1694800305, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBoletoParamsTests/testCreateBoletoPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBoletoParamsTests/testCreateBoletoPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..d7db3a48 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBoletoParamsTests/testCreateBoletoPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZT8tq32lRODe8u +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 575 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:51 GMT +original-request: req_ZT8tq32lRODe8u +stripe-version: 2020-08-27 +idempotency-key: 2732bcc6-246a-43f1-ab93-0a4c05cb9dfd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=Sao%20Paulo&billing_details\[address]\[country]=BR&billing_details\[address]\[line1]=Av\.%20Do%20Brasil%201374&billing_details\[address]\[postal_code]=01310100&billing_details\[address]\[state]=SP&billing_details\[email]=jane%40example\.com&billing_details\[name]=Jane%20Diaz&boleto\[tax_id]=00\.000\.000\/0001-91&payment_user_agent=.*&type=boleto + +{ + "object" : "payment_method", + "boleto" : { + "fingerprint" : "JxmkTwoBYdI5FIBL", + "tax_id" : "00.000.000\/0001-91" + }, + "id" : "pm_1PiS07FY0qyl6XeWy5SRfnri", + "billing_details" : { + "email" : "jane@example.com", + "phone" : null, + "name" : "Jane Diaz", + "address" : { + "state" : "SP", + "country" : "BR", + "line2" : null, + "city" : "Sao Paulo", + "line1" : "Av. Do Brasil 1374", + "postal_code" : "01310100" + } + }, + "livemode" : false, + "created" : 1722391851, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBoletoTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3JYFj9JQVROkWvqT0d2HYaTk.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBoletoTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3JYFj9JQVROkWvqT0d2HYaTk.tail new file mode 100644 index 00000000..93b2858a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodBoletoTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3JYFj9JQVROkWvqT0d2HYaTk.tail @@ -0,0 +1,84 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3JYFj9JQVROkWvqT0d2HYaTk\?client_secret=pi_3JYFj9JQVROkWvqT0d2HYaTk_secret_c2PniS4q2A7XhZ9mbFwOTpN08&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uSgGcrUQPJBkIw +Content-Length: 1385 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:51 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "brl", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "boleto" : { + "fingerprint" : null, + "tax_id" : "00.000.000\/0001-91" + }, + "id" : "pm_1JYFj9JQVROkWvqTdNqC2Skq", + "billing_details" : { + "email" : "test@example.com", + "phone" : null, + "name" : "Jane Diaz", + "address" : { + "state" : "SP", + "country" : "BR", + "line2" : null, + "city" : "Sao Paulo", + "line1" : "Av. Do Brasil 1374", + "postal_code" : "01310100" + } + }, + "livemode" : false, + "created" : 1631303375, + "allow_redisplay" : "unspecified", + "type" : "boleto", + "customer" : null + }, + "client_secret" : "pi_3JYFj9JQVROkWvqT0d2HYaTk_secret_c2PniS4q2A7XhZ9mbFwOTpN08", + "id" : "pi_3JYFj9JQVROkWvqT0d2HYaTk", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "boleto" + ], + "setup_future_usage" : null, + "created" : 1631303375, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCardTest/testCorrectParsing/0000_get_v1_payment_intents_pi_1H5J4RFY0qyl6XeWFTpgue7g.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCardTest/testCorrectParsing/0000_get_v1_payment_intents_pi_1H5J4RFY0qyl6XeWFTpgue7g.tail new file mode 100644 index 00000000..92bb2d88 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCardTest/testCorrectParsing/0000_get_v1_payment_intents_pi_1H5J4RFY0qyl6XeWFTpgue7g.tail @@ -0,0 +1,110 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1H5J4RFY0qyl6XeWFTpgue7g\?client_secret=pi_1H5J4RFY0qyl6XeWFTpgue7g_secret_1SS59M0x65qWMaX2wEB03iwVE&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_b4uONOxAWYoagp +Content-Length: 1894 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 2000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1H5J3EFY0qyl6XeWG3Q3ZLrF", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 7, + "exp_year" : 2021, + "country" : "US" + }, + "livemode" : false, + "created" : 1594851969, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null + }, + "client_secret" : "pi_1H5J4RFY0qyl6XeWFTpgue7g_secret_1SS59M0x65qWMaX2wEB03iwVE", + "id" : "pi_1H5J4RFY0qyl6XeWFTpgue7g", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1594852043, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCashAppParamsTests/testCreateCashAppPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCashAppParamsTests/testCreateCashAppPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..07f38718 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCashAppParamsTests/testCreateCashAppPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BJM9h9x9UCa14D +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 495 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:52 GMT +original-request: req_BJM9h9x9UCa14D +stripe-version: 2020-08-27 +idempotency-key: 9bfb2e42-5396-45fe-8ca6-a92d7c2288de +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=cashapp + +{ + "object" : "payment_method", + "id" : "pm_1PiS08FY0qyl6XeW3oGjHM4B", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391852, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCashAppTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3MMa4NFY0qyl6XeW1FM3HOts.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCashAppTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3MMa4NFY0qyl6XeW1FM3HOts.tail new file mode 100644 index 00000000..d946e5d6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodCashAppTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3MMa4NFY0qyl6XeW1FM3HOts.tail @@ -0,0 +1,89 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3MMa4NFY0qyl6XeW1FM3HOts\?client_secret=pi_3MMa4NFY0qyl6XeW1FM3HOts_secret_b4HQ5YksK3mfe7zZaxBlWCark&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qm4Img1cmiaM5E +Content-Length: 1419 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:52 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 6000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 6000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1MMa4NFY0qyl6XeWBHEKck68", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1672850643, + "allow_redisplay" : "unspecified", + "type" : "cashapp", + "customer" : null, + "cashapp" : { + "cashtag" : null, + "buyer_id" : null + } + }, + "client_secret" : "pi_3MMa4NFY0qyl6XeW1FM3HOts_secret_b4HQ5YksK3mfe7zZaxBlWCark", + "id" : "pi_3MMa4NFY0qyl6XeW1FM3HOts", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "cashapp" + ], + "setup_future_usage" : null, + "created" : 1672850643, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodEPSParamsTests/testCreateEPSPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodEPSParamsTests/testCreateEPSPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..0ee9c06b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodEPSParamsTests/testCreateEPSPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GxWud5BRqsv268 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 471 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:53 GMT +original-request: req_GxWud5BRqsv268 +stripe-version: 2020-08-27 +idempotency-key: b6e9cd0a-760c-48aa-9859-5f0fba66d1ab +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&payment_user_agent=.*&type=eps + +{ + "object" : "payment_method", + "id" : "pm_1PiS08FY0qyl6XeWtPPzKtig", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "eps" : { + "bank" : null + }, + "livemode" : false, + "created" : 1722391852, + "allow_redisplay" : "unspecified", + "type" : "eps", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodEPSTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1Gj0rqFY0qyl6XeWrug30CPz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodEPSTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1Gj0rqFY0qyl6XeWrug30CPz.tail new file mode 100644 index 00000000..ff2a4845 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodEPSTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1Gj0rqFY0qyl6XeWrug30CPz.tail @@ -0,0 +1,88 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1Gj0rqFY0qyl6XeWrug30CPz\?client_secret=pi_1Gj0rqFY0qyl6XeWrug30CPz_secret_tKyf8QOKtiIrE3NSEkWCkBbyy&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Yw7OsZuAQsacUg +Content-Length: 1382 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:53 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 1099, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Gj0rtFY0qyl6XeWeZQVtItl", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Test", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "eps" : { + "bank" : null + }, + "livemode" : false, + "created" : 1589538857, + "allow_redisplay" : "unspecified", + "type" : "eps", + "customer" : null + }, + "client_secret" : "pi_1Gj0rqFY0qyl6XeWrug30CPz_secret_tKyf8QOKtiIrE3NSEkWCkBbyy", + "id" : "pi_1Gj0rqFY0qyl6XeWrug30CPz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "eps" + ], + "setup_future_usage" : null, + "created" : 1589538854, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAlipayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAlipayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..76ca2df7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAlipayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sEa5o5MpXIWMOu +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:53 GMT +original-request: req_sEa5o5MpXIWMOu +stripe-version: 2020-08-27 +idempotency-key: b9cfb9df-6b49-490a-a007-a7b67ed6394f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alipay + +{ + "object" : "payment_method", + "alipay" : { + + }, + "id" : "pm_1PiS09KlwPmebFhpO6LzywAm", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391853, + "allow_redisplay" : "unspecified", + "type" : "alipay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAlmaPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAlmaPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..7908499a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAlmaPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_x4pb0uHc8riDT4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 444 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:53 GMT +original-request: req_x4pb0uHc8riDT4 +stripe-version: 2020-08-27 +idempotency-key: 42bb7412-a28e-42c8-b57c-4469e31a8910 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=alma + +{ + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiS09KG6vc7r7YCx9lM1vxq", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391853, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAmazonPayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAmazonPayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..5752d8d6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateAmazonPayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cFfItV0R2OqQd3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 456 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:53 GMT +original-request: req_cFfItV0R2OqQd3 +stripe-version: 2020-08-27 +idempotency-key: fb57c5ed-11aa-4e53-a096-2bc2c950507b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=amazon_pay + +{ + "object" : "payment_method", + "amazon_pay" : { + + }, + "id" : "pm_1PiS09FY0qyl6XeWfyOqXnVH", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391853, + "allow_redisplay" : "unspecified", + "type" : "amazon_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBLIKPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBLIKPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..64f635d5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBLIKPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LOuWRcuPHnXKYU +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 444 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:54 GMT +original-request: req_LOuWRcuPHnXKYU +stripe-version: 2020-08-27 +idempotency-key: b1c185ad-2874-4881-9b68-ff2db6a3eac0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=blik + +{ + "object" : "payment_method", + "id" : "pm_1PiS0AFY0qyl6XeWT8I4vQZL", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "blik" : { + + }, + "allow_redisplay" : "unspecified", + "created" : 1722391854, + "customer" : null, + "type" : "blik" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBacsPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBacsPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..1da26b01 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBacsPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,57 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Oogz7mGxEN02dW +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 623 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:54 GMT +original-request: req_Oogz7mGxEN02dW +stripe-version: 2020-08-27 +idempotency-key: 1767598b-96ec-4f22-bb57-93baf0330adb +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&bacs_debit\[account_number]=00012345&bacs_debit\[sort_code]=108800&billing_details\[address]\[city]=London&billing_details\[address]\[country]=GB&billing_details\[address]\[line1]=Stripe%2C%207th%20Floor%20The%20Bower%20Warehouse&billing_details\[address]\[postal_code]=EC1V%209NR&billing_details\[email]=email%40email\.com&billing_details\[name]=Isaac%20Asimov&billing_details\[phone]=555-555-5555&payment_user_agent=.*&type=bacs_debit + +{ + "object" : "payment_method", + "id" : "pm_1PiS0AL6pqDH2fDJRsmUOc40", + "billing_details" : { + "email" : "email@email.com", + "phone" : "555-555-5555", + "name" : "Isaac Asimov", + "address" : { + "state" : null, + "country" : "GB", + "line2" : null, + "city" : "London", + "line1" : "Stripe, 7th Floor The Bower Warehouse", + "postal_code" : "EC1V 9NR" + } + }, + "livemode" : false, + "bacs_debit" : { + "fingerprint" : "UkSG0HfCGxxrja1H", + "last4" : "2345", + "sort_code" : "108800" + }, + "allow_redisplay" : "unspecified", + "created" : 1722391854, + "customer" : null, + "type" : "bacs_debit" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBilliePaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBilliePaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..1ebfdd50 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateBilliePaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,54 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_mAyI3His1PHobs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 458 +Vary: Origin +Date: Fri, 19 Jul 2024 22:36:15 GMT +original-request: req_mAyI3His1PHobs +stripe-version: 2020-08-27 +idempotency-key: 014b66d9-5866-4d15-b6ab-a6a6a9922da9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "billie" : { + + }, + "id" : "pm_1PePPPGoesj9fw9Qw6BEzUSO", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1721428575, + "allow_redisplay" : "unspecified", + "type" : "billie", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateCardPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateCardPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..a873bafd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateCardPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hDaOLpGDRhtPj4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 999 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:54 GMT +original-request: req_hDaOLpGDRhtPj4 +stripe-version: 2020-08-27 +idempotency-key: 26091350-08b1-4d9e-a0dd-31f587f588cf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=San%20Francisco&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=150%20Townsend%20St&billing_details\[address]\[line2]=4th%20Floor&billing_details\[address]\[postal_code]=94103&billing_details\[address]\[state]=CA&billing_details\[email]=email%40email\.com&billing_details\[name]=Isaac%20Asimov&billing_details\[phone]=555-555-5555&card\[cvc]=100&card\[exp_month]=10&card\[exp_year]=2028&card\[number]=4242424242424242&metadata\[test_key]=test_value&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS0AFY0qyl6XeWdL2fwlAr", + "billing_details" : { + "email" : "email@email.com", + "phone" : "555-555-5555", + "name" : "Isaac Asimov", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "4th Floor", + "city" : "San Francisco", + "line1" : "150 Townsend St", + "postal_code" : "94103" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 10, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391854, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateCryptoPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateCryptoPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..291a3431 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateCryptoPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,56 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET, HEAD, PUT, PATCH, POST, DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report" +x-wc: A +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report"}],"include_subdomains":true} +request-id: req_gaAnTguxIFGJWF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Thu, 21 Nov 2024 22:23:20 GMT +original-request: req_gaAnTguxIFGJWF +stripe-version: 2020-08-27 +idempotency-key: 891d3387-37e8-4ac5-aeb3-aac7cd5d7d22 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=crypto + +{ + "object" : "payment_method", + "crypto" : { + + }, + "id" : "pm_1QNimSFY0qyl6XeWhTt9lkIP", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1732227800, + "allow_redisplay" : "unspecified", + "type" : "crypto", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateMobilePayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateMobilePayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..a0566f00 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateMobilePayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WJ7oU8vFCHRPI9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 454 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:55 GMT +original-request: req_WJ7oU8vFCHRPI9 +stripe-version: 2020-08-27 +idempotency-key: 5dc71631-d960-4b71-8f4d-733bc09df4d5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=mobilepay + +{ + "object" : "payment_method", + "id" : "pm_1PiS0BKG6vc7r7YCznoMWsOk", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722391855, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateMultibancoPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateMultibancoPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..c047c745 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateMultibancoPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EjWWXYAUQpCGEb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 460 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:55 GMT +original-request: req_EjWWXYAUQpCGEb +stripe-version: 2020-08-27 +idempotency-key: 9e488cb5-ca35-4013-9e62-19d3464e47e5 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=tester%40example\.com&payment_user_agent=.*&type=alma + +{ + "object" : "payment_method", + "alma" : { + + }, + "id" : "pm_1PiS0BFY0qyl6XeW0ZIL2GHi", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391855, + "allow_redisplay" : "unspecified", + "type" : "alma", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateSatispayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateSatispayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..a5209642 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateSatispayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_2qeVPGGf21nmKB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 452 +Vary: Origin +Date: Fri, 02 Aug 2024 00:26:13 GMT +original-request: req_2qeVPGGf21nmKB +stripe-version: 2020-08-27 +idempotency-key: 40b875bb-b037-41f3-b293-a44d841fbbdd +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=satispay + +{ + "object" : "payment_method", + "id" : "pm_1Pj9JxIFbdis1OxTkSiiELbJ", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1722558373, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateSunbitPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateSunbitPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..118a7671 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testCreateSunbitPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_H5iqkDGACC9p41 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 448 +Vary: Origin +Date: Wed, 31 Jul 2024 02:10:55 GMT +original-request: req_H5iqkDGACC9p41 +stripe-version: 2020-08-27 +idempotency-key: bd04bea0-7914-411a-a5c6-db791adeccc9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=sunbit + +{ + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1PiS0BFY0qyl6XeWhTroGsAM", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391855, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..e3dfb150 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=%2F5QUtpnrMmBbkk3n5hqIo7StMhPC4dwxTnp9CCEfNwCgKzufbisrvUH%2BgMN4b3Tuda6WmlDb8hhHVB73Q59SkP3dDS6vmIA9Nb3qSEzD4ADN46xn9fLQEr3LK0%2BsjvnsNWfWD1ufKUOLr8r0FwiZ99xs40XKfBjx37uqdsf2ZVNa3q9aGx1nkDKL7JQP0GlctQ8%2BexWgWTFQigspSqHEdZOQOhaw3JLGh3JMUmUfy24%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a96d523deee0c2383b57c8a0ec8e9db0;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 05 Sep 2024 15:40:16 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLFFIVGZQazVURmpvVVhHaU5hTmJ3RkEwcEtuZ1gybGo_00Yper1OvS","customer":"cus_QnINed3dyqy6p6"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..be4e0185 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_aG2o1dSLL2zJFK +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:16 GMT +original-request: req_aG2o1dSLL2zJFK +stripe-version: 2020-08-27 +idempotency-key: 64b54dfa-9336-414e-a880-ab58bc8ac06b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PvhnAFY0qyl6XeWJhOV2gTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550816, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0002_post_v1_payment_methods_pm_1PvhnAFY0qyl6XeWJhOV2gTR_attach.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0002_post_v1_payment_methods_pm_1PvhnAFY0qyl6XeWJhOV2gTR_attach.tail new file mode 100644 index 00000000..5b31acdf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0002_post_v1_payment_methods_pm_1PvhnAFY0qyl6XeWJhOV2gTR_attach.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PvhnAFY0qyl6XeWJhOV2gTR\/attach$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method%2Fattach; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_B0fWebHydUIxFZ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 986 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:17 GMT +original-request: req_B0fWebHydUIxFZ +stripe-version: 2020-08-27 +idempotency-key: 21de0252-9d1e-4572-bd2f-eaec679bc303 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: customer=cus_QnINed3dyqy6p6 + +{ + "object" : "payment_method", + "id" : "pm_1PvhnAFY0qyl6XeWJhOV2gTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550816, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0003_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0003_post_v1_payment_methods.tail new file mode 100644 index 00000000..283ab662 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0003_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PVRIWhFJ2iskqy +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:17 GMT +original-request: req_PVRIWhFJ2iskqy +stripe-version: 2020-08-27 +idempotency-key: 4fd5fd1b-7bb1-4b2a-a5bd-a943c4b6761e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0004_post_v1_payment_methods_pm_1PvhnBFY0qyl6XeWuTiGb7An_attach.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0004_post_v1_payment_methods_pm_1PvhnBFY0qyl6XeWuTiGb7An_attach.tail new file mode 100644 index 00000000..0a6bb1b0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0004_post_v1_payment_methods_pm_1PvhnBFY0qyl6XeWuTiGb7An_attach.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PvhnBFY0qyl6XeWuTiGb7An\/attach$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method%2Fattach; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hi2nWEXqhOeUO1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 986 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:18 GMT +original-request: req_hi2nWEXqhOeUO1 +stripe-version: 2020-08-27 +idempotency-key: e91797cb-8819-4a98-a26b-eb067d9d0eca +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: customer=cus_QnINed3dyqy6p6 + +{ + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0005_post_create_customer_session_cs.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0005_post_create_customer_session_cs.tail new file mode 100644 index 00000000..3dff6ea5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0005_post_create_customer_session_cs.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_customer_session_cs$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=T1oYsCUEvcfXNHn3iFypY50bOw6sEMyAV0dBhddDWovR188p7C2FCMg8%2FR2UurUqcFI0MymYXSKAmkhA2J3qBmwbuIBL64DNfwUlfIg8%2FX7ZF1S7BYMvmd%2Bo4NPnZ7j2AQr90DqVj1jt0lFWurypUVP694qhpTHNx5u%2F5ZwZOvYddsWNfOHV77NoD5wJJkOxP8BjHjLP4WnIoAEtqrONIvFUk1hJobV%2FD%2FM9vU97Ypc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: b51b33e6a4de6ca4ef73504ac541eaa1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Thu, 05 Sep 2024 15:40:18 GMT +x-robots-tag: noindex, nofollow +Content-Length: 128 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"customer_session_client_secret":"cuss_secret_QnINPOYcMZI0vmycMIw02mKkoeL8OWtNyZYLfSUsnuS5Gfi","customer":"cus_QnINed3dyqy6p6"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0006_get_v1_elements_sessions.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0006_get_v1_elements_sessions.tail new file mode 100644 index 00000000..196efe8e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0006_get_v1_elements_sessions.tail @@ -0,0 +1,511 @@ +GET +https:\/\/api\.stripe\.com\/v1\/elements\/sessions\?client_default_payment_method=pm_1PvhnBFY0qyl6XeWuTiGb7An&customer_session_client_secret=cuss_secret_QnINPOYcMZI0vmycMIw02mKkoeL8OWtNyZYLfSUsnuS5Gfi&deferred_intent%5Bamount%5D=5000&deferred_intent%5Bcapture_method%5D=automatic&deferred_intent%5Bcurrency%5D=usd&deferred_intent%5Bmode%5D=payment&deferred_intent%5Bsetup_future_usage%5D=off_session&key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&locale=en-US&type=deferred_intent$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Felements%2Fsessions; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=ocs-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=ocs-bapi-srv"}],"include_subdomains":true} +request-id: req_sLautoRh8J5CT0 +Content-Length: 18360 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_preference" : { + "country_code" : "US", + "object" : "payment_method_preference", + "type" : "deferred_intent", + "ordered_payment_method_types" : [ + "card", + "cashapp", + "amazon_pay", + "us_bank_account" + ] + }, + "capability_enabled_card_networks" : [ + "cartes_bancaires" + ], + "flags" : { + "elements_enable_external_payment_method_wallets_india" : false, + "elements_enable_external_payment_method_check" : false, + "elements_enable_external_payment_method_fonix" : false, + "elements_enable_external_payment_method_au_pay" : false, + "elements_enable_invalid_country_for_pm_error" : false, + "elements_enable_external_payment_method_ebt_snap" : false, + "elements_enable_external_payment_method_v_pay" : false, + "elements_enable_link_in_passthrough_ece" : false, + "elements_enable_passive_hcaptcha_in_payment_method_creation" : true, + "legacy_confirmation_tokens" : false, + "elements_enable_external_payment_method_swish" : false, + "elements_enable_external_payment_method_online_banking_poland" : false, + "elements_enable_read_allow_redisplay" : false, + "elements_enable_card_brand_choice_payment_element_spm" : true, + "elements_enable_external_payment_method_divido" : false, + "elements_enable_external_payment_method_tng" : false, + "elements_enable_express_checkout_button_demo_pay" : false, + "elements_enable_external_payment_method_tabby" : false, + "elements_enable_mx_card_installments" : false, + "link_payment_method_nicknames" : true, + "elements_enable_external_payment_method_bpay" : false, + "elements_enable_external_payment_method_laybuy" : false, + "elements_enable_external_payment_method_bluecode" : false, + "elements_enable_external_payment_method_grabpay_later" : false, + "elements_disable_klarna_ece_crossborder_validation" : false, + "elements_disable_payment_element_card_country_zip_validations" : false, + "elements_enable_external_payment_method_billie" : false, + "elements_enable_external_payment_method_postfinance" : false, + "elements_enable_external_payment_method_scalapay" : false, + "elements_enable_external_payment_method_famipay" : false, + "link_doi_optional_experiment_rollout" : true, + "elements_enable_save_for_future_payments_pre_check" : false, + "show_swish_redirect_and_qr_code_auth_flows" : true, + "elements_enable_external_payment_method_online_banking_finland" : false, + "elements_enable_external_payment_method_nexi_pay" : false, + "elements_enable_external_payment_method_pay_easy" : false, + "elements_enable_external_payment_method_sequra" : false, + "elements_spm_set_as_default" : true, + "elements_enable_external_payment_method_paidy" : false, + "elements_enable_write_allow_redisplay" : false, + "elements_enable_external_payment_method_online_banking_thailand" : false, + "elements_enable_external_payment_method_momo" : false, + "elements_enable_external_payment_method_payrexx" : false, + "elements_appearance_payment_accordion_overrides" : false, + "link_purchase_protections_rollout" : false, + "elements_enable_external_payment_method_walley" : false, + "elements_enable_external_payment_method_mb_way" : false, + "elements_enable_external_payment_method_rabbitline_pay" : false, + "disable_cbc_in_link_popup" : false, + "elements_enable_external_payment_method_gopay" : false, + "elements_enable_mobilepay" : false, + "elements_enable_external_payment_method_knet" : false, + "elements_enable_external_payment_method_pledg" : false, + "elements_enable_klarna_unified_offer" : true, + "elements_enable_external_payment_method_postepay" : false, + "elements_enable_19_digit_pans" : false, + "elements_enable_external_payment_method_aplazo" : false, + "elements_enable_external_payment_method_dapp" : false, + "elements_enable_external_payment_method_atone" : false, + "elements_enable_external_payment_method_dankort" : false, + "elements_enable_external_payment_method_fawry" : false, + "elements_enable_external_payment_method_payconiq" : false, + "elements_enable_external_payment_method_vipps" : false, + "elements_enable_external_payment_method_paysafecard" : false, + "elements_enable_external_payment_method_eftpos_australia" : false, + "elements_enable_external_payment_method_wechat_mobile" : false, + "elements_spm_messages" : false, + "elements_enable_external_payment_method_azupay" : false, + "ece_apple_pay_payment_request_passthrough" : false, + "elements_enable_external_payment_method_online_banking_czech_republic" : false, + "elements_enable_external_payment_method_aplazame" : false, + "elements_enable_external_payment_method_rakuten_pay" : false, + "elements_luxe_qr_ui_on_web_enabled" : false, + "paypal_express_checkout_recurring_support" : false, + "elements_enable_external_payment_method_paypo" : false, + "elements_enable_external_payment_method_netbanking" : false, + "elements_enable_external_payment_method_pix_international" : false, + "elements_enable_external_payment_method_poli" : false, + "elements_enable_external_payment_method_kriya" : false, + "financial_connections_enable_deferred_intent_flow" : true, + "elements_enable_external_payment_method_hands_in" : false, + "elements_enable_external_payment_method_younitedpay" : false, + "elements_enable_passive_captcha_on_payment_intent_update" : true, + "elements_enable_br_card_installments" : false, + "elements_enable_external_payment_method_atome" : false, + "elements_enable_external_payment_method_mercado_pago" : false, + "elements_enable_external_payment_method_amazon_pay" : false, + "elements_enable_external_payment_method_line_pay" : false, + "elements_enable_external_payment_method_paytm" : false, + "elements_enable_external_payment_method_catch" : false, + "elements_enable_external_payment_method_skrill" : false, + "elements_enable_external_payment_method_paypay" : false, + "elements_enable_klarna_express_checkout" : false, + "elements_enable_external_payment_method_ratepay" : false, + "elements_enable_external_payment_method_sezzle" : false, + "elements_enable_external_payment_method_bankaxept" : false, + "elements_enable_external_payment_method_satispay" : false, + "elements_enable_external_payment_method_dbarai" : false, + "elements_enable_external_payment_method_titres_restaurant" : false, + "elements_enable_external_payment_method_coinbase_pay" : false, + "elements_enable_external_payment_method_trustly" : false, + "elements_enable_external_payment_method_kbc" : false, + "elements_enable_external_payment_method_bizum" : false, + "elements_enable_external_payment_method_gcash" : false, + "elements_enable_card_brand_choice_payment_element_link" : true, + "elements_enable_external_payment_method_payit" : false, + "link_doi_ungated_behavior_rollout" : true, + "elements_enable_blik" : true, + "elements_enable_south_korea_market_underlying_pms" : false, + "elements_enable_external_payment_method_girocard" : false, + "elements_enable_external_payment_method_oney" : false, + "elements_enable_external_payment_method_humm" : false, + "elements_enable_external_payment_method_iwocapay" : false, + "elements_enable_external_payment_method_benefit" : false, + "elements_enable_external_payment_method_twint" : false, + "link_enable_card_brand_choice" : true, + "elements_enable_external_payment_method_paybright" : false, + "elements_enable_link_spm" : true, + "elements_spm_max_visible_payment_methods" : false, + "elements_disable_link_email_otp" : false, + "cbc_in_link_popup" : true, + "elements_enable_external_payment_method_picpay" : false, + "elements_enable_external_payment_method_planpay" : false, + "elements_enable_external_payment_method_samsung_pay" : false, + "elements_enable_external_payment_method_truelayer" : false, + "elements_enable_passive_captcha" : true, + "elements_disable_paypal_express" : false, + "elements_disable_recurring_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_alipay_mobile" : false, + "elements_enable_external_payment_method_merpay" : false, + "elements_enable_external_payment_method_payu" : false, + "elements_disable_express_checkout_button_amazon_pay" : false, + "elements_enable_external_payment_method_venmo" : false, + "elements_enable_external_payment_method_mondu" : false, + "elements_enable_external_payment_method_paydirekt" : false, + "elements_stop_move_focus_to_first_errored_field" : true, + "elements_enable_external_payment_method_online_banking_slovakia" : false, + "elements_enable_external_payment_method_interac" : false, + "link_new_learn_more_modal_enabled" : true, + "use_link_views" : false, + "elements_enable_external_payment_method_mybank" : false + }, + "merchant_logo_url" : null, + "session_id" : "elements_session_1IiiTsS7Nbl", + "account_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_id" : "acct_1G6m1pFY0qyl6XeW", + "merchant_currency" : "usd", + "card_brand_choice" : { + "eligible" : false, + "preferred_networks" : [ + "cartes_bancaires" + ], + "supported_cobranded_networks" : { + "cartes_bancaires" : false + } + }, + "shipping_address_settings" : { + "autocomplete_allowed" : false + }, + "external_payment_method_data" : null, + "meta_pay_signed_container_context" : null, + "apple_pay_preference" : "enabled", + "lpm_promotions" : null, + "merchant_country" : "US", + "google_pay_preference" : "enabled", + "paypal_express_config" : { + "client_id" : null, + "paypal_merchant_id" : null + }, + "experiments_data" : { + "arb_id" : "5a53e1ac-a3c8-466c-a30b-6419ebe045b9", + "experiment_assignments" : { + "elements_merchant_ui_api_srv" : "control", + "link_popup_webview_option_ios" : "control", + "ocs_buyer_xp_elements_lpm_holdback" : "control" + } + }, + "legacy_customer" : null, + "link_settings" : { + "link_enable_webauthn_for_link_popup" : false, + "link_no_code_default_values_recall" : true, + "link_payment_session_context" : null, + "link_default_opt_in" : "NONE", + "link_elements_pageload_sign_up_disabled" : false, + "link_funding_sources_onboarding_unavailable_from_holdback" : [ + + ], + "link_crypto_onramp_bank_upsell" : false, + "link_funding_sources" : [ + + ], + "link_disabled_reasons" : { + "payment_element_payment_method_mode" : [ + "link_not_specified_in_payment_method_types" + ], + "payment_element_passthrough_mode" : [ + "automatic_payment_methods_enabled", + "link_not_enabled_on_payment_config", + "not_gated_into_enable_m2_passthrough_mode" + ] + }, + "link_elements_is_crypto_onramp" : false, + "link_payment_element_disabled_by_targeting" : false, + "link_popup_webview_option" : "shared", + "link_targeting_results" : { + "payment_element_passthrough_mode" : null + }, + "link_disable_email_otp" : false, + "link_local_storage_login_enabled" : false, + "link_in_optional_default_opt_in_experiment" : false, + "link_enable_instant_debits_in_testmode" : false, + "link_global_holdback_on" : false, + "link_session_storage_login_enabled" : false, + "link_payment_element_enable_webauthn_login" : false, + "link_payment_method_nicknames" : false, + "link_authenticated_change_event_enabled" : false, + "link_bank_onboarding_enabled" : false, + "link_hcaptcha_rqdata" : null, + "link_m2_default_integration_enabled" : false, + "link_financial_incentives_experiment_enabled" : false, + "link_passthrough_mode_enabled" : false, + "link_no_code_default_values_identification" : true, + "link_enable_email_otp_for_link_popup" : false, + "link_pay_button_element_enabled" : true, + "link_crypto_onramp_elements_logout_disabled" : false, + "link_mode" : null, + "link_no_code_default_values_usage" : true, + "link_hcaptcha_site_key" : "20000000-ffff-ffff-ffff-000000000002", + "link_crypto_onramp_force_cvc_reverification" : false, + "link_email_verification_login_enabled" : false, + "link_only_for_payment_method_types_enabled" : false, + "link_bank_incentives_enabled" : false, + "link_funding_sources_onboarding_enabled" : [ + + ], + "link_pm_killswitch_on_in_elements" : false + }, + "passive_captcha" : { + "site_key" : "20000000-ffff-ffff-ffff-000000000002", + "rqdata" : null + }, + "payment_method_specs" : [ + { + "async" : false, + "fields" : [ + + ], + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light@3x-46eb8b8a4a252b78d7b4c3b96d4ed7ae.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-amazonpay_light-22cdec0f5f5609554a34fa62fa583f23.svg" + }, + "type" : "amazon_pay", + "next_action_spec" : { + "confirm_response_status_specs" : { + "requires_action" : { + "type" : "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs" : { + "requires_action" : { + "type" : "canceled" + }, + "succeeded" : { + "type" : "finished" + } + } + } + }, + { + "async" : false, + "type" : "card", + "fields" : [ + + ] + }, + { + "async" : false, + "fields" : [ + + ], + "type" : "cashapp", + "selector_icon" : { + "light_theme_png" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", + "light_theme_svg" : "https:\/\/js.stripe.com\/v3\/fingerprinted\/img\/payment-methods\/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" + } + } + ], + "prefill_selectors" : { + "default_values" : { + "email" : [ + + ], + "merchant_provides_default_values_on_update" : true + } + }, + "unactivated_payment_method_types" : [ + "cashapp", + "amazon_pay", + "us_bank_account" + ], + "unverified_payment_methods_on_domain" : [ + "apple_pay" + ], + "order" : null, + "link_purchase_protections_data" : { + "type" : null, + "is_eligible" : false + }, + "apple_pay_merchant_token_webhook_url" : "https:\/\/pm-hooks.stripe.com\/apple_pay\/merchant_token\/pDq7tf9uieoQWMVJixFwuOve\/acct_1G6m1pFY0qyl6XeW\/", + "customer" : { + "payment_methods" : [ + { + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" + } + ], + "payment_methods_with_link_details" : [ + { + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" + }, + "link_payment_details" : null + } + ], + "default_payment_method" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "customer_session" : { + "object" : "customer_session", + "api_key_expiry" : 1725552618, + "id" : "cuss_1PvhnCFY0qyl6XeWRnoQDjZC", + "livemode" : false, + "components" : { + "customer_sheet" : { + "enabled" : false, + "features" : null + }, + "pricing_table" : { + "enabled" : false + }, + "mobile_payment_element" : { + "enabled" : true, + "features" : { + "payment_method_remove" : "enabled", + "payment_method_allow_redisplay_filters" : [ + "unspecified", + "limited", + "always" + ], + "payment_method_save_allow_redisplay_override" : null, + "payment_method_save" : "enabled", + "payment_method_redisplay" : "enabled" + } + }, + "payment_element" : { + "enabled" : false, + "features" : null + }, + "buy_button" : { + "enabled" : false + }, + "payment_sheet" : { + "enabled" : false, + "features" : null + } + }, + "customer" : "cus_QnINed3dyqy6p6", + "api_key" : "ek_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLDBLcUswWncyS1p5RHVQRm5MckZRa0h3bGFGREpCNjY_00rKYq1YQu" + } + }, + "klarna_express_config" : { + "klarna_mid" : null + }, + "business_name" : "CI Stuff", + "ordered_payment_method_types_and_wallets" : [ + "card", + "apple_pay", + "cashapp", + "amazon_pay", + "google_pay", + "us_bank_account" + ], + "customer_error" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0007_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0007_get_v1_payment_methods.tail new file mode 100644 index 00000000..dbe0f4f1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0007_get_v1_payment_methods.tail @@ -0,0 +1,129 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QnINed3dyqy6p6&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0HdjdbIEN9vFB3 +Content-Length: 2451 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" + }, + { + "object" : "payment_method", + "id" : "pm_1PvhnAFY0qyl6XeWJhOV2gTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550816, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0008_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0008_get_v1_payment_methods.tail new file mode 100644 index 00000000..d0fee73a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0008_get_v1_payment_methods.tail @@ -0,0 +1,129 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QnINed3dyqy6p6&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_9pjgImHWFsqkRN +Content-Length: 2451 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:19 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + { + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" + }, + { + "object" : "payment_method", + "id" : "pm_1PvhnAFY0qyl6XeWJhOV2gTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550816, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_QnINed3dyqy6p6" + } + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0009_post_v1_payment_methods_pm_1PvhnBFY0qyl6XeWuTiGb7An_detach.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0009_post_v1_payment_methods_pm_1PvhnBFY0qyl6XeWuTiGb7An_detach.tail new file mode 100644 index 00000000..23973210 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0009_post_v1_payment_methods_pm_1PvhnBFY0qyl6XeWuTiGb7An_detach.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PvhnBFY0qyl6XeWuTiGb7An\/detach$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method%2Fdetach; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dI22LbFtfXcq86 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 970 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:19 GMT +original-request: req_dI22LbFtfXcq86 +stripe-version: 2020-08-27 +idempotency-key: 04a4e2c8-469f-4c36-8e12-1b372a103e5e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PvhnBFY0qyl6XeWuTiGb7An", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550817, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0010_post_v1_payment_methods_pm_1PvhnAFY0qyl6XeWJhOV2gTR_detach.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0010_post_v1_payment_methods_pm_1PvhnAFY0qyl6XeWJhOV2gTR_detach.tail new file mode 100644 index 00000000..4b48e62c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0010_post_v1_payment_methods_pm_1PvhnAFY0qyl6XeWJhOV2gTR_detach.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PvhnAFY0qyl6XeWJhOV2gTR\/detach$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method%2Fdetach; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bJwM4aI7d0k44O +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 970 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:20 GMT +original-request: req_bJwM4aI7d0k44O +stripe-version: 2020-08-27 +idempotency-key: e3a98fa9-93a5-4c8c-8452-661f99297626 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PvhnAFY0qyl6XeWJhOV2gTR", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "96sroMqLCHmUdUpL", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1725550816, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0011_get_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0011_get_v1_payment_methods.tail new file mode 100644 index 00000000..03006b40 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testMulitpleCardCreationWithCustomerSessionAndMultiDelete/0011_get_v1_payment_methods.tail @@ -0,0 +1,34 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_methods\?customer=cus_QnINed3dyqy6p6&type=card$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_DqAktYk3TbVZ36 +Content-Length: 89 +Vary: Origin +Date: Thu, 05 Sep 2024 15:40:20 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "has_more" : false, + "object" : "list", + "data" : [ + + ], + "url" : "\/v1\/payment_methods" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0000_post_create_ephemeral_key.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0000_post_create_ephemeral_key.tail new file mode 100644 index 00000000..8ebf9cf5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0000_post_create_ephemeral_key.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_ephemeral_key$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=wEXsCeuCJDgn5BobYEflKZ1pJ4dletVunzxJ7F7B7lWKjDM7sELeAGByvPyCP852JOVJfS4QLANi8R1FOXq%2FH7V87uY2sAAI60j4M9iiJmPekZEfrLQJs7bvqLMlh1jBClmR4rogOGdXWz3GHPsWTc%2FsVnnli2cbIMIc3bvZP142%2Bunohpvwt5H7KoEy4TYP0DWwu4CALIqxlHP6PjcC6PJELkX%2FTlcyfjuX9vL74S4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 1174a4cb3741d062a4114262f94a2a03 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:00 GMT +x-robots-tag: noindex, nofollow +Content-Length: 149 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"ephemeral_key_secret":"ek_test_YWNjdF8xSnRnZlFLRzZ2YzdyN1lDLE03ZXhVY1lWNDU2cm90cnZwbmw3RTd1Wk80V1BKc2E_005CvtQMAx","customer":"cus_PTf9mhkFv9ZGXl"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0001_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0001_post_v1_payment_methods.tail new file mode 100644 index 00000000..751b3b01 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0001_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_HFBzGNJcCCw2n3 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:00 GMT +original-request: req_HFBzGNJcCCw2n3 +stripe-version: 2020-08-27 +idempotency-key: c4a29f1c-3b0f-49cd-b722-b5c4b70a0654 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=1&card\[exp_year]=2025&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS0GKG6vc7r7YC33E9uYEd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391860, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0002_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd_attach.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0002_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd_attach.tail new file mode 100644 index 00000000..729022b8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0002_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd_attach.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PiS0GKG6vc7r7YC33E9uYEd\/attach$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method%2Fattach; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_nQIit2pttbM2Go +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 986 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:01 GMT +original-request: req_nQIit2pttbM2Go +stripe-version: 2020-08-27 +idempotency-key: 4ac68fe9-ea4f-4175-b614-f257cef2696f +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: customer=cus_PTf9mhkFv9ZGXl + +{ + "object" : "payment_method", + "id" : "pm_1PiS0GKG6vc7r7YC33E9uYEd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "h6YYkRwMl4I317L0", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2025, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391860, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_PTf9mhkFv9ZGXl" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0003_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0003_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd.tail new file mode 100644 index 00000000..da20ef2d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0003_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd.tail @@ -0,0 +1,78 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PiS0GKG6vc7r7YC33E9uYEd$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_6raFbFrJMsLGdm +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 986 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:01 GMT +original-request: req_6raFbFrJMsLGdm +stripe-version: 2020-08-27 +idempotency-key: dad743d1-5068-4f96-a88e-fc6665f5aa0b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[exp_year]=2026 + +{ + "object" : "payment_method", + "id" : "pm_1PiS0GKG6vc7r7YC33E9uYEd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "h6YYkRwMl4I317L0", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2026, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391860, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : "cus_PTf9mhkFv9ZGXl" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0004_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd_detach.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0004_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd_detach.tail new file mode 100644 index 00000000..eec21126 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodFunctionalTest/testUpdateCardPaymentMethod/0004_post_v1_payment_methods_pm_1PiS0GKG6vc7r7YC33E9uYEd_detach.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods\/pm_1PiS0GKG6vc7r7YC33E9uYEd\/detach$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods%2F%3Apayment_method%2Fdetach; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0WAOHQg7g4ZAXG +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 970 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:01 GMT +original-request: req_0WAOHQg7g4ZAXG +stripe-version: 2020-08-27 +idempotency-key: cdfe7f9c-d7b1-4905-a10d-965cd3e3d112 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PiS0GKG6vc7r7YC33E9uYEd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "fingerprint" : "h6YYkRwMl4I317L0", + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 1, + "exp_year" : 2026, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391860, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGiropayParamsTests/testCreateGiropayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGiropayParamsTests/testCreateGiropayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..1c53f863 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGiropayParamsTests/testCreateGiropayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lt5Joe9xAEODHb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 459 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:02 GMT +original-request: req_lt5Joe9xAEODHb +stripe-version: 2020-08-27 +idempotency-key: dac3eb55-39a7-491d-814a-2ac6b2a0e362 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&payment_user_agent=.*&type=giropay + +{ + "object" : "payment_method", + "id" : "pm_1PiS0IFY0qyl6XeWFdRARDar", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "giropay" : { + + }, + "livemode" : false, + "created" : 1722391862, + "allow_redisplay" : "unspecified", + "type" : "giropay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGiropayTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GfsdtFY0qyl6XeWLnepIXCI.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGiropayTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GfsdtFY0qyl6XeWLnepIXCI.tail new file mode 100644 index 00000000..ec7c18a6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGiropayTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GfsdtFY0qyl6XeWLnepIXCI.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GfsdtFY0qyl6XeWLnepIXCI\?client_secret=pi_1GfsdtFY0qyl6XeWLnepIXCI_secret_bLJRSeSY7fBjDXnwh9BUKilMW&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yRoZKWddOfsrEU +Content-Length: 1422 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:02 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 1099, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1GfsdvFY0qyl6XeWIbMOt7pm", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "giropay" : { + + }, + "livemode" : false, + "created" : 1588792255, + "allow_redisplay" : "unspecified", + "type" : "giropay", + "customer" : null + }, + "client_secret" : "pi_1GfsdtFY0qyl6XeWLnepIXCI_secret_bLJRSeSY7fBjDXnwh9BUKilMW", + "id" : "pi_1GfsdtFY0qyl6XeWLnepIXCI", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "sofort", + "giropay" + ], + "setup_future_usage" : null, + "created" : 1588792253, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGrabPayParamsTest/testCreateGrabPayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGrabPayParamsTest/testCreateGrabPayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..f4e6c9a9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodGrabPayParamsTest/testCreateGrabPayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yvTLFMKUBep9vP +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 459 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:02 GMT +original-request: req_yvTLFMKUBep9vP +stripe-version: 2020-08-27 +idempotency-key: 90949145-c8b8-4fce-a4f3-b7a02f03d5f3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jenny%20Rosen&payment_user_agent=.*&type=grabpay + +{ + "object" : "payment_method", + "id" : "pm_1PiS0IAOnZToJom1FsxihnA5", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "grabpay" : { + + }, + "created" : 1722391862, + "allow_redisplay" : "unspecified", + "type" : "grabpay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodKlarnaParamsTests/testCreateKlarnaPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodKlarnaParamsTests/testCreateKlarnaPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..9636ffb9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodKlarnaParamsTests/testCreateKlarnaPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_cC81PPXMlYwKoJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 487 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:02 GMT +original-request: req_cC81PPXMlYwKoJ +stripe-version: 2020-08-27 +idempotency-key: 665d9c31-2f5e-4be3-a192-69cbbcc6a519 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[address]\[city]=New%20York&billing_details\[address]\[country]=US&billing_details\[address]\[line1]=55%20John%20St&billing_details\[address]\[line2]=%233B&billing_details\[address]\[postal_code]=10002&billing_details\[address]\[state]=NY&billing_details\[email]=foo%40example\.com&billing_details\[name]=John%20Smith&payment_user_agent=.*&type=klarna + +{ + "object" : "payment_method", + "klarna" : { + + }, + "id" : "pm_1PiS0IFY0qyl6XeWNEaOBKcj", + "billing_details" : { + "email" : "foo@example.com", + "phone" : null, + "name" : "John Smith", + "address" : { + "state" : "NY", + "country" : "US", + "line2" : "#3B", + "city" : "New York", + "line1" : "55 John St", + "postal_code" : "10002" + } + }, + "livemode" : false, + "created" : 1722391862, + "allow_redisplay" : "unspecified", + "type" : "klarna", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodKlarnaTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Jn3kUFY0qyl6XeW0mCp95UD.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodKlarnaTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Jn3kUFY0qyl6XeW0mCp95UD.tail new file mode 100644 index 00000000..2ab1bf77 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodKlarnaTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Jn3kUFY0qyl6XeW0mCp95UD.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Jn3kUFY0qyl6XeW0mCp95UD\?client_secret=pi_3Jn3kUFY0qyl6XeW0mCp95UD_secret_28aNjjd1zsySFWvGoSzgcR5Qw&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hHSJrL0MBirH9x +Content-Length: 891 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 1099, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3Jn3kUFY0qyl6XeW0mCp95UD_secret_28aNjjd1zsySFWvGoSzgcR5Qw", + "id" : "pi_3Jn3kUFY0qyl6XeW0mCp95UD", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "klarna" + ], + "setup_future_usage" : null, + "created" : 1634832250, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMobilePayParamsTests/testCreateMobilePayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMobilePayParamsTests/testCreateMobilePayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..977413d6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMobilePayParamsTests/testCreateMobilePayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_eyioF9rdgUMXPy +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 454 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:03 GMT +original-request: req_eyioF9rdgUMXPy +stripe-version: 2020-08-27 +idempotency-key: 8a5cea49-8a10-4a63-a82d-d8b4fa365890 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=mobilepay + +{ + "object" : "payment_method", + "id" : "pm_1PiS0JFY0qyl6XeWeP4pEpxI", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1722391863, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMobilePayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3PGVQJKG6vc7r7YC1Xs7oiWw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMobilePayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3PGVQJKG6vc7r7YC1Xs7oiWw.tail new file mode 100644 index 00000000..bb7744e6 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMobilePayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3PGVQJKG6vc7r7YC1Xs7oiWw.tail @@ -0,0 +1,84 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PGVQJKG6vc7r7YC1Xs7oiWw\?client_secret=pi_3PGVQJKG6vc7r7YC1Xs7oiWw_secret_5cqzEtQ059azmV1GmkLRA7Lvt&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ekycJl6Ytr5P5D +Content-Length: 1322 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:03 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PGVQXKG6vc7r7YCwXnMTd3X", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "mobilepay" : { + + }, + "created" : 1715731837, + "allow_redisplay" : "unspecified", + "type" : "mobilepay", + "customer" : null + }, + "client_secret" : "pi_3PGVQJKG6vc7r7YC1Xs7oiWw_secret_5cqzEtQ059azmV1GmkLRA7Lvt", + "id" : "pi_3PGVQJKG6vc7r7YC1Xs7oiWw", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "mobilepay" + ], + "setup_future_usage" : null, + "created" : 1715731823, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodModernTest/testCreateCardPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodModernTest/testCreateCardPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..226e343e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodModernTest/testCreateCardPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,76 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_t3DH3L4SXxDIF8 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 999 +Vary: Origin +Date: Fri, 19 Jul 2024 22:37:13 GMT +original-request: req_t3DH3L4SXxDIF8 +stripe-version: 2020-08-27 +idempotency-key: ecf620c1-a30c-4b7b-8909-94845ce44fd9 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "payment_method", + "id" : "pm_1PePQLFY0qyl6XeWYOPBkUoz", + "billing_details" : { + "email" : "email@email.com", + "phone" : "555-555-5555", + "name" : "Isaac Asimov", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "4th Floor", + "city" : "San Francisco", + "line1" : "150 Townsend St", + "postal_code" : "94103" + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2028, + "country" : "US" + }, + "livemode" : false, + "created" : 1721428633, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodModernTest/testCreateCardPaymentMethodWithAdditionalAPIStuff/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodModernTest/testCreateCardPaymentMethodWithAdditionalAPIStuff/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..6776407e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodModernTest/testCreateCardPaymentMethodWithAdditionalAPIStuff/0000_post_v1_payment_methods.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yNzKGV13qIgUSd +Content-Length: 401 +Vary: Origin +Date: Fri, 19 Jul 2024 22:37:13 GMT +original-request: req_yNzKGV13qIgUSd +stripe-version: 2020-08-27 +idempotency-key: d55981df-a2de-4e0a-962d-ccf0281aaa2e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff + +{ + "error" : { + "code" : "parameter_unknown", + "message" : "Received unknown parameter: billing_details[address][invalid_thing]", + "param" : "billing_details[address][invalid_thing]", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_yNzKGV13qIgUSd?t=1721428633", + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/parameter-unknown" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMultibancoParamsTests/testCreateMultibancoPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMultibancoParamsTests/testCreateMultibancoPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..bfd07f58 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMultibancoParamsTests/testCreateMultibancoPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wxwYq9T7aJeQbc +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 472 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:03 GMT +original-request: req_wxwYq9T7aJeQbc +stripe-version: 2020-08-27 +idempotency-key: 7d24bcf1-9f0e-4bd7-ad9d-fabcc3d3ac16 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=tester%40example\.com&payment_user_agent=.*&type=multibanco + +{ + "object" : "payment_method", + "id" : "pm_1PiS0JFY0qyl6XeWp3UOjR4G", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391863, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMultibancoTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3P8R5lFY0qyl6XeW0byterUo.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMultibancoTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3P8R5lFY0qyl6XeW0byterUo.tail new file mode 100644 index 00000000..453bfc0d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodMultibancoTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3P8R5lFY0qyl6XeW0byterUo.tail @@ -0,0 +1,88 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3P8R5lFY0qyl6XeW0byterUo\?client_secret=pi_3P8R5lFY0qyl6XeW0byterUo_secret_seOTE1wwZqkjBte83FjHalgsW&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_hVYeb9JPx4F3u0 +Content-Length: 1410 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:04 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1P8R5lFY0qyl6XeWAsEVbpVB", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1713808549, + "allow_redisplay" : "unspecified", + "multibanco" : { + + }, + "customer" : null, + "type" : "multibanco" + }, + "client_secret" : "pi_3P8R5lFY0qyl6XeW0byterUo_secret_seOTE1wwZqkjBte83FjHalgsW", + "id" : "pi_3P8R5lFY0qyl6XeW0byterUo", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "multibanco" + ], + "setup_future_usage" : null, + "created" : 1713808549, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodNetBankingParamsTests/testCreateNetBankingPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodNetBankingParamsTests/testCreateNetBankingPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..107df020 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodNetBankingParamsTests/testCreateNetBankingPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_rqs4wvMiwxENGT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 488 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:05 GMT +original-request: req_rqs4wvMiwxENGT +stripe-version: 2020-08-27 +idempotency-key: c4b9e528-f495-4b5b-8840-be90d422b65c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&netbanking\[bank]=icici&payment_user_agent=.*&type=netbanking + +{ + "object" : "payment_method", + "netbanking" : { + "bank" : "icici" + }, + "id" : "pm_1PiS0KBte6TMTRd4bnB2DFgj", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391864, + "allow_redisplay" : "unspecified", + "type" : "netbanking", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodNetBankingTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HoPqsBte6TMTRd4jX0PwrFa.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodNetBankingTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HoPqsBte6TMTRd4jX0PwrFa.tail new file mode 100644 index 00000000..6b1d0310 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodNetBankingTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HoPqsBte6TMTRd4jX0PwrFa.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1HoPqsBte6TMTRd4jX0PwrFa\?client_secret=pi_1HoPqsBte6TMTRd4jX0PwrFa_secret_ThiIwyssre9qjJ6gtmghC21fk&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_KSIH4sEIlLhdUl +Content-Length: 1409 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "inr", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "netbanking" : { + "bank" : "hdfc" + }, + "id" : "pm_1HoPqxBte6TMTRd4HzxWAwKK", + "billing_details" : { + "email" : "jenny@example.com", + "phone" : "(555) 555-5555", + "name" : "Jenny Rosen", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : "#345", + "city" : "San Francisco", + "line1" : "123 Market St", + "postal_code" : "94107" + } + }, + "livemode" : false, + "created" : 1605602875, + "allow_redisplay" : "unspecified", + "type" : "netbanking", + "customer" : null + }, + "client_secret" : "pi_1HoPqsBte6TMTRd4jX0PwrFa_secret_ThiIwyssre9qjJ6gtmghC21fk", + "id" : "pi_1HoPqsBte6TMTRd4jX0PwrFa", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "upi", + "netbanking" + ], + "setup_future_usage" : null, + "created" : 1605602870, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOXXOParamsTests/testCreateOXXOPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOXXOParamsTests/testCreateOXXOPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..35c9108d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOXXOParamsTests/testCreateOXXOPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0TYHubHYZztvOc +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 461 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:06 GMT +original-request: req_0TYHubHYZztvOc +stripe-version: 2020-08-27 +idempotency-key: 4c5df763-6722-4863-bab9-f3ddd0dd1378 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=test%40test\.com&billing_details\[name]=Jane%20Doe&payment_user_agent=.*&type=oxxo + +{ + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1PiS0MFY0qyl6XeWAA7bVXhg", + "billing_details" : { + "email" : "test@test.com", + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391866, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOXXOTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GvAdyHNG4o8pO5l0dr078gf.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOXXOTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GvAdyHNG4o8pO5l0dr078gf.tail new file mode 100644 index 00000000..5e8f2ba8 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOXXOTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GvAdyHNG4o8pO5l0dr078gf.tail @@ -0,0 +1,83 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GvAdyHNG4o8pO5l0dr078gf\?client_secret=pi_1GvAdyHNG4o8pO5l0dr078gf_secret_h0tJE5mSX9BPEkmpKSh93jBXi&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_lI480uNUEm1NUT +Content-Length: 1276 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:06 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 2000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "mxn", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "oxxo" : { + + }, + "id" : "pm_1GvAeRHNG4o8pO5libBvJ7Pu", + "billing_details" : { + "email" : "test@test.com", + "phone" : null, + "name" : "ios sdk", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1592436399, + "allow_redisplay" : "unspecified", + "type" : "oxxo", + "customer" : null + }, + "client_secret" : "pi_1GvAdyHNG4o8pO5l0dr078gf_secret_h0tJE5mSX9BPEkmpKSh93jBXi", + "id" : "pi_1GvAdyHNG4o8pO5l0dr078gf", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "oxxo" + ], + "setup_future_usage" : null, + "created" : 1592436370, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..c83d7f34 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=6sTNvKoigI8o7oYn6%2FdWa0%2FmE9NT%2Fi%2FkVKRQewCbJXolo6JJH2XXaad%2BuJUASICS5ymmotNK2UxwTEqpOSn6VYF4%2BLL4MCDFgGiAVtu1842QUHojAV6WKtW7kCoN1o%2FppprsJl4mogVNmyvaKdcmGjXNelVNyrc1XMUh1xVrPLD9XqTBcpKnp9UzSqAT0rcfruBPHPmT8uutwDYMRogZU%2FfxyayKwiIrmtnR%2BSd1TWo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 92032c8dd43a74fcb0b0abf3c048d91f;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0MFY0qyl6XeW02iKCVoz","secret":"pi_3PiS0MFY0qyl6XeW02iKCVoz_secret_dRN0LxIw5diDJLwfOr9yIjxLm","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0001_get_v1_payment_intents_pi_3PiS0MFY0qyl6XeW02iKCVoz.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0001_get_v1_payment_intents_pi_3PiS0MFY0qyl6XeW02iKCVoz.tail new file mode 100644 index 00000000..38de01b3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0001_get_v1_payment_intents_pi_3PiS0MFY0qyl6XeW02iKCVoz.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0MFY0qyl6XeW02iKCVoz\?client_secret=pi_3PiS0MFY0qyl6XeW02iKCVoz_secret_dRN0LxIw5diDJLwfOr9yIjxLm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LOa9wEZ7bkAMdQ +Content-Length: 1002 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "skip" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "requires_payment_method", + "payment_method" : null, + "client_secret" : "pi_3PiS0MFY0qyl6XeW02iKCVoz_secret_dRN0LxIw5diDJLwfOr9yIjxLm", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS0MFY0qyl6XeW02iKCVoz", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1722391866, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0002_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0002_post_create_payment_intent.tail new file mode 100644 index 00000000..27164a6f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0002_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=1aJI%2BELjofFL%2FpWEEZ%2Bxf%2BwKqh4HravvVajUjCK88fosTXrPO97dJRb3SFqivXXbJ2ThOjBMk%2FoEQh5Wp1wb8au5lIYrLfcw9R6K7loSGXf5GNKC6UNLvIkRGRfALe3Xl%2FSFkCPsoAokypmOohdrTbZOWqApHwtB%2B3OpSIUGOk3CcRm2okh8VpZTAi4fBKQT3u6OfBesXIbWt6S7kJ53i%2BHj3o8xoBp1uDogduD6%2Brc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: d75690dd76022244a324f4015d4d02e0 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:07 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0NFY0qyl6XeW1qkmS1Of","secret":"pi_3PiS0NFY0qyl6XeW1qkmS1Of_secret_ntKqtLDgrszy1imMoTzV9Wj6q","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0003_get_v1_payment_intents_pi_3PiS0NFY0qyl6XeW1qkmS1Of.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0003_get_v1_payment_intents_pi_3PiS0NFY0qyl6XeW1qkmS1Of.tail new file mode 100644 index 00000000..092fe6c2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0003_get_v1_payment_intents_pi_3PiS0NFY0qyl6XeW1qkmS1Of.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0NFY0qyl6XeW1qkmS1Of\?client_secret=pi_3PiS0NFY0qyl6XeW1qkmS1Of_secret_ntKqtLDgrszy1imMoTzV9Wj6q$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wIiy7IPCU8nxzW +Content-Length: 1007 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:07 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "requires_payment_method", + "payment_method" : null, + "client_secret" : "pi_3PiS0NFY0qyl6XeW1qkmS1Of_secret_ntKqtLDgrszy1imMoTzV9Wj6q", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS0NFY0qyl6XeW1qkmS1Of", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1722391867, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0004_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0004_post_create_payment_intent.tail new file mode 100644 index 00000000..81a9b00a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0004_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=IxyvzQCWv1SYRVCk2nFtwrc2OHRIJNCmM0Ei4b9ETarf%2BtQa%2FjJZ8hBRrtWhmbagmvzZxMp7cwL0Qw9uaM1mysB9NBpCoqIQDOcePdMZEHFLZpDurYiDspe%2BCL6nJVrxCp7oFg3bT%2F4TIz%2Bne280BX2zRADilg2s9pZYh%2Fa2tImjblVTGjsKkvA8FfryxYiBk1%2Ff7K9dYA9C5hNFSMM5D1Ao%2BYuWI%2BnUHuMRe%2BEg%2F4Q%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: fd5cca9926bea07c6f0e8626516e38b7 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0OFY0qyl6XeW1vPzg3R0","secret":"pi_3PiS0OFY0qyl6XeW1vPzg3R0_secret_3Ss1BkuHG6t0abBjqyfmIO0Ro","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0005_get_v1_payment_intents_pi_3PiS0OFY0qyl6XeW1vPzg3R0.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0005_get_v1_payment_intents_pi_3PiS0OFY0qyl6XeW1vPzg3R0.tail new file mode 100644 index 00000000..f4c5b3b0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0005_get_v1_payment_intents_pi_3PiS0OFY0qyl6XeW1vPzg3R0.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0OFY0qyl6XeW1vPzg3R0\?client_secret=pi_3PiS0OFY0qyl6XeW1vPzg3R0_secret_3Ss1BkuHG6t0abBjqyfmIO0Ro$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BsAwZJEbYlHliX +Content-Length: 1005 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:08 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "instant" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "requires_payment_method", + "payment_method" : null, + "client_secret" : "pi_3PiS0OFY0qyl6XeW1vPzg3R0_secret_3Ss1BkuHG6t0abBjqyfmIO0Ro", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS0OFY0qyl6XeW1vPzg3R0", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1722391868, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0006_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0006_post_create_payment_intent.tail new file mode 100644 index 00000000..0ae6452d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0006_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Jilh7ooUT85vCsSbWmRbPx7y5C6vKKVSN3C9BRgUB2rt2%2BrehdR7D0oAm7%2BFVCmVnG5n2qRFsLgNMLtwUc9EunG8ocAp9mls6xCvOQJGFqZJOI2m1UmRHItDnd3ACzU8hDgj9nvgvVjPZYgSBdiYi%2B%2Fwo6BkVE4wUhBmtrETnDFjEBbezIlE0uI7AISNKAcmBQrkkui2LxtxguBXf7mdALLV813c1H8FTuMU8v7UeLg%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0471e676008cef2653035099234a08d5 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:08 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0OFY0qyl6XeW0aiyhz3B","secret":"pi_3PiS0OFY0qyl6XeW0aiyhz3B_secret_IjkLYuJ6yV1O6ZxmGqzmE2mFB","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0007_get_v1_payment_intents_pi_3PiS0OFY0qyl6XeW0aiyhz3B.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0007_get_v1_payment_intents_pi_3PiS0OFY0qyl6XeW0aiyhz3B.tail new file mode 100644 index 00000000..a49a1d29 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0007_get_v1_payment_intents_pi_3PiS0OFY0qyl6XeW0aiyhz3B.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0OFY0qyl6XeW0aiyhz3B\?client_secret=pi_3PiS0OFY0qyl6XeW0aiyhz3B_secret_IjkLYuJ6yV1O6ZxmGqzmE2mFB$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_ZZwnBybdrPzVEn +Content-Length: 1011 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "microdeposits" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "requires_payment_method", + "payment_method" : null, + "client_secret" : "pi_3PiS0OFY0qyl6XeW0aiyhz3B_secret_IjkLYuJ6yV1O6ZxmGqzmE2mFB", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS0OFY0qyl6XeW0aiyhz3B", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1722391868, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0008_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0008_post_create_payment_intent.tail new file mode 100644 index 00000000..e56a3820 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0008_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=xWuaUXdh%2FC9QpMPt4wxac%2BB11rnksQk%2F91mQap7nXtOiqYFjYFKJ8wuu%2Brm82Xqs1UZ%2FQTtq0L9ZLpcymcDEV9khMETfDFlSaIve%2BPraABs13yechk1%2FQdOYtJBYeYX2%2Bqzr5OH1SmKEnvc9DG3ybeJqA7V%2FFFptkGGfEZDfO9Nbh9rtfG%2BZYWpHkNLA40qrL1JM9zwLt6iYJ4mHQ4fmfZNDlGIq%2BEmpJRHPFKloR8E%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 21a6dd0532fd24b9ba49820548bdf7f6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:09 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0PFY0qyl6XeW12ednumf","secret":"pi_3PiS0PFY0qyl6XeW12ednumf_secret_6IpFpvAFqqskqK6aQ3HDyHK7X","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0009_get_v1_payment_intents_pi_3PiS0PFY0qyl6XeW12ednumf.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0009_get_v1_payment_intents_pi_3PiS0PFY0qyl6XeW12ednumf.tail new file mode 100644 index 00000000..2a6102e9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsPaymentIntent/0009_get_v1_payment_intents_pi_3PiS0PFY0qyl6XeW12ednumf.tail @@ -0,0 +1,69 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0PFY0qyl6XeW12ednumf\?client_secret=pi_3PiS0PFY0qyl6XeW12ednumf_secret_6IpFpvAFqqskqK6aQ3HDyHK7X$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_PCB8FnkiFvtxnu +Content-Length: 1013 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:09 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "instant_or_skip" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "requires_payment_method", + "payment_method" : null, + "client_secret" : "pi_3PiS0PFY0qyl6XeW12ednumf_secret_6IpFpvAFqqskqK6aQ3HDyHK7X", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3PiS0PFY0qyl6XeW12ednumf", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1722391869, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..789b744f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=us6G2H8ulRQx6mQDl%2BamoPgv32mrzNFezRZTwMy3WbHNBxsV6MbRn9JaJILK95y4fGIphTnkP%2FFmMM3NggjGilHjtJ6h28%2Bup7HV3poQTnkTmWYHyrIXIRGkKlAtpjlJeYmoZqqV4B3ojBZI6YwZa%2F5LakwtbWRMBquusaeebzyxB34nrFvJFeXohMk0pacfJbbffapSMFSqM8HemhS4V%2FLcRH9YmHm%2BJF0wLHOUmls%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 16353194990fde3a5875d6bb711d8705 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0QFY0qyl6XeWGpO1S6kt","secret":"seti_1PiS0QFY0qyl6XeWGpO1S6kt_secret_QZbCt8jhVzKDpyWGQ9UvDqpjILi5WeA","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0001_get_v1_setup_intents_seti_1PiS0QFY0qyl6XeWGpO1S6kt.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0001_get_v1_setup_intents_seti_1PiS0QFY0qyl6XeWGpO1S6kt.tail new file mode 100644 index 00000000..b3414fa5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0001_get_v1_setup_intents_seti_1PiS0QFY0qyl6XeWGpO1S6kt.tail @@ -0,0 +1,50 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0QFY0qyl6XeWGpO1S6kt\?client_secret=seti_1PiS0QFY0qyl6XeWGpO1S6kt_secret_QZbCt8jhVzKDpyWGQ9UvDqpjILi5WeA$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_wsta6CEfDjFyMf +Content-Length: 646 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS0QFY0qyl6XeWGpO1S6kt", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391870, + "client_secret" : "seti_1PiS0QFY0qyl6XeWGpO1S6kt_secret_QZbCt8jhVzKDpyWGQ9UvDqpjILi5WeA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "skip" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0002_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0002_post_create_setup_intent.tail new file mode 100644 index 00000000..c0ce8790 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0002_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=xQMPMEdJoS4lY3EwaoCHg5JBd6e%2Btc5SyOe1UyiKASqu%2FGYsZzgnRuLl3RgFmIebREk%2Bg94YsE8w0MrxOaNmk%2BL3aP6jQeNQRo4X8YWprEx%2FxI8PHgdrxZqvGDy5hVVhKOO7pI%2BoZpBxyvdKnxJpdryyLp92JMy43rWdklmd%2Fn8cs%2Fh1o1%2BbFDgHt6NPCRWaXoUyCdiRSmJCjDa60q0PvmBY%2BKEsmLCzSRXVhENxPz4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 08ac34ee922a5dd024a223027426b500 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:10 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0QFY0qyl6XeWj297PnQP","secret":"seti_1PiS0QFY0qyl6XeWj297PnQP_secret_QZbCvlYluHUchqL2IpgYGGEyrsyO3Jm","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0003_get_v1_setup_intents_seti_1PiS0QFY0qyl6XeWj297PnQP.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0003_get_v1_setup_intents_seti_1PiS0QFY0qyl6XeWj297PnQP.tail new file mode 100644 index 00000000..c4824797 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0003_get_v1_setup_intents_seti_1PiS0QFY0qyl6XeWj297PnQP.tail @@ -0,0 +1,50 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0QFY0qyl6XeWj297PnQP\?client_secret=seti_1PiS0QFY0qyl6XeWj297PnQP_secret_QZbCvlYluHUchqL2IpgYGGEyrsyO3Jm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TPUTXO5k78hFNt +Content-Length: 651 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:10 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS0QFY0qyl6XeWj297PnQP", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391870, + "client_secret" : "seti_1PiS0QFY0qyl6XeWj297PnQP_secret_QZbCvlYluHUchqL2IpgYGGEyrsyO3Jm", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0004_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0004_post_create_setup_intent.tail new file mode 100644 index 00000000..33ac7364 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0004_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=9jrPx%2F%2B6qZf7e8dn7VtGFQrUDtx7V4P4%2BHmKFvP2CEF%2B%2Bjo6ZiQp83faLmVuAAEWQ1qlmRJeBmI3Nrms0zcZccDsNBeHVFy3cr8DRZ8FcPDF1b%2BtJLqJTixZWCOQjDyvyqnVrwEDSw2JZr93uG7%2FTdOEg8TXgq4mhWR3IkluoCHutdjEWl%2FZmJLb%2BUzG5M9xJlcbQwpVpFBCHTsq%2BB640w3E%2Bp2oaVufsO%2BtgwyZNHc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 4a2a8a1e4e5010d1816fc98c069496af +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0RFY0qyl6XeWGRj0nMpO","secret":"seti_1PiS0RFY0qyl6XeWGRj0nMpO_secret_QZbCOoKj6uCSuapxHSGRO94L42aEjgA","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0005_get_v1_setup_intents_seti_1PiS0RFY0qyl6XeWGRj0nMpO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0005_get_v1_setup_intents_seti_1PiS0RFY0qyl6XeWGRj0nMpO.tail new file mode 100644 index 00000000..6b5ffe4c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0005_get_v1_setup_intents_seti_1PiS0RFY0qyl6XeWGRj0nMpO.tail @@ -0,0 +1,50 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0RFY0qyl6XeWGRj0nMpO\?client_secret=seti_1PiS0RFY0qyl6XeWGRj0nMpO_secret_QZbCOoKj6uCSuapxHSGRO94L42aEjgA$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SZCO5owa2YN1Xd +Content-Length: 649 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:11 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS0RFY0qyl6XeWGRj0nMpO", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391871, + "client_secret" : "seti_1PiS0RFY0qyl6XeWGRj0nMpO_secret_QZbCOoKj6uCSuapxHSGRO94L42aEjgA", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "instant" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0006_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0006_post_create_setup_intent.tail new file mode 100644 index 00000000..2968e15e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0006_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=QDT0KGy7AZZa7jgvAfih0o7YLProAfDywaIfV0mS0MsXrnU%2BQmn7kqec61vOiYN3lb1mhtddUtVdFNJgiTaDSK6FqbsdEqhh8Rzpx529%2B%2B9Un9eQ2rZ3kF9PtkkBvZmzfUMckKPiYDo5yb%2B8WEoebam7B390g2O2Ua3wquKrp%2B4jlSzmtb8OofGyVx0%2Bj4YvsnbAbehhfM57RAKqDOb5BN86qQpfuNHTY9i3xL8fsjc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 63d5cd351b788c4d448f992402f303da +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:11 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0RFY0qyl6XeWlU8f2VRe","secret":"seti_1PiS0RFY0qyl6XeWlU8f2VRe_secret_QZbC77PvKOxgGaGmwNLzzU9hlHXPbZv","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0007_get_v1_setup_intents_seti_1PiS0RFY0qyl6XeWlU8f2VRe.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0007_get_v1_setup_intents_seti_1PiS0RFY0qyl6XeWlU8f2VRe.tail new file mode 100644 index 00000000..0415b671 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0007_get_v1_setup_intents_seti_1PiS0RFY0qyl6XeWlU8f2VRe.tail @@ -0,0 +1,50 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0RFY0qyl6XeWlU8f2VRe\?client_secret=seti_1PiS0RFY0qyl6XeWlU8f2VRe_secret_QZbC77PvKOxgGaGmwNLzzU9hlHXPbZv$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Hv8PoRU6U0QHrB +Content-Length: 655 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS0RFY0qyl6XeWlU8f2VRe", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391871, + "client_secret" : "seti_1PiS0RFY0qyl6XeWlU8f2VRe_secret_QZbC77PvKOxgGaGmwNLzzU9hlHXPbZv", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "microdeposits" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0008_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0008_post_create_setup_intent.tail new file mode 100644 index 00000000..0e5bcf42 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0008_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=TSUTknRmOMFK81VKPDmb4Gt7x3qjzKK%2Bm2%2FyD1Kg1oinIjmUw%2FJE3EigBue6%2BqRwOk8ufyXEuj0UmM%2FoNeFdj%2BPCmfQyJHwCzT5zpshtipd8sY7s%2BKeYu8GmZzNEJb36iPEy44v0LX7Ov%2Bzbs9WL2RfGqwvypvx%2ByBU43jxET0nWXpGQ%2B%2FGtMC1ZvRYtFinbCtu7tuur8wN9tzSRVFqf4xQ5Iz7nPUIs8MMityyB%2FgI%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 5fd952ea2ef4006292fe7a7b2cb214bb +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:12 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0SFY0qyl6XeWWOPVOd70","secret":"seti_1PiS0SFY0qyl6XeWWOPVOd70_secret_QZbCCecabVeoPHbE3MUV8u2kA26D2qN","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0009_get_v1_setup_intents_seti_1PiS0SFY0qyl6XeWWOPVOd70.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0009_get_v1_setup_intents_seti_1PiS0SFY0qyl6XeWWOPVOd70.tail new file mode 100644 index 00000000..4bd6e2d1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodOptionsTest/testUSBankAccountOptionsSetupIntent/0009_get_v1_setup_intents_seti_1PiS0SFY0qyl6XeWWOPVOd70.tail @@ -0,0 +1,50 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0SFY0qyl6XeWWOPVOd70\?client_secret=seti_1PiS0SFY0qyl6XeWWOPVOd70_secret_QZbCCecabVeoPHbE3MUV8u2kA26D2qN$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_AjL5sTSrR2vRpW +Content-Length: 657 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:12 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS0SFY0qyl6XeWWOPVOd70", + "description" : null, + "next_action" : null, + "status" : "requires_payment_method", + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391872, + "client_secret" : "seti_1PiS0SFY0qyl6XeWWOPVOd70_secret_QZbCCecabVeoPHbE3MUV8u2kA26D2qN", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "instant_or_skip" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPayPalParamsTests/testCreatePayPalPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPayPalParamsTests/testCreatePayPalPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..09ed347c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPayPalParamsTests/testCreatePayPalPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,59 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_vNnQObzKpe9OsM +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 577 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:13 GMT +original-request: req_vNnQObzKpe9OsM +stripe-version: 2020-08-27 +idempotency-key: 9b7f2c5e-1d5c-47d4-81ba-7b4a08623b2a +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jane%20Doe&payment_user_agent=.*&type=paypal + +{ + "object" : "payment_method", + "paypal" : { + "payer_email" : null, + "payer_id" : null, + "country" : null, + "verified_email" : null, + "fingerprint" : null + }, + "id" : "pm_1PiS0SFY0qyl6XeWWqqPmhDz", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391872, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPayPalTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HcI17FY0qyl6XeWcFAAbZCw.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPayPalTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HcI17FY0qyl6XeWcFAAbZCw.tail new file mode 100644 index 00000000..6ef41164 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPayPalTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HcI17FY0qyl6XeWcFAAbZCw.tail @@ -0,0 +1,98 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1HcI17FY0qyl6XeWcFAAbZCw\?client_secret=pi_1HcI17FY0qyl6XeWcFAAbZCw_secret_oAZ9OCoeyIg8EPeBEdF96ZJOT&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_eSYqS4axOdnP8c +Content-Length: 1971 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : { + "code" : "payment_intent_payment_attempt_failed", + "message" : "The latest payment attempt of this PaymentIntent has expired. You can provide payment_method_data or a new PaymentMethod to attempt to fulfill this PaymentIntent again.", + "payment_method" : { + "object" : "payment_method", + "paypal" : { + "payer_email" : null, + "payer_id" : null, + "country" : null, + "verified_email" : null, + "fingerprint" : null + }, + "id" : "pm_1HcI17FY0qyl6XeWPymHiiJd", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jane Doe", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1602712817, + "allow_redisplay" : "unspecified", + "type" : "paypal", + "customer" : null + }, + "type" : "invalid_request_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/payment-intent-payment-attempt-failed" + }, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_1HcI17FY0qyl6XeWcFAAbZCw_secret_oAZ9OCoeyIg8EPeBEdF96ZJOT", + "id" : "pi_1HcI17FY0qyl6XeWcFAAbZCw", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "paypal" + ], + "setup_future_usage" : null, + "created" : 1602712817, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPrzelewy24ParamsTests/testCreatePrzelewy24PaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPrzelewy24ParamsTests/testCreatePrzelewy24PaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..4b383afe --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPrzelewy24ParamsTests/testCreatePrzelewy24PaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_SnWaiICk35nHJ0 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 475 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:13 GMT +original-request: req_SnWaiICk35nHJ0 +stripe-version: 2020-08-27 +idempotency-key: ab24b58f-7fed-4c10-8ea6-8aa7b2934f71 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=email%40email\.com&metadata\[test_key]=test_value&payment_user_agent=.*&type=p24 + +{ + "object" : "payment_method", + "id" : "pm_1PiS0TFY0qyl6XeW9G5sO47M", + "billing_details" : { + "email" : "email@email.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391873, + "allow_redisplay" : "unspecified", + "type" : "p24", + "customer" : null, + "p24" : { + "bank" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPrzelewy24Tests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GciHFFY0qyl6XeWp9RdhmFF.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPrzelewy24Tests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GciHFFY0qyl6XeWp9RdhmFF.tail new file mode 100644 index 00000000..8fe5c035 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodPrzelewy24Tests/testCorrectParsing/0000_get_v1_payment_intents_pi_1GciHFFY0qyl6XeWp9RdhmFF.tail @@ -0,0 +1,88 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1GciHFFY0qyl6XeWp9RdhmFF\?client_secret=pi_1GciHFFY0qyl6XeWp9RdhmFF_secret_rFeERcidL1O5o1lwQUcIjLEZz&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_B6UCzN5bIsUOG4 +Content-Length: 1405 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:13 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_confirmation", + "object" : "payment_intent", + "currency" : "pln", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1GciAYFY0qyl6XeWVLY0nczF", + "billing_details" : { + "email" : "email@email.com", + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1588037010, + "allow_redisplay" : "unspecified", + "type" : "p24", + "customer" : null, + "p24" : { + "bank" : null + } + }, + "client_secret" : "pi_1GciHFFY0qyl6XeWp9RdhmFF_secret_rFeERcidL1O5o1lwQUcIjLEZz", + "id" : "pi_1GciHFFY0qyl6XeWp9RdhmFF", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "p24" + ], + "setup_future_usage" : null, + "created" : 1588037425, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodRevolutPayParamsTests/testCreateRevolutPayPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodRevolutPayParamsTests/testCreateRevolutPayPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..81663ff0 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodRevolutPayParamsTests/testCreateRevolutPayPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_dIVuo310rAMzRM +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 458 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:14 GMT +original-request: req_dIVuo310rAMzRM +stripe-version: 2020-08-27 +idempotency-key: d162f1fd-5d99-4eab-a245-4995811e8280 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=revolut_pay + +{ + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1PiS0TGoesj9fw9QQcqdydHx", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391873, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodRevolutPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodRevolutPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail new file mode 100644 index 00000000..82668f52 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodRevolutPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3NqgBBGoesj9fw9Q1TkY7iBp\?client_secret=pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LzftOX1YPcTc5h +Content-Length: 1306 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 299, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "gbp", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "revolut_pay" : { + + }, + "id" : "pm_1NqgBGGoesj9fw9QGikN8q6H", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1694800310, + "allow_redisplay" : "unspecified", + "type" : "revolut_pay", + "customer" : null + }, + "client_secret" : "pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6", + "id" : "pi_3NqgBBGoesj9fw9Q1TkY7iBp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "paypal", + "revolut_pay" + ], + "setup_future_usage" : null, + "created" : 1694800305, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSatispayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3PXrkJIFbdis1OxT0XLmWug3.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSatispayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3PXrkJIFbdis1OxT0XLmWug3.tail new file mode 100644 index 00000000..75bafd97 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSatispayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3PXrkJIFbdis1OxT0XLmWug3.tail @@ -0,0 +1,92 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PXrkJIFbdis1OxT0XLmWug3\?client_secret=pi_3PXrkJIFbdis1OxT0XLmWug3_secret_oZfKhPPuB4KNqR4H3f34ZgDvY&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_JpfuZkr5MIKkLq +Content-Length: 1473 +Vary: Origin +Date: Fri, 02 Aug 2024 00:25:29 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 5099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PXrkMIFbdis1OxTjdQPAgwX", + "billing_details" : { + "email" : "email@email.com", + "phone" : "+18008675309", + "name" : "Jenny Rosen", + "address" : { + "state" : "CA", + "country" : "US", + "line2" : null, + "city" : "South San Francisco", + "line1" : "354 Oyster Point Blvd", + "postal_code" : "94080" + } + }, + "livemode" : false, + "satispay" : { + + }, + "created" : 1719869210, + "allow_redisplay" : "unspecified", + "type" : "satispay", + "customer" : null + }, + "client_secret" : "pi_3PXrkJIFbdis1OxT0XLmWug3_secret_oZfKhPPuB4KNqR4H3f34ZgDvY", + "id" : "pi_3PXrkJIFbdis1OxT0XLmWug3", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "ideal", + "sepa_debit", + "bancontact", + "sofort", + "eps", + "giropay", + "p24", + "multibanco", + "satispay" + ], + "setup_future_usage" : null, + "created" : 1719869207, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSofortParamsTests/testCreateSofortPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSofortParamsTests/testCreateSofortPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..0f22c697 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSofortParamsTests/testCreateSofortPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TSawmokYtfNzTu +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 480 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:14 GMT +original-request: req_TSawmokYtfNzTu +stripe-version: 2020-08-27 +idempotency-key: 63a392be-0612-46c9-ad6e-78b46b5eb562 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&payment_user_agent=.*&sofort\[country]=DE&type=sofort + +{ + "object" : "payment_method", + "sofort" : { + "country" : "DE" + }, + "id" : "pm_1PiS0UFY0qyl6XeWH7xsTxDB", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391874, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSofortTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HDdfSFY0qyl6XeWjk7ogYVV.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSofortTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HDdfSFY0qyl6XeWjk7ogYVV.tail new file mode 100644 index 00000000..4487ca6a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSofortTests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HDdfSFY0qyl6XeWjk7ogYVV.tail @@ -0,0 +1,90 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1HDdfSFY0qyl6XeWjk7ogYVV\?client_secret=pi_1HDdfSFY0qyl6XeWjk7ogYVV_secret_5ikjoct7F271A4Bp6t7HkHwUo&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_0jWNBF3m9DGrxp +Content-Length: 1438 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:14 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "eur", + "last_payment_error" : null, + "amount_subtotal" : 1099, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : { + "object" : "payment_method", + "sofort" : { + "country" : "DE" + }, + "id" : "pm_1HDdfTFY0qyl6XeWrbY6UdTb", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1596837843, + "allow_redisplay" : "unspecified", + "type" : "sofort", + "customer" : null + }, + "client_secret" : "pi_1HDdfSFY0qyl6XeWjk7ogYVV_secret_5ikjoct7F271A4Bp6t7HkHwUo", + "id" : "pi_1HDdfSFY0qyl6XeWjk7ogYVV", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "sofort", + "giropay" + ], + "setup_future_usage" : null, + "created" : 1596837842, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSunbitPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSunbitPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail new file mode 100644 index 00000000..b12be515 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSunbitPayTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3NqgBBGoesj9fw9Q1TkY7iBp.tail @@ -0,0 +1,84 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3NqgBBGoesj9fw9Q1TkY7iBp\?client_secret=pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_EsLy5vEbyBufVT +Content-Length: 1306 +Vary: Origin +Date: Fri, 19 Jul 2024 22:36:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 299, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "sunbit" : { + + }, + "id" : "pm_1NqgBGGoesj9fw9QGikN8q6H", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1694800310, + "allow_redisplay" : "unspecified", + "type" : "sunbit", + "customer" : null + }, + "client_secret" : "pi_3NqgBBGoesj9fw9Q1TkY7iBp_secret_Ha7VfLCwaAuhEOshZiNnIDjh6", + "id" : "pi_3NqgBBGoesj9fw9Q1TkY7iBp", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "sunbit" + ], + "setup_future_usage" : null, + "created" : 1694800305, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSwishParamsTests/testCreateSwishPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSwishParamsTests/testCreateSwishPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..20dc8837 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSwishParamsTests/testCreateSwishPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yuCindtRNjkVk9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 446 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:15 GMT +original-request: req_yuCindtRNjkVk9 +stripe-version: 2020-08-27 +idempotency-key: df25f66c-813a-454e-b623-7633ff1f25bf +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&payment_user_agent=.*&type=swish + +{ + "object" : "payment_method", + "id" : "pm_1PiS0VKG6vc7r7YCUVJWzhxg", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1722391875, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSwishTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Nsu6oKG6vc7r7YC1FJJPNjg.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSwishTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Nsu6oKG6vc7r7YC1FJJPNjg.tail new file mode 100644 index 00000000..7ab5826a --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodSwishTests/testObjectDecoding/0000_get_v1_payment_intents_pi_3Nsu6oKG6vc7r7YC1FJJPNjg.tail @@ -0,0 +1,83 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3Nsu6oKG6vc7r7YC1FJJPNjg\?client_secret=pi_3Nsu6oKG6vc7r7YC1FJJPNjg_secret_wQtTkgmjgOMSqN7lje5RCtzrm&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_yaX1H0nvcvK4xe +Content-Length: 1282 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:15 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "sek", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1Nsu6zKG6vc7r7YCby0LS0Sb", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "swish" : { + + }, + "created" : 1695330517, + "allow_redisplay" : "unspecified", + "type" : "swish", + "customer" : null + }, + "client_secret" : "pi_3Nsu6oKG6vc7r7YC1FJJPNjg_secret_wQtTkgmjgOMSqN7lje5RCtzrm", + "id" : "pi_3Nsu6oKG6vc7r7YC1FJJPNjg", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "swish" + ], + "setup_future_usage" : null, + "created" : 1695330506, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUPIParamsTests/testCreateUPIPaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUPIParamsTests/testCreateUPIPaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..a278470c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUPIParamsTests/testCreateUPIPaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_RYng0zSpbmLL9d +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 484 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:16 GMT +original-request: req_RYng0zSpbmLL9d +stripe-version: 2020-08-27 +idempotency-key: 3f21027f-dce9-497f-80e0-9af3b673d779 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[name]=Jenny%20Rosen&metadata\[test_key]=test_value&payment_user_agent=.*&type=upi&upi\[vpa]=somevpa%40hdfcbank + +{ + "object" : "payment_method", + "upi" : { + "vpa" : "somevpa@hdfcbank" + }, + "id" : "pm_1PiS0WBte6TMTRd4jUTFoW5Y", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : "Jenny Rosen", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "created" : 1722391876, + "allow_redisplay" : "unspecified", + "type" : "upi", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUPITests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HlYxxBte6TMTRd48W66zjTJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUPITests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HlYxxBte6TMTRd48W66zjTJ.tail new file mode 100644 index 00000000..f46292f2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUPITests/testCorrectParsing/0000_get_v1_payment_intents_pi_1HlYxxBte6TMTRd48W66zjTJ.tail @@ -0,0 +1,85 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_1HlYxxBte6TMTRd48W66zjTJ\?client_secret=pi_1HlYxxBte6TMTRd48W66zjTJ_secret_TgB7p7e7aTRbr22UT6N6KNrSm&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_sCrYU8MDn7cTEJ +Content-Length: 1387 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:17 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1099, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "inr", + "last_payment_error" : null, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "payment_method" : { + "object" : "payment_method", + "upi" : { + "vpa" : "a@v" + }, + "id" : "pm_1HlYxyBte6TMTRd4mmtLewD9", + "billing_details" : { + "email" : "jenny@example.com", + "phone" : "(555) 555-5555", + "name" : "Jenny Rosen", + "address" : { + "state" : "RJ", + "country" : "IN", + "line2" : "#345", + "city" : "Jaipur", + "line1" : "123 Market St", + "postal_code" : "302033" + } + }, + "livemode" : false, + "created" : 1604922922, + "allow_redisplay" : "unspecified", + "type" : "upi", + "customer" : null + }, + "client_secret" : "pi_1HlYxxBte6TMTRd48W66zjTJ_secret_TgB7p7e7aTRbr22UT6N6KNrSm", + "id" : "pi_1HlYxxBte6TMTRd48W66zjTJ", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card", + "upi", + "netbanking" + ], + "setup_future_usage" : null, + "created" : 1604922921, + "description" : "Example PaymentIntent" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodcheckingindividual/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodcheckingindividual/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..4ffa9203 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodcheckingindividual/0000_post_v1_payment_methods.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Tv1cknPaF59niJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 865 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:17 GMT +original-request: req_Tv1cknPaF59niJ +stripe-version: 2020-08-27 +idempotency-key: d3e0cb2d-6956-4969-acbc-6bcda41976d0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=tester%40example\.com&billing_details\[name]=iOS%20CI%20Tester&payment_user_agent=.*&type=us_bank_account&us_bank_account\[account_holder_type]=individual&us_bank_account\[account_number]=000123456789&us_bank_account\[account_type]=checking&us_bank_account\[routing_number]=110000000 + +{ + "object" : "payment_method", + "id" : "pm_1PiS0XFY0qyl6XeWlsKwirbr", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1722391877, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodcheckingindividualsetwithstring/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodcheckingindividualsetwithstring/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..1cb6a44d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodcheckingindividualsetwithstring/0000_post_v1_payment_methods.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_uhxuCzvU8EIhZH +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 865 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:18 GMT +original-request: req_uhxuCzvU8EIhZH +stripe-version: 2020-08-27 +idempotency-key: 46e298e8-816c-41a2-b203-e71959398f11 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=tester%40example\.com&billing_details\[name]=iOS%20CI%20Tester&payment_user_agent=.*&type=us_bank_account&us_bank_account\[account_holder_type]=individual&us_bank_account\[account_number]=000123456789&us_bank_account\[account_type]=checking&us_bank_account\[routing_number]=110000000 + +{ + "object" : "payment_method", + "id" : "pm_1PiS0XFY0qyl6XeWqxNs5mT2", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1722391878, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodsavingscompany/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodsavingscompany/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..cbd1cc06 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodsavingscompany/0000_post_v1_payment_methods.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_zYgPL4jbooxvya +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 861 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:18 GMT +original-request: req_zYgPL4jbooxvya +stripe-version: 2020-08-27 +idempotency-key: 3499bc72-9c2a-48f5-a2d2-73fd409fcf48 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=tester%40example\.com&billing_details\[name]=iOS%20CI%20Tester&payment_user_agent=.*&type=us_bank_account&us_bank_account\[account_holder_type]=company&us_bank_account\[account_number]=000123456789&us_bank_account\[account_type]=savings&us_bank_account\[routing_number]=110000000 + +{ + "object" : "payment_method", + "id" : "pm_1PiS0YFY0qyl6XeWVYIpijic", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "company", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "savings" + }, + "created" : 1722391878, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodsavingscompanysetwithstring/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodsavingscompanysetwithstring/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..5e831614 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountParamsTest/testCreateUSBankAccountPaymentMethodsavingscompanysetwithstring/0000_post_v1_payment_methods.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_TizMMWstqHAGVb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 861 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:18 GMT +original-request: req_TizMMWstqHAGVb +stripe-version: 2020-08-27 +idempotency-key: f7cc5775-c681-416b-b755-2c8180191754 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&billing_details\[email]=tester%40example\.com&billing_details\[name]=iOS%20CI%20Tester&payment_user_agent=.*&type=us_bank_account&us_bank_account\[account_holder_type]=company&us_bank_account\[account_number]=000123456789&us_bank_account\[account_type]=savings&us_bank_account\[routing_number]=110000000 + +{ + "object" : "payment_method", + "id" : "pm_1PiS0YFY0qyl6XeWWG8eJPyn", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "company", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "savings" + }, + "created" : 1722391878, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountTest/testObjectDecoding/0000_get_v1_payment_intents_pi_3KhHLqFY0qyl6XeW1X2ZMsOT.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountTest/testObjectDecoding/0000_get_v1_payment_intents_pi_3KhHLqFY0qyl6XeW1X2ZMsOT.tail new file mode 100644 index 00000000..89f2918b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPPaymentMethodUSBankAccountTest/testObjectDecoding/0000_get_v1_payment_intents_pi_3KhHLqFY0qyl6XeW1X2ZMsOT.tail @@ -0,0 +1,106 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3KhHLqFY0qyl6XeW1X2ZMsOT\?client_secret=pi_3KhHLqFY0qyl6XeW1X2ZMsOT_secret_k5bOLoKJEW8ZhQFpokL0OrpbU&expand%5B0%5D=payment_method$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_bEoAQQk3vGIpZy +Content-Length: 1928 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:18 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 1000, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + }, + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 1000, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "status" : "succeeded", + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1KhHLqFY0qyl6XeWyGCJiLyj", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1648230906, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null + }, + "client_secret" : "pi_3KhHLqFY0qyl6XeW1X2ZMsOT_secret_k5bOLoKJEW8ZhQFpokL0OrpbU", + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "id" : "pi_3KhHLqFY0qyl6XeW1X2ZMsOT", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "us_bank_account" + ], + "setup_future_usage" : null, + "created" : 1648230906, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPRadarSessionFunctionalTest/testCreateWithoutInitialFraudDetection/0000_post_v1_radar_session.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPRadarSessionFunctionalTest/testCreateWithoutInitialFraudDetection/0000_post_v1_radar_session.tail new file mode 100644 index 00000000..7b4b8442 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPRadarSessionFunctionalTest/testCreateWithoutInitialFraudDetection/0000_post_v1_radar_session.tail @@ -0,0 +1,32 @@ +POST +https:\/\/api\.stripe\.com\/v1\/radar\/session$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fradar%2Fsession; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_7fseg9qlPnRe79 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 42 +Vary: Origin +Date: Fri, 19 Jul 2024 22:36:41 GMT +original-request: req_7fseg9qlPnRe79 +stripe-version: 2020-08-27 +idempotency-key: dd45e5f3-a37a-4f16-999f-ba17fb39275c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "rse_1PePPpFY0qyl6XeWak8Y2EAp" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPRadarSessionFunctionalTest/testCreateWithoutInitialFraudDetection/0001_post_v1_radar_session.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPRadarSessionFunctionalTest/testCreateWithoutInitialFraudDetection/0001_post_v1_radar_session.tail new file mode 100644 index 00000000..ad078963 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPRadarSessionFunctionalTest/testCreateWithoutInitialFraudDetection/0001_post_v1_radar_session.tail @@ -0,0 +1,32 @@ +POST +https:\/\/api\.stripe\.com\/v1\/radar\/session$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fradar%2Fsession; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_g3Vncoyl9vYqcS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 42 +Vary: Origin +Date: Fri, 19 Jul 2024 22:36:41 GMT +original-request: req_g3Vncoyl9vYqcS +stripe-version: 2020-08-27 +idempotency-key: a070777b-0d22-4db4-933f-6c3663227716 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "rse_1PePPpFY0qyl6XeWjtl7OYOe" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmAUBECSDebitSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmAUBECSDebitSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..24920a08 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmAUBECSDebitSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=C1DxciCQKH2cPH1AzxWCH3NzJKK8PKf0FEgYMpa630LHwutWhonPrwU2PAwlBmmJBSPwnKQmzlt32SnvFHP32YYu%2B66aOdMgxy6OyS0sjooZ5ChH4%2FaoI9mCpfbkAjIQ3yPAZwcnfeHSc0PQQJiX235bN12C4oPCKInesoLbUjwSSUixe6hRgJa57ufA83rTKQtI4RJ9J1r3F8pdjFMdN8KmzrDeSvfUekgnzHCycHc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: a14b0801f4de9839e49365e660d27ce2;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:20 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0aF7QokQdxByBD1C7K6i","secret":"seti_1PiS0aF7QokQdxByBD1C7K6i_secret_QZbDnRAJVK3mkEVtIeo4eFV4UuBu71S","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmAUBECSDebitSetupIntent/0001_post_v1_setup_intents_seti_1PiS0aF7QokQdxByBD1C7K6i_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmAUBECSDebitSetupIntent/0001_post_v1_setup_intents_seti_1PiS0aF7QokQdxByBD1C7K6i_confirm.tail new file mode 100644 index 00000000..11ee100d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmAUBECSDebitSetupIntent/0001_post_v1_setup_intents_seti_1PiS0aF7QokQdxByBD1C7K6i_confirm.tail @@ -0,0 +1,49 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0aF7QokQdxByBD1C7K6i\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_308umbJbMRQxZC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 553 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:20 GMT +original-request: req_308umbJbMRQxZC +stripe-version: 2020-08-27 +idempotency-key: 4fdbb4c2-3101-40d9-aac8-bc24ac244b75 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0aF7QokQdxByBD1C7K6i_secret_QZbDnRAJVK3mkEVtIeo4eFV4UuBu71S&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[au_becs_debit]\[account_number]=000123456&payment_method_data\[au_becs_debit]\[bsb_number]=000000&payment_method_data\[billing_details]\[email]=jrosen%40example\.com&payment_method_data\[billing_details]\[name]=Jenny%20Rosen&payment_method_data\[metadata]\[test_key]=test_value&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=au_becs_debit + +{ + "id" : "seti_1PiS0aF7QokQdxByBD1C7K6i", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS0aF7QokQdxByXUUqjYhA", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "au_becs_debit" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391880, + "client_secret" : "seti_1PiS0aF7QokQdxByBD1C7K6i_secret_QZbDnRAJVK3mkEVtIeo4eFV4UuBu71S", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentSucceeds/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentSucceeds/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..19506354 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentSucceeds/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=bs%2BK%2Fjhm0LhQpzfUl7tjQgDCl5dAtd%2F0jVwAz2tmFRs9PsqfznjRRGsh3mqY%2BbjjnQDx3Da577ta2hiywJjhV8SZ3S458XgOHTkusrtNiEaUa0v2M%2FPwPoiaKL5SraoC%2BoaO7SnhFEgD3dpVvpMGrzPwLENoeW6VteFctRKCNmzOo3rpjraY%2FLQQEQL90UjwgszfiC7XYDpjhRigwxRDDANQuAQ4fmnaAQUa8oe5GZ0%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 3e21f59b26c554d770c643ffb49af549 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:21 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0bFY0qyl6XeWH07n8saf","secret":"seti_1PiS0bFY0qyl6XeWH07n8saf_secret_QZbDfY7qEd5EuPQ0oxx4h2c1NQE1s3q","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentSucceeds/0001_post_v1_setup_intents_seti_1PiS0bFY0qyl6XeWH07n8saf_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentSucceeds/0001_post_v1_setup_intents_seti_1PiS0bFY0qyl6XeWH07n8saf_confirm.tail new file mode 100644 index 00000000..8dc4f536 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentSucceeds/0001_post_v1_setup_intents_seti_1PiS0bFY0qyl6XeWH07n8saf_confirm.tail @@ -0,0 +1,55 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0bFY0qyl6XeWH07n8saf\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_4rRraBzFg5JwBv +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 992 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:21 GMT +original-request: req_4rRraBzFg5JwBv +stripe-version: 2020-08-27 +idempotency-key: 3c0866d1-bd76-4200-bad0-55f02f73653c +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0bFY0qyl6XeWH07n8saf_secret_QZbDfY7qEd5EuPQ0oxx4h2c1NQE1s3q&payment_method=pm_card_authenticationRequired&return_url=example-app-scheme%3A\/\/authorized + +{ + "id" : "seti_1PiS0bFY0qyl6XeWH07n8saf", + "description" : null, + "next_action" : { + "type" : "redirect_to_url", + "redirect_to_url" : { + "return_url" : "example-app-scheme:\/\/authorized", + "url" : "https:\/\/hooks.stripe.com\/3d_secure_2\/hosted?merchant=acct_1G6m1pFY0qyl6XeW&publishable_key=pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6&setup_intent=seti_1PiS0bFY0qyl6XeWH07n8saf&setup_intent_client_secret=seti_1PiS0bFY0qyl6XeWH07n8saf_secret_QZbDfY7qEd5EuPQ0oxx4h2c1NQE1s3q&source=src_1PiS0bFY0qyl6XeWpwUmlOE4" + } + }, + "livemode" : false, + "payment_method" : "pm_1PiS0bFY0qyl6XeWhKUa7RUj", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391881, + "client_secret" : "seti_1PiS0bFY0qyl6XeWH07n8saf_secret_QZbDfY7qEd5EuPQ0oxx4h2c1NQE1s3q", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_action" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..45c8b271 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Ydc4OE4farcPybOoo4JwgjHMBEtXjg1o9i4jGKEXyX48t7KSb3Hj%2BMCDaxf4TTzKEyNO05%2Bh7%2Fj5TMkwVJE8EldrRptzvvIViRzI8F1SGMnHr5L7SZKTpD77pelNw7BZ7WjxmStFizwBZeHL3KaXpRDgLA7Nmd2XyrGw%2BnlpOBjKHeRmcaaKsen%2FymVAjdQAqlNahHnPaLJFh4kIZZfp6NyNVnRzIkU%2BJk5E4WUV7yA%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ed1d023aab343b833d961799da373b03 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:22 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0cFY0qyl6XeWrqnyI4iv","secret":"seti_1PiS0cFY0qyl6XeWrqnyI4iv_secret_QZbDOMu0JbJt4Y2sYKEmcuKu58XB03k","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0001_post_v1_setup_intents_seti_1PiS0cFY0qyl6XeWrqnyI4iv_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0001_post_v1_setup_intents_seti_1PiS0cFY0qyl6XeWrqnyI4iv_confirm.tail new file mode 100644 index 00000000..2a664611 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0001_post_v1_setup_intents_seti_1PiS0cFY0qyl6XeWrqnyI4iv_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0cFY0qyl6XeWrqnyI4iv\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_B7rM1GTjt5aYD4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:22 GMT +original-request: req_B7rM1GTjt5aYD4 +stripe-version: 2020-08-27 +idempotency-key: 8921c836-2640-44f3-afe4-afc31a0108c3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0cFY0qyl6XeWrqnyI4iv_secret_QZbDOMu0JbJt4Y2sYKEmcuKu58XB03k&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=tester%40example\.com&payment_method_data\[billing_details]\[name]=iOS%20CI%20Tester&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=us_bank_account&payment_method_data\[us_bank_account]\[account_holder_type]=individual&payment_method_data\[us_bank_account]\[account_number]=000123456789&payment_method_data\[us_bank_account]\[account_type]=checking&payment_method_data\[us_bank_account]\[routing_number]=110000000 + +{ + "id" : "seti_1PiS0cFY0qyl6XeWrqnyI4iv", + "description" : null, + "next_action" : { + "type" : "verify_with_microdeposits", + "verify_with_microdeposits" : { + "microdeposit_type" : "descriptor_code", + "arrival_date" : 1722495600, + "hosted_verification_url" : "https:\/\/payments.stripe.com\/microdeposit\/sacs_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHNhX25vbmNlX1FaYkRRbjlDdnFFTDJ2SEdzWHZTNnQzNGkyREtHaXM0000kIITI9fv" + } + }, + "status" : "requires_action", + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS0cFY0qyl6XeWd9GklFBe", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1722391882, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391882, + "client_secret" : "seti_1PiS0cFY0qyl6XeWrqnyI4iv_secret_QZbDOMu0JbJt4Y2sYKEmcuKu58XB03k", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0002_post_v1_setup_intents_seti_1PiS0cFY0qyl6XeWrqnyI4iv_verify_microdeposits.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0002_post_v1_setup_intents_seti_1PiS0cFY0qyl6XeWrqnyI4iv_verify_microdeposits.tail new file mode 100644 index 00000000..e0f468ea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithAmounts/0002_post_v1_setup_intents_seti_1PiS0cFY0qyl6XeWrqnyI4iv_verify_microdeposits.tail @@ -0,0 +1,54 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0cFY0qyl6XeWrqnyI4iv\/verify_microdeposits$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fverify_microdeposits; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_RIdox9YpToGzgw +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 662 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:23 GMT +original-request: req_RIdox9YpToGzgw +stripe-version: 2020-08-27 +idempotency-key: d823c6da-c098-4331-9699-ab0dd7a5b222 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amounts\[0]=32&amounts\[1]=45&client_secret=seti_1PiS0cFY0qyl6XeWrqnyI4iv_secret_QZbDOMu0JbJt4Y2sYKEmcuKu58XB03k + +{ + "id" : "seti_1PiS0cFY0qyl6XeWrqnyI4iv", + "description" : null, + "next_action" : null, + "status" : "succeeded", + "livemode" : false, + "payment_method" : "pm_1PiS0cFY0qyl6XeWd9GklFBe", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391882, + "client_secret" : "seti_1PiS0cFY0qyl6XeWrqnyI4iv_secret_QZbDOMu0JbJt4Y2sYKEmcuKu58XB03k", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..79ae8af4 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=xmFVb%2FSR9XhZaXkYq%2FRx3cYgSQS7V3qRz6RQ7KMWcK4AooomtLtChX0VLaHwAURJrtyh47A9tAyT7tAbGolEuD1T7w03DRSLy0O9e%2Fnej7mfD5XJA7EfK6rjeT5vWoWWMWiX04%2BKqpPH%2BuwjnQb9aTsMv3la3tV4hd32bAqLpWgbQCtDnz3AjXLsIYSkjqaDr0DBw0%2BTLJG4nznQAdCpSbK7lN5ZpdWcgj8Fe0XPmnc%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 10d525dfd1dda843d82db0178094d05d +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:23 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0dFY0qyl6XeW8aP6E5LA","secret":"seti_1PiS0dFY0qyl6XeW8aP6E5LA_secret_QZbD7c3NYfw3wD4eKIqvyB94t8XjPjQ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0001_post_v1_setup_intents_seti_1PiS0dFY0qyl6XeW8aP6E5LA_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0001_post_v1_setup_intents_seti_1PiS0dFY0qyl6XeW8aP6E5LA_confirm.tail new file mode 100644 index 00000000..ddb889cf --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0001_post_v1_setup_intents_seti_1PiS0dFY0qyl6XeW8aP6E5LA_confirm.tail @@ -0,0 +1,98 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0dFY0qyl6XeW8aP6E5LA\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_LJBpxsOfA2CTb7 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1923 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:24 GMT +original-request: req_LJBpxsOfA2CTb7 +stripe-version: 2020-08-27 +idempotency-key: 4a4c23b1-5761-4c6d-9dc1-d87a49677894 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0dFY0qyl6XeW8aP6E5LA_secret_QZbD7c3NYfw3wD4eKIqvyB94t8XjPjQ&expand\[0]=payment_method&mandate_data\[customer_acceptance]\[online]\[infer_from_client]=true&mandate_data\[customer_acceptance]\[type]=online&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[billing_details]\[email]=tester%40example\.com&payment_method_data\[billing_details]\[name]=iOS%20CI%20Tester&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=us_bank_account&payment_method_data\[us_bank_account]\[account_holder_type]=individual&payment_method_data\[us_bank_account]\[account_number]=000123456789&payment_method_data\[us_bank_account]\[account_type]=checking&payment_method_data\[us_bank_account]\[routing_number]=110000000 + +{ + "id" : "seti_1PiS0dFY0qyl6XeW8aP6E5LA", + "description" : null, + "next_action" : { + "type" : "verify_with_microdeposits", + "verify_with_microdeposits" : { + "microdeposit_type" : "descriptor_code", + "arrival_date" : 1722495600, + "hosted_verification_url" : "https:\/\/payments.stripe.com\/microdeposit\/sacs_test_YWNjdF8xRzZtMXBGWTBxeWw2WGVXLHNhX25vbmNlX1FaYkRnQ1VzSE5mS3VRN0xvamdrT0kzNDVmTnZ5WXo0000m82vbg0e" + } + }, + "status" : "requires_action", + "livemode" : false, + "payment_method" : { + "object" : "payment_method", + "id" : "pm_1PiS0dFY0qyl6XeWLLdJvtVQ", + "billing_details" : { + "email" : "tester@example.com", + "phone" : null, + "name" : "iOS CI Tester", + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "livemode" : false, + "us_bank_account" : { + "bank_name" : "STRIPE TEST BANK", + "fingerprint" : "ickfX9sbxIyAlbuh", + "financial_connections_account" : null, + "routing_number" : "110000000", + "last4" : "6789", + "account_holder_type" : "individual", + "networks" : { + "supported" : [ + "ach" + ], + "preferred" : "ach" + }, + "status_details" : null, + "account_type" : "checking" + }, + "created" : 1722391883, + "allow_redisplay" : "unspecified", + "type" : "us_bank_account", + "customer" : null + }, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391883, + "client_secret" : "seti_1PiS0dFY0qyl6XeW8aP6E5LA_secret_QZbD7c3NYfw3wD4eKIqvyB94t8XjPjQ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0002_post_v1_setup_intents_seti_1PiS0dFY0qyl6XeW8aP6E5LA_verify_microdeposits.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0002_post_v1_setup_intents_seti_1PiS0dFY0qyl6XeW8aP6E5LA_verify_microdeposits.tail new file mode 100644 index 00000000..2deba63f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testConfirmSetupIntentWithUSBankAccountverifyWithDescriptorCode/0002_post_v1_setup_intents_seti_1PiS0dFY0qyl6XeW8aP6E5LA_verify_microdeposits.tail @@ -0,0 +1,54 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0dFY0qyl6XeW8aP6E5LA\/verify_microdeposits$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fverify_microdeposits; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_I0p8Cb5kghV5oe +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 662 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:24 GMT +original-request: req_I0p8Cb5kghV5oe +stripe-version: 2020-08-27 +idempotency-key: a9a3be8c-9a29-4bcc-843f-fea3c28fde95 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0dFY0qyl6XeW8aP6E5LA_secret_QZbD7c3NYfw3wD4eKIqvyB94t8XjPjQ&descriptor_code=SM11AA + +{ + "id" : "seti_1PiS0dFY0qyl6XeW8aP6E5LA", + "description" : null, + "next_action" : null, + "status" : "succeeded", + "livemode" : false, + "payment_method" : "pm_1PiS0dFY0qyl6XeWLLdJvtVQ", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "us_bank_account" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391883, + "client_secret" : "seti_1PiS0dFY0qyl6XeW8aP6E5LA_secret_QZbD7c3NYfw3wD4eKIqvyB94t8XjPjQ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "payment_method_options" : { + "us_bank_account" : { + "verification_method" : "automatic" + } + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testCreateSetupIntentWithTestingServer/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testCreateSetupIntentWithTestingServer/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..01a72a33 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testCreateSetupIntentWithTestingServer/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=qOa6btu7x3M7%2F8T8HuVGuge7MLHGtWTC3Oq3MQyQVFJzr%2Bm%2FINcERwInsJhZ%2F30%2FNeWRO9bF8C9xcKz6V45wH7dHbeUqtJu7eVnr%2F7wP2OgoaONTNoalOgmsKQg6Qs4ChqzrMYvbgCO2qzAaso4ugVgWEdfodW9K4tpKtqvoITNKVpouiVjW48KYvNYATbwGxDUiT5f09nVmLQHoDYPbDgNXhyG15%2Fe2z5ykbi3VOwY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: e5eb5bf6bc0f55908a8c83a8bd4979d6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:25 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0eFY0qyl6XeWmaprCCd0","secret":"seti_1PiS0eFY0qyl6XeWmaprCCd0_secret_QZbDEPDadrjMljWyc6zJDtPYdeQLHUM","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testRetrieveSetupIntentSucceeds/0000_get_v1_setup_intents_seti_1GGCuIFY0qyl6XeWVfbQK6b3.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testRetrieveSetupIntentSucceeds/0000_get_v1_setup_intents_seti_1GGCuIFY0qyl6XeWVfbQK6b3.tail new file mode 100644 index 00000000..95621d9d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSetupIntentFunctionalTestSwift/testRetrieveSetupIntentSucceeds/0000_get_v1_setup_intents_seti_1GGCuIFY0qyl6XeWVfbQK6b3.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1GGCuIFY0qyl6XeWVfbQK6b3\?client_secret=seti_1GGCuIFY0qyl6XeWVfbQK6b3_secret_GnoX2tzX2JpvxsrcykRSVna2lrYLKew$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7pF54oP11SX4he +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:25 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1GGCuIFY0qyl6XeWVfbQK6b3", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1582673622, + "client_secret" : "seti_1GGCuIFY0qyl6XeWVfbQK6b3_secret_GnoX2tzX2JpvxsrcykRSVna2lrYLKew", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcealipay/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcealipay/0000_post_v1_sources.tail new file mode 100644 index 00000000..344a88d1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcealipay/0000_post_v1_sources.tail @@ -0,0 +1,65 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_9O7fiuhzBHpV4e +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 947 +Vary: Origin +Date: Wed, 31 Jul 2024 05:46:15 GMT +original-request: req_9O7fiuhzBHpV4e +stripe-version: 2020-08-27 +idempotency-key: 661c563a-1e84-4143-80d2-7ca8db232cbc +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: alipay\[app_bundle_id]=com\.apple\.dt\.xctest\.tool&alipay\[app_version_key]=.*&amount=1099¤cy=usd&guid=.*&metadata\[foo]=bar&muid=.*&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=alipay + +{ + "id" : "src_1PiVMYFY0qyl6XeWGhx9Hpl7", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "alipay", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiVMYFY0qyl6XeWGhx9Hpl7?client_secret=src_client_secret_FaCGUvyUpUMVD4Hst9kEjZuj", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "alipay" : { + "native_url" : null, + "statement_descriptor" : null, + "data_string" : null + }, + "created" : 1722404774, + "client_secret" : "src_client_secret_FaCGUvyUpUMVD4Hst9kEjZuj", + "flow" : "redirect", + "currency" : "usd", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcebancontact/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcebancontact/0000_post_v1_sources.tail new file mode 100644 index 00000000..a9402a33 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcebancontact/0000_post_v1_sources.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_cBxTaXce7g3fuJ +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1043 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:28 GMT +original-request: req_cBxTaXce7g3fuJ +stripe-version: 2020-08-27 +idempotency-key: 2450c3cc-99c4-43a6-8898-6e850934ad46 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099&bancontact\[statement_descriptor]=ORDER%20AT123¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=bancontact + +{ + "id" : "src_1PiS0iFY0qyl6XeWsKZoc2qo", + "bancontact" : { + "statement_descriptor" : "ORDER AT123", + "bank_name" : null, + "bic" : null, + "bank_code" : null, + "iban_last4" : null, + "preferred_language" : null + }, + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "bancontact", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0iFY0qyl6XeWsKZoc2qo?client_secret=src_client_secret_jz0gDHEjeV5SsTx35I0eYan3", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391888, + "client_secret" : "src_client_secret_jz0gDHEjeV5SsTx35I0eYan3", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcecard/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcecard/0000_post_v1_sources.tail new file mode 100644 index 00000000..25486181 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcecard/0000_post_v1_sources.tail @@ -0,0 +1,76 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_GHw8ONtOznl1Gi +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1072 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:29 GMT +original-request: req_GHw8ONtOznl1Gi +stripe-version: 2020-08-27 +idempotency-key: 6c31c19e-144d-4ed7-b6f5-0e7a70beb941 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[exp_month]=6&card\[exp_year]=2050&card\[number]=4242%204242%204242%204242&guid=.*&metadata\[foo]=bar&muid=.*&owner\[address]\[city]=New%20York&owner\[address]\[country]=USA&owner\[address]\[line1]=123%20Fake%20Street&owner\[address]\[line2]=Apartment%204&owner\[address]\[postal_code]=10002&owner\[address]\[state]=NY&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&sid=.*&type=card + +{ + "id" : "src_1PiS0jFY0qyl6XeWSBrq6eOL", + "livemode" : false, + "amount" : null, + "owner" : { + "address" : { + "state" : "NY", + "country" : "USA", + "line2" : "Apartment 4", + "city" : "New York", + "line1" : "123 Fake Street", + "postal_code" : "10002" + }, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "reusable", + "statement_descriptor" : null, + "type" : "card", + "object" : "source", + "card" : { + "last4" : "4242", + "dynamic_last4" : null, + "funding" : "credit", + "brand" : "Visa", + "exp_month" : 6, + "exp_year" : 2050, + "address_zip_check" : "unchecked", + "cvc_check" : null, + "tokenization_method" : null, + "address_line1_check" : "unchecked", + "country" : "US", + "name" : null, + "three_d_secure" : "optional" + }, + "created" : 1722391889, + "client_secret" : "src_client_secret_7Dr2yzkQeRHKM8hLugS7lAec", + "flow" : "none", + "currency" : null, + "status" : "chargeable" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceeps/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceeps/0000_post_v1_sources.tail new file mode 100644 index 00000000..0a3fb4ff --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceeps/0000_post_v1_sources.tail @@ -0,0 +1,64 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_j8hnAf2q5pa5fC +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 933 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:29 GMT +original-request: req_j8hnAf2q5pa5fC +stripe-version: 2020-08-27 +idempotency-key: 07c04d73-2a82-4b97-bbf2-89377ceeac9e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&statement_descriptor=ORDER%20AT123&type=eps + +{ + "id" : "src_1PiS0jFY0qyl6XeWWmYKSmDF", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "single_use", + "statement_descriptor" : "ORDER AT123", + "type" : "eps", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0jFY0qyl6XeWWmYKSmDF?client_secret=src_client_secret_dWfOoTKieYuwniANYi3qG9pP", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391889, + "client_secret" : "src_client_secret_dWfOoTKieYuwniANYi3qG9pP", + "eps" : { + "reference" : null, + "statement_descriptor" : null + }, + "currency" : "eur", + "flow" : "redirect", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceepsnostatementdescriptor/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceepsnostatementdescriptor/0000_post_v1_sources.tail new file mode 100644 index 00000000..9b5de42f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceepsnostatementdescriptor/0000_post_v1_sources.tail @@ -0,0 +1,64 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_7zeDQrPv64ZM6Z +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 924 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:30 GMT +original-request: req_7zeDQrPv64ZM6Z +stripe-version: 2020-08-27 +idempotency-key: 28cc287a-a5ea-4661-8990-94389b0b8aed +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=eps + +{ + "id" : "src_1PiS0jFY0qyl6XeWI0iArPah", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "eps", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0jFY0qyl6XeWI0iArPah?client_secret=src_client_secret_qex4yuY7lpZtYWp5rCXB09Va", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391889, + "client_secret" : "src_client_secret_qex4yuY7lpZtYWp5rCXB09Va", + "eps" : { + "reference" : null, + "statement_descriptor" : null + }, + "currency" : "eur", + "flow" : "redirect", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceideal/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceideal/0000_post_v1_sources.tail new file mode 100644 index 00000000..3710feaa --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceideal/0000_post_v1_sources.tail @@ -0,0 +1,66 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_arOkUyENnQnb6C +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 974 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:30 GMT +original-request: req_arOkUyENnQnb6C +stripe-version: 2020-08-27 +idempotency-key: 5a9a4dc6-db9b-4aa9-8f84-ed216bfae5a7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&ideal\[bank]=ing&ideal\[statement_descriptor]=ORDER%20AT123&metadata\[foo]=bar&muid=.*&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=ideal + +{ + "id" : "src_1PiS0kFY0qyl6XeWNvzqlftd", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "ideal", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0kFY0qyl6XeWNvzqlftd?client_secret=src_client_secret_3w5vSGtmrMmvlu83NiNQ3lc1", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "ideal" : { + "bank" : "ing", + "bic" : null, + "iban_last4" : null, + "statement_descriptor" : "ORDER AT123" + }, + "created" : 1722391890, + "client_secret" : "src_client_secret_3w5vSGtmrMmvlu83NiNQ3lc1", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceidealemptyOptionalFields/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceidealemptyOptionalFields/0000_post_v1_sources.tail new file mode 100644 index 00000000..5eb004c1 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceidealemptyOptionalFields/0000_post_v1_sources.tail @@ -0,0 +1,66 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_cnn25bjGEUxl1j +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 955 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:30 GMT +original-request: req_cnn25bjGEUxl1j +stripe-version: 2020-08-27 +idempotency-key: 7de5dd66-3ab9-45a3-96c7-cbf0f68371ec +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&muid=.*&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=ideal + +{ + "id" : "src_1PiS0kFY0qyl6XeWGoIV9EsG", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "ideal", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0kFY0qyl6XeWGoIV9EsG?client_secret=src_client_secret_vGXdOX8DccIxn56CWJ2yKbzX", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "ideal" : { + "bank" : null, + "bic" : null, + "iban_last4" : null, + "statement_descriptor" : null + }, + "created" : 1722391890, + "client_secret" : "src_client_secret_vGXdOX8DccIxn56CWJ2yKbzX", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceidealmissingOptionalFields/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceidealmissingOptionalFields/0000_post_v1_sources.tail new file mode 100644 index 00000000..238e7225 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceidealmissingOptionalFields/0000_post_v1_sources.tail @@ -0,0 +1,66 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_rAK1vdoMRZfE6u +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 955 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:31 GMT +original-request: req_rAK1vdoMRZfE6u +stripe-version: 2020-08-27 +idempotency-key: 3745878e-fe4c-4125-9049-33e9251fc442 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&muid=.*&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=ideal + +{ + "id" : "src_1PiS0lFY0qyl6XeWKePp35US", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "ideal", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0lFY0qyl6XeWKePp35US?client_secret=src_client_secret_0IONjk2OTKZjcyCWbMF93iSO", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "ideal" : { + "bank" : null, + "bic" : null, + "iban_last4" : null, + "statement_descriptor" : null + }, + "created" : 1722391891, + "client_secret" : "src_client_secret_0IONjk2OTKZjcyCWbMF93iSO", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceklarna/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceklarna/0000_post_v1_sources.tail new file mode 100644 index 00000000..51a21bfd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourceklarna/0000_post_v1_sources.tail @@ -0,0 +1,109 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_pUiOIPAfbTqjKs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 4331 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:32 GMT +original-request: req_pUiOIPAfbTqjKs +stripe-version: 2020-08-27 +idempotency-key: 86e56173-f579-4924-829b-ded298fdf2f4 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=600¤cy=GBP&flow=redirect&guid=.*&klarna\[custom_payment_methods]=&klarna\[first_name]=Arthur&klarna\[last_name]=Dent&klarna\[owner_dob_day]=11&klarna\[owner_dob_month]=03&klarna\[owner_dob_year]=1952&klarna\[product]=payment&klarna\[purchase_country]=GB&muid=.*&owner\[address]\[city]=London&owner\[address]\[country]=GB&owner\[address]\[line1]=29%20Arlington%20Avenue&owner\[address]\[postal_code]=N1%207BE&owner\[email]=test%40example\.com&owner\[phone]=02012267709&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/return\?redirect_merchant_name%3Dxctest&sid=.*&source_order\[items]\[0]\[amount]=500&source_order\[items]\[0]\[currency]=GBP&source_order\[items]\[0]\[description]=Test%20Item&source_order\[items]\[0]\[quantity]=2&source_order\[items]\[0]\[type]=sku&source_order\[items]\[1]\[amount]=100&source_order\[items]\[1]\[currency]=GBP&source_order\[items]\[1]\[description]=Tax&source_order\[items]\[1]\[quantity]=1&source_order\[items]\[1]\[type]=tax&type=klarna + +{ + "id" : "src_1PiS0lGoesj9fw9QWWUhWHRE", + "livemode" : false, + "amount" : 600, + "owner" : { + "address" : { + "state" : null, + "country" : "GB", + "line2" : null, + "city" : "London", + "line1" : "29 Arlington Avenue", + "postal_code" : "N1 7BE" + }, + "phone" : "02012267709", + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : "test@example.com", + "name" : null + }, + "source_order" : { + "amount" : 600, + "currency" : "gbp", + "email" : "test@example.com", + "items" : [ + { + "amount" : 500, + "currency" : "gbp", + "quantity" : 2, + "description" : "Test Item", + "type" : "sku", + "parent" : null + }, + { + "amount" : 100, + "currency" : "gbp", + "quantity" : 1, + "description" : "Tax", + "type" : "tax", + "parent" : null + } + ] + }, + "klarna" : { + "payment_method_categories" : "pay_over_time,pay_later", + "owner_dob_year" : "1952", + "client_token" : "eyJhbGciOiJSUzI1NiIsImtpZCI6IjgyMzA1ZWJjLWI4MTEtMzYzNy1hYTRjLTY2ZWNhMTg3NGYzZCJ9.eyJzZXNzaW9uX2lkIjoiNzNiODlkY2UtNGNiNy02YWFhLTljOGUtZTVjOGQ3NzZjODk3IiwiYmFzZV91cmwiOiJodHRwczovL2pzLnBsYXlncm91bmQua2xhcm5hLmNvbS9ldS9rcCIsImRlc2lnbiI6ImtsYXJuYSIsImxhbmd1YWdlIjoiZW4iLCJwdXJjaGFzZV9jb3VudHJ5IjoiR0IiLCJlbnZpcm9ubWVudCI6InBsYXlncm91bmQiLCJtZXJjaGFudF9uYW1lIjoiU3RyaXBlIFRlc3QgKEVVKSIsInNlc3Npb25fdHlwZSI6IlBBWU1FTlRTIiwiY2xpZW50X2V2ZW50X2Jhc2VfdXJsIjoiaHR0cHM6Ly9ldS5wbGF5Z3JvdW5kLmtsYXJuYWV2dC5jb20iLCJzY2hlbWUiOnRydWUsImV4cGVyaW1lbnRzIjpbeyJuYW1lIjoia3BjLXBzZWwtNDQyOSIsInZhcmlhdGUiOiJhIn0seyJuYW1lIjoia3AtY2xpZW50LW9uZS1wdXJjaGFzZS1mbG93IiwidmFyaWF0ZSI6InZhcmlhdGUtMSJ9LHsibmFtZSI6ImtwYy0xay1zZXJ2aWNlIiwidmFyaWF0ZSI6InZhcmlhdGUtMSJ9LHsibmFtZSI6ImtwLWNsaWVudC11dG9waWEtc3RhdGljLXdpZGdldCIsInZhcmlhdGUiOiJpbmRleCIsInBhcmFtZXRlcnMiOnsiZHluYW1pYyI6InRydWUifX0seyJuYW1lIjoia3AtY2xpZW50LXV0b3BpYS1mbG93IiwidmFyaWF0ZSI6InZhcmlhdGUtMSJ9LHsibmFtZSI6ImluLWFwcC1zZGstbmV3LWludGVybmFsLWJyb3dzZXIiLCJwYXJhbWV0ZXJzIjp7InZhcmlhdGVfaWQiOiJuZXctaW50ZXJuYWwtYnJvd3Nlci1lbmFibGUifX0seyJuYW1lIjoia3AtY2xpZW50LXV0b3BpYS1zZGstZmxvdyIsInZhcmlhdGUiOiJ2YXJpYXRlLTEifSx7Im5hbWUiOiJpbi1hcHAtc2RrLWNhcmQtc2Nhbm5pbmciLCJwYXJhbWV0ZXJzIjp7InZhcmlhdGVfaWQiOiJjYXJkLXNjYW5uaW5nLWVuYWJsZSJ9fV0sInJlZ2lvbiI6ImV1Iiwib3JkZXJfYW1vdW50Ijo2MDAsIm9mZmVyaW5nX29wdHMiOjAsIm9vIjoiN3MiLCJ2ZXJzaW9uIjoidjEuMTAuMC0xNTkwLWczZWJjMzkwNyJ9.c5DfBp__BCXZoOSAYjOc3x1hvNvDmAM6VxLtU9oCiMiXLuYCEehK8zmWO_GeoYSRJgv4PfXSgQ0HaaPhZRu1jKQXp3BMYRgkxU8YX4Eb92VZ2NfPnA2qf0qPT3R8tRvXwAqnOVLlnlSX3IhukidliLyyxh-Fyleid-d439caly416ccXdxnR6A9aWwJeY11MDMHnZhL_rs7XxVJ97acYQCtt1oaVgx4-tc8RlDUahb0JRLjlXtGT2YVCiJghQdM7czpEWIPCTA7bc8XOPot_9Ez47dxcnZv22GQ4uNsqDJtVSx5EdgCEt2UM3DyokZ6FoD_jjARbDt-D-k4BEe7S6Q", + "owner_dob_day" : "11", + "pay_later_asset_urls_standard" : "https:\/\/x.klarnacdn.net\/payment-method\/assets\/badges\/generic\/klarna.svg", + "pay_later_name" : "Pay in 30 days", + "pay_over_time_asset_urls_standard" : "https:\/\/x.klarnacdn.net\/payment-method\/assets\/badges\/generic\/klarna.svg", + "owner_dob_month" : "03", + "pay_later_asset_urls_descriptive" : "https:\/\/x.klarnacdn.net\/payment-method\/assets\/badges\/generic\/klarna.svg", + "pay_later_redirect_url" : "https:\/\/pay.playground.klarna.com\/eu\/hpp\/payments\/3FyUP4z", + "purchase_country" : "GB", + "first_name" : "Arthur", + "payment_intents_redirect_url" : "https:\/\/pay.playground.klarna.com\/eu\/hpp\/payments\/3gvP59e", + "pay_over_time_asset_urls_descriptive" : "https:\/\/x.klarnacdn.net\/payment-method\/assets\/badges\/generic\/klarna.svg", + "last_name" : "Dent", + "pay_over_time_name" : "3 interest-free instalments", + "pay_over_time_redirect_url" : "https:\/\/pay.playground.klarna.com\/eu\/hpp\/payments\/3NjHHHA" + }, + "statement_descriptor" : null, + "type" : "klarna", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0lGoesj9fw9QWWUhWHRE?client_secret=src_client_secret_oK3whWBTtAqPRlO6sW0WUbzR", + "return_url" : "https:\/\/shop.example.com\/return?redirect_merchant_name=xctest" + }, + "usage" : "single_use", + "object" : "source", + "created" : 1722391891, + "client_secret" : "src_client_secret_oK3whWBTtAqPRlO6sW0WUbzR", + "flow" : "redirect", + "currency" : "gbp", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcemultibanco/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcemultibanco/0000_post_v1_sources.tail new file mode 100644 index 00000000..0171a97d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcemultibanco/0000_post_v1_sources.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_wSA7SOgQnxP2zf +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1520 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:33 GMT +original-request: req_wSA7SOgQnxP2zf +stripe-version: 2020-08-27 +idempotency-key: ebdd0e28-385e-4ba5-93da-b93e27845226 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[email]=user%40example\.com&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=multibanco + +{ + "id" : "src_1PiS0mFY0qyl6XeWKViR8Jz3", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : "user@example.com", + "name" : null + }, + "receiver" : { + "refund_attributes_status" : "missing", + "amount_received" : 0, + "amount_charged" : 0, + "amount_returned" : 0, + "refund_attributes_method" : "email", + "address" : "999999999-12345" + }, + "statement_descriptor" : null, + "usage" : "single_use", + "type" : "multibanco", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0mFY0qyl6XeWKViR8Jz3?client_secret=src_client_secret_f1xarIofVJqmj6nfWFV3OPzv", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "multibanco" : { + "entity" : "12345", + "reference" : "999999999", + "refund_account_holder_address_city" : null, + "refund_account_holder_address_country" : null, + "refund_account_holder_address_line1" : null, + "refund_account_holder_name" : null, + "refund_iban" : null, + "refund_account_holder_address_postal_code" : null, + "refund_account_holder_address_line2" : null, + "refund_account_holder_address_state" : null + }, + "created" : 1722391892, + "client_secret" : "src_client_secret_f1xarIofVJqmj6nfWFV3OPzv", + "flow" : "receiver", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcep24/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcep24/0000_post_v1_sources.tail new file mode 100644 index 00000000..d46211ba --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcep24/0000_post_v1_sources.tail @@ -0,0 +1,63 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_S1PgQ8cysvqnVS +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 931 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:33 GMT +original-request: req_S1PgQ8cysvqnVS +stripe-version: 2020-08-27 +idempotency-key: 1ea49540-bc33-4384-b83b-d96ea5ea9590 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[email]=user%40example\.com&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&type=p24 + +{ + "id" : "src_1PiS0nFY0qyl6XeWL4whb4z8", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : "user@example.com", + "name" : "Jenny Rosen" + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "p24", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0nFY0qyl6XeWL4whb4z8?client_secret=src_client_secret_71OTWbLjIuMMvJxpGFGfmCC0", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391893, + "client_secret" : "src_client_secret_71OTWbLjIuMMvJxpGFGfmCC0", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending", + "p24" : { + "reference" : "P24-N01-101-101 STRIPE POLAND" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesepaDebit/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesepaDebit/0000_post_v1_sources.tail new file mode 100644 index 00000000..f3b1ad0c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesepaDebit/0000_post_v1_sources.tail @@ -0,0 +1,87 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_hmOU9Nk23VTrbs +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1536 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:33 GMT +original-request: req_hmOU9Nk23VTrbs +stripe-version: 2020-08-27 +idempotency-key: 9ac331c0-de61-4ace-9889-0b72cd73cdd7 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: currency=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[address]\[city]=Berlin&owner\[address]\[country]=DE&owner\[address]\[line1]=Nollendorfstra%C3%9Fe%2027&owner\[address]\[postal_code]=10777&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&sepa_debit\[iban]=DE89370400440532013000&sid=.*&type=sepa_debit + +{ + "id" : "src_1PiS0nFY0qyl6XeWjW6GEwL7", + "mandate" : { + "amount" : null, + "reference" : "YPAW1R0GCELVHSLT", + "url" : "https:\/\/hooks.stripe.com\/adapter\/sepa_debit\/file\/src_1PiS0nFY0qyl6XeWjW6GEwL7\/src_client_secret_E3jQ2xd1vvLKdZgJDEB43kIn", + "acceptance" : { + "online" : null, + "status" : "pending", + "date" : null, + "offline" : null, + "type" : null, + "ip" : null, + "user_agent" : null + }, + "notification_method" : "none", + "currency" : null, + "interval" : "variable" + }, + "livemode" : false, + "amount" : null, + "owner" : { + "address" : { + "state" : null, + "country" : "DE", + "line2" : null, + "city" : "Berlin", + "line1" : "Nollendorfstraße 27", + "postal_code" : "10777" + }, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "reusable", + "statement_descriptor" : null, + "type" : "sepa_debit", + "object" : "source", + "created" : 1722391893, + "client_secret" : "src_client_secret_E3jQ2xd1vvLKdZgJDEB43kIn", + "flow" : "none", + "currency" : "eur", + "status" : "chargeable", + "sepa_debit" : { + "branch_code" : null, + "country" : "DE", + "fingerprint" : "MamVJqscD6xUvxgA", + "mandate_url" : "https:\/\/hooks.stripe.com\/adapter\/sepa_debit\/file\/src_1PiS0nFY0qyl6XeWjW6GEwL7\/src_client_secret_E3jQ2xd1vvLKdZgJDEB43kIn", + "last4" : "3000", + "mandate_reference" : "YPAW1R0GCELVHSLT", + "bank_code" : "37040044" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesepaDebitNoAddress/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesepaDebitNoAddress/0000_post_v1_sources.tail new file mode 100644 index 00000000..f0167cee --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesepaDebitNoAddress/0000_post_v1_sources.tail @@ -0,0 +1,80 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_yGtysdVRHC2b3V +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1376 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:34 GMT +original-request: req_yGtysdVRHC2b3V +stripe-version: 2020-08-27 +idempotency-key: b50239ae-d7e4-4e9f-a739-2eeef5ace714 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: currency=eur&guid=.*&metadata\[foo]=bar&muid=.*&owner\[name]=Jenny%20Rosen&payment_user_agent=.*&sepa_debit\[iban]=DE89370400440532013000&sid=.*&type=sepa_debit + +{ + "id" : "src_1PiS0nFY0qyl6XeWB2EW1E5N", + "mandate" : { + "amount" : null, + "reference" : "G7A9L6RXFVARGV5W", + "url" : "https:\/\/hooks.stripe.com\/adapter\/sepa_debit\/file\/src_1PiS0nFY0qyl6XeWB2EW1E5N\/src_client_secret_JGPfLJbqA7pbNoTnms8lyJnE", + "acceptance" : { + "online" : null, + "status" : "pending", + "date" : null, + "offline" : null, + "type" : null, + "ip" : null, + "user_agent" : null + }, + "notification_method" : "none", + "currency" : null, + "interval" : "variable" + }, + "livemode" : false, + "amount" : null, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : "Jenny Rosen" + }, + "usage" : "reusable", + "statement_descriptor" : null, + "type" : "sepa_debit", + "object" : "source", + "created" : 1722391893, + "client_secret" : "src_client_secret_JGPfLJbqA7pbNoTnms8lyJnE", + "flow" : "none", + "currency" : "eur", + "status" : "chargeable", + "sepa_debit" : { + "branch_code" : null, + "country" : "DE", + "fingerprint" : "MamVJqscD6xUvxgA", + "mandate_url" : "https:\/\/hooks.stripe.com\/adapter\/sepa_debit\/file\/src_1PiS0nFY0qyl6XeWB2EW1E5N\/src_client_secret_JGPfLJbqA7pbNoTnms8lyJnE", + "last4" : "3000", + "mandate_reference" : "G7A9L6RXFVARGV5W", + "bank_code" : "37040044" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesofort/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesofort/0000_post_v1_sources.tail new file mode 100644 index 00000000..9f199776 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcesofort/0000_post_v1_sources.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_1quYR7BXT3OpRX +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1049 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:34 GMT +original-request: req_1quYR7BXT3OpRX +stripe-version: 2020-08-27 +idempotency-key: c1278c5a-b492-4b49-b576-c46789c00979 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtABC\?redirect_merchant_name%3Dxctest&sid=.*&sofort\[country]=DE&sofort\[statement_descriptor]=ORDER%20AT11990&type=sofort + +{ + "id" : "src_1PiS0oFY0qyl6XeW8lQ3Ewzt", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "sofort", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0oFY0qyl6XeW8lQ3Ewzt?client_secret=src_client_secret_0tM74igZiPfwUTqiXmJn0qvo", + "return_url" : "https:\/\/shop.example.com\/crtABC?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391894, + "sofort" : { + "bic" : null, + "country" : "DE", + "bank_name" : null, + "preferred_language" : null, + "statement_descriptor" : "ORDER AT11990", + "iban_last4" : null, + "bank_code" : null + }, + "client_secret" : "src_client_secret_0tM74igZiPfwUTqiXmJn0qvo", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcewechatPaytestMode/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcewechatPaytestMode/0000_post_v1_sources.tail new file mode 100644 index 00000000..234f11ea --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testCreateSourcewechatPaytestMode/0000_post_v1_sources.tail @@ -0,0 +1,65 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_uIcjyJ4bGHlOme +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1337 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:34 GMT +original-request: req_uIcjyJ4bGHlOme +stripe-version: 2020-08-27 +idempotency-key: 800b3c32-915f-420e-a66b-db41c2c54f54 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1010¤cy=usd&guid=.*&muid=.*&payment_user_agent=.*&sid=.*&type=wechat&wechat\[appid]=wxa0df51ec63e578ce + +{ + "id" : "src_1PiS0oBNJ02ErVOjHzozu8Iy", + "livemode" : false, + "amount" : 1010, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "wechat" : { + "android_sign" : "3E8506CC5377916837EE105A0C1045D33DC6BDA4C46790DFC741A61C52858711", + "android_package" : "Sign=WXPay", + "android_appId" : "wxa0df51ec63e578ce", + "ios_native_url" : "weixin:\/\/app\/wxa0df51ec63e578ce\/pay\/?appId=wxa0df51ec63e578ce&nonceStr=hnILeuadnCgkhJKQBGrCcXuB3vx0o6rV&package=Sign%3DWXPay&partnerId=&prepayId=test_transaction&timeStamp=1722391894&sign=3E8506CC5377916837EE105A0C1045D33DC6BDA4C46790DFC741A61C52858711", + "android_timeStamp" : "1722391894", + "qr_code_url" : "https:\/\/stripe.com\/sources\/test_source?client_secret=src_client_secret_tZACRWORPbzyKC1C5dxPXoVV&source=src_1PiS0oBNJ02ErVOjHzozu8Iy", + "android_nonceStr" : "hnILeuadnCgkhJKQBGrCcXuB3vx0o6rV", + "android_prepayId" : "test_transaction", + "prepay_id" : "test_transaction" + }, + "type" : "wechat", + "object" : "source", + "created" : 1722391894, + "client_secret" : "src_client_secret_tZACRWORPbzyKC1C5dxPXoVV", + "flow" : "none", + "currency" : "usd", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testRetrieveSourcesofort/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testRetrieveSourcesofort/0000_post_v1_sources.tail new file mode 100644 index 00000000..b200de66 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testRetrieveSourcesofort/0000_post_v1_sources.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_UHM1a7qGHGbt1J +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 1042 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:35 GMT +original-request: req_UHM1a7qGHGbt1J +stripe-version: 2020-08-27 +idempotency-key: 7ef2d5ab-215f-4c89-9fa9-9ba254e85902 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: amount=1099¤cy=eur&guid=.*&metadata\[foo]=bar&muid=.*&payment_user_agent=.*&redirect\[return_url]=https%3A\/\/shop\.example\.com\/crtA6B28E1\?redirect_merchant_name%3Dxctest&sid=.*&sofort\[country]=DE&type=sofort + +{ + "id" : "src_1PiS0oBbvEcIpqUbSyzeANyO", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "sofort", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0oBbvEcIpqUbSyzeANyO?client_secret=src_client_secret_nwMSRCeCacfWGIfxE50eTiHJ", + "return_url" : "https:\/\/shop.example.com\/crtA6B28E1?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391894, + "sofort" : { + "bic" : null, + "country" : "DE", + "bank_name" : null, + "preferred_language" : null, + "statement_descriptor" : null, + "iban_last4" : null, + "bank_code" : null + }, + "client_secret" : "src_client_secret_nwMSRCeCacfWGIfxE50eTiHJ", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testRetrieveSourcesofort/0001_get_v1_sources_src_1PiS0oBbvEcIpqUbSyzeANyO.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testRetrieveSourcesofort/0001_get_v1_sources_src_1PiS0oBbvEcIpqUbSyzeANyO.tail new file mode 100644 index 00000000..08ced140 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/STPSourceFunctionalTest/testRetrieveSourcesofort/0001_get_v1_sources_src_1PiS0oBbvEcIpqUbSyzeANyO.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/sources\/src_1PiS0oBbvEcIpqUbSyzeANyO\?client_secret=src_client_secret_nwMSRCeCacfWGIfxE50eTiHJ$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources%2F%3Asource; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_sPSI9SwzdVVuLe +Content-Length: 1042 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:35 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "src_1PiS0oBbvEcIpqUbSyzeANyO", + "livemode" : false, + "amount" : 1099, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "single_use", + "statement_descriptor" : null, + "type" : "sofort", + "redirect" : { + "status" : "pending", + "failure_reason" : null, + "url" : "https:\/\/hooks.stripe.com\/redirect\/authenticate\/src_1PiS0oBbvEcIpqUbSyzeANyO?client_secret=src_client_secret_nwMSRCeCacfWGIfxE50eTiHJ", + "return_url" : "https:\/\/shop.example.com\/crtA6B28E1?redirect_merchant_name=xctest" + }, + "object" : "source", + "created" : 1722391894, + "sofort" : { + "bic" : null, + "country" : "DE", + "bank_name" : null, + "preferred_language" : null, + "statement_descriptor" : null, + "iban_last4" : null, + "bank_code" : null + }, + "client_secret" : "src_client_secret_nwMSRCeCacfWGIfxE50eTiHJ", + "flow" : "redirect", + "currency" : "eur", + "status" : "pending" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCVCUpdate/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCVCUpdate/0000_post_v1_tokens.tail new file mode 100644 index 00000000..1148476f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCVCUpdate/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_MJBnHuThFHSXjF +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 186 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:41 GMT +original-request: req_MJBnHuThFHSXjF +stripe-version: 2020-08-27 +idempotency-key: 379cd654-771c-4124-a23b-56ed718e0d9b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: cvc_update\[cvc]=123&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "cvctok_1PiS0vFY0qyl6XeWaW08ELmh", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391901, + "used" : false, + "type" : "cvc_update" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCardToken/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCardToken/0000_post_v1_tokens.tail new file mode 100644 index 00000000..8e5b90b3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCardToken/0000_post_v1_tokens.tail @@ -0,0 +1,65 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_8r9lLocqMhH1PB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 791 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:35 GMT +original-request: req_8r9lLocqMhH1PB +stripe-version: 2020-08-27 +idempotency-key: 441a6c07-f77e-4691-88dc-fd271be0178e +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=42&card\[number]=4242424242424242&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "tok_1PiS0pFY0qyl6XeWY6Mg4EyV", + "card" : { + "address_line1_check" : null, + "dynamic_last4" : null, + "last4" : "4242", + "address_line2" : null, + "address_city" : null, + "address_zip_check" : null, + "address_zip" : null, + "country" : "US", + "object" : "card", + "address_line1" : null, + "address_state" : null, + "brand" : "Visa", + "cvc_check" : "unchecked", + "exp_month" : 12, + "networks" : { + "preferred" : null + }, + "name" : null, + "funding" : "credit", + "id" : "card_1PiS0pFY0qyl6XeW2xGKPIKR", + "tokenization_method" : null, + "address_country" : null, + "wallet" : null, + "exp_year" : 2042 + }, + "client_ip" : "136.24.137.206", + "livemode" : false, + "created" : 1722391895, + "used" : false, + "type" : "card" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..84d36898 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=RzFv%2FfpMUnIEItNN2WEcpwFGk8zZ7T9hQhr8L3ZdZuiDk6%2FBOLGKJyvmTdvTELqI55p9vC9tG1EmrxWgZdZr%2FbtVUy6mHECuOu3ybAyu5wYEyuwHbG0iBj%2Bv8RX%2Ba3BSv7fnZru4g0pnl2kDl53EtpI3N6Jwg6qcMc6%2F1PwnUYd4xWkEXktSPlorgV2IkGyB3OySq2pMXYoNIlv7UguXGTckmk%2F8pQKQnM9JjS64yhY%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: ed121d447da18d81c5e4255afe881e19;o=1 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0qFY0qyl6XeW0SQF0wEH","secret":"pi_3PiS0qFY0qyl6XeW0SQF0wEH_secret_DEydRJ8QPbN7uWYoxMoxFpIlf","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0001_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0001_post_create_payment_intent.tail new file mode 100644 index 00000000..30862666 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0001_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=0lp9%2BSdpOX086DohZhB1oQ%2BYCfqqYsN4YeQZ22%2F7XD7At97hnx7co%2Fvaa19qfLf766XBS2BDwgWLhg3LfxNJWUbtn1dz4Flj5YBXBTSVGAgm2tBihv%2FIlFE27UTSxYXuN8V8zD2%2BHObacQ8K7dZWx9%2BanHlxzNsovl6AQs2VIarY8rOjGKe%2Ftj1h%2BG7Z0x6SJbFnX5%2F45I7Ve8uGHdcuftuyQtG84pPylyacvyb1YVw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 56477d396c92d7d76bbbbedd006086ac +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:36 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0qFY0qyl6XeW13o46Tjh","secret":"pi_3PiS0qFY0qyl6XeW13o46Tjh_secret_PRYFVixi2YKHpqB6SxgdVnOXZ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0002_post_v1_payment_intents_pi_3PiS0qFY0qyl6XeW13o46Tjh_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0002_post_v1_payment_intents_pi_3PiS0qFY0qyl6XeW13o46Tjh_confirm.tail new file mode 100644 index 00000000..87e23629 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0002_post_v1_payment_intents_pi_3PiS0qFY0qyl6XeW13o46Tjh_confirm.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0qFY0qyl6XeW13o46Tjh\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_npFpQtpb1zPhXb +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:37 GMT +original-request: req_npFpQtpb1zPhXb +stripe-version: 2020-08-27 +idempotency-key: 206ca1f0-59a9-4a93-abf2-1ea97a802727 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS0qFY0qyl6XeW13o46Tjh_secret_PRYFVixi2YKHpqB6SxgdVnOXZ&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=42&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS0qFY0qyl6XeWhKrzIHs4", + "client_secret" : "pi_3PiS0qFY0qyl6XeW13o46Tjh_secret_PRYFVixi2YKHpqB6SxgdVnOXZ", + "id" : "pi_3PiS0qFY0qyl6XeW13o46Tjh", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391896, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0003_post_v1_payment_intents_pi_3PiS0qFY0qyl6XeW0SQF0wEH_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0003_post_v1_payment_intents_pi_3PiS0qFY0qyl6XeW0SQF0wEH_confirm.tail new file mode 100644 index 00000000..24a77a42 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmPaymentIntent/0003_post_v1_payment_intents_pi_3PiS0qFY0qyl6XeW0SQF0wEH_confirm.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0qFY0qyl6XeW0SQF0wEH\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_jsXlKVMGz2c17q +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 898 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:37 GMT +original-request: req_jsXlKVMGz2c17q +stripe-version: 2020-08-27 +idempotency-key: ecd0d979-6484-4406-9c8e-78e98e260d9b +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS0qFY0qyl6XeW0SQF0wEH_secret_DEydRJ8QPbN7uWYoxMoxFpIlf&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=42&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "succeeded", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : "pm_1PiS0qFY0qyl6XeWr9A0DFCy", + "client_secret" : "pi_3PiS0qFY0qyl6XeW0SQF0wEH_secret_DEydRJ8QPbN7uWYoxMoxFpIlf", + "id" : "pi_3PiS0qFY0qyl6XeW0SQF0wEH", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391896, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..bd064342 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=UoehoYvrMLHLlo6e5mzlqBElXikIxgU2WvdKUYBMmBGB%2B7lWwCpdHdhfQriKK07bMcIaAfBuLWSX1RQqfzvi2YeWTRHCAr13Q4XKWWwBidZ%2BUQpVpbNNH0td49GdQiehIryheklPhf%2Fun71XGlAnkiHjS%2FMYvg5HGVXt1AYF9i%2BbJjEdIxrlhgnEeJWAKGtyfAosLQali3tqvoD3Smewgwv%2FKbtmZ%2BOPw7Rz%2FjU9aa4%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 477cb441a068957c31809b09db6be593 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:37 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0rFY0qyl6XeWQlOZvEao","secret":"seti_1PiS0rFY0qyl6XeWQlOZvEao_secret_QZbDmwrNoi6mTQPB1QrAmeGQq1lIoo3","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmSetupIntent/0001_post_v1_setup_intents_seti_1PiS0rFY0qyl6XeWQlOZvEao_confirm.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmSetupIntent/0001_post_v1_setup_intents_seti_1PiS0rFY0qyl6XeWQlOZvEao_confirm.tail new file mode 100644 index 00000000..ea4ee7f3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testConfirmSetupIntent/0001_post_v1_setup_intents_seti_1PiS0rFY0qyl6XeWQlOZvEao_confirm.tail @@ -0,0 +1,49 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0rFY0qyl6XeWQlOZvEao\/confirm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Fconfirm; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_7QJzzqwPJc7J5Q +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 544 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:38 GMT +original-request: req_7QJzzqwPJc7J5Q +stripe-version: 2020-08-27 +idempotency-key: e2d582e5-5a39-4eb9-beb3-ed6f1d380147 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0rFY0qyl6XeWQlOZvEao_secret_QZbDmwrNoi6mTQPB1QrAmeGQq1lIoo3&payment_method_data\[allow_redisplay]=unspecified&payment_method_data\[card]\[cvc]=123&payment_method_data\[card]\[exp_month]=12&payment_method_data\[card]\[exp_year]=42&payment_method_data\[card]\[number]=4242424242424242&payment_method_data\[payment_user_agent]=.*&payment_method_data\[type]=card + +{ + "id" : "seti_1PiS0rFY0qyl6XeWQlOZvEao", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : "pm_1PiS0sFY0qyl6XeWyY2RnGeQ", + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391897, + "client_secret" : "seti_1PiS0rFY0qyl6XeWQlOZvEao_secret_QZbDmwrNoi6mTQPB1QrAmeGQq1lIoo3", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "succeeded" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0000_post_v1_tokens.tail new file mode 100644 index 00000000..2120d7da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0000_post_v1_tokens.tail @@ -0,0 +1,37 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WJ4VjFH80Wum04 +Content-Length: 321 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:38 GMT +original-request: req_WJ4VjFH80Wum04 +stripe-version: 2020-08-27 +idempotency-key: 95828c9b-170a-4421-a531-1892aa4c9e1d +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[name]=Test%20Testerson&guid=.*&muid=.*&payment_user_agent=.*&pk_token=%7B%22version%22%3A%22EC_v1%22%2C%22data%22%3A%22lF8RBjPvhc2GuhjEh7qFNijDJjxD\/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6%2BzeBx8OocvalHZ5ba\/WA\/tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY%2ButmdORhxyH4nUw2wuEY4lAE7\/GtEU\/RNDhaKx\/m93l0oLlk84qD1ynTA5JP3gjkdX%2BRK23iCAZDScXCcCU0OnYlJV8sDyf3%2B8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2%2BDNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3%2ByrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E%2BLlBdXiREmlz4u1EA%3D%22%2C%22signature%22%3A%22MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O\/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22\/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUI\/JJxE%2BT5O8n5sT2KGw\/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB\/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB\/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw%2BSoyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6%2BW5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS%2B\/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou\/ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT\/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk%2BTvJ%2BbE9ihsP6K7\/S5LMA8GA1UdEwEB\/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH\/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr\/0F%2B3ZD3VNoo6%2B8ZyBXkK3ifiY95tZn5jVQQ2PnenC\/gIwMi3VRCGwowV3bF3zODuQZ\/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf\/iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR%2BL6OVBH6iTPIFMBNPcq6gAAAAAAAA%3D%22%2C%22header%22%3A%7B%22transactionId%22%3A%22a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9%22%2C%22ephemeralPublicKey%22%3A%22MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV\/P7CQztw28IvYozvKvjBPQ%3D%3D%22%2C%22publicKeyHash%22%3A%22yRcyn7njT6JL3AY9nmg0KD\/xm\/ch7gW1sGl2OuEucZY%3D%22%7D%7D&pk_token_instrument_name=Master%20Charge&sid=.* + +{ + "error" : { + "message" : "The certificate used to sign your request is invalid. For help troubleshooting, please visit https:\/\/stripe.com\/docs\/apple-pay\/apps#troubleshooting.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_WJ4VjFH80Wum04?t=1722391898" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0001_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0001_post_v1_tokens.tail new file mode 100644 index 00000000..b285d9da --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0001_post_v1_tokens.tail @@ -0,0 +1,37 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tWFwcby52k77i3 +Content-Length: 321 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:38 GMT +original-request: req_tWFwcby52k77i3 +stripe-version: 2020-08-27 +idempotency-key: 557f1691-e7cd-4759-a7b2-6d828cc82e22 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[name]=Test%20Testerson&guid=.*&muid=.*&payment_user_agent=.*&pk_token=%7B%22version%22%3A%22EC_v1%22%2C%22data%22%3A%22lF8RBjPvhc2GuhjEh7qFNijDJjxD\/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6%2BzeBx8OocvalHZ5ba\/WA\/tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY%2ButmdORhxyH4nUw2wuEY4lAE7\/GtEU\/RNDhaKx\/m93l0oLlk84qD1ynTA5JP3gjkdX%2BRK23iCAZDScXCcCU0OnYlJV8sDyf3%2B8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2%2BDNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3%2ByrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E%2BLlBdXiREmlz4u1EA%3D%22%2C%22signature%22%3A%22MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O\/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22\/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUI\/JJxE%2BT5O8n5sT2KGw\/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB\/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB\/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw%2BSoyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6%2BW5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS%2B\/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou\/ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT\/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk%2BTvJ%2BbE9ihsP6K7\/S5LMA8GA1UdEwEB\/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH\/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr\/0F%2B3ZD3VNoo6%2B8ZyBXkK3ifiY95tZn5jVQQ2PnenC\/gIwMi3VRCGwowV3bF3zODuQZ\/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf\/iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR%2BL6OVBH6iTPIFMBNPcq6gAAAAAAAA%3D%22%2C%22header%22%3A%7B%22transactionId%22%3A%22a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9%22%2C%22ephemeralPublicKey%22%3A%22MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV\/P7CQztw28IvYozvKvjBPQ%3D%3D%22%2C%22publicKeyHash%22%3A%22yRcyn7njT6JL3AY9nmg0KD\/xm\/ch7gW1sGl2OuEucZY%3D%22%7D%7D&pk_token_instrument_name=Master%20Charge&sid=.* + +{ + "error" : { + "message" : "The certificate used to sign your request is invalid. For help troubleshooting, please visit https:\/\/stripe.com\/docs\/apple-pay\/apps#troubleshooting.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_tWFwcby52k77i3?t=1722391898" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0002_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0002_post_v1_tokens.tail new file mode 100644 index 00000000..d95a7cb7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateApplePayToken/0002_post_v1_tokens.tail @@ -0,0 +1,37 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +400 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_06Unw27IxNIN7F +Content-Length: 321 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:38 GMT +original-request: req_06Unw27IxNIN7F +stripe-version: 2020-08-27 +idempotency-key: 81a560e6-596d-41bb-9591-c544d83fc621 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[name]=Test%20Testerson&guid=.*&muid=.*&payment_user_agent=.*&pk_token=%7B%22version%22%3A%22EC_v1%22%2C%22data%22%3A%22lF8RBjPvhc2GuhjEh7qFNijDJjxD\/ApmGdQhgn8tpJcJDOwn2E1BkOfSvnhrR8BUGT6%2BzeBx8OocvalHZ5ba\/WA\/tDxGhcEcOMp8sIJrXMVcJ6WqT5P1ZY%2ButmdORhxyH4nUw2wuEY4lAE7\/GtEU\/RNDhaKx\/m93l0oLlk84qD1ynTA5JP3gjkdX%2BRK23iCAZDScXCcCU0OnYlJV8sDyf3%2B8hIo0gpN43AxoY6N1xAsVbGsO4ZjSCahaXbgt0egFug3s7Fyt9W4uzu07SKKCA2%2BDNZeZeerefpN1d1YbiCNlxFmffZKLCGdFERc7Ci3%2ByrHWWnYhKdQh8FeKCiiAvY5gbZJgQ91lNumCuP1IkHdHqxYI0qFk9c2R6KStJDtoUbVEYbxwnGdEJJPiMPjuKlgi7E%2BLlBdXiREmlz4u1EA%3D%22%2C%22signature%22%3A%22MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCAMIID4jCCA4igAwIBAgIIJEPyqAad9XcwCgYIKoZIzj0EAwIwejEuMCwGA1UEAwwlQXBwbGUgQXBwbGljYXRpb24gSW50ZWdyYXRpb24gQ0EgLSBHMzEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTE0MDkyNTIyMDYxMVoXDTE5MDkyNDIyMDYxMVowXzElMCMGA1UEAwwcZWNjLXNtcC1icm9rZXItc2lnbl9VQzQtUFJPRDEUMBIGA1UECwwLaU9TIFN5c3RlbXMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwhV37evWx7Ihj2jdcJChIY3HsL1vLCg9hGCV2Ur0pUEbg0IO2BHzQH6DMx8cVMP36zIg1rrV1O\/0komJPnwPE6OCAhEwggINMEUGCCsGAQUFBwEBBDkwNzA1BggrBgEFBQcwAYYpaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AwNC1hcHBsZWFpY2EzMDEwHQYDVR0OBBYEFJRX22\/VdIGGiYl2L35XhQfnm1gkMAwGA1UdEwEB\/wQCMAAwHwYDVR0jBBgwFoAUI\/JJxE%2BT5O8n5sT2KGw\/orv9LkswggEdBgNVHSAEggEUMIIBEDCCAQwGCSqGSIb3Y2QFATCB\/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwuYXBwbGUuY29tL2FwcGxlYWljYTMuY3JsMA4GA1UdDwEB\/wQEAwIHgDAPBgkqhkiG92NkBh0EAgUAMAoGCCqGSM49BAMCA0gAMEUCIHKKnw%2BSoyq5mXQr1V62c0BXKpaHodYu9TWXEPUWPpbpAiEAkTecfW6%2BW5l0r0ADfzTCPq2YtbS39w01XIayqBNy8bEwggLuMIICdaADAgECAghJbS%2B\/OpjalzAKBggqhkjOPQQDAjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzAeFw0xNDA1MDYyMzQ2MzBaFw0yOTA1MDYyMzQ2MzBaMHoxLjAsBgNVBAMMJUFwcGxlIEFwcGxpY2F0aW9uIEludGVncmF0aW9uIENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPAXEYQZ12SF1RpeJYEHduiAou\/ee65N4I38S5PhM1bVZls1riLQl3YNIk57ugj9dhfOiMt2u2ZwvsjoKYT\/VEWjgfcwgfQwRgYIKwYBBQUHAQEEOjA4MDYGCCsGAQUFBzABhipodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDA0LWFwcGxlcm9vdGNhZzMwHQYDVR0OBBYEFCPyScRPk%2BTvJ%2BbE9ihsP6K7\/S5LMA8GA1UdEwEB\/wQFMAMBAf8wHwYDVR0jBBgwFoAUu7DeoVgziJqkipnevr3rr9rLJKswNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5hcHBsZS5jb20vYXBwbGVyb290Y2FnMy5jcmwwDgYDVR0PAQH\/BAQDAgEGMBAGCiqGSIb3Y2QGAg4EAgUAMAoGCCqGSM49BAMCA2cAMGQCMDrPcoNRFpmxhvs1w1bKYr\/0F%2B3ZD3VNoo6%2B8ZyBXkK3ifiY95tZn5jVQQ2PnenC\/gIwMi3VRCGwowV3bF3zODuQZ\/0XfCwhbZZPxnJpghJvVPh6fRuZy5sJiSFhBpkPCZIdAAAxggFeMIIBWgIBATCBhjB6MS4wLAYDVQQDDCVBcHBsZSBBcHBsaWNhdGlvbiBJbnRlZ3JhdGlvbiBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMCCCRD8qgGnfV3MA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQxMjIyMDIxMzQyWjAvBgkqhkiG9w0BCQQxIgQgUak8LCvAswLOnY2vlZf\/iG3q04omAr3zV8YTtqvORGYwCgYIKoZIzj0EAwIERjBEAiAuPXMqEQqiTjYadOAvNmohP2yquB4owoQNjuAETkFXMAIgcH6zOxnbTTFmlEocqMztWR%2BL6OVBH6iTPIFMBNPcq6gAAAAAAAA%3D%22%2C%22header%22%3A%7B%22transactionId%22%3A%22a530c7d68b6a69791d8864df2646c8aa3d09d33b56d8f8162ab23e1b26afe5e9%22%2C%22ephemeralPublicKey%22%3A%22MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhKpIc6wTNQGy39bHM0a0qziDb20jMBFZT9XKSdjGULpDGRdyil6MLwMyIf3lQxaV\/P7CQztw28IvYozvKvjBPQ%3D%3D%22%2C%22publicKeyHash%22%3A%22yRcyn7njT6JL3AY9nmg0KD\/xm\/ch7gW1sGl2OuEucZY%3D%22%7D%7D&pk_token_instrument_name=Master%20Charge&sid=.* + +{ + "error" : { + "message" : "The certificate used to sign your request is invalid. For help troubleshooting, please visit https:\/\/stripe.com\/docs\/apple-pay\/apps#troubleshooting.", + "type" : "invalid_request_error", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_06Unw27IxNIN7F?t=1722391898" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateConnectAccount/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateConnectAccount/0000_post_v1_tokens.tail new file mode 100644 index 00000000..10801864 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateConnectAccount/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_E4c0nrQHLmXvNT +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 179 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:39 GMT +original-request: req_E4c0nrQHLmXvNT +stripe-version: 2020-08-27 +idempotency-key: b09afd59-814d-4e9b-af1b-9d4b44a4f400 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: account\[business_type]=company&account\[company]\[name]=Company&account\[tos_shown_and_accepted]=false&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "ct_1PiS0tFY0qyl6XeWHT8gYlFY", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391899, + "used" : false, + "type" : "account" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreatePaymentMethod/0000_post_v1_payment_methods.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreatePaymentMethod/0000_post_v1_payment_methods.tail new file mode 100644 index 00000000..3c3ca40f --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreatePaymentMethod/0000_post_v1_payment_methods.tail @@ -0,0 +1,77 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_methods$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_methods; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_GuWRnAvUavJdei +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 932 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:39 GMT +original-request: req_GuWRnAvUavJdei +stripe-version: 2020-08-27 +idempotency-key: ea0d1619-a486-4670-8104-820c4ceec3c0 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: allow_redisplay=unspecified&card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=42&card\[number]=4242424242424242&payment_user_agent=.*&type=card + +{ + "object" : "payment_method", + "id" : "pm_1PiS0tFY0qyl6XeWT6vootQf", + "billing_details" : { + "email" : null, + "phone" : null, + "name" : null, + "address" : { + "state" : null, + "country" : null, + "line2" : null, + "city" : null, + "line1" : null, + "postal_code" : null + } + }, + "card" : { + "last4" : "4242", + "funding" : "credit", + "generated_from" : null, + "networks" : { + "available" : [ + "visa" + ], + "preferred" : null + }, + "brand" : "visa", + "checks" : { + "address_postal_code_check" : null, + "cvc_check" : null, + "address_line1_check" : null + }, + "three_d_secure_usage" : { + "supported" : true + }, + "wallet" : null, + "display_brand" : "visa", + "exp_month" : 12, + "exp_year" : 2042, + "country" : "US" + }, + "livemode" : false, + "created" : 1722391899, + "allow_redisplay" : "unspecified", + "type" : "card", + "customer" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRadarSession/0000_post_v1_radar_session.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRadarSession/0000_post_v1_radar_session.tail new file mode 100644 index 00000000..a7ddf1bd --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRadarSession/0000_post_v1_radar_session.tail @@ -0,0 +1,33 @@ +POST +https:\/\/api\.stripe\.com\/v1\/radar\/session$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fradar%2Fsession; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_DSsGlw0kRV2DC1 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 42 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:39 GMT +original-request: req_DSsGlw0kRV2DC1 +stripe-version: 2020-08-27 +idempotency-key: 9ff52de9-82bf-4816-95e9-ec18c25a93f6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "id" : "rse_1PiS0tFY0qyl6XeW2jHhjii9" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0000_post_v1_sources.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0000_post_v1_sources.tail new file mode 100644 index 00000000..14da3b39 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0000_post_v1_sources.tail @@ -0,0 +1,69 @@ +POST +https:\/\/api\.stripe\.com\/v1\/sources$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_viYKv8uMRdqJzY +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 890 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:40 GMT +original-request: req_viYKv8uMRdqJzY +stripe-version: 2020-08-27 +idempotency-key: 7e2a66fd-61e6-4e63-9e19-e057ebc806d2 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=42&card\[number]=4242424242424242&guid=.*&muid=.*&payment_user_agent=.*&sid=.*&type=card + +{ + "id" : "src_1PiS0tFY0qyl6XeWYLZvQ3TJ", + "livemode" : false, + "amount" : null, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "reusable", + "statement_descriptor" : null, + "type" : "card", + "object" : "source", + "card" : { + "last4" : "4242", + "dynamic_last4" : null, + "funding" : "credit", + "brand" : "Visa", + "exp_month" : 12, + "exp_year" : 2042, + "address_zip_check" : null, + "cvc_check" : "unchecked", + "tokenization_method" : null, + "address_line1_check" : null, + "country" : "US", + "name" : null, + "three_d_secure" : "optional" + }, + "created" : 1722391899, + "client_secret" : "src_client_secret_mjLC7gR0F6DvyOB450CDRspu", + "flow" : "none", + "currency" : null, + "status" : "chargeable" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0001_get_v1_sources_src_1PiS0tFY0qyl6XeWYLZvQ3TJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0001_get_v1_sources_src_1PiS0tFY0qyl6XeWYLZvQ3TJ.tail new file mode 100644 index 00000000..6d371959 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0001_get_v1_sources_src_1PiS0tFY0qyl6XeWYLZvQ3TJ.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/sources\/src_1PiS0tFY0qyl6XeWYLZvQ3TJ\?client_secret=src_client_secret_mjLC7gR0F6DvyOB450CDRspu$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources%2F%3Asource; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_1nEdVgF2hOPYFS +Content-Length: 890 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "src_1PiS0tFY0qyl6XeWYLZvQ3TJ", + "livemode" : false, + "amount" : null, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "reusable", + "statement_descriptor" : null, + "type" : "card", + "object" : "source", + "card" : { + "last4" : "4242", + "dynamic_last4" : null, + "funding" : "credit", + "brand" : "Visa", + "exp_month" : 12, + "exp_year" : 2042, + "address_zip_check" : null, + "cvc_check" : "unchecked", + "tokenization_method" : null, + "address_line1_check" : null, + "country" : "US", + "name" : null, + "three_d_secure" : "optional" + }, + "created" : 1722391899, + "client_secret" : "src_client_secret_mjLC7gR0F6DvyOB450CDRspu", + "flow" : "none", + "currency" : null, + "status" : "chargeable" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0002_get_v1_sources_src_1PiS0tFY0qyl6XeWYLZvQ3TJ.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0002_get_v1_sources_src_1PiS0tFY0qyl6XeWYLZvQ3TJ.tail new file mode 100644 index 00000000..ea5158f2 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateRetrieveAndPollSource/0002_get_v1_sources_src_1PiS0tFY0qyl6XeWYLZvQ3TJ.tail @@ -0,0 +1,65 @@ +GET +https:\/\/api\.stripe\.com\/v1\/sources\/src_1PiS0tFY0qyl6XeWYLZvQ3TJ\?client_secret=src_client_secret_mjLC7gR0F6DvyOB450CDRspu$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsources%2F%3Asource; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=mono-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=mono-bapi-srv"}],"include_subdomains":true} +request-id: req_e8oe9D11Zq8R4m +Content-Length: 890 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:40 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "src_1PiS0tFY0qyl6XeWYLZvQ3TJ", + "livemode" : false, + "amount" : null, + "owner" : { + "address" : null, + "phone" : null, + "verified_address" : null, + "verified_phone" : null, + "verified_email" : null, + "verified_name" : null, + "email" : null, + "name" : null + }, + "usage" : "reusable", + "statement_descriptor" : null, + "type" : "card", + "object" : "source", + "card" : { + "last4" : "4242", + "dynamic_last4" : null, + "funding" : "credit", + "brand" : "Visa", + "exp_month" : 12, + "exp_year" : 2042, + "address_zip_check" : null, + "cvc_check" : "unchecked", + "tokenization_method" : null, + "address_line1_check" : null, + "country" : "US", + "name" : null, + "three_d_secure" : "optional" + }, + "created" : 1722391899, + "client_secret" : "src_client_secret_mjLC7gR0F6DvyOB450CDRspu", + "flow" : "none", + "currency" : null, + "status" : "chargeable" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithBankAccount/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithBankAccount/0000_post_v1_tokens.tail new file mode 100644 index 00000000..57134434 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithBankAccount/0000_post_v1_tokens.tail @@ -0,0 +1,53 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_OeAhMxLPKAgPvt +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 557 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:40 GMT +original-request: req_OeAhMxLPKAgPvt +stripe-version: 2020-08-27 +idempotency-key: 55011ee7-d1e8-4d8c-a33b-93a4bd6b4739 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: bank_account\[account_holder_type]=individual&bank_account\[account_number]=000123456789&bank_account\[country]=US&bank_account\[routing_number]=110000000&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "object" : "token", + "id" : "btok_1PiS0uFY0qyl6XeWG95daTrP", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391900, + "used" : false, + "type" : "bank_account", + "bank_account" : { + "id" : "ba_1PiS0uFY0qyl6XeWp31xJjZJ", + "account_holder_type" : "individual", + "last4" : "6789", + "bank_name" : "STRIPE TEST BANK", + "account_type" : null, + "status" : "new", + "account_holder_name" : null, + "routing_number" : "110000000", + "object" : "bank_account", + "country" : "US", + "currency" : "usd", + "name" : null + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithPII/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithPII/0000_post_v1_tokens.tail new file mode 100644 index 00000000..9d3041e9 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithPII/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_WRDdY4aIJuLjP9 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 176 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:40 GMT +original-request: req_WRDdY4aIJuLjP9 +stripe-version: 2020-08-27 +idempotency-key: d8694aa4-b64b-4884-8b38-84f80eeb5ad3 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pii\[personal_id_number]=123456789&sid=.* + +{ + "object" : "token", + "id" : "pii_1PiS0uFY0qyl6XeWWk0jhETG", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391900, + "used" : false, + "type" : "pii" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithSSNLast4/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithSSNLast4/0000_post_v1_tokens.tail new file mode 100644 index 00000000..afe5907c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testCreateTokenWithSSNLast4/0000_post_v1_tokens.tail @@ -0,0 +1,39 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_BaZPVb0nVQW2wB +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 176 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:41 GMT +original-request: req_BaZPVb0nVQW2wB +stripe-version: 2020-08-27 +idempotency-key: 9feb238c-0f2b-4513-bcad-c2cccbed6549 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: guid=.*&muid=.*&payment_user_agent=.*&pii\[ssn_last_4]=1234&sid=.* + +{ + "object" : "token", + "id" : "pii_1PiS0vFY0qyl6XeWdLTJ8eGJ", + "livemode" : false, + "client_ip" : "136.24.137.206", + "created" : 1722391901, + "used" : false, + "type" : "pii" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testPKPaymentError/0000_post_v1_tokens.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testPKPaymentError/0000_post_v1_tokens.tail new file mode 100644 index 00000000..c60d3113 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testPKPaymentError/0000_post_v1_tokens.tail @@ -0,0 +1,41 @@ +POST +https:\/\/api\.stripe\.com\/v1\/tokens$ +402 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ftokens; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_tFcMjtq23ijH7S +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 335 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:41 GMT +original-request: req_tFcMjtq23ijH7S +stripe-version: 2020-08-27 +idempotency-key: ba98a4ae-2554-4f3a-984b-690357fff443 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +Content-Language: en-us +x-content-type-options: nosniff +X-Stripe-Mock-Request: card\[cvc]=123&card\[exp_month]=12&card\[exp_year]=20&card\[number]=4242424242424242&guid=.*&muid=.*&payment_user_agent=.*&sid=.* + +{ + "error" : { + "code" : "invalid_expiry_year", + "message" : "Your card's expiration year is invalid.", + "param" : "exp_year", + "request_log_url" : "https:\/\/dashboard.stripe.com\/test\/logs\/req_tFcMjtq23ijH7S?t=1722391901", + "type" : "card_error", + "doc_url" : "https:\/\/stripe.com\/docs\/error-codes\/invalid-expiry-year" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshPaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshPaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..4c5732e3 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshPaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=W2ECbj674PYCjmyLOjxfNtRdRjL7dYhWiBP2iJuylIvl9yl3kP0FAtwjCGXts1UrN63Z3Mla0fhv0mJSvxN7OAxq69jb8w0unUqp%2Fodyz1j5Qp1zYcYuvG55w6NAVKhhrubWpRAboqJMN3tDitKCdOletO1aPgsotLe%2FpsrJR0TQd9868UX1kvbkjWsnEpBcuLicaA45VHabLSzDzNxO9nU%2BTwuSkZhtlsKGVbe2vTM%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 0c5c86a566207ab84a6b2923201b880f +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0vFY0qyl6XeW1D8gxVy2","secret":"pi_3PiS0vFY0qyl6XeW1D8gxVy2_secret_1nSpGCXRs4vZ3y3SlLQXIKL4G","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshPaymentIntent/0001_post_v1_payment_intents_pi_3PiS0vFY0qyl6XeW1D8gxVy2_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshPaymentIntent/0001_post_v1_payment_intents_pi_3PiS0vFY0qyl6XeW1D8gxVy2_refresh.tail new file mode 100644 index 00000000..89889c8b --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshPaymentIntent/0001_post_v1_payment_intents_pi_3PiS0vFY0qyl6XeW1D8gxVy2_refresh.tail @@ -0,0 +1,68 @@ +POST +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0vFY0qyl6XeW1D8gxVy2\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_p1ERh1WNqA5WVE +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:42 GMT +original-request: req_p1ERh1WNqA5WVE +stripe-version: 2020-08-27 +idempotency-key: 4b7c0874-a35b-4105-98bf-b985a8abfd50 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=pi_3PiS0vFY0qyl6XeW1D8gxVy2_secret_1nSpGCXRs4vZ3y3SlLQXIKL4G&expand\[0]=payment_method + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS0vFY0qyl6XeW1D8gxVy2_secret_1nSpGCXRs4vZ3y3SlLQXIKL4G", + "id" : "pi_3PiS0vFY0qyl6XeW1D8gxVy2", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391901, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..758afe47 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=tz8F6d34zOiFGOMa5T56HnARr6J%2FJKbXjWRdS7l8v80F8h%2B7zgsbvPEAHHhHTnrY5lLRIFpQpb6%2FC%2FoQoM93OTYc%2BCDUJkF68s1qfVYG6ZDuVZGyBktsoyciXdaQP%2B4ekxT86%2F06gh7zdgsF49%2BJDuo7kzY9x09BVuX5jClIxbpbSKv3xhurOiXek%2F%2FtvGKZSY5TA64GUcR%2FkG2sn2z6%2BS0j38kkKh5YFZUmCc3Yyjo%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: dcd8acd9ab894fdf0f9fff1edd5595d6 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:42 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0wFY0qyl6XeWYrQ3d3Gb","secret":"seti_1PiS0wFY0qyl6XeWYrQ3d3Gb_secret_QZbD3Dl10eUnEjNbHyuOsF0g0mvYQiZ","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshSetupIntent/0001_post_v1_setup_intents_seti_1PiS0wFY0qyl6XeWYrQ3d3Gb_refresh.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshSetupIntent/0001_post_v1_setup_intents_seti_1PiS0wFY0qyl6XeWYrQ3d3Gb_refresh.tail new file mode 100644 index 00000000..c534aff7 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRefreshSetupIntent/0001_post_v1_setup_intents_seti_1PiS0wFY0qyl6XeWYrQ3d3Gb_refresh.tail @@ -0,0 +1,49 @@ +POST +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0wFY0qyl6XeWYrQ3d3Gb\/refresh$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent%2Frefresh; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +stripe-should-retry: false +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_B9L1ku1DUAsJZ4 +x-stripe-routing-context-priority-tier: api-testmode +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:42 GMT +original-request: req_B9L1ku1DUAsJZ4 +stripe-version: 2020-08-27 +idempotency-key: 5cbe9c47-0fa4-4ba9-8c35-bcc1175b6fd6 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff +X-Stripe-Mock-Request: client_secret=seti_1PiS0wFY0qyl6XeWYrQ3d3Gb_secret_QZbD3Dl10eUnEjNbHyuOsF0g0mvYQiZ&expand\[0]=payment_method + +{ + "id" : "seti_1PiS0wFY0qyl6XeWYrQ3d3Gb", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391902, + "client_secret" : "seti_1PiS0wFY0qyl6XeWYrQ3d3Gb_secret_QZbD3Dl10eUnEjNbHyuOsF0g0mvYQiZ", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0000_post_create_payment_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0000_post_create_payment_intent.tail new file mode 100644 index 00000000..d3a5429c --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0000_post_create_payment_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_payment_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=Dyah%2BetgHVKcgyJGGxBOXzMomMN%2BS1HMmFAHL0vRNjqQKJW49%2Femu0ExdUxkgXP9%2BJQW7K0ldFa9qEB5siH%2Fh8SVHPAaXJMyHNbKqxf67j7qEXF6OdQVOXvVsF61NDaEZnWQO7pocJnJLir1jvJgVhtdSjblcKbIkzWzynC1ONsg5whgMeeEAGEp%2Ban%2FQWr7vkVYLQNMFTwM3gEd7enC1xieUA2xa%2B4KTTpg5T7x58Q%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 376d85ef1e4f1f0b70633045176b5c6b +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 147 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"pi_3PiS0xFY0qyl6XeW1QvdM01s","secret":"pi_3PiS0xFY0qyl6XeW1QvdM01s_secret_YLfwD4q1Qu3jywZFeQpGFdMbm","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0001_get_v1_payment_intents_pi_3PiS0xFY0qyl6XeW1QvdM01s.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0001_get_v1_payment_intents_pi_3PiS0xFY0qyl6XeW1QvdM01s.tail new file mode 100644 index 00000000..cce7b8db --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0001_get_v1_payment_intents_pi_3PiS0xFY0qyl6XeW1QvdM01s.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0xFY0qyl6XeW1QvdM01s\?client_secret=pi_3PiS0xFY0qyl6XeW1QvdM01s_secret_YLfwD4q1Qu3jywZFeQpGFdMbm&expand%5B0%5D=metadata$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_kpvH15hObfJIuB +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS0xFY0qyl6XeW1QvdM01s_secret_YLfwD4q1Qu3jywZFeQpGFdMbm", + "id" : "pi_3PiS0xFY0qyl6XeW1QvdM01s", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391903, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0002_get_v1_payment_intents_pi_3PiS0xFY0qyl6XeW1QvdM01s.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0002_get_v1_payment_intents_pi_3PiS0xFY0qyl6XeW1QvdM01s.tail new file mode 100644 index 00000000..c2e24147 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrievePaymentIntent/0002_get_v1_payment_intents_pi_3PiS0xFY0qyl6XeW1QvdM01s.tail @@ -0,0 +1,64 @@ +GET +https:\/\/api\.stripe\.com\/v1\/payment_intents\/pi_3PiS0xFY0qyl6XeW1QvdM01s\?client_secret=pi_3PiS0xFY0qyl6XeW1QvdM01s_secret_YLfwD4q1Qu3jywZFeQpGFdMbm$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fpayment_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_Pqt9P5zwOgmFVW +Content-Length: 887 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:43 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "payment_method_configuration_details" : null, + "canceled_at" : null, + "source" : null, + "amount" : 100, + "capture_method" : "automatic", + "livemode" : false, + "shipping" : null, + "status" : "requires_payment_method", + "object" : "payment_intent", + "currency" : "usd", + "last_payment_error" : null, + "amount_subtotal" : 100, + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "next_action" : null, + "total_details" : { + "amount_discount" : 0, + "amount_tax" : 0 + }, + "payment_method" : null, + "client_secret" : "pi_3PiS0xFY0qyl6XeW1QvdM01s_secret_YLfwD4q1Qu3jywZFeQpGFdMbm", + "id" : "pi_3PiS0xFY0qyl6XeW1QvdM01s", + "confirmation_method" : "automatic", + "amount_details" : { + "tip" : { + + } + }, + "processing" : null, + "receipt_email" : null, + "payment_method_types" : [ + "card" + ], + "setup_future_usage" : null, + "created" : 1722391903, + "description" : null +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrieveSetupIntent/0000_post_create_setup_intent.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrieveSetupIntent/0000_post_create_setup_intent.tail new file mode 100644 index 00000000..4acd0bf5 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrieveSetupIntent/0000_post_create_setup_intent.tail @@ -0,0 +1,18 @@ +POST +https:\/\/stp-mobile-ci-test-backend-e1b3\.stripedemos\.com\/create_setup_intent$ +200 +text/html +Content-Type: text/html;charset=utf-8 +Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 +Set-Cookie: rack.session=MSQPNPINznLVUsb6Yh7uVYLaoSyjNeQ0X%2FirBr9tG5euJkL9fBDGTkoG8gFxUlDl23UkDPXj3e7LWxTuZPvMj%2FTlP6NJbe%2BhYt4yKHIJCeuySXga6xYin9AmuVeGdwHjtN7T1evKoTMM1qZ%2BkThrx0t7EkuC9oa13%2BVWYv9Rlq4BJL2RJKF%2Fll6idfoo78fjyPYrN%2FEPC%2Bo9XK%2FD4m3fsJhrvIjxV6NQiWIzrY%2BR0uw%3D; path=/ +Server: Google Frontend +x-cloud-trace-context: 54b36d47cb4e8cc4b3d2a7414e171f05 +Via: 1.1 google +x-xss-protection: 1; mode=block +Date: Wed, 31 Jul 2024 02:11:43 GMT +x-robots-tag: noindex, nofollow +Content-Length: 157 +x-content-type-options: nosniff +x-frame-options: SAMEORIGIN + +{"intent":"seti_1PiS0xFY0qyl6XeWSVqYxcJi","secret":"seti_1PiS0xFY0qyl6XeWSVqYxcJi_secret_QZbD511oXU23lXowGXxE2hAdTAnImPE","status":"requires_payment_method"} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrieveSetupIntent/0001_get_v1_setup_intents_seti_1PiS0xFY0qyl6XeWSVqYxcJi.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrieveSetupIntent/0001_get_v1_setup_intents_seti_1PiS0xFY0qyl6XeWSVqYxcJi.tail new file mode 100644 index 00000000..af3e0a70 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testRetrieveSetupIntent/0001_get_v1_setup_intents_seti_1PiS0xFY0qyl6XeWSVqYxcJi.tail @@ -0,0 +1,45 @@ +GET +https:\/\/api\.stripe\.com\/v1\/setup_intents\/seti_1PiS0xFY0qyl6XeWSVqYxcJi\?client_secret=seti_1PiS0xFY0qyl6XeWSVqYxcJi_secret_QZbD511oXU23lXowGXxE2hAdTAnImPE$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Fsetup_intents%2F%3Aintent; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=payins-bapi-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: api-testmode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=payins-bapi-srv"}],"include_subdomains":true} +request-id: req_qHiGxnVCOGwqO7 +Content-Length: 533 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:44 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "id" : "seti_1PiS0xFY0qyl6XeWSVqYxcJi", + "description" : null, + "next_action" : null, + "livemode" : false, + "payment_method" : null, + "payment_method_configuration_details" : null, + "usage" : "off_session", + "payment_method_types" : [ + "card" + ], + "object" : "setup_intent", + "last_setup_error" : null, + "created" : 1722391903, + "client_secret" : "seti_1PiS0xFY0qyl6XeWSVqYxcJi_secret_QZbD511oXU23lXowGXxE2hAdTAnImPE", + "automatic_payment_methods" : null, + "cancellation_reason" : null, + "status" : "requires_payment_method" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testUploadFile/0000_post_v1_files.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testUploadFile/0000_post_v1_files.tail new file mode 100644 index 00000000..24d038ce --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/StripeAPIBridgeNetworkTest/testUploadFile/0000_post_v1_files.tail @@ -0,0 +1,38 @@ +POST +https:\/\/uploads\.stripe\.com\/v1\/files$ +200 +application/json +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +content-security-policy: report-uri https://q.stripe.com/csp-report?p=v1%2Ffiles; block-all-mixed-content; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; img-src 'self'; script-src 'self' 'report-sample'; style-src 'self' +Server: nginx +Cache-Control: no-cache, no-store +reporting-endpoints: coop="https://q.stripe.com/coop-report?s=grpc-upload-srv" +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +cross-origin-opener-policy-report-only: same-origin; report-to="coop" +Access-Control-Allow-Origin: * +x-stripe-routing-context-priority-tier: livemode +x-stripe-priority-routing-enabled: true +report-to: {"group":"coop","max_age":8640,"endpoints":[{"url":"https://q.stripe.com/coop-report?s=grpc-upload-srv"}],"include_subdomains":true} +request-id: req_GmE1J4mbaJyHB1 +Content-Length: 310 +Vary: Origin +Date: Wed, 31 Jul 2024 02:11:45 GMT +stripe-version: 2020-08-27 +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +access-control-allow-credentials: true +Content-Type: application/json +x-content-type-options: nosniff + +{ + "object" : "file", + "id" : "file_1PiS0yFY0qyl6XeWKuuhAxKA", + "expires_at" : 1745719904, + "purpose" : "dispute_evidence", + "size" : 160, + "created" : 1722391904, + "filename" : "image.jpg", + "title" : null, + "type" : "jpg", + "url" : "https:\/\/files.stripe.com\/v1\/files\/file_1PiS0yFY0qyl6XeWKuuhAxKA\/contents" +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidate/0000_get_edge-internal_card-metadata.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidate/0000_get_edge-internal_card-metadata.tail new file mode 100644 index 00000000..63b9c4fc --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidate/0000_get_edge-internal_card-metadata.tail @@ -0,0 +1,37 @@ +GET +https:\/\/api\.stripe\.com\/edge-internal\/card-metadata\?bin_prefix=623551$ +200 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +Content-Encoding: gzip +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +timing-allow-origin: * +Date: Wed, 31 Jul 2024 03:47:44 GMT +access-control-allow-credentials: true +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin + +{ + "data" : [ + { + "pan_length" : 19, + "brand" : "UNIONPAY", + "country" : "CN", + "account_range_high" : "6235519999999999999", + "account_range_low" : "6235510000000000000", + "funding" : "DEBIT" + }, + { + "pan_length" : 19, + "brand" : "NYCE", + "country" : "CN", + "account_range_high" : "6235519999999999999", + "account_range_low" : "6235510000000000000", + "funding" : "DEBIT" + } + ] +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidateWhenCallFails/0000_get_edge-internal_card-metadata.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidateWhenCallFails/0000_get_edge-internal_card-metadata.tail new file mode 100644 index 00000000..7804e5ec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidateWhenCallFails/0000_get_edge-internal_card-metadata.tail @@ -0,0 +1,24 @@ +GET +https:\/\/api\.stripe\.com\/edge-internal\/card-metadata\?bin_prefix=623551$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 31 Jul 2024 03:47:44 GMT +access-control-allow-credentials: true +Content-Length: 342 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Basic realm="Stripe" + +{ + "error" : { + "message" : "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https:\/\/stripe.com\/docs\/api#authentication for details, or we can help at https:\/\/support.stripe.com\/.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidateWhenCallFails/0001_get_edge-internal_card-metadata.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidateWhenCallFails/0001_get_edge-internal_card-metadata.tail new file mode 100644 index 00000000..7804e5ec --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testBINRangeThatRequiresNetworkCallToValidateWhenCallFails/0001_get_edge-internal_card-metadata.tail @@ -0,0 +1,24 @@ +GET +https:\/\/api\.stripe\.com\/edge-internal\/card-metadata\?bin_prefix=623551$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 31 Jul 2024 03:47:44 GMT +access-control-allow-credentials: true +Content-Length: 342 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Basic realm="Stripe" + +{ + "error" : { + "message" : "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https:\/\/stripe.com\/docs\/api#authentication for details, or we can help at https:\/\/support.stripe.com\/.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testPANValidation/0000_get_edge-internal_card-metadata.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testPANValidation/0000_get_edge-internal_card-metadata.tail new file mode 100644 index 00000000..1643070e --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testPANValidation/0000_get_edge-internal_card-metadata.tail @@ -0,0 +1,24 @@ +GET +https:\/\/api\.stripe\.com\/edge-internal\/card-metadata\?bin_prefix=620000$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Wed, 31 Jul 2024 03:47:45 GMT +access-control-allow-credentials: true +Content-Length: 342 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Basic realm="Stripe" + +{ + "error" : { + "message" : "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https:\/\/stripe.com\/docs\/api#authentication for details, or we can help at https:\/\/support.stripe.com\/.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testPANValidationcardBrandFiltering/0000_get_edge-internal_card-metadata.tail b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testPANValidationcardBrandFiltering/0000_get_edge-internal_card-metadata.tail new file mode 100644 index 00000000..e2d32a83 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/Resources/recorded_network_traffic/TextFieldElementCardTest/testPANValidationcardBrandFiltering/0000_get_edge-internal_card-metadata.tail @@ -0,0 +1,25 @@ +GET +https:\/\/api\.stripe\.com\/edge-internal\/card-metadata\?bin_prefix=620000$ +401 +application/json +Content-Type: application/json +Access-Control-Allow-Origin: * +x-wc: A +access-control-allow-methods: DELETE, GET, HEAD, PATCH, POST, PUT +Server: nginx +access-control-expose-headers: Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required +access-control-max-age: 300 +Cache-Control: no-cache, no-store +Date: Fri, 18 Oct 2024 15:55:01 GMT +access-control-allow-credentials: true +Content-Length: 342 +Strict-Transport-Security: max-age=63072000; includeSubDomains; preload +Vary: Origin +Www-Authenticate: Basic realm="Stripe" + +{ + "error" : { + "message" : "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https:\/\/stripe.com\/docs\/api#authentication for details, or we can help at https:\/\/support.stripe.com\/.", + "type" : "invalid_request_error" + } +} \ No newline at end of file diff --git a/StripePayments/StripePaymentsTestUtils/STPFixtures+Swift.swift b/StripePayments/StripePaymentsTestUtils/STPFixtures+Swift.swift new file mode 100644 index 00000000..074a997d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/STPFixtures+Swift.swift @@ -0,0 +1,111 @@ +// +// STPFixtures+Swift.swift +// StripeiOSTests +// +// Created by Yuki Tokuhiro on 3/22/23. +// + +import Foundation +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments + +public extension STPFixtures { + static func paymentMethodBillingDetails() -> STPPaymentMethodBillingDetails { + let billingDetails = STPPaymentMethodBillingDetails() + billingDetails.name = "Jane Doe" + billingDetails.email = "foo@bar.com" + billingDetails.phone = "5555555555" + billingDetails.address = STPPaymentMethodAddress() + billingDetails.address?.line1 = "510 Townsend St." + billingDetails.address?.line2 = "Line 2" + billingDetails.address?.city = "San Francisco" + billingDetails.address?.state = "CA" + billingDetails.address?.country = "US" + billingDetails.address?.postalCode = "94102" + return billingDetails + } + + static func paymentIntent( + paymentMethodTypes: [String], + setupFutureUsage: STPPaymentIntentSetupFutureUsage = .none, + currency: String = "usd", + status: STPPaymentIntentStatus = .requiresPaymentMethod, + paymentMethod: [AnyHashable: Any]? = nil, + nextAction: STPIntentActionType? = nil + ) -> STPPaymentIntent { + var apiResponse: [AnyHashable: Any] = [ + "id": "123", + "client_secret": "sec", + "amount": 2345, + "currency": currency, + "status": STPPaymentIntentStatus.string(from: status), + "livemode": false, + "created": 1652736692.0, + "payment_method_types": paymentMethodTypes, + ] + if let setupFutureUsage = setupFutureUsage.stringValue { + apiResponse["setup_future_usage"] = setupFutureUsage + } + if let paymentMethod = paymentMethod { + apiResponse["payment_method"] = paymentMethod + } + if let nextAction = nextAction { + apiResponse["next_action"] = ["type": nextAction.stringValue] + } + return STPPaymentIntent.decodedObject(fromAPIResponse: apiResponse)! + } + + static func setupIntent( + paymentMethodTypes: [String], + status: STPSetupIntentStatus = .requiresPaymentMethod, + paymentMethod: [AnyHashable: Any]? = nil, + nextAction: STPIntentActionType? = nil + ) -> STPSetupIntent { + var apiResponse: [AnyHashable: Any] = [ + "id": "123", + "client_secret": "sec", + "status": STPSetupIntentStatus.string(from: status), + "livemode": false, + "created": 1652736692.0, + "payment_method_types": paymentMethodTypes, + ] + + if let paymentMethod = paymentMethod { + apiResponse["payment_method"] = paymentMethod + } + if let nextAction = nextAction { + apiResponse["next_action"] = ["type": nextAction.stringValue] + } + return STPSetupIntent.decodedObject(fromAPIResponse: apiResponse)! + } + + static func usBankAccountPaymentMethod(bankName: String? = nil) -> STPPaymentMethod { + var json = STPTestUtils.jsonNamed("USBankAccountPaymentMethod") as? [String: Any] + if let bankName = bankName { + var usBankAccountData = json?["us_bank_account"] as? [String: Any] + usBankAccountData?["bank_name"] = bankName + json?["us_bank_account"] = usBankAccountData + } + return STPPaymentMethod.decodedObject(fromAPIResponse: json)! + } + + static func sepaDebitPaymentMethod() -> STPPaymentMethod { + let json = STPTestUtils.jsonNamed("SEPADebitPaymentMethod") + return STPPaymentMethod.decodedObject(fromAPIResponse: json)! + } +} + +public extension STPPaymentMethodParams { + static func _testValidCardValue() -> STPPaymentMethodParams { + return _testCardValue() + } + + static func _testCardValue(number: String = "4242424242424242") -> STPPaymentMethodParams { + let cardParams = STPPaymentMethodCardParams() + cardParams.number = number + cardParams.cvc = "123" + cardParams.expYear = (Calendar.current.dateComponents([.year], from: Date()).year! + 1) as NSNumber + cardParams.expMonth = 01 + return STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil) + } +} diff --git a/StripePayments/StripePaymentsTestUtils/STPNetworkStubbingTestCase.swift b/StripePayments/StripePaymentsTestUtils/STPNetworkStubbingTestCase.swift new file mode 100644 index 00000000..17a8dc02 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/STPNetworkStubbingTestCase.swift @@ -0,0 +1,225 @@ +// +// STPNetworkStubbingTestCase.swift +// StripeiOS Tests +// +// Created by Jack Flintermann on 11/24/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +import OHHTTPStubs +@testable@_spi(STP) import StripeCore +import XCTest + +/// Test cases that subclass `STPNetworkStubbingTestCase` will automatically capture all network traffic when run with `recordingMode = YES` and save it to disk. When run with `recordingMode = NO`, they will use the persisted request/response pairs, and raise an exception if an unexpected HTTP request is made. +/// ⚠️ Warning: `STPAPIClient`s created before `setUp` is called are not recorded! +/// To write manual requests, try APIStubbedTestCase instead. +@objc(STPNetworkStubbingTestCase) open class STPNetworkStubbingTestCase: XCTestCase { + /// Set this to YES to record all traffic during this test. The test will then fail, to remind you to set this back to NO before pushing. + open var recordingMode = false + + /// Set this to YES to disable network mocking entirely (e.g. in a nightly test) + open var disableMocking = false + + /// If `true` (the default), URL parameters will be recorded in requests. + /// Disable this if your test case sends paramters that may change (e.g. the time), as otherwise the requests may not match during playback. + open var strictParamsEnforcement = true + + /// If `true` (the default), the recorder will always follow redirects. + /// Otherwise, the recorder will record the body of the HTTP redirect request. + /// Disable this when testing the STPPaymentHandler "UnredirectableSessionDelegate" behavior. + open var followRedirects = true + + open override func setUp() { + super.setUp() + + recordingMode = ProcessInfo.processInfo.environment["STP_RECORD_NETWORK"] != nil + disableMocking = ProcessInfo.processInfo.environment["STP_NO_NETWORK_MOCKS"] != nil + + if disableMocking { + // Don't set this up + return + } + + // Set some default FraudDetectionData + FraudDetectionData.shared.sid = "00000000-0000-0000-0000-000000000000" + FraudDetectionData.shared.muid = "00000000-0000-0000-0000-000000000000" + FraudDetectionData.shared.guid = "00000000-0000-0000-0000-000000000000" + FraudDetectionData.shared.sidCreationDate = Date() + + // Set the STPTestingAPIClient to use the sharedURLSessionConfig so that we can intercept requests from it too + STPTestingAPIClient.shared.sessionConfig = + StripeAPIConfiguration.sharedUrlSessionConfiguration + + // Enable the Debug Params headers. We'll record these and include them in the header list. + StripeAPIConfiguration.includeDebugParamsHeader = true + + // [self name] returns a string like `-[STPMyTestCase testThing]` - this transforms it into the recorded path `recorded_network_traffic/STPMyTestCase/testThing`. + let rawComponents = name.components(separatedBy: " ") + assert(rawComponents.count == 2, "Invalid format received from XCTest#name: \(name)") + var components: [AnyHashable] = [] + (rawComponents as NSArray).enumerateObjects({ component, _, _ in + components.append( + (component as! NSString).components( + separatedBy: CharacterSet.alphanumerics.inverted + ) + .joined() + ) + }) + + let testClass = components[0] as! NSString + let testMethod = components[1] as! String + let relativePath = ("recorded_network_traffic" as NSString).appendingPathComponent( + testClass.appendingPathComponent(testMethod) + ) + + if recordingMode { + #if targetEnvironment(simulator) + #else + // Must be in the simulator, so that we can write recorded traffic into the repo. + assert(false, "Tests executed in recording mode must be run in the simulator.") + #endif + let config = StripeAPIConfiguration.sharedUrlSessionConfiguration + let recorder = SWHttpTrafficRecorder.shared() + + // Creates filenames like `post_v1_tokens_0.tail`. + var count = 0 + if strictParamsEnforcement { + // Just record the full URL, don't try to strip out params + recorder?.urlRegexPatternBlock = { request, _ in + // Need to escape this to fit in a regex (e.g. \? instead of ? before the query) + return NSRegularExpression.escapedPattern(for: request?.url?.absoluteString ?? "") + } + recorder?.postBodyTransformBlock = { _, postBody in + // Regex filter these: + let escapedBody = NSRegularExpression.escapedPattern(for: postBody ?? "") + // Then remove any params that may contain UUIDs or other random data + return replaceNondeterministicParams(escapedBody) + } + } else { + recorder?.urlRegexPatternBlock = nil + recorder?.postBodyTransformBlock = { _, _ in + return "" + } + } + recorder?.followRedirects = followRedirects + recorder?.fileNamingBlock = { request, _, _ in + let method = request!.httpMethod?.lowercased() + let urlPath = request!.url?.path.replacingOccurrences(of: "/", with: "_") + var fileName = "\(String(format: "%04d", count))_\(method ?? "")\(urlPath ?? "")" + fileName = + URL(fileURLWithPath: fileName).appendingPathExtension("tail").lastPathComponent + count += 1 + return fileName + } + + // The goal is for `basePath` to be e.g. `~/stripe-ios/Stripe/StripeiOSTests` + // A little gross/hardcoded (but it works fine); feel free to improve this... + let testDirectoryName = "stripe-ios/StripePayments/StripePaymentsTestUtils" + var basePath = "\(#file)" + while !basePath.hasSuffix(testDirectoryName) { + assert( + basePath.contains(testDirectoryName), + "Not in a subdirectory of \(testDirectoryName): \(#file)" + ) + basePath = URL(fileURLWithPath: basePath).deletingLastPathComponent().path + } + + let recordingPath = URL(fileURLWithPath: basePath) + .appendingPathComponent("Resources") + .appendingPathComponent(relativePath) + .path + // Delete existing stubs + do { + try FileManager.default.removeItem(atPath: recordingPath) + } catch { + } + guard + (try? SWHttpTrafficRecorder.shared().startRecording( + atPath: recordingPath, + for: config + )) != nil + else { + assert(false, "Error recording requests") + return + } + + // Make sure to fail, to remind ourselves to turn this off + addTeardownBlock { + XCTFail( + "Network traffic has been recorded - re-run with self.recordingMode = NO for this test to succeed" + ) + } + } else { + // Stubs are evaluated in the reverse order that they are added, so if the network is hit and no other stub is matched, raise an exception + HTTPStubs.stubRequests( + passingTest: { _ in + return true + }, + withStubResponse: { request in + XCTFail("Attempted to hit the live network at \(request.url?.path ?? "")") + return HTTPStubsResponse() + } + ) + + // Note: in order to make this work, the stub files (end in .tail) must be added to the test bundle during Build Phases/Copy Resources Step. + let bundle = Bundle(for: STPNetworkStubbingTestCase.self) + let url = bundle.url(forResource: relativePath, withExtension: nil) + if url != nil { + var stubError: NSError? + HTTPStubs.stubRequestsUsingMocktails( + atPath: relativePath, + in: bundle, + error: &stubError, + removeAfterUse: true + ) + if let stubError = stubError { + XCTFail("Error stubbing requests: \(stubError)") + } + } else { + print("No stubs found - all network access will raise an exception.") + } + } + } + + open override func tearDown() { + super.tearDown() + + if disableMocking { + // No teardown needed + return + } + + // Additional calls to `setFileNamingBlock` will be ignored if you don't do this + SWHttpTrafficRecorder.shared().stopRecording() + + // Don't accidentally keep any stubs around during the next test run + HTTPStubs.removeAllStubs() + } +} + +// Function to filter out some common UUIDs or other request parameters that may change +private func replaceNondeterministicParams(_ input: String) -> String { + let componentsToFilter = [ + "guid=", // Fraud detection data + "muid=", + "sid=", + "[guid]=", + "[muid]=", + "[sid]=", + "app_version_key", // Current version of Xcode, for Alipay + + "payment_user_agent", // Contains the SDK version number + "pk_token_transaction_id", // Random string + ] + var components = input.components(separatedBy: "&") + + for (index, component) in components.enumerated() { + if componentsToFilter.first(where: { component.contains($0) }) != nil { + let parts = component.components(separatedBy: "=") + XCTAssertEqual(parts.count, 2, "Invalid portion of query string: index\(index), component: \(component)") + components[index] = "\(parts[0])=.*" + } + } + + return components.joined(separator: "&") +} diff --git a/StripePayments/StripePaymentsTestUtils/STPTestAPIClient+Swift.swift b/StripePayments/StripePaymentsTestUtils/STPTestAPIClient+Swift.swift new file mode 100644 index 00000000..1fc3c294 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/STPTestAPIClient+Swift.swift @@ -0,0 +1,11 @@ +// +// STPTestAPIClient+Swift.swift +// StripeiOS Tests +// +// Created by Cameron Sabol on 4/21/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Just redeclares constants in STPTestingAPIClient.h for ease of use in Swift tests diff --git a/StripePayments/StripePaymentsTestUtils/STPTestingAPIClient+Swift.swift b/StripePayments/StripePaymentsTestUtils/STPTestingAPIClient+Swift.swift new file mode 100644 index 00000000..d4013189 --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/STPTestingAPIClient+Swift.swift @@ -0,0 +1,172 @@ +// +// STPTestingAPIClient+Swift.swift +// StripeiOSTests +// +// Created by Yuki Tokuhiro on 6/25/23. +// + +import Foundation +@_exported import StripePaymentsObjcTestUtils + +extension STPTestingAPIClient { + static let STPTestingBackendURL = "https://stp-mobile-ci-test-backend-e1b3.stripedemos.com/" + public static var shared: STPTestingAPIClient { + return .shared() + } + + func fetchPaymentIntent( + types: [String], + currency: String = "eur", + amount: Int? = nil, + merchantCountry: String? = "us", + paymentMethodID: String? = nil, + shouldSavePM: Bool = false, + customerID: String? = nil, + confirm: Bool = false, + otherParams: [String: Any] = [:], + completion: @escaping (Result<(String), Error>) -> Void + ) { + var params = [String: Any]() + params["amount"] = amount ?? 5050 + params["currency"] = currency + params["payment_method_types"] = types + params["confirm"] = confirm + if let paymentMethodID { + params["payment_method"] = paymentMethodID + } + if shouldSavePM { + params["payment_method_options"] = ["card": ["setup_future_usage": "off_session"]] + } + if let customerID { + params["customer"] = customerID + } + params.merge(otherParams) { _, b in b } + + createPaymentIntent( + withParams: params, + account: merchantCountry + ) { clientSecret, error in + guard let clientSecret = clientSecret, + error == nil + else { + completion(.failure(error!)) + return + } + + completion(.success(clientSecret)) + } + } + + func fetchPaymentIntent( + types: [String], + currency: String = "eur", + amount: Int? = nil, + merchantCountry: String? = "us", + paymentMethodID: String? = nil, + shouldSavePM: Bool = false, + customerID: String? = nil, + confirm: Bool = false, + otherParams: [String: Any] = [:] + ) async throws -> String { + try await withCheckedThrowingContinuation { continuation in + fetchPaymentIntent( + types: types, + currency: currency, + amount: amount, + merchantCountry: merchantCountry, + paymentMethodID: paymentMethodID, + shouldSavePM: shouldSavePM, + customerID: customerID, + confirm: confirm, + otherParams: otherParams + ) { result in + continuation.resume(with: result) + } + } + } + + func fetchSetupIntent( + types: [String], + merchantCountry: String? = "us", + paymentMethodID: String? = nil, + customerID: String? = nil, + confirm: Bool = false, + otherParams: [String: Any] = [:] + ) async throws -> String { + var params = [String: Any]() + params["payment_method_types"] = types + params["confirm"] = confirm + if let paymentMethodID { + params["payment_method"] = paymentMethodID + } + if let customerID { + params["customer"] = customerID + } + params.merge(otherParams) { _, b in b } + return try await withCheckedThrowingContinuation { continuation in + createSetupIntent(withParams: params, + account: merchantCountry) { clientSecret, error in + guard let clientSecret = clientSecret, + error == nil + else { + continuation.resume(throwing: error!) + return + } + continuation.resume(returning: clientSecret) + } + } + } + + // MARK: - /create_ephemeral_key + + struct CreateEphemeralKeyResponse: Decodable { + let ephemeralKeySecret: String + let customer: String + } + + struct CreateCustomerSessionResponse: Decodable { + let customerSessionClientSecret: String + let customer: String + } + + func fetchCustomerAndEphemeralKey( + customerID: String? = nil, + merchantCountry: String? = "us" + ) async throws -> CreateEphemeralKeyResponse { + let params = [ + "customer_id": customerID, + "account": merchantCountry, + ] + return try await makeRequest(endpoint: "create_ephemeral_key", params: params) + } + + func fetchCustomerAndCustomerSessionClientSecret( + customerID: String? = nil, + merchantCountry: String? = "us" + ) async throws -> CreateCustomerSessionResponse { + let params = [ + "component_name": "mobile_payment_element", + "customer_id": customerID, + "account": merchantCountry, + ] + return try await makeRequest(endpoint: "create_customer_session_cs", params: params) + } + + // MARK: - Helpers + + fileprivate func makeRequest( + endpoint: String, + params: [String: String?] + ) async throws -> ResponseType { + let session = URLSession(configuration: sessionConfig) + let url = URL(string: STPTestingAPIClient.STPTestingBackendURL + endpoint)! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try! JSONSerialization.data(withJSONObject: params) + let (data, _) = try await session.data(for: request) + let jsonDecoder = JSONDecoder() + jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase + return try jsonDecoder.decode(ResponseType.self, from: data) + } +} diff --git a/StripePayments/StripePaymentsTestUtils/StripePaymentsTestUtils.h b/StripePayments/StripePaymentsTestUtils/StripePaymentsTestUtils.h new file mode 100644 index 00000000..cf73955d --- /dev/null +++ b/StripePayments/StripePaymentsTestUtils/StripePaymentsTestUtils.h @@ -0,0 +1,16 @@ +// +// StripePaymentsTestUtils.h +// StripePaymentsTestUtils +// + +#import + +//! Project version number for StripePaymentsTestUtils. +FOUNDATION_EXPORT double StripePaymentsTestUtilsVersionNumber; + +//! Project version string for StripePaymentsTestUtils. +FOUNDATION_EXPORT const unsigned char StripePaymentsTestUtilsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripePayments/StripePaymentsTests/Captcha/DispatchQueue__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/DispatchQueue__Tests.swift new file mode 100644 index 00000000..df4eba31 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/DispatchQueue__Tests.swift @@ -0,0 +1,188 @@ +// +// DispatchQueue__Tests.swift +// HCaptcha +// +// Created by Flávio Caetano on 21/12/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class DispatchQueue__Tests: XCTestCase { + + // MARK: Throttle + + func test__Throttle_Nil_Context() { + // Execute closure called once + let exp0 = expectation(description: "did call single closure") + + DispatchQueue.main.throttle(deadline: .now() + 0.01) { + exp0.fulfill() + } + + waitForExpectations(timeout: 1) + + // Does not execute first closure + let exp1 = expectation(description: "did call last closure") + DispatchQueue.main.throttle(deadline: .now() + 0.01) { + XCTFail("Shouldn't be called") + } + + DispatchQueue.main.throttle( + deadline: .now() + 0.01, + action: exp1.fulfill + ) + + waitForExpectations(timeout: 1) + } + + func test__Throttle_Context() { + // Execute closure called once + let exp0 = expectation(description: "did call single closure") + let c0 = UUID() + + DispatchQueue.main.throttle( + deadline: .now() + 0.01, + context: c0, + action: exp0.fulfill + ) + + waitForExpectations(timeout: 1) + + // Does not execute first closure + let exp1 = expectation(description: "execute on valid context") + let c1 = UUID() + DispatchQueue.main.throttle(deadline: .now() + 0.01, context: c1) { + XCTFail("Shouldn't be called") + } + + DispatchQueue.main.throttle( + deadline: .now() + 0.01, + context: c1, + action: exp1.fulfill + ) + + // Execute in a different context + let exp2 = expectation(description: "execute on different context") + let c2 = UUID() + DispatchQueue.main.throttle( + deadline: .now() + 0.01, + context: c2, + action: exp2.fulfill + ) + + waitForExpectations(timeout: 1) + } + + // MARK: Debounce + + func test__Debounce_Nil_Context() { + // Does not execute sequenced closures + let exp0 = expectation(description: "did call first closure") + + DispatchQueue.main.debounce( + interval: 0.01, + action: exp0.fulfill + ) + + DispatchQueue.main.debounce(interval: 0) { + XCTFail("Shouldn't be called") + } + + waitForExpectations(timeout: 1) + + // Executes closure after previous has timed out + let exp1 = expectation(description: "did call closure") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + DispatchQueue.main.debounce( + interval: 0.01, + action: exp1.fulfill + ) + } + + waitForExpectations(timeout: 3) + } + + func test__Debounce_Context() { + // Does not execute sequenced closures + let exp0 = expectation(description: "did call first closure") + let c0 = UUID() + + DispatchQueue.main.debounce( + interval: 0.01, + context: c0, + action: exp0.fulfill + ) + + DispatchQueue.main.debounce(interval: 0, context: c0) { + XCTFail("Shouldn't be called") + } + + // Execute in a different context + let c1 = UUID() + let exp1 = expectation(description: "executes in different context") + DispatchQueue.main.debounce( + interval: 0, + context: c1, + action: exp1.fulfill + ) + + waitForExpectations(timeout: 1) + + // Executes closure after previous has timed out + let exp2 = expectation(description: "did call closure") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) { + DispatchQueue.main.debounce( + interval: 0.01, + context: c0, + action: exp2.fulfill + ) + } + + waitForExpectations(timeout: 5) + } + + // MARK: Once + + func test__Once__Single_Dispatch() { + let token = 3 + var dispatchCount = 0 + + // Does dispatch the given action + DispatchQueue.once(token: token) { + dispatchCount = 1 + } + + XCTAssertEqual(dispatchCount, 1) + + // Does not dispatch again for the same token + DispatchQueue.once(token: token) { + dispatchCount = 2 + } + + XCTAssertEqual(dispatchCount, 1) + } + + func test__Once__Multiple_Dispatches() { + let token1 = 4 + var didDispatch1 = false + + // Does dispatch the given action + DispatchQueue.once(token: token1) { + didDispatch1 = true + } + + XCTAssertTrue(didDispatch1) + + // Dispatch for a different token + let token2 = 6 + var didDispatch2 = false + + DispatchQueue.once(token: token2) { + didDispatch2 = true + } + + XCTAssertTrue(didDispatch2) + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptchaDecoder__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptchaDecoder__Tests.swift new file mode 100644 index 00000000..6c569092 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptchaDecoder__Tests.swift @@ -0,0 +1,219 @@ +// +// HCaptchaDecoder__Tests.swift +// HCaptcha +// +// Created by Flávio Caetano on 13/04/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +@testable import StripePayments + +import WebKit +import XCTest + +class HCaptchaDecoder__Tests: XCTestCase { + fileprivate typealias Result = HCaptchaDecoder.Result + + fileprivate var assertResult: ((Result) -> Void)? + fileprivate var decoder: HCaptchaDecoder! + + override func setUp() { + super.setUp() + + decoder = HCaptchaDecoder { [weak self] result in + self?.assertResult?(result) + } + } + + func test__Send_Error() { + let exp = expectation(description: "send error message") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let err = HCaptchaError.random() + decoder.send(error: err) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertNotNil(result) + XCTAssertEqual(result, .error(err)) + } + + func test__Decode__Wrong_Format() { + let exp = expectation(description: "send unsupported message") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: "foobar") + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(HCaptchaError.wrongMessageFormat)) + } + + func test__Decode__Unexpected_Action() { + let exp = expectation(description: "send message with unexpected action") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["action": "bar"]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(HCaptchaError.wrongMessageFormat)) + } + + func test__Decode__ShowHCaptcha() { + let exp = expectation(description: "send showHCaptcha message") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["action": "showHCaptcha"]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .showHCaptcha) + } + + func test__Decode__Token() { + let exp = expectation(description: "send token message") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let token = UUID().uuidString + let message = MockMessage(message: ["token": token]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .token(token)) + } + + func test__Decode__DidLoad() { + let exp = expectation(description: "send did load message") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["action": "didLoad"]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .didLoad) + } + + func test__Decode__Error_Setup_Failed() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 29]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.failedSetup)) + } + + func test__Decode__Error_Response_Expired() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 15]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.sessionTimeout)) + } + + func test__Decode__Error_Render_Failed() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 31]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.rateLimit)) + } + + func test__Decode__Error_Wrong_Format() { + let exp = expectation(description: "send error") + var result: Result? + + assertResult = { res in + result = res + exp.fulfill() + } + + // Send + let message = MockMessage(message: ["error": 26]) + decoder.send(message: message) + + waitForExpectations(timeout: 1) + + // Check + XCTAssertEqual(result, .error(.wrongMessageFormat)) + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptchaResult__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptchaResult__Tests.swift new file mode 100644 index 00000000..33188c52 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptchaResult__Tests.swift @@ -0,0 +1,38 @@ +// +// HCaptchaResult__Tests.swift +// HCaptcha +// +// Created by Flávio Caetano on 06/03/18. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class HCaptchaResult__Tests: XCTestCase { + func test__Get_Token() { + let token = UUID().uuidString + let manager = HCaptchaWebViewManager() + let result = HCaptchaResult(manager, token: token) + + do { + let value = try result.dematerialize() + XCTAssertEqual(value, token) + } catch let err { + XCTFail(err.localizedDescription) + } + } + + func test__Get_Token__Error() { + let error = HCaptchaError.random() + let manager = HCaptchaWebViewManager() + let result = HCaptchaResult(manager, error: error) + + do { + _ = try result.dematerialize() + XCTFail("Shouldn't have completed") + } catch let err { + XCTAssertEqual(err as? HCaptchaError, error) + } + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptchaWebViewManager__HTML__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptchaWebViewManager__HTML__Tests.swift new file mode 100644 index 00000000..31c824be --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptchaWebViewManager__HTML__Tests.swift @@ -0,0 +1,58 @@ +// +// HCaptchaWebViewManager__HTML__Tests.swift +// HCaptcha_Tests +// +// Created by Aleksey Berezka on 17.08.2021. +// Copyright © 2021 HCaptcha. All rights reserved. +// + +@testable import StripePayments + +import WebKit +import XCTest + +class HCaptchaWebViewManager__HTML__Tests: XCTestCase { + var webViewContentIsAvailable: XCTestExpectation! + var webViewContent: String? + + override func setUpWithError() throws { + try super.setUpWithError() + + webViewContentIsAvailable = expectation(description: "get webview content") + } + + override func tearDownWithError() throws { + webViewContentIsAvailable = nil + + try super.tearDownWithError() + } + + func test__Size_Is_Mapped_Into_HTML() { + let manager = HCaptchaWebViewManager(html: "size: ${size}", size: .compact) + waitForWebViewContent(manager: manager) + XCTAssertEqual(webViewContent, "size: compact") + } + + func test__Orientation_Is_Mapped_Into_HTML() { + let manager = HCaptchaWebViewManager(html: "orientation: ${orientation}", orientation: .portrait) + waitForWebViewContent(manager: manager) + XCTAssertEqual(webViewContent, "orientation: portrait") + } +} + +extension HCaptchaWebViewManager__HTML__Tests: WKNavigationDelegate { + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + webView.evaluateJavaScript("document.body.innerHTML", + completionHandler: { [weak self] (result: Any?, _) in + self?.webViewContentIsAvailable.fulfill() + self?.webViewContent = result as? String + }) + } +} + +extension HCaptchaWebViewManager__HTML__Tests { + func waitForWebViewContent(manager: HCaptchaWebViewManager) { + manager.webView.navigationDelegate = self + wait(for: [webViewContentIsAvailable], timeout: 5) + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptchaWebViewManager__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptchaWebViewManager__Tests.swift new file mode 100644 index 00000000..cf8e60d9 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptchaWebViewManager__Tests.swift @@ -0,0 +1,649 @@ +// +// HCaptchaWebViewManager__Tests.swift +// HCaptcha +// +// Created by Flávio Caetano on 13/04/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +@testable import StripePayments + +import WebKit +import XCTest + +class HCaptchaWebViewManager__Tests: XCTestCase { + + fileprivate var apiKey: String! + fileprivate var presenterView: UIView! + + override func setUp() { + super.setUp() + + presenterView = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController?.view + apiKey = UUID().uuidString + } + + override func tearDown() { + presenterView = nil + apiKey = nil + + super.tearDown() + } + + // MARK: Validate + + func test__Validate__Token() { + let exp0 = expectation(description: "should call configureWebView") + let exp1 = expectation(description: "load token") + var result1: HCaptchaResult? + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey) + manager.configureWebView { _ in + exp0.fulfill() + } + + manager.validate(on: presenterView) { response in + result1 = response + exp1.fulfill() + } + + waitForExpectations(timeout: 10) + + // Verify + XCTAssertNotNil(result1) + XCTAssertNil(result1?.error) + XCTAssertEqual(result1?.token, apiKey) + + // Validate again + let exp2 = expectation(description: "reload token") + var result2: HCaptchaResult? + + // Validate + manager.validate(on: presenterView) { response in + result2 = response + exp2.fulfill() + } + + waitForExpectations(timeout: 10) + + // Verify + XCTAssertNotNil(result2) + XCTAssertNil(result2?.error) + XCTAssertEqual(result2?.token, apiKey) + } + + func test__Validate__Show_HCaptcha() { + let exp = expectation(description: "show hcaptcha") + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}") + manager.configureWebView { _ in + exp.fulfill() + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not call completion") + } + + waitForExpectations(timeout: 10) + } + + func test__Validate__Message_Error() { + let exp0 = expectation(description: "should call configureWebView") + var result: HCaptchaResult? + let exp1 = expectation(description: "message error") + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "\"foobar\"") + manager.configureWebView { _ in + exp0.fulfill() + } + + manager.validate(on: presenterView, resetOnError: false) { response in + result = response + exp1.fulfill() + } + + waitForExpectations(timeout: 10) + + // Verify + XCTAssertNotNil(result) + XCTAssertEqual(result?.error, .wrongMessageFormat) + XCTAssertNil(result?.token) + } + + func test__Validate__JS_Error() { + var result: HCaptchaResult? + let exp0 = expectation(description: "should call configureWebView") + let exp1 = expectation(description: "js error") + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "foobar") + manager.configureWebView { _ in + exp0.fulfill() + } + + manager.validate(on: presenterView, resetOnError: false) { response in + result = response + exp1.fulfill() + } + + waitForExpectations(timeout: 10) + + // Verify + XCTAssertNotNil(result) + XCTAssertNotNil(result?.error) + XCTAssertNil(result?.token) + + switch result?.error { + case .unexpected(let error as NSError): + XCTAssertEqual(error.code, WKError.javaScriptExceptionOccurred.rawValue) + default: + XCTFail("Unexpected error received") + } + } + + // MARK: Configure WebView + + func test__Configure_Web_View__Empty() { + let exp = expectation(description: "configure webview") + + // Configure WebView + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}") + manager.validate(on: presenterView) { _ in + XCTFail("should not call completion") + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + exp.fulfill() + } + + waitForExpectations(timeout: 10) + } + + func test__Configure_Web_View() { + let exp = expectation(description: "configure webview") + + // Configure WebView + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}") + manager.configureWebView { [unowned self] webView in + XCTAssertEqual(webView.superview, self.presenterView) + exp.fulfill() + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not call completion") + } + + waitForExpectations(timeout: 10) + } + + func test__Configure_Web_View__Called_Once() { + var count = 0 + let exp0 = expectation(description: "configure webview") + + // Configure WebView + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}") + manager.configureWebView { _ in + if count < 3 { + manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) } + } + + count += 1 + exp0.fulfill() + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not call completion") + } + + waitForExpectations(timeout: 10) + + let exp1 = expectation(description: "waiting for extra calls") + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: exp1.fulfill) + waitForExpectations(timeout: 2) + + XCTAssertEqual(count, 1) + } + + func test__Configure_Web_View__Called_Again_With_Reset() { + let exp0 = expectation(description: "configure webview 0") + + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}") + // Configure Webview + manager.configureWebView { _ in + manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) } + exp0.fulfill() + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not call completion") + } + + waitForExpectations(timeout: 10) + + // Reset and ensure it calls again + let exp1 = expectation(description: "configure webview 1") + + manager.configureWebView { _ in + manager.webView.evaluateJavaScript("execute();") { XCTAssertNil($1) } + exp1.fulfill() + } + + manager.reset() + waitForExpectations(timeout: 10) + } + + func test__Configure_Web_View__Handle_rqdata_Without_JS_Error() { + let exp0 = expectation(description: "configure webview") + let exp1 = expectation(description: "execute JS complete") + + // Configure WebView + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}", + rqdata: "some rqdata") + manager.configureWebView { _ in + manager.webView.evaluateJavaScript("execute();") { + XCTAssertNil($1) + exp1.fulfill() + } + exp0.fulfill() + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not call completion") + } + + waitForExpectations(timeout: 10) + } + + // MARK: Stop + + func test__Stop() { + let exp = expectation(description: "stop loading") + + // Stop + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}") + manager.stop() + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not validate") + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + exp.fulfill() + } + + waitForExpectations(timeout: 10) + } + + func test__Reset_After_Stop() { + let exp0 = expectation(description: "stop loading") + let exp1 = expectation(description: "configureWebView called") + let exp2 = expectation(description: "token recieved") + + // Stop + let manager = HCaptchaWebViewManager(messageBody: "{token: \"some_token\"}") + manager.stop() + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not validate") + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + exp0.fulfill() + } + + manager.reset() + + manager.configureWebView { _ in + exp1.fulfill() + } + + manager.validate(on: presenterView) { result in + let token = try? result.dematerialize() + XCTAssertEqual("some_token", token) + exp2.fulfill() + } + + waitForExpectations(timeout: 10) + } + + // MARK: Setup + + func test__Key_Setup() { + let exp0 = expectation(description: "should call configureWebView") + let exp1 = expectation(description: "setup key") + var result: HCaptchaResult? + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey) + manager.configureWebView { _ in + exp0.fulfill() + } + + manager.validate(on: presenterView) { response in + result = response + exp1.fulfill() + } + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(result) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.token, apiKey) + } + + func test__Endpoint_Setup() { + let exp0 = expectation(description: "should call configureWebView") + let exp1 = expectation(description: "setup endpoint") + let endpoint = URL(string: "https://some.endpoint")! + var result: HCaptchaResult? + + let manager = HCaptchaWebViewManager(messageBody: "{token: endpoint}", endpoint: endpoint) + manager.configureWebView { _ in + exp0.fulfill() + } + + manager.validate(on: presenterView) { response in + result = response + exp1.fulfill() + } + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(result) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.token, endpoint.absoluteString) + } + + // MARK: Reset + + func test__Reset() { + let exp0 = expectation(description: "should call configureWebView #1") + let exp1 = expectation(description: "fail on first execution") + var result1: HCaptchaResult? + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true) + manager.configureWebView { _ in + exp0.fulfill() + } + + // Error + manager.validate(on: presenterView, resetOnError: false) { result in + result1 = result + exp1.fulfill() + } + + waitForExpectations(timeout: 10) + + let exp2 = expectation(description: "should call configureWebView #2") + manager.configureWebView { _ in + exp2.fulfill() + } + + XCTAssertEqual(result1?.error, .sessionTimeout) + + // Resets and tries again + let exp3 = expectation(description: "validates after reset") + var result3: HCaptchaResult? + + manager.reset() + manager.validate(on: presenterView, resetOnError: false) { result in + result3 = result + exp3.fulfill() + } + + waitForExpectations(timeout: 10) + + XCTAssertNil(result3?.error) + XCTAssertEqual(result3?.token, apiKey) + } + + func test__Validate__Reset_On_Error() { + let exp0 = expectation(description: "should call configureWebView") + var exp0Count = 0 + let exp1 = expectation(description: "should call onEvent") + let exp2 = expectation(description: "fail on first execution") + let exp3 = expectation(description: "hcaptcha opened") + var result: HCaptchaResult? + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey, shouldFail: true) + manager.configureWebView { _ in + exp0Count += 1 + if exp0Count == 2 { + exp0.fulfill() + } + } + + manager.onEvent = { (event, error) in + XCTAssertTrue([.error, .open].contains(event)) + switch event { + case .error: + XCTAssertEqual(.error, event) + XCTAssertEqual(HCaptchaError.sessionTimeout, error as? HCaptchaError) + exp1.fulfill() + case .open: + exp3.fulfill() + default: + XCTFail("Unexpected event \(event)") + } + } + + // Error + manager.validate(on: presenterView, resetOnError: true) { response in + result = response + + exp2.fulfill() + } + + waitForExpectations(timeout: 10) + + XCTAssertNil(result?.error) + XCTAssertEqual(result?.token, apiKey) + } + + func test__Validate__Should_Skip_For_Tests() { + let exp = expectation(description: "did skip validation") + + let manager = HCaptchaWebViewManager() + manager.shouldSkipForTests = true + + manager.completion = { result in + XCTAssertEqual(result.token, "") + exp.fulfill() + } + + manager.validate(on: presenterView) + + waitForExpectations(timeout: 1) + } + + // MARK: Force Challenge Visible + + func test__Force_Visible_Challenge() { + let manager = HCaptchaWebViewManager() + + // Initial value + XCTAssertFalse(manager.forceVisibleChallenge) + + // Set True + manager.forceVisibleChallenge = true + XCTAssertEqual(manager.webView.customUserAgent, "bot/2.1") + + // Set False + manager.forceVisibleChallenge = false + XCTAssertNotEqual(manager.webView.customUserAgent?.isEmpty, false) + } + + // MARK: On Did Finish Loading + + func test__Did_Finish_Loading__Immediate() { + let exp = expectation(description: "did finish loading") + + let manager = HCaptchaWebViewManager() + + // // Should call closure immediately since it's already loaded + manager.onDidFinishLoading = { + manager.onDidFinishLoading = exp.fulfill + } + + waitForExpectations(timeout: 5) + } + + func test__Did_Finish_Loading__Delayed() { + let exp = expectation(description: "did finish loading") + + let manager = HCaptchaWebViewManager(shouldFail: true) + + var called = false + manager.onDidFinishLoading = { + called = true + } + + XCTAssertFalse(called) + + // Reset + manager.onDidFinishLoading = exp.fulfill + manager.reset() + + waitForExpectations(timeout: 5) + } + + func test__Invalid_Theme() { + let exp = expectation(description: "bad theme value") + + let manager = HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}", + apiKey: apiKey, + theme: "[Object object]") // invalid theme + manager.shouldResetOnError = false + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView, resetOnError: false) { response in + XCTAssertEqual(HCaptchaError.htmlLoadError, response.error) + exp.fulfill() + } + + waitForExpectations(timeout: 10) + } + + func test__OnEvent_Open_Callback() { + let exp0 = expectation(description: "should call configureWebView") + let exp1 = expectation(description: "setup key") + let exp2 = expectation(description: "hcaptcha opened") + var result: HCaptchaResult? + + // Validate + let manager = HCaptchaWebViewManager(messageBody: "{token: key}", apiKey: apiKey) + manager.configureWebView { _ in + exp0.fulfill() + } + manager.onEvent = { (event, data) in + XCTAssertNil(data) + XCTAssertEqual(event, .open) + exp1.fulfill() + } + + manager.validate(on: presenterView) { response in + result = response + exp2.fulfill() + } + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(result) + XCTAssertNil(result?.error) + XCTAssertEqual(result?.token, apiKey) + } + + func test__OnEvent_Without_Validation() { + let testParams: [(String, HCaptchaEvent)] = [("onChallengeExpired", .challengeExpired), + ("onExpired", .expired), + ("onClose", .close), ] + + testParams.forEach { (action, expectedEventType) in + let exp0 = expectation(description: "should call configureWebView") + let exp = expectation(description: "challenge expired received") + + let manager = HCaptchaWebViewManager(messageBody: "{action: \"\(action)\"}") + manager.configureWebView { _ in + exp0.fulfill() + } + manager.onEvent = { (event, data) in + XCTAssertNil(data) + XCTAssertEqual(expectedEventType, event) + exp.fulfill() + } + + manager.validate(on: presenterView) { _ in + XCTFail("should not validate") + } + + waitForExpectations(timeout: 5) + } + } + + func test__Open_External_Link() { + let exp0 = expectation(description: "should call configureWebView") + let exp1 = expectation(description: "_target link should be checked") + let exp2 = expectation(description: "_target link should be opened") + + class TestURLOpener: HCaptchaURLOpener { + private let canOpenExpectation: XCTestExpectation + private let openExpectation: XCTestExpectation + + init(_ canOpen: XCTestExpectation, _ open: XCTestExpectation) { + self.canOpenExpectation = canOpen + self.openExpectation = open + } + + func canOpenURL(_ url: URL) -> Bool { + canOpenExpectation.fulfill() + return true + } + + func openURL(_ url: URL) { + openExpectation.fulfill() + } + } + + let manager = HCaptchaWebViewManager(messageBody: "{token: key, action: \"openExternalPage\"}", + apiKey: apiKey, + urlOpener: TestURLOpener(exp1, exp2)) + manager.configureWebView { _ in + exp0.fulfill() + } + wait(for: [exp0], timeout: 5) + manager.validate(on: presenterView) + + wait(for: [exp1, exp2], timeout: 5) + } + + func test__Invalid_HTML() { + let exp = expectation(description: "bad theme value") + + let manager = HCaptchaWebViewManager(messageBody: "{ invalid json", + apiKey: apiKey) + manager.shouldResetOnError = false + manager.configureWebView { _ in + XCTFail("should not ask to configure the webview") + } + + manager.validate(on: presenterView, resetOnError: false) { response in + XCTAssertEqual(HCaptchaError.htmlLoadError, response.error) + exp.fulfill() + } + + waitForExpectations(timeout: 10) + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Bench.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Bench.swift new file mode 100644 index 00000000..fa857076 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Bench.swift @@ -0,0 +1,87 @@ +// +// HCaptcha__Bench.swift +// HCaptcha_Tests +// +// Copyright © 2022 HCaptcha. All rights reserved. +// + +import Foundation +@testable import StripePayments +import XCTest + +class HCaptcha__Bench: XCTestCase { + override func setUp() { + HCaptchaHtml.template = """ + + + + + + +
+ + + """ + } + + let apiKey = "10000000-ffff-ffff-ffff-000000000001" + + func testBenchInit() throws { + self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: true, for: { + _ = try? HCaptcha(apiKey: apiKey, size: .invisible) + self.stopMeasuring() + }) + } + + func testBenchColdrun() throws { + let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 600)) + self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: true, for: { + let exp = expectation(description: "completed") + let hcaptcha = try! HCaptcha(apiKey: apiKey, size: .invisible) + hcaptcha.validate(on: view, completion: { _ in + self.stopMeasuring() + exp.fulfill() + }) + waitForExpectations(timeout: 15) + }) + } + + func testBenchVerify() throws { + let view = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 600)) + let hcaptcha = try? HCaptcha(apiKey: apiKey, size: .invisible) + self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: true, for: { + let exp = expectation(description: "completed") + hcaptcha?.validate(on: view, completion: { _ in + self.stopMeasuring() + exp.fulfill() + }) + waitForExpectations(timeout: 5) + }) + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Config__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Config__Tests.swift new file mode 100644 index 00000000..a5015ee7 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Config__Tests.swift @@ -0,0 +1,135 @@ +// +// HCaptcha__Config__Tests.swift +// HCaptcha_Tests +// +// Created by CAMOBAP on 12/20/21. +// Copyright © 2021 HCaptcha. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class HCaptcha__Config__Tests: XCTestCase { + private let expected = "https://hcaptcha.com/1/api.js?onload=onloadCallback&render=explicit&recaptchacompat=off" + + "&host=some-api-key.ios-sdk.hcaptcha.com&sentry=false&endpoint=https%3A%2F%2Fapi.hcaptcha.com" + + "&assethost=https%3A%2F%2Fnewassets.hcaptcha.com&imghost=https%3A%2F%2Fimgs.hcaptcha.com" + + "&reportapi=https%3A%2F%2Faccounts.hcaptcha.com" + + func createConfig(apiKey: String = "some-api-key", + host: String? = nil, + customTheme: String? = nil) -> HCaptchaConfig? { + return try? HCaptchaConfig(apiKey: apiKey, + infoPlistKey: nil, + baseURL: URL(string: "https://localhost")!, + infoPlistURL: nil, + host: host, + customTheme: customTheme) + } + + func test__Base_URL() { + // Ensures baseURL failure when nil + do { + _ = try HCaptchaConfig(apiKey: "", infoPlistKey: nil, baseURL: nil, infoPlistURL: nil) + XCTFail("Should have failed") + } catch let e as HCaptchaError { + print(e) + XCTAssertEqual(e, HCaptchaError.baseURLNotFound) + } catch let e { + XCTFail("Unexpected error: \(e)") + } + + // Ensures plist url if nil key + let plistURL = URL(string: "https://bar")! + let config1 = try? HCaptchaConfig(apiKey: "", infoPlistKey: nil, baseURL: nil, infoPlistURL: plistURL) + XCTAssertEqual(config1?.baseURL, plistURL) + + // Ensures preference of given url over plist entry + let url = URL(string: "ftp://foo")! + let config2 = try? HCaptchaConfig(apiKey: "", infoPlistKey: nil, baseURL: url, infoPlistURL: plistURL) + XCTAssertEqual(config2?.baseURL, url) + } + + func test__Base_URL_Without_Scheme() { + // Ignores URL with scheme + let goodURL = URL(string: "https://foo.bar")! + let config0 = try? HCaptchaConfig(apiKey: "", infoPlistKey: nil, baseURL: goodURL, infoPlistURL: nil) + XCTAssertEqual(config0?.baseURL, goodURL) + + // Fixes URL without scheme + let badURL = URL(string: "foo")! + let config = try? HCaptchaConfig(apiKey: "", infoPlistKey: nil, baseURL: badURL, infoPlistURL: nil) + XCTAssertEqual(config?.baseURL.absoluteString, "http://" + badURL.absoluteString) + } + + func test__API_Key() { + // Ensures key failure when nil + do { + _ = try HCaptchaConfig(apiKey: nil, infoPlistKey: nil, baseURL: nil, infoPlistURL: nil) + XCTFail("Should have failed") + } catch let e as HCaptchaError { + print(e) + XCTAssertEqual(e, HCaptchaError.apiKeyNotFound) + } catch let e { + XCTFail("Unexpected error: \(e)") + } + + // Ensures plist key if nil key + let plistKey = "bar" + let config1 = try? HCaptchaConfig( + apiKey: nil, + infoPlistKey: plistKey, + baseURL: URL(string: "foo"), + infoPlistURL: nil + ) + XCTAssertEqual(config1?.apiKey, plistKey) + + // Ensures preference of given key over plist entry + let key = "foo" + let config2 = try? HCaptchaConfig( + apiKey: key, + infoPlistKey: plistKey, + baseURL: URL(string: "foo"), + infoPlistURL: nil + ) + XCTAssertEqual(config2?.apiKey, key) + } + + func test__Locale__Nil() { + let config = createConfig() + let actual = config?.getEndpointURL().absoluteString + XCTAssertEqual(actual, expected) + } + + func test__Locale__Valid() { + let locale = "pt-BR" + let config = createConfig() + let actual = config?.getEndpointURL(locale: Locale(identifier: locale)).absoluteString + XCTAssertEqual(actual, "\(expected)&hl=\(locale)") + } + + func test__Custom__Host() { + let host = "custom-host" + let config = createConfig(host: host) + let actual = config?.getEndpointURL().absoluteString + XCTAssertEqual(actual, expected.replacingOccurrences( + of: "some-api-key.ios-sdk.hcaptcha.com", + with: host)) + } + + func test__Custom__Theme() { + let customTheme = """ + { + primary: { + main: "#00FF00" + }, + text: { + heading: "#454545", + body : "#8C8C8C" + } + } + """ + let config = createConfig(customTheme: customTheme) + let actual = config?.getEndpointURL().absoluteString + XCTAssertEqual(actual, expected + "&custom=true") + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Tests.swift b/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Tests.swift new file mode 100644 index 00000000..6f35b0d0 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/HCaptcha__Tests.swift @@ -0,0 +1,142 @@ +// +// HCaptcha__Tests.swift +// HCaptcha +// +// Created by Flávio Caetano on 26/09/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +@testable import StripePayments +import XCTest + +class HCaptcha__Tests: XCTestCase { + fileprivate struct Constants { + struct InfoDictKeys { + static let APIKey = "HCaptchaKey" + static let Domain = "HCaptchaDomain" + } + } + + func test__Force_Visible_Challenge() { + let hcaptcha = HCaptcha(manager: HCaptchaWebViewManager()) + + // Initial value + XCTAssertFalse(hcaptcha.forceVisibleChallenge) + + // Set true + hcaptcha.forceVisibleChallenge = true + XCTAssertTrue(hcaptcha.forceVisibleChallenge) + } + + func test__valid_js_customTheme() { + let customTheme = """ + { + primary: { + main: "#00FF00" + }, + text: { + heading: "#454545", + body : "#8C8C8C" + } + } + """ + do { + _ = try HCaptcha(customTheme: customTheme) + } catch let e { + XCTFail("Unexpected error: \(e)") + } + } + + func test__valid_json_customTheme() { + let customTheme = """ + { + "primary": { + "main": "#00FF00" + }, + "text": { + "heading": "#454545", + "body" : "#8C8C8C" + } + } + """ + do { + _ = try HCaptcha(customTheme: customTheme) + } catch let e { + XCTFail("Unexpected error: \(e)") + } + } + + func test__invalid_js_customTheme() { + let customTheme = """ + { + primary: { + main: "#00FF00" + }, + text: { + heading: "#454545", + body : "#8C8C8C" + } + // } missing last bracket + """ + do { + _ = try HCaptcha(customTheme: customTheme) + XCTFail("Should not be reached. Error expected") + } catch let e as HCaptchaError { + print(e) + XCTAssertEqual(e, HCaptchaError.invalidCustomTheme) + } catch let e { + XCTFail("Unexpected error: \(e)") + } + } + + func test__validate_from_didFinishLoading() { + let exp = expectation(description: "execute js function must be called only once") + let hcaptcha = HCaptcha(manager: HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}")) + hcaptcha.didFinishLoading { + let view = UIApplication.shared.windows.first?.rootViewController?.view + hcaptcha.onEvent { e, _ in + if e == .open { + exp.fulfill() + } + } + hcaptcha.validate(on: view!) { _ in + XCTFail("Should not be called") + } + } + wait(for: [exp], timeout: 10) + } + + func test__reconfigure() { + let exp = expectation(description: "configureWebView called twice") + var configureCounter = 0 + let hcaptcha = HCaptcha(manager: HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}")) + hcaptcha.configureWebView { _ in + configureCounter += 1 + if configureCounter == 2 { + exp.fulfill() + } + } + hcaptcha.didFinishLoading { + let view = UIApplication.shared.windows.first?.rootViewController?.view + hcaptcha.onEvent { e, _ in + if e == .open { + hcaptcha.redrawView() + } + } + hcaptcha.validate(on: view!) { _ in + XCTFail("Should not be called") + } + } + wait(for: [exp], timeout: 10) + } +} + +private extension Bundle { + @objc func failHTMLLoad(_ resource: String, type: String) -> String? { + guard resource == "hcaptcha" && type == "html" else { + return failHTMLLoad(resource, type: type) + } + + return nil + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaConfig+Helpers.swift b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaConfig+Helpers.swift new file mode 100644 index 00000000..cf9c4857 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaConfig+Helpers.swift @@ -0,0 +1,36 @@ +// +// HCaptchaConfig+Helpers.swift +// HCaptcha_Tests +// +// Created by Алексей Берёзка on 17.08.2021. +// Copyright © 2021 HCaptcha. All rights reserved. +// + +import Foundation +@testable import StripePayments + +extension HCaptchaConfig { + init(apiKey: String?, + infoPlistKey: String?, + baseURL: URL?, + infoPlistURL: URL?, + host: String? = nil, + customTheme: String? = nil) throws { + try self.init(apiKey: apiKey, + infoPlistKey: infoPlistKey, + baseURL: baseURL, + infoPlistURL: infoPlistURL, + jsSrc: URL(string: "https://hcaptcha.com/1/api.js")!, + size: .invisible, + orientation: .portrait, + rqdata: nil, + sentry: false, + endpoint: URL(string: "https://api.hcaptcha.com")!, + reportapi: URL(string: "https://accounts.hcaptcha.com")!, + assethost: URL(string: "https://newassets.hcaptcha.com")!, + imghost: URL(string: "https://imgs.hcaptcha.com")!, + host: host, + theme: "light", + customTheme: customTheme) + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaDecoder+Helper.swift b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaDecoder+Helper.swift new file mode 100644 index 00000000..6b521f70 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaDecoder+Helper.swift @@ -0,0 +1,55 @@ +// +// HCaptchaDecoder+Helper.swift +// HCaptcha +// +// Created by Flávio Caetano on 22/12/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +import Foundation +@testable import StripePayments +import WebKit + +class MockMessage: WKScriptMessage { + override var body: Any { + return storedBody + } + + fileprivate let storedBody: Any + + init(message: Any) { + storedBody = message + } +} + +// MARK: - Decoder Helpers +extension HCaptchaDecoder { + func send(message: MockMessage) { + userContentController(WKUserContentController(), didReceive: message) + } +} + +// MARK: - Result Helpers +extension HCaptchaDecoder.Result: Equatable { + var error: HCaptchaError? { + guard case .error(let error) = self else { return nil } + return error + } + + public static func == (lhs: HCaptchaDecoder.Result, rhs: HCaptchaDecoder.Result) -> Bool { + switch (lhs, rhs) { + case (.showHCaptcha, .showHCaptcha), + (.didLoad, .didLoad): + return true + + case (.token(let lht), .token(let rht)): + return lht == rht + + case (.error(let lhe), .error(let rhe)): + return lhe == rhe + + default: + return false + } + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaError+Equatable.swift b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaError+Equatable.swift new file mode 100644 index 00000000..a6516fb6 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaError+Equatable.swift @@ -0,0 +1,46 @@ +// +// HCaptchaError+Equatable.swift +// HCaptcha +// +// Created by Flávio Caetano on 16/10/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +import Foundation +@testable import StripePayments + +extension HCaptchaError: Equatable { + public static func == (lhs: HCaptchaError, rhs: HCaptchaError) -> Bool { + switch (lhs, rhs) { + case (.htmlLoadError, .htmlLoadError), + (.apiKeyNotFound, .apiKeyNotFound), + (.baseURLNotFound, .baseURLNotFound), + (.wrongMessageFormat, .wrongMessageFormat), + (.failedSetup, .failedSetup), + (.sessionTimeout, .sessionTimeout), + (.rateLimit, .rateLimit), + (.invalidCustomTheme, .invalidCustomTheme), + (.networkError, .networkError): + return true + case (.unexpected(let lhe as NSError), .unexpected(let rhe as NSError)): + return lhe == rhe + default: + return false + } + } + + static func random() -> HCaptchaError { + switch Int.random(in: 0...8) { + case 0: return .htmlLoadError + case 1: return .apiKeyNotFound + case 2: return .baseURLNotFound + case 3: return .wrongMessageFormat + case 4: return .failedSetup + case 5: return .sessionTimeout + case 6: return .rateLimit + case 7: return .invalidCustomTheme + case 8: return .networkError + default: return .unexpected(NSError(domain: "HCaptchaTestError", code: 0, userInfo: nil)) + } + } +} diff --git a/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaWebViewManager+Helpers.swift b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaWebViewManager+Helpers.swift new file mode 100644 index 00000000..6147e82a --- /dev/null +++ b/StripePayments/StripePaymentsTests/Captcha/Helpers/HCaptchaWebViewManager+Helpers.swift @@ -0,0 +1,82 @@ +// +// HCaptchaWebViewManager+Helpers.swift +// HCaptcha +// +// Created by Flávio Caetano on 13/04/17. +// Copyright © 2018 HCaptcha. All rights reserved. +// + +import Foundation +@testable import StripePayments +import WebKit + +extension HCaptchaWebViewManager { + private static let unformattedHTML: String! = { + Bundle(for: HCaptchaWebViewManager__Tests.self) + .path(forResource: "mock", ofType: "html") + .flatMap { try? String(contentsOfFile: $0) } + }() + + convenience init( + messageBody: String = "undefined", + apiKey: String? = nil, + endpoint: URL? = nil, + shouldFail: Bool = false, // will fail with retriable sessionTimeout + size: HCaptchaSize = .invisible, + rqdata: String? = nil, + theme: String = "\"light\"", + urlOpener: HCaptchaURLOpener = HCapchaAppURLOpener() + ) { + let html = String(format: HCaptchaWebViewManager.unformattedHTML, + arguments: [ + "message": messageBody, + "shouldFail": shouldFail.description, + ]) + + self.init( + html: html, + apiKey: apiKey, + endpoint: endpoint, + size: size, + rqdata: rqdata, + theme: theme, + urlOpener: urlOpener + ) + } + + convenience init( + html: String, + apiKey: String? = nil, + endpoint: URL? = nil, + size: HCaptchaSize = .invisible, + orientation: HCaptchaOrientation = .portrait, + rqdata: String? = nil, + theme: String = "\"light\"", + urlOpener: HCaptchaURLOpener = HCapchaAppURLOpener() + ) { + let localhost = URL(string: "http://localhost")! + + self.init( + html: html, + apiKey: apiKey ?? UUID().uuidString, + baseURL: localhost, + endpoint: endpoint ?? localhost, + size: size, + orientation: orientation, + rqdata: rqdata, + theme: theme, + urlOpener: urlOpener + ) + } + + func configureWebView(_ configure: @escaping (WKWebView) -> Void) { + configureWebView = configure + } + + func validate(on view: UIView, resetOnError: Bool = true, completion: @escaping (HCaptchaResult) -> Void) { + self.shouldResetOnError = resetOnError + self.completion = completion + + validate(on: view) + } +} diff --git a/StripePayments/StripePaymentsTests/Info.plist b/StripePayments/StripePaymentsTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripePayments/StripePaymentsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripePayments/StripePaymentsTests/STPAnalyticsClient+StripePayments.swift b/StripePayments/StripePaymentsTests/STPAnalyticsClient+StripePayments.swift new file mode 100644 index 00000000..a6d67544 --- /dev/null +++ b/StripePayments/StripePaymentsTests/STPAnalyticsClient+StripePayments.swift @@ -0,0 +1,29 @@ +// +// STPAnalyticsClient+StripePayments.swift +// StripePaymentsTests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +// swift-format-ignore +@_spi(STP) @testable import StripeCore + +// swift-format-ignore +@_spi(STP) @testable import StripePayments + +class STPAnalyticsClientPaymentsUITest: XCTestCase { + func testPaymentsSDKVariantPayload() throws { + // setup + let analytic = GenericPaymentAnalytic( + event: .paymentMethodCreation, + paymentConfiguration: nil, + additionalParams: [:] + ) + let client = STPAnalyticsClient() + let payload = client.payload(from: analytic) + XCTAssertEqual("payments-api", payload["pay_var"] as? String) + } +} diff --git a/StripePayments/StripePaymentsTests/mock.html b/StripePayments/StripePaymentsTests/mock.html new file mode 100644 index 00000000..e6ff52f4 --- /dev/null +++ b/StripePayments/StripePaymentsTests/mock.html @@ -0,0 +1,65 @@ + + + + + + + + + Test external + + diff --git a/StripePaymentsUI/README.md b/StripePaymentsUI/README.md new file mode 100644 index 00000000..7e2f02ae --- /dev/null +++ b/StripePaymentsUI/README.md @@ -0,0 +1,44 @@ +# StripePaymentsUI iOS SDK module + +The StripePaymentsUI module contains UI elements and API bindings for building a custom payment flow using Stripe. + +It contains: + +* [STPPaymentCardTextField](https://stripe.dev/stripe-ios/stripepaymentsui/documentation/stripepaymentsui/stppaymentcardtextfield/), a single-line interface for users to input their credit card details. +* [STPCardFormView](https://stripe.dev/stripe-ios/stripepaymentsui/documentation/stripepaymentsui/stpcardformview), a multi-line interface for users to input their credit card details. +* [STPAUBECSDebitFormView](https://stripe.dev/stripe-ios/stripepaymentsui/documentation/stripepaymentsui/stpaubecsdebitformview), a UIControl that contains all of the necessary fields and legal text for collecting AU BECS Debit payments. + +## Table of contents + + +* [Requirements](#Requirements) +* [Getting started](#Getting-started) + * [Integration](#Integration) + * [Example](#Example) +* [Manual linking](#Manual-linking) + + + +## Requirements + +The StripePaymentsUI module is compatible with apps targeting iOS 13.0 or above. + +## Getting started + +### Integration + +Get started with our [📚 integration guides](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=custom) and [example projects](/Example), or [📘 browse the SDK reference](https://stripe.dev/stripe-ios/stripepaymentsui/documentation/stripepaymentsui) for fine-grained documentation of all the classes and methods in the SDK. + +### Example + +- [Accept a Payment Example](https://github.com/stripe-samples/accept-a-payment/tree/main/custom-payment-flow/client/ios-swiftui) + – This example demonstrates how to collect payment information on iOS using `STPPaymentCardTextField`. + +## Manual linking + +If you link this library manually, use a version from our [releases](https://github.com/stripe/stripe-ios/releases) page and make sure to embed all of the following frameworks: +- `StripeCore.xcframework` +- `StripeUICore.xcframework` +- `Stripe3DS2.xcframework` +- `StripePayments.xcframework` +- `StripePaymentsUI.xcframework` diff --git a/StripePaymentsUI/StripePaymentsUI.xcodeproj/project.pbxproj b/StripePaymentsUI/StripePaymentsUI.xcodeproj/project.pbxproj new file mode 100644 index 00000000..fd386ad3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI.xcodeproj/project.pbxproj @@ -0,0 +1,961 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 127001FFB1E2988E194D4039 /* STPBECSDebitAccountNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A47917AEAE328A795ADD03 /* STPBECSDebitAccountNumberValidator.swift */; }; + 14B5DCA6C906F6D60551ECAB /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9EDAF3534E36069663E6B9F /* String+Localized.swift */; }; + 1B6270ED136F4C0657A87FB3 /* STPCardNumberInputTextFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34073B6642753145E5DEA6F2 /* STPCardNumberInputTextFieldValidator.swift */; }; + 1E564978409AE745342D31CB /* STPCardCVCInputTextFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61378C762E759E2F8FF04ACE /* STPCardCVCInputTextFieldValidator.swift */; }; + 1F3A6355A8BDD75F61E417CC /* Enums+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830CAAFA7D3F03562800E6D9 /* Enums+CustomStringConvertible.swift */; }; + 2B4A598F5C9AFDFD07AB39FF /* NSAttributedString+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA25F39FC75BC81D34E4204D /* NSAttributedString+Stripe.swift */; }; + 2C43175C7B6D2ECE4F494BC4 /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B63CD6DDE18E457A337169B /* StripeCoreTestUtils.framework */; }; + 313F5F872B0BE62200BD98A9 /* Docs.docc in Sources */ = {isa = PBXBuildFile; fileRef = 313F5F862B0BE62200BD98A9 /* Docs.docc */; }; + 3273E873EC3144BA6E6B5382 /* STPInputTextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF5A6CC2985AD7713186F111 /* STPInputTextFieldFormatter.swift */; }; + 331C6EA21D75973496B34CF3 /* STPAUBECSDebitFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978DC21B087406C15D764B27 /* STPAUBECSDebitFormView.swift */; }; + 381E12068537365DC3423575 /* STPStringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 521054B1E6486BD4F285F830 /* STPStringUtils.swift */; }; + 38AC7E1E1449E6DE407F74D6 /* STPCardBrand+PaymentsUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7374606B1DEDFBB6178D8674 /* STPCardBrand+PaymentsUI.swift */; }; + 3B3DA818797C12F8AAE7E28E /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD1E6AEC38AEF70D9E54ADBB /* StripeUICore.framework */; }; + 42EE67A45816D75ED4AA68D0 /* STPPhoneNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68FC7D60D61EA5534ED5D40C /* STPPhoneNumberValidator.swift */; }; + 4E4D3A54B7C0AD2C6ED8AEC5 /* STPImageLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7BD558058D36A8FAA926CBE /* STPImageLibrary.swift */; }; + 4F2317E3CD41D6D903E331C2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFE846D5129FB85A61ADB219 /* XCTest.framework */; }; + 4FE3B76C2BF8F46FF3FFAAA0 /* STPCardLoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C911731DD279086059CA23E /* STPCardLoadingIndicator.swift */; }; + 52014012018F766C62311660 /* STPMultiFormTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BD968A1A81BAD59B63E156E /* STPMultiFormTextField.swift */; }; + 528BE8DF6DD797D45501886F /* au_becs_bsb.json in Resources */ = {isa = PBXBuildFile; fileRef = 9801336237230E00A8D8F6ED /* au_becs_bsb.json */; }; + 5324CDE826773FD808B5F5A3 /* STPCardNumberInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778ECC1469214B0535BA8515 /* STPCardNumberInputTextField.swift */; }; + 54069B3F36BE51B117AE66DE /* UIButton+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB967723AE164AD7FC104111 /* UIButton+Stripe.swift */; }; + 5682C36DC9AE120E30424602 /* STPCardExpiryInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C6E614828A5250B8E5B8B2 /* STPCardExpiryInputTextField.swift */; }; + 58C2EBC7955D5C03C6761B6C /* STPCardExpiryInputTextFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F00416E6FCC1FEA739758F3E /* STPCardExpiryInputTextFieldValidator.swift */; }; + 59775D5885338AB20F13E388 /* STPPaymentCardTextField+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316460AE9760F6682AF5268 /* STPPaymentCardTextField+SwiftUI.swift */; }; + 5B2E3D7442C8F86C75FDE246 /* StripePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDFE1FFD109926218DD38D82 /* StripePayments.framework */; }; + 5C86B4020AAFB78DFD920446 /* STPCardNumberInputTextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E356A0233CD8A099A0FBAE /* STPCardNumberInputTextFieldFormatter.swift */; }; + 5EE7F22388D17B1191ED7DE2 /* STPInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76CFF33486AECE0E7FC99EC /* STPInputTextField.swift */; }; + 5FAAE212EE71172D6BF76165 /* STPCardFormView+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BA4436655DE117219F0C47 /* STPCardFormView+SwiftUI.swift */; }; + 619F0D8BDEE0CD4660C7EC54 /* STPViewWithSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF996E3C6046629EB3FE3E9E /* STPViewWithSeparator.swift */; }; + 653D236856F665222F0B18C2 /* STPLabeledMultiFormTextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B7197CAA08FDC5562111CD /* STPLabeledMultiFormTextFieldView.swift */; }; + 66B5D8F8B4E844E25822EC9E /* StripePaymentsObjcTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C879B38322224FD1B0F7FD75 /* StripePaymentsObjcTestUtils.framework */; }; + 67AA177C99537EF1B881438B /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = AB72B38DF948C628E00C7809 /* OHHTTPStubsSwift */; }; + 6C04056B0B6256A876FD4CAA /* CardBrandView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 773B4B693E579875018D8E5B /* CardBrandView.swift */; }; + 6C9E37FEA36C4C0399224179 /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; productRef = 666F15086CA3B7948999CAEF /* iOSSnapshotTestCase */; }; + 6F0B40B4A889041132E6E22C /* STPLabeledFormTextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C71E8244B6FD51CB26E9AB5 /* STPLabeledFormTextFieldView.swift */; }; + 731261FC9820DD5FEFB0B6CF /* STPAUBECSFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADEC647C11C9B12792B8D129 /* STPAUBECSFormViewModel.swift */; }; + 7C3D5125E4FED3665E598186 /* STPPaymentCardTextFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DF21390D64C22A10A1AF95F /* STPPaymentCardTextFieldSnapshotTests.swift */; }; + 85500DE65102C3B3106B05E4 /* STPPostalCodeInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65191907BE36635822515B62 /* STPPostalCodeInputTextField.swift */; }; + 8B6DAB1A75120A9938526CDB /* STPCardExpiryInputTextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C281AD068A5A8731EE1EE8 /* STPCardExpiryInputTextFieldFormatter.swift */; }; + 8E285418BD0AC81A8E4EEFE5 /* StripePaymentsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8C0921DF28F5F7911653888 /* StripePaymentsUI.framework */; }; + 90B62A87017CA429BE3FFB09 /* PaymentMethodMessagingView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153419E931FD1C7C7241A308 /* PaymentMethodMessagingView+Configuration.swift */; }; + 92F147E82F5A7ADF9C9BC4CE /* STPPostalCodeInputTextFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D9DB808E41F8C09071F109 /* STPPostalCodeInputTextFieldValidator.swift */; }; + 9380DCE78E643C2B8108A5EC /* STPFloatingPlaceholderTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87D9A22F5475D1F8058E38D /* STPFloatingPlaceholderTextField.swift */; }; + 992E387C98107E7038353D05 /* STPAnalyticsClient+PaymentsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5385E30F06B36FEB36D4266 /* STPAnalyticsClient+PaymentsUITests.swift */; }; + 9CCE00DDB77E0B1E5BC30372 /* STPPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF11005974CF20FFAC9671F /* STPPromise.swift */; }; + 9F4C9A403EAF125B8BC56182 /* DynamicImageView+Unknown.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FB38D261CBB35B6D83CE3E /* DynamicImageView+Unknown.swift */; }; + A1FDA8D703659A4F517D9A80 /* StripePaymentsUI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E82CBAFEACB3332DE187BBEA /* StripePaymentsUI.xcassets */; }; + A41B0A2A37AD28D28D9577CA /* STPPostalCodeInputTextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF9AD15E4A50C49A65376DC /* STPPostalCodeInputTextFieldFormatter.swift */; }; + A4A9946052C4A1BCCBA6D508 /* STPPostalCodeValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCF047C291E579AFDE3ED2F /* STPPostalCodeValidator.swift */; }; + A808592926FCB3BA78B862BF /* STPNumericDigitInputTextFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F169F220E75780C60A4DDE88 /* STPNumericDigitInputTextFormatter.swift */; }; + A915992666D7877A26E42231 /* StripePayments+Export.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F4C1C5E227B17DBC07DC42 /* StripePayments+Export.swift */; }; + AC64BA9A0D78486F5DF4A6C4 /* STPCardCVCInputTextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F7E4CB2C3C44058A495AB03 /* STPCardCVCInputTextFieldFormatter.swift */; }; + ACD6549B4AB90EFF2A968209 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = C290D597D57A745334EA462A /* OHHTTPStubs */; }; + AF55861520CD45A87A7EBA1A /* STPValidatedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA351FDB3ACA30EFB319C20 /* STPValidatedTextField.swift */; }; + B6452F5A4B12470928CEE9BC /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = D858052E551EBA77E173E794 /* STPLocalizedString.swift */; }; + BFFAB9D8FEF4C4162555691E /* STPGenericInputPickerField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB0D5F6A154405D705D1F88 /* STPGenericInputPickerField.swift */; }; + C08463DDF21144101AF317D3 /* PaymentMethodMessagingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FDA8CB452D782F948CEB4B /* PaymentMethodMessagingView.swift */; }; + C1C5A72015EF9A69603ABDE9 /* CardElementConfigService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08ABA2EE12ECA411162A060 /* CardElementConfigService.swift */; }; + C805055D566F42647F79131B /* STPCountryPickerInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = A41D5DF2BA2C038B826B222F /* STPCountryPickerInputField.swift */; }; + CA9C9105DD97D6EDF17A3478 /* STPBSBNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66E7B7C80848F4CDFD09F27 /* STPBSBNumberValidator.swift */; }; + CB74C200DE3322F2D9DBDFBE /* CardElementConfigServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A923C063A25FA4DB5CCB6E /* CardElementConfigServiceTests.swift */; }; + DC093951EF43C18D19A1A36B /* STPInputTextFieldValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67058F5B0F916913BF18809B /* STPInputTextFieldValidator.swift */; }; + E097C89AA5D20D6399E59A3E /* StripePaymentsUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 01F8FDBA6B57108EB1A80833 /* StripePaymentsUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E193F62029BD7821D8B6950E /* STPPaymentCardTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE99E179BBF1D07A68C6A58B /* STPPaymentCardTextField.swift */; }; + E1CD37355F5D17A04EE4B0D3 /* STPCardCVCInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6761C35C5BB310D0D40578FB /* STPCardCVCInputTextField.swift */; }; + E4839BD03F0C43B4703291E5 /* STPGenericInputTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB2AFD8B904D07C7F3E1E473 /* STPGenericInputTextField.swift */; }; + E554E00B25258B27E60ABB08 /* STPPaymentCardTextFieldViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB874B9887AA34B648473C8 /* STPPaymentCardTextFieldViewModel.swift */; }; + E6C0372180E95F8010F49E5C /* STPCBCController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D479586D4C04E9813A8F5B37 /* STPCBCController.swift */; }; + EAE0A0ECA1CCB3E2F69EA963 /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B3640AACAD9398E8AD28571 /* StripeCore.framework */; }; + EB866272BEDCA262181F87B9 /* StripePaymentsBundleLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C31F8C932A3DDB0C94781F /* StripePaymentsBundleLocator.swift */; }; + F372A9797BDAAB616BBB8BB8 /* DropDownFieldElement+CardBrand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB1B74C74DACD7C313033F4 /* DropDownFieldElement+CardBrand.swift */; }; + F397BB5EBC54AF5CE851D467 /* STPFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90016F8D8A7EB070DD1426C /* STPFormView.swift */; }; + F45B49EA9C1FE627B2F5A0EE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CDBC414B8A9A28DD518B71D1 /* Localizable.strings */; }; + F662D937F0427B766D60A898 /* Stripe3DS2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86923C0EED63CCA4827A40C9 /* Stripe3DS2.framework */; }; + F920F374499E14606DDA9608 /* STPFormTextFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8165DB2F2C8B257ACF4015C /* STPFormTextFieldContainer.swift */; }; + FADECFCAF335F37ED043D66A /* StripePaymentsTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FCE26DB24F39485B1BF9675 /* StripePaymentsTestUtils.framework */; }; + FBA096A2BC32144CBF491DE5 /* STPCardFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B30F4593F849D1B9395B94FA /* STPCardFormView.swift */; }; + FF849AFC244DE3234980E196 /* STPFormTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD127FC253A2F92DB7404F2 /* STPFormTextField.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 301A65780F8FE00408CF5994 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5A75C038903CA5BA2375E90C /* Project object */; + proxyType = 1; + remoteGlobalIDString = C159BFB17E834CD30378F9F3; + remoteInfo = StripePaymentsUI; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + CE4914E8623300BC66C40EE6 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 01F8FDBA6B57108EB1A80833 /* StripePaymentsUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripePaymentsUI.h; sourceTree = ""; }; + 037A7BAE734C79C1C7816C65 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 08FDA8CB452D782F948CEB4B /* PaymentMethodMessagingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodMessagingView.swift; sourceTree = ""; }; + 09478FB2F62490A3E4C30710 /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 153419E931FD1C7C7241A308 /* PaymentMethodMessagingView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentMethodMessagingView+Configuration.swift"; sourceTree = ""; }; + 15AF4C8964BDCA3C18A231BA /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; + 1749604BB65B36B31E575B1B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 18F4E10018F5B614315EC505 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + 1C7E5161ECE4008A7AACD0C8 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 1F7E4CB2C3C44058A495AB03 /* STPCardCVCInputTextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextFieldFormatter.swift; sourceTree = ""; }; + 2461E21A0EACF292D412ACFB /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + 263A4A30966BC469BCD22C20 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 27A47917AEAE328A795ADD03 /* STPBECSDebitAccountNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBECSDebitAccountNumberValidator.swift; sourceTree = ""; }; + 2894C398480B096B6DE9886E /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 28D9DB808E41F8C09071F109 /* STPPostalCodeInputTextFieldValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextFieldValidator.swift; sourceTree = ""; }; + 29B0AD21FF2C4E8B326DBF98 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 29C7ADB2115EBE7E68F7B078 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + 2B3640AACAD9398E8AD28571 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2F0B90C79DD7D9E7CE7D74DE /* StripePaymentsUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripePaymentsUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 30B86CEBB7B23D6144D16AAD /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + 313F5F862B0BE62200BD98A9 /* Docs.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = Docs.docc; sourceTree = ""; }; + 31AD3BDD2B0C24410080C800 /* StripePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31AD3BE02B0C24450080C800 /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 31AD3BE32B0C24490080C800 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 34073B6642753145E5DEA6F2 /* STPCardNumberInputTextFieldValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardNumberInputTextFieldValidator.swift; sourceTree = ""; }; + 3680B23A13112D27179DE23C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 378D7AAFE63F6D4ADC1D4EC8 /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + 38363C386120CE3196BC3FCD /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + 3FCE26DB24F39485B1BF9675 /* StripePaymentsTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentsTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 404C42905AD44DC636788F8F /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + 40994F33CBFBB521DBA6D572 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 455BB44FF629EF0DFC550308 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 49F6F0EAD4A363935C399744 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + 4AF11005974CF20FFAC9671F /* STPPromise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPromise.swift; sourceTree = ""; }; + 4BB1B74C74DACD7C313033F4 /* DropDownFieldElement+CardBrand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DropDownFieldElement+CardBrand.swift"; sourceTree = ""; }; + 4C71E8244B6FD51CB26E9AB5 /* STPLabeledFormTextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLabeledFormTextFieldView.swift; sourceTree = ""; }; + 4CB9C96F5417ADD614D5DBA5 /* tk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tk; path = tk.lproj/Localizable.strings; sourceTree = ""; }; + 4CCF047C291E579AFDE3ED2F /* STPPostalCodeValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeValidator.swift; sourceTree = ""; }; + 4CE7F2BE40869D265B4E2501 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 500CBFBF3B9888148350E834 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + 521054B1E6486BD4F285F830 /* STPStringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPStringUtils.swift; sourceTree = ""; }; + 55847CB08B388547F06612D1 /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + 558D20EEBB72368213CD701D /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + 5B79A953FFD647CFC46A7C81 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 61378C762E759E2F8FF04ACE /* STPCardCVCInputTextFieldValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextFieldValidator.swift; sourceTree = ""; }; + 634C8C413DE299FF98DBB313 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 637471CFD8720545CA35D0A8 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + 63EA15AA32C5CD57D2204AFC /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 65191907BE36635822515B62 /* STPPostalCodeInputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextField.swift; sourceTree = ""; }; + 67058F5B0F916913BF18809B /* STPInputTextFieldValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPInputTextFieldValidator.swift; sourceTree = ""; }; + 6761C35C5BB310D0D40578FB /* STPCardCVCInputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardCVCInputTextField.swift; sourceTree = ""; }; + 689AC44FC577EFF05BFAF769 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + 68FC7D60D61EA5534ED5D40C /* STPPhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPhoneNumberValidator.swift; sourceTree = ""; }; + 6B63CD6DDE18E457A337169B /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C911731DD279086059CA23E /* STPCardLoadingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardLoadingIndicator.swift; sourceTree = ""; }; + 6CD127FC253A2F92DB7404F2 /* STPFormTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFormTextField.swift; sourceTree = ""; }; + 6DB0D5F6A154405D705D1F88 /* STPGenericInputPickerField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerField.swift; sourceTree = ""; }; + 6FE56C292DBB21E554AADCFC /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + 7374606B1DEDFBB6178D8674 /* STPCardBrand+PaymentsUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPCardBrand+PaymentsUI.swift"; sourceTree = ""; }; + 75BA4436655DE117219F0C47 /* STPCardFormView+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPCardFormView+SwiftUI.swift"; sourceTree = ""; }; + 7678EF919ADE594434B5296C /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + 773B4B693E579875018D8E5B /* CardBrandView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardBrandView.swift; sourceTree = ""; }; + 778ECC1469214B0535BA8515 /* STPCardNumberInputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardNumberInputTextField.swift; sourceTree = ""; }; + 7A914C7345F934E7DFE6F88C /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + 830CAAFA7D3F03562800E6D9 /* Enums+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Enums+CustomStringConvertible.swift"; sourceTree = ""; }; + 84C8D448EAEA46CBF984F258 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 86923C0EED63CCA4827A40C9 /* Stripe3DS2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stripe3DS2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 897899C2829F3E01AEFCF928 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + 8BD968A1A81BAD59B63E156E /* STPMultiFormTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPMultiFormTextField.swift; sourceTree = ""; }; + 978DC21B087406C15D764B27 /* STPAUBECSDebitFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAUBECSDebitFormView.swift; sourceTree = ""; }; + 9801336237230E00A8D8F6ED /* au_becs_bsb.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = au_becs_bsb.json; sourceTree = ""; }; + 99B7197CAA08FDC5562111CD /* STPLabeledMultiFormTextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLabeledMultiFormTextFieldView.swift; sourceTree = ""; }; + 9CD57121330F3FBCBAABF1DF /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 9DF21390D64C22A10A1AF95F /* STPPaymentCardTextFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextFieldSnapshotTests.swift; sourceTree = ""; }; + A0F1784A9E3B6E48528F0EE5 /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + A3C70161894781F319309A10 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + A41D5DF2BA2C038B826B222F /* STPCountryPickerInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCountryPickerInputField.swift; sourceTree = ""; }; + A76CFF33486AECE0E7FC99EC /* STPInputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPInputTextField.swift; sourceTree = ""; }; + A8165DB2F2C8B257ACF4015C /* STPFormTextFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFormTextFieldContainer.swift; sourceTree = ""; }; + A9FB38D261CBB35B6D83CE3E /* DynamicImageView+Unknown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DynamicImageView+Unknown.swift"; sourceTree = ""; }; + AB2AFD8B904D07C7F3E1E473 /* STPGenericInputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputTextField.swift; sourceTree = ""; }; + ACA351FDB3ACA30EFB319C20 /* STPValidatedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPValidatedTextField.swift; sourceTree = ""; }; + ADEC647C11C9B12792B8D129 /* STPAUBECSFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAUBECSFormViewModel.swift; sourceTree = ""; }; + B30F4593F849D1B9395B94FA /* STPCardFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardFormView.swift; sourceTree = ""; }; + B3E356A0233CD8A099A0FBAE /* STPCardNumberInputTextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardNumberInputTextFieldFormatter.swift; sourceTree = ""; }; + B90016F8D8A7EB070DD1426C /* STPFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFormView.swift; sourceTree = ""; }; + B94E13375D267970A3EA32F7 /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + BB967723AE164AD7FC104111 /* UIButton+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Stripe.swift"; sourceTree = ""; }; + BBB874B9887AA34B648473C8 /* STPPaymentCardTextFieldViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextFieldViewModel.swift; sourceTree = ""; }; + BD19B9FF09FF3FF8EFAF1AA9 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + BFE846D5129FB85A61ADB219 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + C08ABA2EE12ECA411162A060 /* CardElementConfigService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardElementConfigService.swift; sourceTree = ""; }; + C090D9C3F1A9AC462A817310 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + C155C768EEED523F37B214ED /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + C288719994E9E9D9DEA122E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + C3C31F8C932A3DDB0C94781F /* StripePaymentsBundleLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripePaymentsBundleLocator.swift; sourceTree = ""; }; + C4D8989B1B30DE6BBF64E2B4 /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + C6A923C063A25FA4DB5CCB6E /* CardElementConfigServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardElementConfigServiceTests.swift; sourceTree = ""; }; + C6C6E614828A5250B8E5B8B2 /* STPCardExpiryInputTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardExpiryInputTextField.swift; sourceTree = ""; }; + C7BD558058D36A8FAA926CBE /* STPImageLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPImageLibrary.swift; sourceTree = ""; }; + C879B38322224FD1B0F7FD75 /* StripePaymentsObjcTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentsObjcTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CDFE1FFD109926218DD38D82 /* StripePayments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePayments.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CF5A6CC2985AD7713186F111 /* STPInputTextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPInputTextFieldFormatter.swift; sourceTree = ""; }; + D1C281AD068A5A8731EE1EE8 /* STPCardExpiryInputTextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardExpiryInputTextFieldFormatter.swift; sourceTree = ""; }; + D316460AE9760F6682AF5268 /* STPPaymentCardTextField+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPPaymentCardTextField+SwiftUI.swift"; sourceTree = ""; }; + D479586D4C04E9813A8F5B37 /* STPCBCController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCBCController.swift; sourceTree = ""; }; + D66E7B7C80848F4CDFD09F27 /* STPBSBNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBSBNumberValidator.swift; sourceTree = ""; }; + D858052E551EBA77E173E794 /* STPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = ""; }; + D875E65D1FC93B45506CB51B /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + D9EDAF3534E36069663E6B9F /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + DACC385F4CE5DE8BCFAA8A49 /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + DB8FAA568A2CF9A7B4616A7F /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + DE92A56ECDD0EFEF8FBB6C2C /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + DF996E3C6046629EB3FE3E9E /* STPViewWithSeparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPViewWithSeparator.swift; sourceTree = ""; }; + E22E366C1397E3E321EFBA72 /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + E45CEE8F2A450AAF95226FAA /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + E5385E30F06B36FEB36D4266 /* STPAnalyticsClient+PaymentsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "STPAnalyticsClient+PaymentsUITests.swift"; sourceTree = ""; }; + E82CBAFEACB3332DE187BBEA /* StripePaymentsUI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StripePaymentsUI.xcassets; sourceTree = ""; }; + E87D9A22F5475D1F8058E38D /* STPFloatingPlaceholderTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPFloatingPlaceholderTextField.swift; sourceTree = ""; }; + E8C0921DF28F5F7911653888 /* StripePaymentsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripePaymentsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E9F4C1C5E227B17DBC07DC42 /* StripePayments+Export.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StripePayments+Export.swift"; sourceTree = ""; }; + EAF9AD15E4A50C49A65376DC /* STPPostalCodeInputTextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPostalCodeInputTextFieldFormatter.swift; sourceTree = ""; }; + F00416E6FCC1FEA739758F3E /* STPCardExpiryInputTextFieldValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardExpiryInputTextFieldValidator.swift; sourceTree = ""; }; + F169F220E75780C60A4DDE88 /* STPNumericDigitInputTextFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPNumericDigitInputTextFormatter.swift; sourceTree = ""; }; + FA25F39FC75BC81D34E4204D /* NSAttributedString+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Stripe.swift"; sourceTree = ""; }; + FD1E6AEC38AEF70D9E54ADBB /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FDFDDFC3D204B8B31EE5FE2B /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + FE99E179BBF1D07A68C6A58B /* STPPaymentCardTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentCardTextField.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 080B9DE24C5BA81F780F8F21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4F2317E3CD41D6D903E331C2 /* XCTest.framework in Frameworks */, + 2C43175C7B6D2ECE4F494BC4 /* StripeCoreTestUtils.framework in Frameworks */, + 66B5D8F8B4E844E25822EC9E /* StripePaymentsObjcTestUtils.framework in Frameworks */, + FADECFCAF335F37ED043D66A /* StripePaymentsTestUtils.framework in Frameworks */, + 8E285418BD0AC81A8E4EEFE5 /* StripePaymentsUI.framework in Frameworks */, + 6C9E37FEA36C4C0399224179 /* iOSSnapshotTestCase in Frameworks */, + ACD6549B4AB90EFF2A968209 /* OHHTTPStubs in Frameworks */, + 67AA177C99537EF1B881438B /* OHHTTPStubsSwift in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B90D4A8DDE24FEFA64A3A0B8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F662D937F0427B766D60A898 /* Stripe3DS2.framework in Frameworks */, + EAE0A0ECA1CCB3E2F69EA963 /* StripeCore.framework in Frameworks */, + 5B2E3D7442C8F86C75FDE246 /* StripePayments.framework in Frameworks */, + 3B3DA818797C12F8AAE7E28E /* StripeUICore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0450FDF5F8EEC5A590B5EA34 /* Categories */ = { + isa = PBXGroup; + children = ( + 830CAAFA7D3F03562800E6D9 /* Enums+CustomStringConvertible.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 05755A7AD4C64F07FADE4596 /* Elements */ = { + isa = PBXGroup; + children = ( + 4BB1B74C74DACD7C313033F4 /* DropDownFieldElement+CardBrand.swift */, + ); + path = Elements; + sourceTree = ""; + }; + 16E5544A79944C8FB4BF6C7A /* Categories */ = { + isa = PBXGroup; + children = ( + FA25F39FC75BC81D34E4204D /* NSAttributedString+Stripe.swift */, + 7374606B1DEDFBB6178D8674 /* STPCardBrand+PaymentsUI.swift */, + BB967723AE164AD7FC104111 /* UIButton+Stripe.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 199A9B8AB68BE566A72856B3 /* Products */ = { + isa = PBXGroup; + children = ( + 86923C0EED63CCA4827A40C9 /* Stripe3DS2.framework */, + 2B3640AACAD9398E8AD28571 /* StripeCore.framework */, + 6B63CD6DDE18E457A337169B /* StripeCoreTestUtils.framework */, + CDFE1FFD109926218DD38D82 /* StripePayments.framework */, + C879B38322224FD1B0F7FD75 /* StripePaymentsObjcTestUtils.framework */, + 3FCE26DB24F39485B1BF9675 /* StripePaymentsTestUtils.framework */, + E8C0921DF28F5F7911653888 /* StripePaymentsUI.framework */, + 2F0B90C79DD7D9E7CE7D74DE /* StripePaymentsUITests.xctest */, + FD1E6AEC38AEF70D9E54ADBB /* StripeUICore.framework */, + ); + name = Products; + sourceTree = ""; + }; + 1A9E5FB9EB1A4FAF11A131DC /* UI */ = { + isa = PBXGroup; + children = ( + 05755A7AD4C64F07FADE4596 /* Elements */, + 646BAFCFDF6E661C0C444D32 /* Views */, + ); + path = UI; + sourceTree = ""; + }; + 2FF82D94D876852704770AA2 /* Inputs */ = { + isa = PBXGroup; + children = ( + E55596BA2BC9F135F3074CC4 /* Card */, + A41D5DF2BA2C038B826B222F /* STPCountryPickerInputField.swift */, + 6DB0D5F6A154405D705D1F88 /* STPGenericInputPickerField.swift */, + AB2AFD8B904D07C7F3E1E473 /* STPGenericInputTextField.swift */, + A76CFF33486AECE0E7FC99EC /* STPInputTextField.swift */, + CF5A6CC2985AD7713186F111 /* STPInputTextFieldFormatter.swift */, + 67058F5B0F916913BF18809B /* STPInputTextFieldValidator.swift */, + F169F220E75780C60A4DDE88 /* STPNumericDigitInputTextFormatter.swift */, + ); + path = Inputs; + sourceTree = ""; + }; + 45D22B72C2DE0D4407BB65DB /* Localizations */ = { + isa = PBXGroup; + children = ( + CDBC414B8A9A28DD518B71D1 /* Localizable.strings */, + ); + path = Localizations; + sourceTree = ""; + }; + 58E2DBA9B8DC2BBCEA45972C /* StripePaymentsUI */ = { + isa = PBXGroup; + children = ( + 313F5F862B0BE62200BD98A9 /* Docs.docc */, + 92BA1D23641756638281C55B /* Resources */, + A62869CC8E06A82A37B603AD /* Source */, + 4CE7F2BE40869D265B4E2501 /* Info.plist */, + 01F8FDBA6B57108EB1A80833 /* StripePaymentsUI.h */, + ); + path = StripePaymentsUI; + sourceTree = ""; + }; + 62140A9DA8A852E23FFACC1C /* JSON */ = { + isa = PBXGroup; + children = ( + 9801336237230E00A8D8F6ED /* au_becs_bsb.json */, + ); + path = JSON; + sourceTree = ""; + }; + 646BAFCFDF6E661C0C444D32 /* Views */ = { + isa = PBXGroup; + children = ( + 2FF82D94D876852704770AA2 /* Inputs */, + 773B4B693E579875018D8E5B /* CardBrandView.swift */, + A9FB38D261CBB35B6D83CE3E /* DynamicImageView+Unknown.swift */, + ADEC647C11C9B12792B8D129 /* STPAUBECSFormViewModel.swift */, + 6C911731DD279086059CA23E /* STPCardLoadingIndicator.swift */, + 6CD127FC253A2F92DB7404F2 /* STPFormTextField.swift */, + 4C71E8244B6FD51CB26E9AB5 /* STPLabeledFormTextFieldView.swift */, + 99B7197CAA08FDC5562111CD /* STPLabeledMultiFormTextFieldView.swift */, + BBB874B9887AA34B648473C8 /* STPPaymentCardTextFieldViewModel.swift */, + ACA351FDB3ACA30EFB319C20 /* STPValidatedTextField.swift */, + DF996E3C6046629EB3FE3E9E /* STPViewWithSeparator.swift */, + ); + path = Views; + sourceTree = ""; + }; + 6DD88910647B8C66BA7429DD /* Internal */ = { + isa = PBXGroup; + children = ( + 16E5544A79944C8FB4BF6C7A /* Categories */, + 1A9E5FB9EB1A4FAF11A131DC /* UI */, + ); + path = Internal; + sourceTree = ""; + }; + 7ADB2E49AFEC066694F8E7D1 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 31AD3BE32B0C24490080C800 /* StripeCore.framework */, + 31AD3BE02B0C24450080C800 /* StripeUICore.framework */, + 31AD3BDD2B0C24410080C800 /* StripePayments.framework */, + BFE846D5129FB85A61ADB219 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 92BA1D23641756638281C55B /* Resources */ = { + isa = PBXGroup; + children = ( + 62140A9DA8A852E23FFACC1C /* JSON */, + 45D22B72C2DE0D4407BB65DB /* Localizations */, + E82CBAFEACB3332DE187BBEA /* StripePaymentsUI.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + 9446D52035EE85A6B33817F0 /* StripePaymentsUITests */ = { + isa = PBXGroup; + children = ( + C6A923C063A25FA4DB5CCB6E /* CardElementConfigServiceTests.swift */, + C288719994E9E9D9DEA122E8 /* Info.plist */, + E5385E30F06B36FEB36D4266 /* STPAnalyticsClient+PaymentsUITests.swift */, + 9DF21390D64C22A10A1AF95F /* STPPaymentCardTextFieldSnapshotTests.swift */, + ); + path = StripePaymentsUITests; + sourceTree = ""; + }; + A1DBCB8A73CEA53E8B88677F /* UI Components */ = { + isa = PBXGroup; + children = ( + 08FDA8CB452D782F948CEB4B /* PaymentMethodMessagingView.swift */, + 153419E931FD1C7C7241A308 /* PaymentMethodMessagingView+Configuration.swift */, + 978DC21B087406C15D764B27 /* STPAUBECSDebitFormView.swift */, + B30F4593F849D1B9395B94FA /* STPCardFormView.swift */, + 75BA4436655DE117219F0C47 /* STPCardFormView+SwiftUI.swift */, + E87D9A22F5475D1F8058E38D /* STPFloatingPlaceholderTextField.swift */, + A8165DB2F2C8B257ACF4015C /* STPFormTextFieldContainer.swift */, + B90016F8D8A7EB070DD1426C /* STPFormView.swift */, + 8BD968A1A81BAD59B63E156E /* STPMultiFormTextField.swift */, + FE99E179BBF1D07A68C6A58B /* STPPaymentCardTextField.swift */, + D316460AE9760F6682AF5268 /* STPPaymentCardTextField+SwiftUI.swift */, + ); + path = "UI Components"; + sourceTree = ""; + }; + A2FBAE5EC2F69C0424DCE966 /* Helpers */ = { + isa = PBXGroup; + children = ( + C08ABA2EE12ECA411162A060 /* CardElementConfigService.swift */, + 27A47917AEAE328A795ADD03 /* STPBECSDebitAccountNumberValidator.swift */, + D66E7B7C80848F4CDFD09F27 /* STPBSBNumberValidator.swift */, + D479586D4C04E9813A8F5B37 /* STPCBCController.swift */, + C7BD558058D36A8FAA926CBE /* STPImageLibrary.swift */, + D858052E551EBA77E173E794 /* STPLocalizedString.swift */, + 68FC7D60D61EA5534ED5D40C /* STPPhoneNumberValidator.swift */, + 4CCF047C291E579AFDE3ED2F /* STPPostalCodeValidator.swift */, + 4AF11005974CF20FFAC9671F /* STPPromise.swift */, + 521054B1E6486BD4F285F830 /* STPStringUtils.swift */, + D9EDAF3534E36069663E6B9F /* String+Localized.swift */, + E9F4C1C5E227B17DBC07DC42 /* StripePayments+Export.swift */, + C3C31F8C932A3DDB0C94781F /* StripePaymentsBundleLocator.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + A62869CC8E06A82A37B603AD /* Source */ = { + isa = PBXGroup; + children = ( + 0450FDF5F8EEC5A590B5EA34 /* Categories */, + A2FBAE5EC2F69C0424DCE966 /* Helpers */, + 6DD88910647B8C66BA7429DD /* Internal */, + A1DBCB8A73CEA53E8B88677F /* UI Components */, + ); + path = Source; + sourceTree = ""; + }; + BE7250EEEC959E5598AFB9FA = { + isa = PBXGroup; + children = ( + E6EE39118733528E48738DF2 /* Project */, + 7ADB2E49AFEC066694F8E7D1 /* Frameworks */, + 199A9B8AB68BE566A72856B3 /* Products */, + ); + sourceTree = ""; + }; + E55596BA2BC9F135F3074CC4 /* Card */ = { + isa = PBXGroup; + children = ( + 6761C35C5BB310D0D40578FB /* STPCardCVCInputTextField.swift */, + 1F7E4CB2C3C44058A495AB03 /* STPCardCVCInputTextFieldFormatter.swift */, + 61378C762E759E2F8FF04ACE /* STPCardCVCInputTextFieldValidator.swift */, + C6C6E614828A5250B8E5B8B2 /* STPCardExpiryInputTextField.swift */, + D1C281AD068A5A8731EE1EE8 /* STPCardExpiryInputTextFieldFormatter.swift */, + F00416E6FCC1FEA739758F3E /* STPCardExpiryInputTextFieldValidator.swift */, + 778ECC1469214B0535BA8515 /* STPCardNumberInputTextField.swift */, + B3E356A0233CD8A099A0FBAE /* STPCardNumberInputTextFieldFormatter.swift */, + 34073B6642753145E5DEA6F2 /* STPCardNumberInputTextFieldValidator.swift */, + 65191907BE36635822515B62 /* STPPostalCodeInputTextField.swift */, + EAF9AD15E4A50C49A65376DC /* STPPostalCodeInputTextFieldFormatter.swift */, + 28D9DB808E41F8C09071F109 /* STPPostalCodeInputTextFieldValidator.swift */, + ); + path = Card; + sourceTree = ""; + }; + E6EE39118733528E48738DF2 /* Project */ = { + isa = PBXGroup; + children = ( + E760746A90857E3575BEBCDA /* BuildConfigurations */, + 58E2DBA9B8DC2BBCEA45972C /* StripePaymentsUI */, + 9446D52035EE85A6B33817F0 /* StripePaymentsUITests */, + ); + name = Project; + sourceTree = ""; + }; + E760746A90857E3575BEBCDA /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 09478FB2F62490A3E4C30710 /* Project-Debug.xcconfig */, + 637471CFD8720545CA35D0A8 /* Project-Release.xcconfig */, + 6FE56C292DBB21E554AADCFC /* StripeiOS Tests-Debug.xcconfig */, + E22E366C1397E3E321EFBA72 /* StripeiOS Tests-Release.xcconfig */, + DACC385F4CE5DE8BCFAA8A49 /* StripeiOS-Debug.xcconfig */, + 378D7AAFE63F6D4ADC1D4EC8 /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 0B3ED58B321B8C3B8C8F3078 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + E097C89AA5D20D6399E59A3E /* StripePaymentsUI.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + C159BFB17E834CD30378F9F3 /* StripePaymentsUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = 701C03CD723265CEBC62C4D5 /* Build configuration list for PBXNativeTarget "StripePaymentsUI" */; + buildPhases = ( + 0B3ED58B321B8C3B8C8F3078 /* Headers */, + 27438CDC7098A318A6672A71 /* Sources */, + 643EE5B9BDDF28D0C19F8601 /* Resources */, + CE4914E8623300BC66C40EE6 /* Embed Frameworks */, + B90D4A8DDE24FEFA64A3A0B8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripePaymentsUI; + productName = StripePaymentsUI; + productReference = E8C0921DF28F5F7911653888 /* StripePaymentsUI.framework */; + productType = "com.apple.product-type.framework"; + }; + CFDEE20ADA9F0099259938AF /* StripePaymentsUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CC4EB44D4453B210FF076CF2 /* Build configuration list for PBXNativeTarget "StripePaymentsUITests" */; + buildPhases = ( + A1F6776A0960C4F06147074E /* Sources */, + D3B646E158379063DC903CD9 /* Resources */, + 080B9DE24C5BA81F780F8F21 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 176864E5FBCD395A07E36D5A /* PBXTargetDependency */, + ); + name = StripePaymentsUITests; + packageProductDependencies = ( + 666F15086CA3B7948999CAEF /* iOSSnapshotTestCase */, + C290D597D57A745334EA462A /* OHHTTPStubs */, + AB72B38DF948C628E00C7809 /* OHHTTPStubsSwift */, + ); + productName = StripePaymentsUITests; + productReference = 2F0B90C79DD7D9E7CE7D74DE /* StripePaymentsUITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5A75C038903CA5BA2375E90C /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = 02E5F9E426DAA8E86C0A1EB9 /* Build configuration list for PBXProject "StripePaymentsUI" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tk, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = BE7250EEEC959E5598AFB9FA; + packageReferences = ( + 4A06DAB6985257ECC67643BD /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, + EBDA13D7CF2595A44DA31C22 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + ); + productRefGroup = 199A9B8AB68BE566A72856B3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C159BFB17E834CD30378F9F3 /* StripePaymentsUI */, + CFDEE20ADA9F0099259938AF /* StripePaymentsUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 643EE5B9BDDF28D0C19F8601 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 528BE8DF6DD797D45501886F /* au_becs_bsb.json in Resources */, + F45B49EA9C1FE627B2F5A0EE /* Localizable.strings in Resources */, + A1FDA8D703659A4F517D9A80 /* StripePaymentsUI.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D3B646E158379063DC903CD9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 27438CDC7098A318A6672A71 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1F3A6355A8BDD75F61E417CC /* Enums+CustomStringConvertible.swift in Sources */, + C1C5A72015EF9A69603ABDE9 /* CardElementConfigService.swift in Sources */, + 127001FFB1E2988E194D4039 /* STPBECSDebitAccountNumberValidator.swift in Sources */, + CA9C9105DD97D6EDF17A3478 /* STPBSBNumberValidator.swift in Sources */, + E6C0372180E95F8010F49E5C /* STPCBCController.swift in Sources */, + 4E4D3A54B7C0AD2C6ED8AEC5 /* STPImageLibrary.swift in Sources */, + B6452F5A4B12470928CEE9BC /* STPLocalizedString.swift in Sources */, + 42EE67A45816D75ED4AA68D0 /* STPPhoneNumberValidator.swift in Sources */, + A4A9946052C4A1BCCBA6D508 /* STPPostalCodeValidator.swift in Sources */, + 9CCE00DDB77E0B1E5BC30372 /* STPPromise.swift in Sources */, + 381E12068537365DC3423575 /* STPStringUtils.swift in Sources */, + 14B5DCA6C906F6D60551ECAB /* String+Localized.swift in Sources */, + A915992666D7877A26E42231 /* StripePayments+Export.swift in Sources */, + EB866272BEDCA262181F87B9 /* StripePaymentsBundleLocator.swift in Sources */, + 2B4A598F5C9AFDFD07AB39FF /* NSAttributedString+Stripe.swift in Sources */, + 38AC7E1E1449E6DE407F74D6 /* STPCardBrand+PaymentsUI.swift in Sources */, + 54069B3F36BE51B117AE66DE /* UIButton+Stripe.swift in Sources */, + F372A9797BDAAB616BBB8BB8 /* DropDownFieldElement+CardBrand.swift in Sources */, + 6C04056B0B6256A876FD4CAA /* CardBrandView.swift in Sources */, + 9F4C9A403EAF125B8BC56182 /* DynamicImageView+Unknown.swift in Sources */, + E1CD37355F5D17A04EE4B0D3 /* STPCardCVCInputTextField.swift in Sources */, + AC64BA9A0D78486F5DF4A6C4 /* STPCardCVCInputTextFieldFormatter.swift in Sources */, + 1E564978409AE745342D31CB /* STPCardCVCInputTextFieldValidator.swift in Sources */, + 5682C36DC9AE120E30424602 /* STPCardExpiryInputTextField.swift in Sources */, + 8B6DAB1A75120A9938526CDB /* STPCardExpiryInputTextFieldFormatter.swift in Sources */, + 58C2EBC7955D5C03C6761B6C /* STPCardExpiryInputTextFieldValidator.swift in Sources */, + 5324CDE826773FD808B5F5A3 /* STPCardNumberInputTextField.swift in Sources */, + 5C86B4020AAFB78DFD920446 /* STPCardNumberInputTextFieldFormatter.swift in Sources */, + 1B6270ED136F4C0657A87FB3 /* STPCardNumberInputTextFieldValidator.swift in Sources */, + 85500DE65102C3B3106B05E4 /* STPPostalCodeInputTextField.swift in Sources */, + A41B0A2A37AD28D28D9577CA /* STPPostalCodeInputTextFieldFormatter.swift in Sources */, + 92F147E82F5A7ADF9C9BC4CE /* STPPostalCodeInputTextFieldValidator.swift in Sources */, + C805055D566F42647F79131B /* STPCountryPickerInputField.swift in Sources */, + BFFAB9D8FEF4C4162555691E /* STPGenericInputPickerField.swift in Sources */, + E4839BD03F0C43B4703291E5 /* STPGenericInputTextField.swift in Sources */, + 5EE7F22388D17B1191ED7DE2 /* STPInputTextField.swift in Sources */, + 3273E873EC3144BA6E6B5382 /* STPInputTextFieldFormatter.swift in Sources */, + DC093951EF43C18D19A1A36B /* STPInputTextFieldValidator.swift in Sources */, + A808592926FCB3BA78B862BF /* STPNumericDigitInputTextFormatter.swift in Sources */, + 731261FC9820DD5FEFB0B6CF /* STPAUBECSFormViewModel.swift in Sources */, + 4FE3B76C2BF8F46FF3FFAAA0 /* STPCardLoadingIndicator.swift in Sources */, + FF849AFC244DE3234980E196 /* STPFormTextField.swift in Sources */, + 6F0B40B4A889041132E6E22C /* STPLabeledFormTextFieldView.swift in Sources */, + 653D236856F665222F0B18C2 /* STPLabeledMultiFormTextFieldView.swift in Sources */, + 313F5F872B0BE62200BD98A9 /* Docs.docc in Sources */, + E554E00B25258B27E60ABB08 /* STPPaymentCardTextFieldViewModel.swift in Sources */, + AF55861520CD45A87A7EBA1A /* STPValidatedTextField.swift in Sources */, + 619F0D8BDEE0CD4660C7EC54 /* STPViewWithSeparator.swift in Sources */, + 90B62A87017CA429BE3FFB09 /* PaymentMethodMessagingView+Configuration.swift in Sources */, + C08463DDF21144101AF317D3 /* PaymentMethodMessagingView.swift in Sources */, + 331C6EA21D75973496B34CF3 /* STPAUBECSDebitFormView.swift in Sources */, + 5FAAE212EE71172D6BF76165 /* STPCardFormView+SwiftUI.swift in Sources */, + FBA096A2BC32144CBF491DE5 /* STPCardFormView.swift in Sources */, + 9380DCE78E643C2B8108A5EC /* STPFloatingPlaceholderTextField.swift in Sources */, + F920F374499E14606DDA9608 /* STPFormTextFieldContainer.swift in Sources */, + F397BB5EBC54AF5CE851D467 /* STPFormView.swift in Sources */, + 52014012018F766C62311660 /* STPMultiFormTextField.swift in Sources */, + 59775D5885338AB20F13E388 /* STPPaymentCardTextField+SwiftUI.swift in Sources */, + E193F62029BD7821D8B6950E /* STPPaymentCardTextField.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1F6776A0960C4F06147074E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CB74C200DE3322F2D9DBDFBE /* CardElementConfigServiceTests.swift in Sources */, + 992E387C98107E7038353D05 /* STPAnalyticsClient+PaymentsUITests.swift in Sources */, + 7C3D5125E4FED3665E598186 /* STPPaymentCardTextFieldSnapshotTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 176864E5FBCD395A07E36D5A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripePaymentsUI; + target = C159BFB17E834CD30378F9F3 /* StripePaymentsUI */; + targetProxy = 301A65780F8FE00408CF5994 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + CDBC414B8A9A28DD518B71D1 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + C4D8989B1B30DE6BBF64E2B4 /* bg-BG */, + 63EA15AA32C5CD57D2204AFC /* ca-ES */, + 55847CB08B388547F06612D1 /* cs-CZ */, + C090D9C3F1A9AC462A817310 /* da */, + 1C7E5161ECE4008A7AACD0C8 /* de */, + 2461E21A0EACF292D412ACFB /* el-GR */, + 634C8C413DE299FF98DBB313 /* en */, + E45CEE8F2A450AAF95226FAA /* en-GB */, + 455BB44FF629EF0DFC550308 /* es */, + 897899C2829F3E01AEFCF928 /* es-419 */, + DE92A56ECDD0EFEF8FBB6C2C /* et-EE */, + 3680B23A13112D27179DE23C /* fi */, + D875E65D1FC93B45506CB51B /* fil */, + BD19B9FF09FF3FF8EFAF1AA9 /* fr */, + 49F6F0EAD4A363935C399744 /* fr-CA */, + DB8FAA568A2CF9A7B4616A7F /* hr */, + 689AC44FC577EFF05BFAF769 /* hu */, + 037A7BAE734C79C1C7816C65 /* id */, + 40994F33CBFBB521DBA6D572 /* it */, + 2894C398480B096B6DE9886E /* ja */, + 29C7ADB2115EBE7E68F7B078 /* ko */, + 30B86CEBB7B23D6144D16AAD /* lt-LT */, + 18F4E10018F5B614315EC505 /* lv-LV */, + FDFDDFC3D204B8B31EE5FE2B /* ms-MY */, + A3C70161894781F319309A10 /* mt */, + 5B79A953FFD647CFC46A7C81 /* nb */, + C155C768EEED523F37B214ED /* nl */, + A0F1784A9E3B6E48528F0EE5 /* nn-NO */, + 15AF4C8964BDCA3C18A231BA /* pl-PL */, + 84C8D448EAEA46CBF984F258 /* pt-BR */, + 500CBFBF3B9888148350E834 /* pt-PT */, + 404C42905AD44DC636788F8F /* ro-RO */, + 1749604BB65B36B31E575B1B /* ru */, + B94E13375D267970A3EA32F7 /* sk-SK */, + 558D20EEBB72368213CD701D /* sl-SI */, + 9CD57121330F3FBCBAABF1DF /* sv */, + 4CB9C96F5417ADD614D5DBA5 /* tk */, + 38363C386120CE3196BC3FCD /* tr */, + 263A4A30966BC469BCD22C20 /* vi */, + 29B0AD21FF2C4E8B326DBF98 /* zh-Hans */, + 7678EF919ADE594434B5296C /* zh-Hant */, + 7A914C7345F934E7DFE6F88C /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 2E1485C80C43DF1F0781E06D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 637471CFD8720545CA35D0A8 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 2F68C82F178D19F60E20B79E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6FE56C292DBB21E554AADCFC /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripePaymentsUITests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripePaymentsUITests; + PRODUCT_NAME = StripePaymentsUITests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Debug; + }; + 35A9B080D9B9528E35B7CFF8 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 09478FB2F62490A3E4C30710 /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 421A61384C2E7B43B6189743 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E22E366C1397E3E321EFBA72 /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripePaymentsUITests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripePaymentsUITests; + PRODUCT_NAME = StripePaymentsUITests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Release; + }; + DB0783873B146C8B452FF684 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 378D7AAFE63F6D4ADC1D4EC8 /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripePaymentsUI/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-payments-ui"; + PRODUCT_NAME = StripePaymentsUI; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; + ED4EF1A6C4021E0C9170DAB3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DACC385F4CE5DE8BCFAA8A49 /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripePaymentsUI/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-payments-ui"; + PRODUCT_NAME = StripePaymentsUI; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 02E5F9E426DAA8E86C0A1EB9 /* Build configuration list for PBXProject "StripePaymentsUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 35A9B080D9B9528E35B7CFF8 /* Debug */, + 2E1485C80C43DF1F0781E06D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 701C03CD723265CEBC62C4D5 /* Build configuration list for PBXNativeTarget "StripePaymentsUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED4EF1A6C4021E0C9170DAB3 /* Debug */, + DB0783873B146C8B452FF684 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CC4EB44D4453B210FF076CF2 /* Build configuration list for PBXNativeTarget "StripePaymentsUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2F68C82F178D19F60E20B79E /* Debug */, + 421A61384C2E7B43B6189743 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 4A06DAB6985257ECC67643BD /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davidme-stripe/OHHTTPStubs"; + requirement = { + branch = "stripe-mock"; + kind = branch; + }; + }; + EBDA13D7CF2595A44DA31C22 /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 666F15086CA3B7948999CAEF /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; + AB72B38DF948C628E00C7809 /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubsSwift; + }; + C290D597D57A745334EA462A /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + productName = OHHTTPStubs; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 5A75C038903CA5BA2375E90C /* Project object */; +} diff --git a/StripePaymentsUI/StripePaymentsUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripePaymentsUI/StripePaymentsUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcshareddata/xcschemes/StripePaymentsUI.xcscheme b/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcshareddata/xcschemes/StripePaymentsUI.xcscheme new file mode 100644 index 00000000..7481eaba --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcshareddata/xcschemes/StripePaymentsUI.xcscheme @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Docs.docc/StripePaymentsUI.md b/StripePaymentsUI/StripePaymentsUI/Docs.docc/StripePaymentsUI.md new file mode 100644 index 00000000..732b6e9f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Docs.docc/StripePaymentsUI.md @@ -0,0 +1,3 @@ +# ``StripePaymentsUI`` + +Placeholder diff --git a/StripePaymentsUI/StripePaymentsUI/Info.plist b/StripePaymentsUI/StripePaymentsUI/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/JSON/au_becs_bsb.json b/StripePaymentsUI/StripePaymentsUI/Resources/JSON/au_becs_bsb.json new file mode 100644 index 00000000..cc94f617 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/JSON/au_becs_bsb.json @@ -0,0 +1,373 @@ +{ + "10": { + "icon": "banksa", + "name": "BankSA (division of Westpac Bank)" + }, + "11": { + "icon": "stgeorges", + "name": "St George Bank (division of Westpac Bank)" + }, + "12": { + "icon": "boq", + "name": "Bank of Queensland" + }, + "14": { + "name": "Rabobank" + }, + "15": { + "name": "Town & Country Bank" + }, + "18": { + "name": "Macquarie Bank" + }, + "19": { + "icon": "bankofmelbourne", + "name": "Bank of Melbourne (division of Westpac Bank)" + }, + "21": { + "name": "JP Morgan Chase Bank" + }, + "22": { + "name": "BNP Paribas" + }, + "23": { + "name": "Bank of America" + }, + "24": { + "name": "Citibank" + }, + "25": { + "name": "BNP Paribas Securities" + }, + "26": { + "name": "Bankers Trust Australia (division of Westpac Bank)" + }, + "29": { + "name": "Bank of Tokyo-Mitsubishi" + }, + "30": { + "icon": "bankwest", + "name": "Bankwest (division of Commonwealth Bank)" + }, + "31": { + "name": "Bankmecu" + }, + "33": { + "icon": "stgeorges", + "name": "St George Bank (division of Westpac Bank)" + }, + "34": { + "name": "HSBC Bank Australia" + }, + "35": { + "name": "Bank of China" + }, + "40": { + "icon": "cba", + "name": "Commonwealth Bank of Australia" + }, + "41": { + "name": "Deutsche Bank" + }, + "42": { + "icon": "cba", + "name": "Commonwealth Bank of Australia" + }, + "45": { + "name": "OCBC Bank" + }, + "46": { + "name": "Advance Bank (division of Westpac Bank)" + }, + "47": { + "name": "Challenge Bank (division of Westpac Bank)" + }, + "48": { + "icon": "suncorpmetway", + "name": "Suncorp-Metway" + }, + "52": { + "icon": "cba", + "name": "Commonwealth Bank of Australia" + }, + "55": { + "icon": "bankofmelbourne", + "name": "Bank of Melbourne (division of Westpac Bank)" + }, + "57": { + "name": "Australian Settlements" + }, + "61": { + "name": "Adelaide Bank (division of Bendigo and Adelaide Bank)" + }, + "70": { + "name": "Indue" + }, + "73": { + "icon": "westpac", + "name": "Westpac Banking Corporation" + }, + "76": { + "icon": "cba", + "name": "Commonwealth Bank of Australia" + }, + "78": { + "icon": "nab", + "name": "National Australia Bank" + }, + "80": { + "name": "Cuscal" + }, + "90": { + "name": "Australia Post" + }, + "325": { + "name": "Beyond Bank Australia" + }, + "432": { + "name": "Standard Chartered Bank" + }, + "510": { + "name": "Citibank N.A." + }, + "512": { + "name": "Community First Credit Union" + }, + "514": { + "name": "QT Mutual Bank" + }, + "517": { + "name": "Australian Settlements Limited" + }, + "533": { + "name": "Bananacoast Community Credit Union" + }, + "611": { + "name": "Select Credit Union" + }, + "630": { + "name": "ABS Building Society" + }, + "632": { + "name": "B&E" + }, + "633": { + "name": "Bendigo Bank" + }, + "634": { + "name": "Uniting Financial Services" + }, + "636": { + "name": "Cuscal Limited" + }, + "637": { + "name": "Greater Building Society" + }, + "638": { + "name": "Heritage Bank" + }, + "639": { + "name": "Home Building Society (division of Bank of Queensland)" + }, + "640": { + "name": "Hume Bank" + }, + "641": { + "name": "IMB" + }, + "642": { + "name": "Australian Defence Credit Union" + }, + "645": { + "name": "Wide Bay Australia" + }, + "646": { + "name": "Maitland Mutual Building Society" + }, + "647": { + "name": "IMB" + }, + "650": { + "name": "Newcastle Permanent Building Society" + }, + "653": { + "name": "Pioneer Permanent Building Society (division of Bank of Queensland)" + }, + "654": { + "name": "ECU Australia" + }, + "655": { + "name": "The Rock Building Society" + }, + "656": { + "name": "Wide Bay Australia" + }, + "657": { + "name": "Greater Building Society" + }, + "659": { + "name": "SGE Credit Union" + }, + "664": { + "icon": "suncorpmetway", + "name": "Suncorp-Metway" + }, + "670": { + "name": "Cuscal Limited" + }, + "676": { + "name": "Gateway Credit Union" + }, + "721": { + "name": "Holiday Coast Credit Union" + }, + "722": { + "name": "Southern Cross Credit" + }, + "723": { + "name": "Heritage Isle Credit Union" + }, + "724": { + "name": "Railways Credit Union" + }, + "725": { + "name": "Judo Bank Pty Ltd" + }, + "728": { + "name": "Summerland Credit Union" + }, + "775": { + "name": "Australian Settlements Limited" + }, + "777": { + "name": "Police & Nurse" + }, + "812": { + "name": "Teachers Mutual Bank" + }, + "813": { + "name": "Capricornian" + }, + "814": { + "name": "Credit Union Australia" + }, + "815": { + "name": "Police Bank" + }, + "817": { + "name": "Warwick Credit Union" + }, + "818": { + "name": "Bank of Communications" + }, + "819": { + "name": "Industrial & Commercial Bank of China" + }, + "823": { + "name": "Encompass Credit Union" + }, + "824": { + "name": "Sutherland Credit Union" + }, + "825": { + "name": "Big Sky Building Society" + }, + "833": { + "name": "Defence Bank Limited" + }, + "880": { + "name": "Heritage Bank" + }, + "882": { + "name": "Maritime Mining & Power Credit Union" + }, + "888": { + "name": "China Construction Bank Corporation" + }, + "889": { + "name": "DBS Bank Ltd." + }, + "911": { + "name": "Sumitomo Mitsui Banking Corporation" + }, + "913": { + "name": "State Street Bank & Trust Company" + }, + "917": { + "name": "Arab Bank Australia" + }, + "918": { + "name": "Mizuho Bank" + }, + "922": { + "name": "United Overseas Bank" + }, + "923": { + "name": "ING Bank" + }, + "931": { + "name": "Mega International Commercial Bank" + }, + "932": { + "name": "Community Mutual" + }, + "936": { + "name": "ING Bank" + }, + "939": { + "name": "AMP Bank" + }, + "941": { + "name": "Delphi Bank (division of Bendigo and Adelaide Bank)" + }, + "942": { + "name": "Bank of Sydney" + }, + "943": { + "name": "Taiwan Business Bank" + }, + "944": { + "name": "Members Equity Bank" + }, + "946": { + "name": "UBS AG" + }, + "951": { + "name": "BOQ Specialist Bank" + }, + "952": { + "name": "Royal Bank of Scotland" + }, + "969": { + "name": "Tyro Payments" + }, + "980": { + "name": "Bank of China" + }, + "985": { + "name": "HSBC Bank Australia" + }, + "01": { + "icon": "anz", + "name": "Australia and New Zealand Banking Group" + }, + "03": { + "icon": "westpac", + "name": "Westpac Banking Corporation" + }, + "04": { + "icon": "westpac", + "name": "Westpac Banking Corporation" + }, + "06": { + "icon": "cba", + "name": "Commonwealth Bank of Australia" + }, + "08": { + "icon": "nab", + "name": "National Australia Bank" + }, + "09": { + "name": "Reserve Bank of Australia" + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/bg-BG.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..5928515a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/bg-BG.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ сметка, завършваща на %2$@"; + +"%1$@ ending in %2$@" = "%1$@ завършващо на %2$@"; + +"%1$@ is not accepted" = "%1$@ не се приема"; + +"Address line 2 (optional)" = "Ред 2 на адреса (по желание)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Банкова сметка"; + +"Billing address" = "Адрес за фактуриране"; + +"CVC" = "CVC"; + +"Card information" = "Информация за картата"; + +"Card number" = "Номер на картата"; + +"Confirm your %@" = "Потвърдете своя %@"; + +"Invalid data." = "Невалидни данни."; + +"MM / YY" = "ММ / ГГ"; + +"MM/YY" = "ММ/ГГ"; + +"Postal Code" = "Пощенски код"; + +"Shipping Address" = "Адрес за доставка"; + +"State / Province / Region" = "Щат/Област/Регион"; + +"The BSB you entered is invalid." = "BSB, който сте въвели, е невалиден."; + +"The selected brand is not allowed" = "Избраната марка не е разрешена"; + +"To scan your card, allow camera access in Settings." = "За да сканирате картата си, разрешете достъп до камерата в Настройки."; + +"Your ZIP is invalid." = "Вашият ZIP код е невалиден."; + +"Your card has expired." = "Вашата карта е изтекла."; + +"Your card number is incomplete." = "Номерът на Вашата карта е непълен."; + +"Your card number is invalid." = "Номерът на Вашата карта е невалиден."; + +"Your card's expiration date is incomplete." = "Срокът на валидност на Вашата карта е непълен."; + +"Your card's expiration date is invalid." = "Датата на валидност на Вашата карта е невалидна."; + +"Your card's expiration month is invalid." = "Месецът на валидност на Вашата карта е невалиден."; + +"Your card's expiration year is invalid." = "Годината на валидност на Вашата карта е невалидна."; + +"Your card's security code is incomplete." = "Кодът за сигурност на Вашата карта е непълен."; + +"Your card's security code is invalid." = "Кодът за сигурност на Вашата карта е невалиден."; + +"Your postal code is invalid." = "Вашият пощенски код е невалиден."; + +"card number" = "номер на картата"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "дата на валидност"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ca-ES.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..281f8e63 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ca-ES.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Compte bancari de %1$@ que acaba en %2$@"; + +"%1$@ ending in %2$@" = "%1$@ que acaba en %2$@"; + +"%1$@ is not accepted" = "No s'accepta %1$@"; + +"Address line 2 (optional)" = "Línia de l'adreça 2 (opcional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Compte bancari"; + +"Billing address" = "Adreça de facturació"; + +"CVC" = "CVC"; + +"Card information" = "Informació de targeta"; + +"Card number" = "Número de targeta"; + +"Confirm your %@" = "Confirmeu el vostre %@"; + +"Invalid data." = "Dades no vàlides."; + +"MM / YY" = "MM / AA"; + +"MM/YY" = "MM / AA"; + +"Postal Code" = "Codi postal"; + +"Shipping Address" = "Adreça d'enviament"; + +"State / Province / Region" = "País / Província / Regió"; + +"The BSB you entered is invalid." = "El BSB que has introduït no és vàlid."; + +"The selected brand is not allowed" = "La marca seleccionada no està permesa"; + +"To scan your card, allow camera access in Settings." = "Per escanejar la teva targeta, permet l'accès a la càmera a Ajusts."; + +"Your ZIP is invalid." = "El teu ZIP no és vàlid."; + +"Your card has expired." = "La targeta ha caducat."; + +"Your card number is incomplete." = "El número de la teva targeta no està complet."; + +"Your card number is invalid." = "El número de la teva targeta no és vàlid."; + +"Your card's expiration date is incomplete." = "La data de caducitat de la teva targeta no està completa."; + +"Your card's expiration date is invalid." = "La data de caducitat de la teva targeta no és vàlida."; + +"Your card's expiration month is invalid." = "El mes de caducitat de la teva targeta no és vàlid."; + +"Your card's expiration year is invalid." = "L'any de caducitat de la teva targeta no és vàlid."; + +"Your card's security code is incomplete." = "El codi de seguretat de la teva targeta no està complet."; + +"Your card's security code is invalid." = "El codi de seguretat de la teva targeta no és vàlid"; + +"Your postal code is invalid." = "El teu codi postal no és vàlid."; + +"card number" = "número de targeta"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "data de caducitat"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/cs-CZ.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..fa9576e8 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Účet %1$@ končící na %2$@"; + +"%1$@ ending in %2$@" = "%1$@ končící za %2$@"; + +"%1$@ is not accepted" = "%1$@ se neakceptuje."; + +"Address line 2 (optional)" = "2. řádek adresy (volitelně)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankovní účet"; + +"Billing address" = "Fakturační adresa"; + +"CVC" = "CVC"; + +"Card information" = "Informace o kartě"; + +"Card number" = "Číslo karty"; + +"Confirm your %@" = "Potvrďte svoje %@"; + +"Invalid data." = "Neplatná data."; + +"MM / YY" = "MM / RR"; + +"MM/YY" = "MM/RR"; + +"Postal Code" = "Poštovní směrovací číslo"; + +"Shipping Address" = "Dodací adresa"; + +"State / Province / Region" = "Stát / provincie / region"; + +"The BSB you entered is invalid." = "Zadaný BSB je neplatný."; + +"The selected brand is not allowed" = "Vybraná značka není povolena"; + +"To scan your card, allow camera access in Settings." = "Chcete-li kartu naskenovat, povolte v Nastavení přístup k fotoaparátu."; + +"Your ZIP is invalid." = "PSČ je neplatné."; + +"Your card has expired." = "Platnost vaší karty vypršela."; + +"Your card number is incomplete." = "Číslo karty je neúplné."; + +"Your card number is invalid." = "Číslo karty je neplatné."; + +"Your card's expiration date is incomplete." = "Datum konce platnosti vaší karty je neúplné."; + +"Your card's expiration date is invalid." = "Datum konce platnosti karty je neplatné."; + +"Your card's expiration month is invalid." = "Rok konce platnosti vaší karty je neplatný."; + +"Your card's expiration year is invalid." = "Rok konce platnosti vaší karty je neplatný."; + +"Your card's security code is incomplete." = "Bezpečnostní kód karty je neúplný."; + +"Your card's security code is invalid." = "Bezpečnostní kód vaší karty je neplatný."; + +"Your postal code is invalid." = "Poštovní směrovací číslo je neplatné"; + +"card number" = "číslo karty"; + +"example@example.com" = "příklad@příklad.com"; + +"expiration date" = "datum konce platnosti"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/da.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/da.lproj/Localizable.strings new file mode 100644 index 00000000..b70ed774 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/da.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@-konto, der ender på %2$@"; + +"%1$@ ending in %2$@" = "%1$@ slutter på %2$@"; + +"%1$@ is not accepted" = "%1$@ accepteres ikke"; + +"Address line 2 (optional)" = "Adresselinje 2 (valgfrit)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankkonto"; + +"Billing address" = "Faktureringsadresse"; + +"CVC" = "CVC"; + +"Card information" = "Kortoplysninger"; + +"Card number" = "Kortnummer"; + +"Confirm your %@" = "Bekræft dit %@"; + +"Invalid data." = "Ugyldige data"; + +"MM / YY" = "MM/ÅÅ"; + +"MM/YY" = "MM/ÅÅ"; + +"Postal Code" = "Postnummer"; + +"Shipping Address" = "Forsendelsesadresse"; + +"State / Province / Region" = "Region"; + +"The BSB you entered is invalid." = "Den BSB, du angav, er ugyldig."; + +"The selected brand is not allowed" = "Det valgte brand er ikke tilladt"; + +"To scan your card, allow camera access in Settings." = "Tillad kameraadgang i Indstillinger for at scanne dit kort."; + +"Your ZIP is invalid." = "Dit postnummer er ugyldigt."; + +"Your card has expired." = "Dit kort er udløbet."; + +"Your card number is incomplete." = "Dit kortnummer er ikke fuldendt."; + +"Your card number is invalid." = "Dit kortnummer er ugyldigt."; + +"Your card's expiration date is incomplete." = "Dit korts udløbsdato er ikke fuldendt."; + +"Your card's expiration date is invalid." = "Dit korts udløbsdato er ugyldig."; + +"Your card's expiration month is invalid." = "Kortets udløbsmåned er ugyldig."; + +"Your card's expiration year is invalid." = "Dit korts udløbsår er ugyldigt."; + +"Your card's security code is incomplete." = "Dit korts sikkerhedskode er ufuldstændig."; + +"Your card's security code is invalid." = "Dit korts sikkerhedskode er ugyldig."; + +"Your postal code is invalid." = "Dit postnummer er ikke fuldendt."; + +"card number" = "kortnummer"; + +"example@example.com" = "eksempel@eksempel.dk"; + +"expiration date" = "udløbsdato"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/de.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/de.lproj/Localizable.strings new file mode 100644 index 00000000..536c2648 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/de.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Bankkonto %1$@ endend auf %2$@"; + +"%1$@ ending in %2$@" = "%1$@, endend auf %2$@"; + +"%1$@ is not accepted" = "%1$@ wird nicht akzeptiert"; + +"Address line 2 (optional)" = "Adresszeile 2 (optional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankkonto"; + +"Billing address" = "Rechnungsadresse"; + +"CVC" = "CVC"; + +"Card information" = "Kartendaten"; + +"Card number" = "Kartennummer"; + +"Confirm your %@" = "%@ bestätigen"; + +"Invalid data." = "Ungültige Daten."; + +"MM / YY" = "MM/JJ"; + +"MM/YY" = "MM/JJ"; + +"Postal Code" = "Postleitzahl"; + +"Shipping Address" = "Versandadresse"; + +"State / Province / Region" = "Bundesland / Kanton / Region"; + +"The BSB you entered is invalid." = "Die eingegebene BSB-Nummer ist ungültig."; + +"The selected brand is not allowed" = "Die ausgewählte Marke ist nicht zulässig."; + +"To scan your card, allow camera access in Settings." = "Um die Karte scannen zu können, müssen Sie unter „Einstellungen“ den Kamerazugriff erlauben."; + +"Your ZIP is invalid." = "Postleitzahl ist ungültig."; + +"Your card has expired." = "Ihre Karte ist abgelaufen."; + +"Your card number is incomplete." = "Kartennummer ist unvollständig."; + +"Your card number is invalid." = "Kartennummer ist ungültig."; + +"Your card's expiration date is incomplete." = "Ablaufdatum der Karte ist unvollständig."; + +"Your card's expiration date is invalid." = "Ablaufdatum der Karte ist ungültig."; + +"Your card's expiration month is invalid." = "Der Ablaufmonat Ihrer Karte ist ungültig."; + +"Your card's expiration year is invalid." = "Das Ablaufjahr der Karte ist ungültig."; + +"Your card's security code is incomplete." = "Sicherheitscode der Karte ist unvollständig."; + +"Your card's security code is invalid." = "Sicherheitscode der Karte ist ungültig."; + +"Your postal code is invalid." = "Postleitzahl ist ungültig"; + +"card number" = "Kartennummer"; + +"example@example.com" = "beispiel@beispiel.com"; + +"expiration date" = "Gültig bis"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/el-GR.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..f5284b96 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/el-GR.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ τραπεζικός λογαριασμός που λήγει σε %2$@"; + +"%1$@ ending in %2$@" = "%1$@ λήγει σε %2$@"; + +"%1$@ is not accepted" = "Η επωνυμία %1$@ δεν είναι αποδεκτή"; + +"Address line 2 (optional)" = "Γραμμή διεύθυνσης 2 (προαιρετικό)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "Κωδικός BSB"; + +"Bank Account" = "Τραπεζικός λογαριασμός"; + +"Billing address" = "Διεύθυνση χρέωσης"; + +"CVC" = "CVC"; + +"Card information" = "Στοιχεία κάρτας"; + +"Card number" = "Αριθμός κάρτας"; + +"Confirm your %@" = "Επιβεβαιώστε τον κωδικό %@"; + +"Invalid data." = "Μη έγκυρα δεδομένα."; + +"MM / YY" = "ΜΜ / ΕΕ"; + +"MM/YY" = "ΜΜ/ΕΕ"; + +"Postal Code" = "Ταχυδρομικός κώδικας"; + +"Shipping Address" = "Διεύθυνση αποστολής"; + +"State / Province / Region" = "Πολιτεία / Επαρχία / Περιοχή"; + +"The BSB you entered is invalid." = "Ο κωδικός BSB που εισάγατε δεν είναι έγκυρος."; + +"The selected brand is not allowed" = "Η επιλεγμένη επωνυμία δεν επιτρέπεται"; + +"To scan your card, allow camera access in Settings." = "Για να σαρώσετε την κάρτα σας, επιτρέψτε την πρόσβαση της κάμερας από τις Ρυθμίσεις."; + +"Your ZIP is invalid." = "Ο ταχυδρομικός κώδικάς σας δεν είναι έγκυρος."; + +"Your card has expired." = "Η κάρτα σας έχει λήξει."; + +"Your card number is incomplete." = "Ο αριθμός της κάρτας σας είναι ελλιπής."; + +"Your card number is invalid." = "Ο αριθμός της κάρτας σας δεν είναι έγκυρος."; + +"Your card's expiration date is incomplete." = "Η ημερομηνία λήξης της κάρτας σας είναι ελλιπής."; + +"Your card's expiration date is invalid." = "Η ημερομηνία λήξης της κάρτας σας δεν είναι έγκυρη."; + +"Your card's expiration month is invalid." = "Ο μήνας λήξης της κάρτας σας δεν είναι έγκυρος."; + +"Your card's expiration year is invalid." = "Το έτος λήξης της κάρτας σας δεν είναι έγκυρο."; + +"Your card's security code is incomplete." = "Ο κωδικός ασφαλείας της κάρτας σας είναι ελλιπής."; + +"Your card's security code is invalid." = "Ο κωδικός ασφαλείας της κάρτας σας δεν είναι έγκυρος."; + +"Your postal code is invalid." = "Ο ταχυδρομικός κώδικάς σας δεν είναι έγκυρος."; + +"card number" = "αριθμός κάρτας"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "ημερομηνία λήξης"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/en-GB.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..c21d6cdb --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/en-GB.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ account ending in %2$@"; + +"%1$@ ending in %2$@" = "%1$@ ending in %2$@"; + +"%1$@ is not accepted" = "%1$@ is not accepted"; + +"Address line 2 (optional)" = "Address line 2 (optional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bank Account"; + +"Billing address" = "Billing address"; + +"CVC" = "CVC"; + +"Card information" = "Card information"; + +"Card number" = "Card number"; + +"Confirm your %@" = "Confirm your %@"; + +"Invalid data." = "Invalid data."; + +"MM / YY" = "MM / YY"; + +"MM/YY" = "MM/YY"; + +"Postal Code" = "Postcode"; + +"Shipping Address" = "Shipping Address"; + +"State / Province / Region" = "County / Region"; + +"The BSB you entered is invalid." = "The BSB you entered is invalid."; + +"The selected brand is not allowed" = "The selected brand is not allowed"; + +"To scan your card, allow camera access in Settings." = "To scan your card, allow camera access in Settings."; + +"Your ZIP is invalid." = "Your ZIP is invalid."; + +"Your card has expired." = "Your card has expired."; + +"Your card number is incomplete." = "Your card number is incomplete."; + +"Your card number is invalid." = "Your card number is invalid."; + +"Your card's expiration date is incomplete." = "Your card's expiration date is incomplete."; + +"Your card's expiration date is invalid." = "Your card's expiration date is invalid."; + +"Your card's expiration month is invalid." = "Your card's expiration month is invalid."; + +"Your card's expiration year is invalid." = "Your card's expiration year is invalid."; + +"Your card's security code is incomplete." = "Your card's security code is incomplete."; + +"Your card's security code is invalid." = "Your card's security code is invalid."; + +"Your postal code is invalid." = "Your postcode is invalid."; + +"card number" = "card number"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "expiry date"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/en.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..9109e3fa --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/en.lproj/Localizable.strings @@ -0,0 +1,105 @@ +/* Details of a saved bank account. '{bank name} account ending in {last 4}' e.g. 'Chase account ending in 4242' */ +"%1$@ account ending in %2$@" = "%1$@ account ending in %2$@"; + +/* Details of a saved card. '{card brand} ending in {last 4}' e.g. 'VISA ending in 4242' */ +"%1$@ ending in %2$@" = "%1$@ ending in %2$@"; + +/* String to inform a user that specific card brands are not accepted. E.g. American Express is not accepted */ +"%1$@ is not accepted" = "%1$@ is not accepted"; + +/* Address line 2 placeholder for billing address form. */ +"Address line 2 (optional)" = "Address line 2 (optional)"; + +/* Text for Apple Pay payment method */ +"Apple Pay" = "Apple Pay"; + +/* Label for Bank Account selection or detail entry form */ +"Bank Account" = "Bank Account"; + +/* Billing address section title for card form entry. */ +"Billing address" = "Billing address"; + +/* Placeholder text for BSB Number entry field for BECS Debit. */ +"BSB" = "BSB"; + +/* Card details entry form header title */ +"Card information" = "Card information"; + +/* accessibility label for text field */ +"card number" = "card number"; + +/* Label for card number entry text field */ +"Card number" = "Card number"; + +/* Section title for entering your CVC. e.g. 'Confirm your CVC' */ +"Confirm your %@" = "Confirm your %@"; + +/* Label for entering CVC in text field */ +"CVC" = "CVC"; + +/* Placeholder string for email entry field. */ +"example@example.com" = "example@example.com"; + +/* accessibility label for text field */ +"expiration date" = "expiration date"; + +/* Spoken during VoiceOver when a form field has failed validation. */ +"Invalid data." = "Invalid data."; + +/* label for text field to enter card expiry */ +"MM / YY" = "MM / YY"; + +/* label for text field to enter card expiry */ +"MM/YY" = "MM/YY"; + +/* Postal code placeholder */ +"Postal Code" = "Postal Code"; + +/* Title for shipping address entry section */ +"Shipping Address" = "Shipping Address"; + +/* Caption for generalized state/province/region field on address form (not tied to a specific country's format) */ +"State / Province / Region" = "State / Province / Region"; + +/* Error string displayed to user when they enter in an invalid BSB number. */ +"The BSB you entered is invalid." = "The BSB you entered is invalid."; + +/* String to inform a user that specific card brands are not accepted. */ +"The selected brand is not allowed" = "The selected brand is not allowed"; + +/* Error when the user hasn't allowed the current app to access the camera when scanning a payment card. 'Settings' is the localized name of the iOS Settings app. */ +"To scan your card, allow camera access in Settings." = "To scan your card, allow camera access in Settings."; + +/* Error message for card details form when expiration date has passed */ +"Your card has expired." = "Your card has expired."; + +/* Error message for card form when card number is incomplete */ +"Your card number is incomplete." = "Your card number is incomplete."; + +/* Error message for card form when card number is invalid */ +"Your card number is invalid." = "Your card number is invalid."; + +/* Error message for card details form when expiration date isn't entered completely */ +"Your card's expiration date is incomplete." = "Your card's expiration date is incomplete."; + +/* Error message for card details form when expiration date is invalid */ +"Your card's expiration date is invalid." = "Your card's expiration date is invalid."; + +/* String to describe an invalid month in expiry date. */ +"Your card's expiration month is invalid." = "Your card's expiration month is invalid."; + +/* String to describe an invalid year in expiry date. */ +"Your card's expiration year is invalid." = "Your card's expiration year is invalid."; + +/* Error message for card entry form when CVC is incomplete. */ +"Your card's security code is incomplete." = "Your card's security code is incomplete."; + +/* Error message for card entry form when CVC is invalid */ +"Your card's security code is invalid." = "Your card's security code is invalid."; + +/* Error message for when postal code in form is invalid */ +"Your postal code is invalid." = "Your postal code is invalid."; + +/* Error message for when postal code in form is invalid (US only) */ +"Your ZIP is invalid." = "Your ZIP is invalid."; + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/es-419.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..185fed5c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/es-419.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Cuenta de %1$@ terminada en %2$@"; + +"%1$@ ending in %2$@" = "%1$@ terminada en %2$@"; + +"%1$@ is not accepted" = "No se acepta %1$@"; + +"Address line 2 (optional)" = "Segunda línea de la dirección (opcional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Cuenta bancaria"; + +"Billing address" = "Dirección de facturación"; + +"CVC" = "CVC"; + +"Card information" = "Información de la tarjeta"; + +"Card number" = "Número de tarjeta"; + +"Confirm your %@" = "Confirma tu %@"; + +"Invalid data." = "Datos no válidos."; + +"MM / YY" = "MM/AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "Código postal"; + +"Shipping Address" = "Dirección de envío"; + +"State / Province / Region" = "Estado/Provincia/Región"; + +"The BSB you entered is invalid." = "El BSB que ingresaste no es válido."; + +"The selected brand is not allowed" = "La marca seleccionada no está permitida"; + +"To scan your card, allow camera access in Settings." = "Para escanear la tarjeta, permite el acceso a la cámara en Configuración."; + +"Your ZIP is invalid." = "El código postal no es válido."; + +"Your card has expired." = "Tu tarjeta venció."; + +"Your card number is incomplete." = "El número de tarjeta está incompleto."; + +"Your card number is invalid." = "El número de tarjeta no es válido."; + +"Your card's expiration date is incomplete." = "La fecha de vencimiento de la tarjeta está incompleta."; + +"Your card's expiration date is invalid." = "La fecha de vencimiento de la tarjeta no es válida."; + +"Your card's expiration month is invalid." = "El mes de vencimiento de la tarjeta no es válido."; + +"Your card's expiration year is invalid." = "El año de vencimiento de la tarjeta no es válido."; + +"Your card's security code is incomplete." = "El código de seguridad de la tarjeta está incompleto."; + +"Your card's security code is invalid." = "El código de seguridad de la tarjeta no es válido."; + +"Your postal code is invalid." = "El código postal no es válido."; + +"card number" = "número de tarjeta"; + +"example@example.com" = "ejemplo@ejemplo.com"; + +"expiration date" = "fecha de vencimiento"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/es.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/es.lproj/Localizable.strings new file mode 100644 index 00000000..4773808b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/es.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Cuenta de %1$@ terminada en %2$@"; + +"%1$@ ending in %2$@" = "%1$@ terminada en %2$@"; + +"%1$@ is not accepted" = "No se acepta %1$@"; + +"Address line 2 (optional)" = "Segunda línea de la dirección (opcional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Cuenta bancaria"; + +"Billing address" = "Dirección de facturación"; + +"CVC" = "CVC"; + +"Card information" = "Información de la tarjeta"; + +"Card number" = "Número de tarjeta"; + +"Confirm your %@" = "Confirma tu %@"; + +"Invalid data." = "Datos no válidos."; + +"MM / YY" = "MM / AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "Código postal"; + +"Shipping Address" = "Dirección de envío"; + +"State / Province / Region" = "Estado, provincia o región"; + +"The BSB you entered is invalid." = "El BSB introducido no es válido."; + +"The selected brand is not allowed" = "La marca seleccionada no está permitida"; + +"To scan your card, allow camera access in Settings." = "Para escanear la tarjeta, autoriza el acceso a la cámara en Ajustes."; + +"Your ZIP is invalid." = "El código ZIP no es válido."; + +"Your card has expired." = "Tu tarjeta ha caducado."; + +"Your card number is incomplete." = "El número de la tarjeta está incompleto."; + +"Your card number is invalid." = "El número de la tarjeta no es válido."; + +"Your card's expiration date is incomplete." = "La fecha de caducidad de la tarjeta está incompleta."; + +"Your card's expiration date is invalid." = "La fecha de caducidad de la tarjeta no es válida."; + +"Your card's expiration month is invalid." = "El mes de caducidad de la tarjeta no es válido."; + +"Your card's expiration year is invalid." = "El año de caducidad de la tarjeta no es válido."; + +"Your card's security code is incomplete." = "El código de seguridad de la tarjeta está incompleto."; + +"Your card's security code is invalid." = "El código de seguridad de la tarjeta no es válido."; + +"Your postal code is invalid." = "El código postal no es válido."; + +"card number" = "número de tarjeta"; + +"example@example.com" = "ejemplo@ejemplo.com"; + +"expiration date" = "fecha de caducidad"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/et-EE.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..dd12eb8a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/et-EE.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ konto lõpuga %2$@"; + +"%1$@ ending in %2$@" = "%1$@, lõpeb nr-ga %2$@"; + +"%1$@ is not accepted" = "%1$@ pole aktsepteeritud"; + +"Address line 2 (optional)" = "Aadressirida 2 (valikuline)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Pangakonto"; + +"Billing address" = "Arveldusaadress"; + +"CVC" = "CVC"; + +"Card information" = "Kaardi andmed"; + +"Card number" = "Kaardinumber"; + +"Confirm your %@" = "Kinnitage oma %@"; + +"Invalid data." = "Valed andmed."; + +"MM / YY" = "KK / AA"; + +"MM/YY" = "KK/AA"; + +"Postal Code" = "Sihtnumber"; + +"Shipping Address" = "Tarneaadress"; + +"State / Province / Region" = "Maakond"; + +"The BSB you entered is invalid." = "Sisestatud BSB-number ei ole kehtiv."; + +"The selected brand is not allowed" = "Valitud tootemark pole lubatud"; + +"To scan your card, allow camera access in Settings." = "Kaardi skannimiseks lubage seadetes juurdepääs kaamerale."; + +"Your ZIP is invalid." = "Teie sihtnumber on kehtetu."; + +"Your card has expired." = "Teie kaart on aegunud."; + +"Your card number is incomplete." = "Teie kaardinumber on puudulik."; + +"Your card number is invalid." = "Teie kaardinumber on kehtetu."; + +"Your card's expiration date is incomplete." = "Kaardi aegumiskuupäev on puudulik."; + +"Your card's expiration date is invalid." = "Teie kaardi aegumiskuupäev on kehtetu."; + +"Your card's expiration month is invalid." = "Kaardi aegumiskuu on kehtetu."; + +"Your card's expiration year is invalid." = "Kaardi aegumisaasta on kehtetu."; + +"Your card's security code is incomplete." = "Teie kaardi turvakood on puudulik."; + +"Your card's security code is invalid." = "Kaardi turvakood on kehtetu."; + +"Your postal code is invalid." = "Teie sihtnumber on kehtetu."; + +"card number" = "kaardinumber"; + +"example@example.com" = "näide@näide.com"; + +"expiration date" = "aegumiskuupäev"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fi.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fi.lproj/Localizable.strings new file mode 100644 index 00000000..7aab97fc --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fi.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ -tili päättyy numeroihin %2$@"; + +"%1$@ ending in %2$@" = "%1$@ päättyy numeroihin %2$@"; + +"%1$@ is not accepted" = "%1$@ ei hyväksytä"; + +"Address line 2 (optional)" = "Osoiterivi 2 (valinnainen)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Pankkitili"; + +"Billing address" = "Laskutusosoite"; + +"CVC" = "CVC-tunnus"; + +"Card information" = "Kortin tiedot"; + +"Card number" = "Kortin numero"; + +"Confirm your %@" = "Vahvista %@"; + +"Invalid data." = "Virheelliset tiedot."; + +"MM / YY" = "KK/VV"; + +"MM/YY" = "KK/VV"; + +"Postal Code" = "Postinumero"; + +"Shipping Address" = "Lähetysosoite"; + +"State / Province / Region" = "Osavaltio/maakunta/alue"; + +"The BSB you entered is invalid." = "Antamasi BSB-numero on virheellinen."; + +"The selected brand is not allowed" = "Valittu merkki ei ole sallittu"; + +"To scan your card, allow camera access in Settings." = "Anna kameralle käyttöoikeus, jotta voit skannata kortin."; + +"Your ZIP is invalid." = "Postinumero on virheellinen."; + +"Your card has expired." = "Korttisi on vanhentunut."; + +"Your card number is incomplete." = "Kortin numero on puutteellinen."; + +"Your card number is invalid." = "Kortin numero on virheellinen."; + +"Your card's expiration date is incomplete." = "Kortin voimassaoloaika on puutteellinen."; + +"Your card's expiration date is invalid." = "Kortin voimassaoloaika on virheellinen."; + +"Your card's expiration month is invalid." = "Kortin erääntymiskuukausi ei kelpaa."; + +"Your card's expiration year is invalid." = "Kortin voimassaolovuosi ei kelpaa."; + +"Your card's security code is incomplete." = "Kortin turvakoodi on puutteellinen."; + +"Your card's security code is invalid." = "Kortin turvakoodi on virheellinen."; + +"Your postal code is invalid." = "Postinumeroon virheellinen."; + +"card number" = "kortin numero"; + +"example@example.com" = "esimerkki@esimerkki.com"; + +"expiration date" = "erääntymispäivä"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fil.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fil.lproj/Localizable.strings new file mode 100644 index 00000000..b68e5381 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fil.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ account na nagtatapos sa %2$@"; + +"%1$@ ending in %2$@" = "%1$@ nagtatapos sa %2$@"; + +"%1$@ is not accepted" = "Ang %1$@ ay hindi tinatanggap"; + +"Address line 2 (optional)" = "Pangalawang linya ng adres"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Account sa Bangko"; + +"Billing address" = "Adres para sa billing"; + +"CVC" = "CVC"; + +"Card information" = "Impormasyon ng kard"; + +"Card number" = "Numero ng kard"; + +"Confirm your %@" = "Kumpirmahin ang iyong %@"; + +"Invalid data." = "Data na di balido."; + +"MM / YY" = "BB / TT"; + +"MM/YY" = "BB / TT"; + +"Postal Code" = "Postal Code"; + +"Shipping Address" = "Adres ng Nagpadala"; + +"State / Province / Region" = "State / Probinsiya / Rehiyon"; + +"The BSB you entered is invalid." = "Ang BSB na ipinasok mo ay di balido."; + +"The selected brand is not allowed" = "Ang napiling brand ay hindi pinapayagan"; + +"To scan your card, allow camera access in Settings." = "Upang ma-scan ang iyong kard, pahintulutan ang access ng kamera sa Settings."; + +"Your ZIP is invalid." = "Ang iyong ZIP ay di balido."; + +"Your card has expired." = "Napaso na ang iyong kard."; + +"Your card number is incomplete." = "Ang numero ng iyong kard ay di kumpleto."; + +"Your card number is invalid." = "Ang numero ng iyong kard ay di balido."; + +"Your card's expiration date is incomplete." = "Ang petsa ng pagkapaso ng iyong kard ay di kumpleto."; + +"Your card's expiration date is invalid." = "Ang petsa ng pagkapaso ng iyong kard ay di balido."; + +"Your card's expiration month is invalid." = "Ang buwan ng pagkapaso ng iyong kard ay di balido."; + +"Your card's expiration year is invalid." = "Ang taon ng pagkapaso ng iyong kard ay di balido."; + +"Your card's security code is incomplete." = "Ang security code ng iyong kard ay di kumpleto."; + +"Your card's security code is invalid." = "Ang security code ng iyong kard ay di balido."; + +"Your postal code is invalid." = "Ang iyong postal code ay di balido."; + +"card number" = "numero ng kard"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "Petsa ng pagkapaso"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fr-CA.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..f6a9ad1b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fr-CA.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "compte %1$@ se terminant par %2$@"; + +"%1$@ ending in %2$@" = "%1$@ se terminant par %2$@"; + +"%1$@ is not accepted" = "%1$@ n'est pas accepté"; + +"Address line 2 (optional)" = "Ligne d'adresse 2 (facultatif)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Compte bancaire"; + +"Billing address" = "Adresse de facturation"; + +"CVC" = "CVC"; + +"Card information" = "Informations de la carte"; + +"Card number" = "Numéro de carte"; + +"Confirm your %@" = "Confirmez votre %@"; + +"Invalid data." = "Donnée non valide."; + +"MM / YY" = "MM/AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "Code postal"; + +"Shipping Address" = "Adresse de livraison"; + +"State / Province / Region" = "État/Province/Région"; + +"The BSB you entered is invalid." = "Le numéro BSB que vous avez saisi n'est pas valide."; + +"The selected brand is not allowed" = "La marque sélectionnée n'est pas autorisée"; + +"To scan your card, allow camera access in Settings." = "Pour scanner votre carte, autorisez l'accès à l'appareil photo dans vos paramètres."; + +"Your ZIP is invalid." = "Votre code postal n'est pas valide."; + +"Your card has expired." = "Votre carte a expiré."; + +"Your card number is incomplete." = "Votre numéro de carte est incomplet."; + +"Your card number is invalid." = "Votre numéro de carte n'est pas valide."; + +"Your card's expiration date is incomplete." = "La date d'expiration de votre carte est incomplète."; + +"Your card's expiration date is invalid." = "La date d'expiration de votre carte n'est pas valide."; + +"Your card's expiration month is invalid." = "Le mois d'expiration de votre carte n'est pas valide."; + +"Your card's expiration year is invalid." = "L'année d'expiration de votre carte n'est pas valide."; + +"Your card's security code is incomplete." = "Le code de sécurité de votre carte est incomplet."; + +"Your card's security code is invalid." = "Le code de sécurité de votre carte n'est pas valide."; + +"Your postal code is invalid." = "Votre code postal n'est pas valide."; + +"card number" = "numéro de carte"; + +"example@example.com" = "exemple@exemple.com"; + +"expiration date" = "date d'expiration"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fr.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fr.lproj/Localizable.strings new file mode 100644 index 00000000..d0c73563 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/fr.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "compte %1$@ se terminant par %2$@"; + +"%1$@ ending in %2$@" = "%1$@ se terminant par %2$@"; + +"%1$@ is not accepted" = "%1$@ n'est pas accepté"; + +"Address line 2 (optional)" = "Adresse - Ligne 2 (facultatif)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Compte bancaire"; + +"Billing address" = "Adresse de facturation"; + +"CVC" = "Code CVC"; + +"Card information" = "Informations concernant la carte bancaire"; + +"Card number" = "Numéro de carte bancaire"; + +"Confirm your %@" = "Confirmez votre %@"; + +"Invalid data." = "Donnée non valide."; + +"MM / YY" = "MM / AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "Code postal"; + +"Shipping Address" = "Adresse de livraison"; + +"State / Province / Region" = "État/Province/Région"; + +"The BSB you entered is invalid." = "Le numéro BSB que vous avez saisi n'est pas valide."; + +"The selected brand is not allowed" = "La marque sélectionnée n'est pas autorisée"; + +"To scan your card, allow camera access in Settings." = "Pour numériser votre carte bancaire, autorisez l'accès à l'appareil photo dans vos paramètres."; + +"Your ZIP is invalid." = "Votre code postal n'est pas valide."; + +"Your card has expired." = "Votre carte a expiré."; + +"Your card number is incomplete." = "Votre numéro de carte bancaire est incomplet."; + +"Your card number is invalid." = "Votre numéro de carte bancaire n'est pas valide."; + +"Your card's expiration date is incomplete." = "La date d'expiration de votre carte bancaire est incomplète."; + +"Your card's expiration date is invalid." = "La date d'expiration de votre carte bancaire n'est pas valide."; + +"Your card's expiration month is invalid." = "Le mois d'expiration de votre carte n'est pas valide."; + +"Your card's expiration year is invalid." = "L'année d'expiration de votre carte n'est pas valide."; + +"Your card's security code is incomplete." = "Le code de sécurité de votre carte bancaire est incomplet."; + +"Your card's security code is invalid." = "Le code de sécurité de votre carte bancaire n'est pas valide."; + +"Your postal code is invalid." = "Votre code postal n'est pas valide."; + +"card number" = "numéro de carte"; + +"example@example.com" = "exemple@exemple.fr"; + +"expiration date" = "date d'expiration"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/hr.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/hr.lproj/Localizable.strings new file mode 100644 index 00000000..1df715cc --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/hr.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ račun koji završava s %2$@"; + +"%1$@ ending in %2$@" = "%1$@ završava na %2$@"; + +"%1$@ is not accepted" = "%1$@ nije prihvaćeno"; + +"Address line 2 (optional)" = "Ulica i kućni broj 2 (neobavezno)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankovni račun"; + +"Billing address" = "Adresa za naplatu"; + +"CVC" = "CVC"; + +"Card information" = "Informacije o kartici"; + +"Card number" = "Broj kartice"; + +"Confirm your %@" = "Potvrdite svoj %@"; + +"Invalid data." = "Nevaljani podaci."; + +"MM / YY" = "MM / GG"; + +"MM/YY" = "MM/GG"; + +"Postal Code" = "Poštanski broj"; + +"Shipping Address" = "Adresa za otpremu"; + +"State / Province / Region" = "Država / pokrajina / regija"; + +"The BSB you entered is invalid." = "Uneseni BSB nije valjan."; + +"The selected brand is not allowed" = "Odabrana marka kartice nije dozvoljena"; + +"To scan your card, allow camera access in Settings." = "Da biste skenirali karticu, dopustite pristup kameri u Postavkama."; + +"Your ZIP is invalid." = "Vaš ZIP broj nije valjan."; + +"Your card has expired." = "Vaša kartica je istekla."; + +"Your card number is incomplete." = "Broj vaše kartice nije kompletan."; + +"Your card number is invalid." = "Broj vaše kartice nije valjan."; + +"Your card's expiration date is incomplete." = "Datum isteka vaše kartice nije kompletan."; + +"Your card's expiration date is invalid." = "Datum isteka vaše kartice nije valjan."; + +"Your card's expiration month is invalid." = "Mjesec isteka kartice nije valjan."; + +"Your card's expiration year is invalid." = "Godina isteka kartice nije valjana."; + +"Your card's security code is incomplete." = "Sigurnosni kod vaše kartice nije kompletan."; + +"Your card's security code is invalid." = "Sigurnosni kod vaše kartice nije valjan."; + +"Your postal code is invalid." = "Vaš poštanski broj nije valjan."; + +"card number" = "broj kartice"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "Datum isteka"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/hu.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/hu.lproj/Localizable.strings new file mode 100644 index 00000000..8f28740b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/hu.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%2$@ végződésű számla %1$@"; + +"%1$@ ending in %2$@" = "%1$@ végződése %2$@"; + +"%1$@ is not accepted" = "%1$@ nem elfogadott"; + +"Address line 2 (optional)" = "Cím 2. sora (nem kötelező)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankszámla"; + +"Billing address" = "Számlázási cím"; + +"CVC" = "CVC"; + +"Card information" = "Kártyaadatok"; + +"Card number" = "Kártyaszám"; + +"Confirm your %@" = "%@ megerősítése"; + +"Invalid data." = "Érvénytelen adat."; + +"MM / YY" = "HH / ÉÉ"; + +"MM/YY" = "HH/ÉÉ"; + +"Postal Code" = "Postai irányítószám"; + +"Shipping Address" = "Szállítási cím"; + +"State / Province / Region" = "Állam/tartomány/régió"; + +"The BSB you entered is invalid." = "A beírt BSB-szám érvénytelen."; + +"The selected brand is not allowed" = "A kiválasztott kártyamárka nem engedélyezett"; + +"To scan your card, allow camera access in Settings." = "A kártya beolvasásához engedélyezze a kamerához való hozzáférést a Beállításokban."; + +"Your ZIP is invalid." = "A ZIP érvénytelen."; + +"Your card has expired." = "A kártyája lejárt."; + +"Your card number is incomplete." = "A kártyaszám hiányos."; + +"Your card number is invalid." = "A kártyaszám érvénytelen."; + +"Your card's expiration date is incomplete." = "A kártya lejárati dátuma hiányos."; + +"Your card's expiration date is invalid." = "A kártya lejárati dátuma érvénytelen."; + +"Your card's expiration month is invalid." = "Kártyájának lejárati hónapja érvénytelen."; + +"Your card's expiration year is invalid." = "Kártyájának lejárati éve érvénytelen."; + +"Your card's security code is incomplete." = "A kártya biztonsági kódja hiányos."; + +"Your card's security code is invalid." = "A kártya biztonsági kódja érvénytelen."; + +"Your postal code is invalid." = "Az irányítószám érvénytelen."; + +"card number" = "kártyaszám"; + +"example@example.com" = "valaki@example.com"; + +"expiration date" = "lejárati dátum"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/id.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/id.lproj/Localizable.strings new file mode 100644 index 00000000..3265229e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/id.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Rekening %1$@ berakhiran dengan %2$@"; + +"%1$@ ending in %2$@" = "%1$@ berakhiran %2$@"; + +"%1$@ is not accepted" = "%1$@ tidak diterima"; + +"Address line 2 (optional)" = "Baris alamat ke-2 (opsional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Rekening bank"; + +"Billing address" = "Alamat tagihan"; + +"CVC" = "CVC"; + +"Card information" = "Informasi kartu"; + +"Card number" = "Nomor kartu"; + +"Confirm your %@" = "Konfirmasikan %@ Anda"; + +"Invalid data." = "Data tidak valid."; + +"MM / YY" = "BB / TT"; + +"MM/YY" = "BB/TT"; + +"Postal Code" = "Kode Pos"; + +"Shipping Address" = "Alamat Pengiriman"; + +"State / Province / Region" = "Negara Bagian / Provinsi / Wilayah"; + +"The BSB you entered is invalid." = "BSB yang Anda masukkan tidak valid."; + +"The selected brand is not allowed" = "Brand yang dipilih tidak diizinkan"; + +"To scan your card, allow camera access in Settings." = "Untuk memindai kartu Anda, izinkan akses kamera di Pengaturan."; + +"Your ZIP is invalid." = "ZIP Anda tidak valid."; + +"Your card has expired." = "Kartu Anda telah kedaluwarsa."; + +"Your card number is incomplete." = "Nomor kartu Anda tidak lengkap."; + +"Your card number is invalid." = "Nomor kartu Anda tidak valid."; + +"Your card's expiration date is incomplete." = "Tanggal kedaluwarsa kartu Anda tidak lengkap."; + +"Your card's expiration date is invalid." = "Tanggal kedaluwarsa kartu Anda tidak valid."; + +"Your card's expiration month is invalid." = "Bulan kedaluwarsa kartu Anda tidak valid."; + +"Your card's expiration year is invalid." = "Tahun kedaluwarsa kartu Anda tidak valid."; + +"Your card's security code is incomplete." = "Kode keamanan kartu Anda tidak lengkap."; + +"Your card's security code is invalid." = "Kode keamanan kartu Anda tidak valid."; + +"Your postal code is invalid." = "Kode pos Anda tidak valid."; + +"card number" = "nomor kartu"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "Tanggal kedaluwarsa"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/it.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/it.lproj/Localizable.strings new file mode 100644 index 00000000..5c30aace --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/it.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Conto %1$@ che termina con %2$@"; + +"%1$@ ending in %2$@" = "%1$@ che termina con %2$@"; + +"%1$@ is not accepted" = "Il brand %1$@ non è stato accettato"; + +"Address line 2 (optional)" = "Indirizzo riga 2 (facoltativo)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Conto bancario"; + +"Billing address" = "Indirizzo di fatturazione"; + +"CVC" = "CVC"; + +"Card information" = "Dati della carta"; + +"Card number" = "Numero carta"; + +"Confirm your %@" = "Conferma il tuo %@"; + +"Invalid data." = "Dati non validi."; + +"MM / YY" = "MM / AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "Codice postale"; + +"Shipping Address" = "Indirizzo di spedizione"; + +"State / Province / Region" = "Stato/Provincia/Regione"; + +"The BSB you entered is invalid." = "Il numero BSB inserito non è valido"; + +"The selected brand is not allowed" = "Il brand selezionato non è disponibile."; + +"To scan your card, allow camera access in Settings." = "Per scansionare la carta, concedi l'accesso alla fotocamera in Impostazioni."; + +"Your ZIP is invalid." = "Il CAP non è valido."; + +"Your card has expired." = "La tua carta è scaduta."; + +"Your card number is incomplete." = "Il numero della carta è incompleto."; + +"Your card number is invalid." = "Il numero della carta non è valido."; + +"Your card's expiration date is incomplete." = "La data di scadenza della carta è incompleta."; + +"Your card's expiration date is invalid." = "La data di scadenza della carta non è valida."; + +"Your card's expiration month is invalid." = "Il mese di scadenza della carta non è valido."; + +"Your card's expiration year is invalid." = "L'anno di scadenza della carta non è valido."; + +"Your card's security code is incomplete." = "Il codice di sicurezza della carta è incompleto."; + +"Your card's security code is invalid." = "Il codice di sicurezza della carta non è valido."; + +"Your postal code is invalid." = "Il codice postale non è valido."; + +"card number" = "numero carta"; + +"example@example.com" = "esempio@esempio.com"; + +"expiration date" = "data di scadenza"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ja.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ja.lproj/Localizable.strings new file mode 100644 index 00000000..f875722f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ja.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "末尾が %2$@ の %1$@ 口座"; + +"%1$@ ending in %2$@" = "末尾が %2$@ の %1$@"; + +"%1$@ is not accepted" = "%1$@ は受け付けられていません"; + +"Address line 2 (optional)" = "住所 (2 行目、省略可)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "銀行口座"; + +"Billing address" = "請求先住所"; + +"CVC" = "セキュリティコード"; + +"Card information" = "カード情報"; + +"Card number" = "カード番号"; + +"Confirm your %@" = "%@ を確認してください"; + +"Invalid data." = "データが無効です。"; + +"MM / YY" = "月 / 年"; + +"MM/YY" = "MM/YY"; + +"Postal Code" = "郵便番号"; + +"Shipping Address" = "配送先住所"; + +"State / Province / Region" = "都道府県"; + +"The BSB you entered is invalid." = "入力した BSB コードが無効です。"; + +"The selected brand is not allowed" = "選択したブランドは許可されていません"; + +"To scan your card, allow camera access in Settings." = "カードをスキャンするには、設定でカメラへのアクセスを許可してください。"; + +"Your ZIP is invalid." = "郵便番号が無効です。"; + +"Your card has expired." = "カードの有効期限が切れています。"; + +"Your card number is incomplete." = "カード番号に不備があります。"; + +"Your card number is invalid." = "カード番号が無効です。"; + +"Your card's expiration date is incomplete." = "カードの有効期限の日付に不備があります。"; + +"Your card's expiration date is invalid." = "カードの有効期限の日付が無効です。"; + +"Your card's expiration month is invalid." = "カードの有効期限の月が無効です。"; + +"Your card's expiration year is invalid." = "カードの有効期限の年が無効です。"; + +"Your card's security code is incomplete." = "カードのセキュリティコードに不備があります。"; + +"Your card's security code is invalid." = "カードのセキュリティコードが無効です。"; + +"Your postal code is invalid." = "郵便番号が無効です"; + +"card number" = "カード番号"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "有効期限"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ko.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ko.lproj/Localizable.strings new file mode 100644 index 00000000..6e5d2142 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ko.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%2$@(으)로 끝나는 %1$@ 계좌"; + +"%1$@ ending in %2$@" = "%2$@(으)로 끝나는 %1$@"; + +"%1$@ is not accepted" = "%1$@이(가) 허용되지 않습니다."; + +"Address line 2 (optional)" = "주소란 2(선택 사항)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "은행 계좌"; + +"Billing address" = "청구 주소"; + +"CVC" = "CVC"; + +"Card information" = "카드 정보"; + +"Card number" = "카드 번호"; + +"Confirm your %@" = "%@ 확인"; + +"Invalid data." = "잘못된 데이터입니다."; + +"MM / YY" = "MM/YY"; + +"MM/YY" = "MM/YY"; + +"Postal Code" = "우편번호"; + +"Shipping Address" = "배송 주소"; + +"State / Province / Region" = "주 / 도 / 지역"; + +"The BSB you entered is invalid." = "입력하신 BSB가 유효하지 않습니다."; + +"The selected brand is not allowed" = "선택한 브랜드는 허용되지 않습니다."; + +"To scan your card, allow camera access in Settings." = "카드를 스캔하려면 설정에서 카메라 액세스를 허용하세요."; + +"Your ZIP is invalid." = "우편번호가 유효하지 않습니다."; + +"Your card has expired." = "카드가 만료되었습니다."; + +"Your card number is incomplete." = "카드 번호가 불완전합니다."; + +"Your card number is invalid." = "카드 번호가 유효하지 않습니다."; + +"Your card's expiration date is incomplete." = "카드의 만료 날짜가 불완전합니다."; + +"Your card's expiration date is invalid." = "카드의 만료 날짜가 유효하지 않습니다."; + +"Your card's expiration month is invalid." = "카드의 만료 월이 유효하지 않습니다."; + +"Your card's expiration year is invalid." = "카드의 만료 연도가 유효하지 않습니다."; + +"Your card's security code is incomplete." = "카드의 보안 코드가 불완전합니다."; + +"Your card's security code is invalid." = "카드의 보안 코드가 유효하지 않습니다."; + +"Your postal code is invalid." = "우편번호가 유효하지 않습니다."; + +"card number" = "카드 번호"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "만료 날짜"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/lt-LT.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..034429d5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/lt-LT.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ sąskaita, kurios paskutiniai skaičiai %2$@"; + +"%1$@ ending in %2$@" = "%1$@, kuri baigiasi %2$@"; + +"%1$@ is not accepted" = "„%1$@“ nepriimamas"; + +"Address line 2 (optional)" = "2 adreso eilutė (pasirenkama)"; + +"Apple Pay" = "„Apple Pay“"; + +"BSB" = "BSB"; + +"Bank Account" = "Banko sąskaita"; + +"Billing address" = "Atsiskaitymo adresas"; + +"CVC" = "CVC"; + +"Card information" = "Kortelės duomenys"; + +"Card number" = "Kortelės numeris"; + +"Confirm your %@" = "Patvirtinkite savo %@"; + +"Invalid data." = "Netinkami duomenys."; + +"MM / YY" = "mm / MM"; + +"MM/YY" = "mm / MM"; + +"Postal Code" = "Pašto kodas"; + +"Shipping Address" = "Siuntimo adresas"; + +"State / Province / Region" = "Valstija / provincija / regionas"; + +"The BSB you entered is invalid." = "Įvestas netinkamas BSB numeris."; + +"The selected brand is not allowed" = "Pasirinktas prekės ženklas neleidžiamas"; + +"To scan your card, allow camera access in Settings." = "Kad nuskaitytumėte kortelę, nustatymuose suteikite prieigą fotoaparatui."; + +"Your ZIP is invalid." = "Jūsų pašto kodas yra netinkamas."; + +"Your card has expired." = "Jūsų kortelė baigė galioti."; + +"Your card number is incomplete." = "Neišsamus kortelės numeris."; + +"Your card number is invalid." = "Netinkamas kortelės numeris."; + +"Your card's expiration date is incomplete." = "Neišsami kortelės galiojimo pabaigos data."; + +"Your card's expiration date is invalid." = "Netinkama kortelės galiojimo pabaigos data."; + +"Your card's expiration month is invalid." = "Negaliojantis kortelės galiojimo pabaigos mėnuo."; + +"Your card's expiration year is invalid." = "Kortelės galiojimo pabaigos metai neteisingi."; + +"Your card's security code is incomplete." = "Neišsamus kortelės saugos kodas."; + +"Your card's security code is invalid." = "Kortelės saugos kodas negalioja."; + +"Your postal code is invalid." = "Jūsų pašto kodas yra netinkamas."; + +"card number" = "kortelės numeris"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "galiojimo pabaigos data"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/lv-LV.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..aa9c1ccd --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/lv-LV.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@konts, kas beidzas ar%2$@"; + +"%1$@ ending in %2$@" = "%1$@, kuras numura pēdējie cipari ir %2$@"; + +"%1$@ is not accepted" = "%1$@ netiek pieņemts"; + +"Address line 2 (optional)" = "2. adreses rindiņa (nav obligāti)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankas konts"; + +"Billing address" = "Norēķinu adrese"; + +"CVC" = "CVC"; + +"Card information" = "Kartes informācija"; + +"Card number" = "Kartes numurs"; + +"Confirm your %@" = "Apstipriniet savu %@"; + +"Invalid data." = "Nederīgi dati."; + +"MM / YY" = "MM/GG"; + +"MM/YY" = "MM/GG"; + +"Postal Code" = "Pasta indekss"; + +"Shipping Address" = "Piegādes adrese"; + +"State / Province / Region" = "Novads/štats/province/reģions"; + +"The BSB you entered is invalid." = "Ievadītais BSB nav derīgs."; + +"The selected brand is not allowed" = "Atlasītais zīmols nav atļauts"; + +"To scan your card, allow camera access in Settings." = "Lai noskenētu karti, Iestatījumos atļaujiet piekļuvi kamerai."; + +"Your ZIP is invalid." = "Pasta indekss nav derīgs."; + +"Your card has expired." = "Kartes derīguma termiņš ir beidzies."; + +"Your card number is incomplete." = "Kartes numurs nav pilnīgs."; + +"Your card number is invalid." = "Kartes numurs nav derīgs."; + +"Your card's expiration date is incomplete." = "Kartes derīguma termiņa datums nav pilnīgs."; + +"Your card's expiration date is invalid." = "Kartes derīguma termiņa beigu datums nav derīgs."; + +"Your card's expiration month is invalid." = "Kartes derīguma termiņa mēnesis nav derīgs."; + +"Your card's expiration year is invalid." = "Kartes derīguma termiņa gads nav derīgs."; + +"Your card's security code is incomplete." = "Kartes drošības kods nav pilnīgs."; + +"Your card's security code is invalid." = "Kartes drošības kods nav derīgs."; + +"Your postal code is invalid." = "Pasta indekss nav derīgs."; + +"card number" = "kartes numurs"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "derīguma termiņš"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ms-MY.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..45ae2f8a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ms-MY.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "akaun %1$@ yang berakhir dengan %2$@"; + +"%1$@ ending in %2$@" = "%1$@ – berakhiran %2$@"; + +"%1$@ is not accepted" = "%1$@ tidak diterima"; + +"Address line 2 (optional)" = "Alamat baris 2 (opsyenal)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Akaun Bank"; + +"Billing address" = "Alamat pengebilan"; + +"CVC" = "CVC"; + +"Card information" = "Maklumat kad"; + +"Card number" = "Nombor kad"; + +"Confirm your %@" = "Sahkan %@ anda"; + +"Invalid data." = "Data tidak sah."; + +"MM / YY" = "BB / TT"; + +"MM/YY" = "BB/TT"; + +"Postal Code" = "Poskod"; + +"Shipping Address" = "Alamat Pengiriman"; + +"State / Province / Region" = "Negeri/Wilayah/Rantau"; + +"The BSB you entered is invalid." = "BSB yang anda masukkan tidak sah."; + +"The selected brand is not allowed" = "Jenama yang dipilih tidak dibenarkan"; + +"To scan your card, allow camera access in Settings." = "Untuk mengimbas kad anda, benarkan akses kamera dalam Tetapan."; + +"Your ZIP is invalid." = "Kod ZIP anda tidak sah."; + +"Your card has expired." = "Kad anda telah tamat tempoh."; + +"Your card number is incomplete." = "Nombor kad anda tidak lengkap."; + +"Your card number is invalid." = "Nombor kad anda tidak sah."; + +"Your card's expiration date is incomplete." = "Tarikh tamat tempoh kad anda tidak lengkap."; + +"Your card's expiration date is invalid." = "Tarikh tamat tempoh kad anda tidak sah."; + +"Your card's expiration month is invalid." = "Bulan tamat tempoh kad anda tidak sah."; + +"Your card's expiration year is invalid." = "Tahun tamat tempoh kad anda tidak sah."; + +"Your card's security code is incomplete." = "Kod keselamatan kad anda tidak lengkap."; + +"Your card's security code is invalid." = "Kod keselamatan kad anda tidak sah."; + +"Your postal code is invalid." = "Poskod anda tidak sah."; + +"card number" = "nombor kad"; + +"example@example.com" = "contoh@contoh.com"; + +"expiration date" = "Tarikh tamat tempoh"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/mt.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/mt.lproj/Localizable.strings new file mode 100644 index 00000000..9d3299f9 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/mt.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Kont ta' %1$@ li jintemm biċ-ċifri %2$@"; + +"%1$@ ending in %2$@" = "%1$@ li jispiċċa %2$@"; + +"%1$@ is not accepted" = "%1$@ mhix aċċettata"; + +"Address line 2 (optional)" = "Indirizz linja 2 (mhux obbligatorju)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Kont bankarju"; + +"Billing address" = "Indirizz tal-kont"; + +"CVC" = "CVC"; + +"Card information" = "L-informazzjoni tal-kard"; + +"Card number" = "In-numru tal-kard"; + +"Confirm your %@" = "Ikkonferma l-%@ tiegħek"; + +"Invalid data." = "Data invalida."; + +"MM / YY" = "XX / SS"; + +"MM/YY" = "XX/SS"; + +"Postal Code" = "Kodiċi Postali"; + +"Shipping Address" = "L-Indirizz tat-Trasport tal-Merkanzija"; + +"State / Province / Region" = "Stat / Provinċja / Reġjun"; + +"The BSB you entered is invalid." = "Il-BSB li daħħalt mhux validu."; + +"The selected brand is not allowed" = "Ix-xorta ta' karta magħżula ma tistax tintuża"; + +"To scan your card, allow camera access in Settings." = "Biex tiskennja l-kard tiegħek, ippermetti l-aċċess tal-kamera fis-Settings."; + +"Your ZIP is invalid." = "Il-kodiċi postali tiegħek mhux validu."; + +"Your card has expired." = "Il-karta tiegħek skadiet."; + +"Your card number is incomplete." = "In-numru tal-kard tiegħek mhux komplut."; + +"Your card number is invalid." = "In-numru tal-kard tiegħek mhux validu."; + +"Your card's expiration date is incomplete." = "Id-data tal-iskadenza tal-kard tiegħek mhijiex kompluta."; + +"Your card's expiration date is invalid." = "Id-data tal-iskadenza tal-kard tiegħek mhijiex valida."; + +"Your card's expiration month is invalid." = "Ix-xahar tal-iskadenza tal-kard tiegħek mhux validu."; + +"Your card's expiration year is invalid." = "Is-sena tal-iskadenza tal-kard tiegħek mhijiex valida."; + +"Your card's security code is incomplete." = "Il-kodiċi tas-sigurtà tal-kard tiegħek mhux komplut."; + +"Your card's security code is invalid." = "Il-kodiċi tas-sigurtà tal-kard tiegħek mhux validu."; + +"Your postal code is invalid." = "Il-kodiċi postali tiegħek mhux validu."; + +"card number" = "in-numru tal-kard"; + +"example@example.com" = "eżempju@eżempju.com"; + +"expiration date" = "id-data tal-iskadenza"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nb.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nb.lproj/Localizable.strings new file mode 100644 index 00000000..701271c2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nb.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@-konto som slutter på %2$@"; + +"%1$@ ending in %2$@" = "%1$@ som slutter på %2$@"; + +"%1$@ is not accepted" = "%1$@ er ikke godkjent"; + +"Address line 2 (optional)" = "Adresselinje 2 (valgfritt)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankkonto"; + +"Billing address" = "Fakturaadresse"; + +"CVC" = "CVC"; + +"Card information" = "Kortinformasjon"; + +"Card number" = "Kortnummer"; + +"Confirm your %@" = "Bekreft %@"; + +"Invalid data." = "Ugyldige data."; + +"MM / YY" = "MM / ÅÅ"; + +"MM/YY" = "MM/ÅÅ"; + +"Postal Code" = "Postnummer"; + +"Shipping Address" = "Forsendelsesadresse"; + +"State / Province / Region" = "Stat / Provins / Region"; + +"The BSB you entered is invalid." = "Du skrev inn ugyldig BSB."; + +"The selected brand is not allowed" = "Det valgte merket er ikke godkjent"; + +"To scan your card, allow camera access in Settings." = "Gi tilgang til kamera i Innstillinger for å skanne kortet."; + +"Your ZIP is invalid." = "ZIP er ugyldig."; + +"Your card has expired." = "Kortet ditt er utløpt."; + +"Your card number is incomplete." = "Kortnummeret er ufullstendig."; + +"Your card number is invalid." = "Kortnummeret er ugyldig."; + +"Your card's expiration date is incomplete." = "Kortets utløpsdato er ufullstendig."; + +"Your card's expiration date is invalid." = "Kortets utløpsdato er ugyldig."; + +"Your card's expiration month is invalid." = "Kortets utløpsmåned er ugyldig."; + +"Your card's expiration year is invalid." = "Kortets utløpsår er ugyldig."; + +"Your card's security code is incomplete." = "Kortets sikkerhetskode er ufullstendig."; + +"Your card's security code is invalid." = "Kortets sikkerhetskode er ugyldig."; + +"Your postal code is invalid." = "Postnummeret er ugyldig."; + +"card number" = "kortnummer"; + +"example@example.com" = "eksempel@example.com"; + +"expiration date" = "utløpsdato"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nl.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nl.lproj/Localizable.strings new file mode 100644 index 00000000..62cb1350 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nl.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@-rekening die eindigt op %2$@"; + +"%1$@ ending in %2$@" = "%1$@ eindigend op %2$@"; + +"%1$@ is not accepted" = "%1$@ wordt niet geaccepteerd"; + +"Address line 2 (optional)" = "Adresregel 2 (optioneel)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankrekening"; + +"Billing address" = "Factuuradres"; + +"CVC" = "Cvc"; + +"Card information" = "Kaartgegevens"; + +"Card number" = "Kaartnummer"; + +"Confirm your %@" = "Je %@ bevestigen"; + +"Invalid data." = "Ongeldige gegevens."; + +"MM / YY" = "MM / JJ"; + +"MM/YY" = "MM/JJ"; + +"Postal Code" = "Postcode"; + +"Shipping Address" = "Verzendadres"; + +"State / Province / Region" = "Staat/provincie/regio"; + +"The BSB you entered is invalid." = "Het ingevoerde BSB-nummer is ongeldig."; + +"The selected brand is not allowed" = "Het geselecteerde merk is niet toegestaan"; + +"To scan your card, allow camera access in Settings." = "Sta toegang tot je camera toe in Instellingen om je kaart te scannen."; + +"Your ZIP is invalid." = "Je ZIP is ongeldig."; + +"Your card has expired." = "Je betaalkaart is verlopen."; + +"Your card number is incomplete." = "Je kaartnummer is onvolledig."; + +"Your card number is invalid." = "Je kaartnummer is ongeldig."; + +"Your card's expiration date is incomplete." = "De vervaldatum van je kaart is onvolledig."; + +"Your card's expiration date is invalid." = "De vervaldatum van je kaart is ongeldig."; + +"Your card's expiration month is invalid." = "De vervalmaand van de kaart is ongeldig."; + +"Your card's expiration year is invalid." = "Het vervaljaar van de kaart is ongeldig."; + +"Your card's security code is incomplete." = "De beveiligingscode van je kaart is onvolledig."; + +"Your card's security code is invalid." = "De beveiligingscode van je kaart is ongeldig."; + +"Your postal code is invalid." = "Je postcode is ongeldig."; + +"card number" = "kaartnummer"; + +"example@example.com" = "voorbeeld@voorbeeld.com"; + +"expiration date" = "vervaldatum"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nn-NO.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..ed5d80b2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/nn-NO.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ konto som sluttar på %2$@"; + +"%1$@ ending in %2$@" = "%1$@ sluttar på %2$@"; + +"%1$@ is not accepted" = "%1$@ er ikkje godteke"; + +"Address line 2 (optional)" = "Adresselinje 2 (valfri)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB-kode"; + +"Bank Account" = "Bankkonto"; + +"Billing address" = "Fakturaadresse"; + +"CVC" = "CVC"; + +"Card information" = "Kortinformasjon"; + +"Card number" = "Kortnummer"; + +"Confirm your %@" = "Stadfest %@"; + +"Invalid data." = "Ugyldige data."; + +"MM / YY" = "MM / ÅÅ"; + +"MM/YY" = "MM/ÅÅ"; + +"Postal Code" = "Postnummer"; + +"Shipping Address" = "Sendingsadresse"; + +"State / Province / Region" = "Stat/område/region"; + +"The BSB you entered is invalid." = "BSB-koden du skreiv inn er ugyldig."; + +"The selected brand is not allowed" = "Det valde merket er ikkje tillate"; + +"To scan your card, allow camera access in Settings." = "Gi tilgang til kameraet i Innstillingar for å skanne kortet."; + +"Your ZIP is invalid." = "ZIP-koden er ugyldig."; + +"Your card has expired." = "Kortet er gått ut."; + +"Your card number is incomplete." = "Kortnummeret er ufullstendig."; + +"Your card number is invalid." = "Kortnummeret ditt er ugyldig."; + +"Your card's expiration date is incomplete." = "Utløpsdatoen til kortet er ufullstendig."; + +"Your card's expiration date is invalid." = "Utløpsdatoen til kortet er ugyldig."; + +"Your card's expiration month is invalid." = "Utløpsmånaden på kortet er ugyldig."; + +"Your card's expiration year is invalid." = "Utløpsåret på kortet ditt er ugyldig."; + +"Your card's security code is incomplete." = "Sikkerheitskoden til kortet er ufullstendig."; + +"Your card's security code is invalid." = "Sikkerheitskoden til kortet er ugyldig."; + +"Your postal code is invalid." = "Postnummeret ditt er ugyldig."; + +"card number" = "kortnummer"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "utløpsdato"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pl-PL.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..540fb2f2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pl-PL.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ konto kończące się %2$@"; + +"%1$@ ending in %2$@" = "%1$@ kończy się %2$@"; + +"%1$@ is not accepted" = "Marka %1$@ nie jest akceptowana"; + +"Address line 2 (optional)" = "Linia adresu 2 (opcja)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Konto bankowe"; + +"Billing address" = "Adres rozliczeniowy"; + +"CVC" = "CVC"; + +"Card information" = "Dane karty"; + +"Card number" = "Nr karty"; + +"Confirm your %@" = "Potwierdź swój kod %@"; + +"Invalid data." = "Nieprawidłowe dane."; + +"MM / YY" = "MM / RR"; + +"MM/YY" = "MM/RR"; + +"Postal Code" = "Kod pocztowy"; + +"Shipping Address" = "Adres dostawy"; + +"State / Province / Region" = "Województwo/prowincja/region"; + +"The BSB you entered is invalid." = "Wprowadzony numer BSB jest nieprawidłowy."; + +"The selected brand is not allowed" = "Wybrana marka jest niedozwolona"; + +"To scan your card, allow camera access in Settings." = "Aby zeskanować kartę, zezwól na dostęp do aparatu w Ustawieniach."; + +"Your ZIP is invalid." = "Nieprawidłowy kod pocztowy (ZIP)."; + +"Your card has expired." = "Ważność Twojej karty wygasła."; + +"Your card number is incomplete." = "Numer karty jest niekompletny."; + +"Your card number is invalid." = "Numer karty jest nieprawidłowy."; + +"Your card's expiration date is incomplete." = "Data ważności karty jest niepełna."; + +"Your card's expiration date is invalid." = "Data ważności karty jest nieprawidłowa."; + +"Your card's expiration month is invalid." = "Miesiąc daty ważności karty jest nieprawidłowy."; + +"Your card's expiration year is invalid." = "Rok daty ważności karty jest nieprawidłowy"; + +"Your card's security code is incomplete." = "Kod zabezpieczający karty jest niekompletny."; + +"Your card's security code is invalid." = "Kod bezpieczeństwa karty jest nieprawidłowy."; + +"Your postal code is invalid." = "Nieprawidłowy kod pocztowy."; + +"card number" = "nr karty"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "data ważności"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pt-BR.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..45c008b6 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pt-BR.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Conta %1$@ de final %2$@"; + +"%1$@ ending in %2$@" = "%1$@ com final %2$@"; + +"%1$@ is not accepted" = "%1$@ não é aceito"; + +"Address line 2 (optional)" = "Endereço – Linha 2 (opcional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Conta bancária"; + +"Billing address" = "Endereço de cobrança"; + +"CVC" = "CVC"; + +"Card information" = "Dados do cartão"; + +"Card number" = "Número do cartão"; + +"Confirm your %@" = "Confirme seu %@"; + +"Invalid data." = "Dados inválidos."; + +"MM / YY" = "MM / AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "Código postal"; + +"Shipping Address" = "Endereço de envio"; + +"State / Province / Region" = "Estado/Província/Região"; + +"The BSB you entered is invalid." = "O BSB inserido é inválido."; + +"The selected brand is not allowed" = "A marca selecionada não é permitida"; + +"To scan your card, allow camera access in Settings." = "Para escanear seu cartão, autorize o acesso à câmera nos Ajustes."; + +"Your ZIP is invalid." = "Código postal inválido."; + +"Your card has expired." = "O cartão expirou."; + +"Your card number is incomplete." = "Número do cartão incompleto."; + +"Your card number is invalid." = "Número do cartão inválido."; + +"Your card's expiration date is incomplete." = "Data de validade incompleta."; + +"Your card's expiration date is invalid." = "Data de validade inválida."; + +"Your card's expiration month is invalid." = "O mês de validade do cartão é inválido."; + +"Your card's expiration year is invalid." = "Ano de validade inválido."; + +"Your card's security code is incomplete." = "Código de segurança incompleto."; + +"Your card's security code is invalid." = "Código de segurança inválido."; + +"Your postal code is invalid." = "Código postal inválido."; + +"card number" = "número do cartão"; + +"example@example.com" = "exemplo@exemplo.com"; + +"expiration date" = "data de expiração"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pt-PT.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..da1eef4f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/pt-PT.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Conta %1$@ que termina em %2$@"; + +"%1$@ ending in %2$@" = "%1$@ a terminar em %2$@"; + +"%1$@ is not accepted" = "%1$@ não é aceite"; + +"Address line 2 (optional)" = "Linha de endereço 2 (opcional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Conta bancária"; + +"Billing address" = "Endereço de faturação"; + +"CVC" = "CVC"; + +"Card information" = "Informações do cartão"; + +"Card number" = "Número do cartão"; + +"Confirm your %@" = "Confirme o seu %@"; + +"Invalid data." = "Dados inválidos."; + +"MM / YY" = "MM/AA"; + +"MM/YY" = "MM/AA"; + +"Postal Code" = "C.P."; + +"Shipping Address" = "Endereço de envio"; + +"State / Province / Region" = "Estado/Província/Região"; + +"The BSB you entered is invalid." = "O BSB que introduziu é inválido."; + +"The selected brand is not allowed" = "A marca selecionada não é permitida"; + +"To scan your card, allow camera access in Settings." = "Para ler o seu cartão, permita o acesso à câmara nas Definições."; + +"Your ZIP is invalid." = "O seu código ZIP é inválido."; + +"Your card has expired." = "O seu cartão expirou."; + +"Your card number is incomplete." = "O número do seu cartão está incompleto."; + +"Your card number is invalid." = "O número do seu cartão é inválido."; + +"Your card's expiration date is incomplete." = "A data de validade do seu cartão está incompleta."; + +"Your card's expiration date is invalid." = "A data de validade do seu cartão é inválida."; + +"Your card's expiration month is invalid." = "O mês de validade do seu cartão é inválido."; + +"Your card's expiration year is invalid." = "O ano de validade do seu cartão é inválido."; + +"Your card's security code is incomplete." = "O código de segurança do seu cartão está incompleto."; + +"Your card's security code is invalid." = "O código de segurança do seu cartão é inválido."; + +"Your postal code is invalid." = "O seu código postal é inválido."; + +"card number" = "número do cartão"; + +"example@example.com" = "exemplo@exemplo.com"; + +"expiration date" = "Data de validade"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ro-RO.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..8b8289e3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ro-RO.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Contul la %1$@ care se termină în %2$@"; + +"%1$@ ending in %2$@" = "%1$@ se termină în %2$@"; + +"%1$@ is not accepted" = "%1$@ nu este acceptat"; + +"Address line 2 (optional)" = "Rândul 2 pentru adresă (opțional)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "Număr BSB"; + +"Bank Account" = "Cont bancar"; + +"Billing address" = "Adresă de facturare"; + +"CVC" = "Cod CVC"; + +"Card information" = "Informații privind cardul"; + +"Card number" = "Număr card"; + +"Confirm your %@" = "Confirmați-vă %@"; + +"Invalid data." = "Datele nu sunt valide."; + +"MM / YY" = "LL/AA"; + +"MM/YY" = "LL/AA"; + +"Postal Code" = "Cod poștal"; + +"Shipping Address" = "Adresa de expediere"; + +"State / Province / Region" = "Stat"; + +"The BSB you entered is invalid." = "Numărul BSB pe care l-ați introdus nu este valid."; + +"The selected brand is not allowed" = "Marca selectată nu este permisă"; + +"To scan your card, allow camera access in Settings." = "Pentru a vă scana cardul, permiteți accesul camerei din Setări."; + +"Your ZIP is invalid." = "Codul dvs. poștal nu este valid."; + +"Your card has expired." = "Cardul dvs. a expirat."; + +"Your card number is incomplete." = "Numărul cardului dvs. nu este complet."; + +"Your card number is invalid." = "Numărul cardului dvs. nu este valid."; + +"Your card's expiration date is incomplete." = "Data de expirare a cardului dvs. nu este completă."; + +"Your card's expiration date is invalid." = "Data de expirare a cardului dvs. nu este validă."; + +"Your card's expiration month is invalid." = "Luna de expirare a cardului dvs. nu este validă."; + +"Your card's expiration year is invalid." = "Anul de expirare al cardului dvs. nu este valid."; + +"Your card's security code is incomplete." = "Codul de securitate al cardului dvs. nu este complet."; + +"Your card's security code is invalid." = "Codul de securitate al cardului dvs. nu este valid."; + +"Your postal code is invalid." = "Codul dvs. poștal nu este valid."; + +"card number" = "număr card"; + +"example@example.com" = "exemplu@exemplu.com"; + +"expiration date" = "data de expirare"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ru.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ru.lproj/Localizable.strings new file mode 100644 index 00000000..9bdbd3b3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/ru.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ счет, номер которого оканчивается на %2$@"; + +"%1$@ ending in %2$@" = "%1$@, оканч. на %2$@"; + +"%1$@ is not accepted" = "%1$@ не принимается"; + +"Address line 2 (optional)" = "Адрес (строка 2) (необязательно)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Банковский счет"; + +"Billing address" = "Адрес для выставления счета"; + +"CVC" = "Код CVV/CVC"; + +"Card information" = "Данные платежной карты"; + +"Card number" = "Номер карты"; + +"Confirm your %@" = "Подтвердите %@"; + +"Invalid data." = "Недопустимые данные."; + +"MM / YY" = "ММ / ГГ"; + +"MM/YY" = "ММ/ГГ"; + +"Postal Code" = "Почтовый индекс"; + +"Shipping Address" = "Адрес доставки"; + +"State / Province / Region" = "Штат / провинция / регион"; + +"The BSB you entered is invalid." = "Введенный код BSB неверен."; + +"The selected brand is not allowed" = "Выбранный бренд недоступен"; + +"To scan your card, allow camera access in Settings." = "Чтобы сканировать карту, войдите в \"Настройки\" и разрешите доступ к камере."; + +"Your ZIP is invalid." = "Введенный почтовый индекс неверен."; + +"Your card has expired." = "Срок действия карты истек."; + +"Your card number is incomplete." = "Номер карты введен не полностью."; + +"Your card number is invalid." = "Номер карты недействителен."; + +"Your card's expiration date is incomplete." = "Дата истечения срока действия карты введена не полностью."; + +"Your card's expiration date is invalid." = "Дата истечения срока действия карты недействительна."; + +"Your card's expiration month is invalid." = "Недопустимый месяц окончания действия карты."; + +"Your card's expiration year is invalid." = "Недопустимый год окончания действия карты."; + +"Your card's security code is incomplete." = "Код CVV/CVC карты введен не полностью."; + +"Your card's security code is invalid." = "Код CVV/CVC карты недействителен."; + +"Your postal code is invalid." = "Введенный почтовый индекс недействителен."; + +"card number" = "номер карты"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "срок окончания действия"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sk-SK.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..0c709610 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sk-SK.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@ účet končiaci na %2$@"; + +"%1$@ ending in %2$@" = "Karta %1$@ s poslednými štyrmi číslami %2$@"; + +"%1$@ is not accepted" = "%1$@ sa neakceptuje"; + +"Address line 2 (optional)" = "Riadok adresy 2 (voliteľne)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankový účet"; + +"Billing address" = "Fakturačná adresa"; + +"CVC" = "CVC"; + +"Card information" = "Informácie o karte"; + +"Card number" = "Číslo karty"; + +"Confirm your %@" = "Potvrďte svoje číslo %@"; + +"Invalid data." = "Neplatné údaje."; + +"MM / YY" = "MM / RR"; + +"MM/YY" = "MM/RR"; + +"Postal Code" = "PSČ"; + +"Shipping Address" = "Dodacia adresa"; + +"State / Province / Region" = "Štát/provincia/región"; + +"The BSB you entered is invalid." = "Zadané BSB je neplatné."; + +"The selected brand is not allowed" = "Vybraná značka nie je povolená"; + +"To scan your card, allow camera access in Settings." = "Na nasnímanie vašej karty umožnite prístup ku kamere pod položkou Nastavenia."; + +"Your ZIP is invalid." = "Vaše PSČ je neplatné."; + +"Your card has expired." = "Platnosť vašej karty vypršala."; + +"Your card number is incomplete." = "Číslo vašej karty je neúplné."; + +"Your card number is invalid." = "Číslo vašej karty je neplatné."; + +"Your card's expiration date is incomplete." = "Dátum vypršania platnosti vašej karty je neúplný."; + +"Your card's expiration date is invalid." = "Dátum vypršania platnosti vašej karty je neplatný."; + +"Your card's expiration month is invalid." = "Mesiac vypršania platnosti vašej karty je neplatný."; + +"Your card's expiration year is invalid." = "Rok ukončenia platnosti vašej karty je neplatný."; + +"Your card's security code is incomplete." = "Bezpečnostný kód vašej karty je neúplný."; + +"Your card's security code is invalid." = "Bezpečnostný kód vašej karty je neplatný."; + +"Your postal code is invalid." = "Vaše PSČ je neplatné."; + +"card number" = "číslo karty"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "dátum exspirácie"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sl-SI.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..81eae5cb --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sl-SI.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Račun za %1$@, ki se konča s števkami %2$@"; + +"%1$@ ending in %2$@" = "%1$@, ki se konča s/z %2$@"; + +"%1$@ is not accepted" = "Kartica %1$@ ni sprejeta"; + +"Address line 2 (optional)" = "Naslov 2 (izbirno)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bančni račun"; + +"Billing address" = "Naslov plačnika računa"; + +"CVC" = "CVC"; + +"Card information" = "Podatki o kartici"; + +"Card number" = "Številka kartice"; + +"Confirm your %@" = "Potrdite svojo kodo %@"; + +"Invalid data." = "Neveljavni podatki."; + +"MM / YY" = "MM/LL"; + +"MM/YY" = "MM/LL"; + +"Postal Code" = "Poštna številka"; + +"Shipping Address" = "Dostavni naslov"; + +"State / Province / Region" = "Zvezna država/provinca/regija"; + +"The BSB you entered is invalid." = "Vneseni BSB ni veljaven."; + +"The selected brand is not allowed" = "Izbrana blagovna znamka ni dovoljena"; + +"To scan your card, allow camera access in Settings." = "Če želite optično prebrati kartico, dovolite dostop kameri v nastavitvah."; + +"Your ZIP is invalid." = "Vaša poštna številka ni veljavna."; + +"Your card has expired." = "Vaša kartica je potekla."; + +"Your card number is incomplete." = "Številka vaše kartice ni popolna."; + +"Your card number is invalid." = "Številka vaše kartice ni veljavna."; + +"Your card's expiration date is incomplete." = "Datum poteka vaše kartice ni popoln."; + +"Your card's expiration date is invalid." = "Datum poteka vaše kartice ni veljaven."; + +"Your card's expiration month is invalid." = "Mesec poteka vaše kartice ni veljaven."; + +"Your card's expiration year is invalid." = "Leto poteka vaše kartice ni veljavno."; + +"Your card's security code is incomplete." = "Varnostna koda vaše kartice ni popolna."; + +"Your card's security code is invalid." = "Varnostna koda vaše kartice ni veljavna."; + +"Your postal code is invalid." = "Vaša poštna številka ni veljavna."; + +"card number" = "številka kartice"; + +"example@example.com" = "primer@primer.com"; + +"expiration date" = "datum poteka"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sv.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sv.lproj/Localizable.strings new file mode 100644 index 00000000..9d58477c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/sv.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%1$@-konto som slutar på %2$@"; + +"%1$@ ending in %2$@" = "%1$@ med slutsiffrorna %2$@"; + +"%1$@ is not accepted" = "%1$@ accepteras inte"; + +"Address line 2 (optional)" = "Adressrad 2 (valfritt)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Bankkonto"; + +"Billing address" = "Faktureringsadress"; + +"CVC" = "CVC-kod"; + +"Card information" = "Kortinformation"; + +"Card number" = "Kortnummer"; + +"Confirm your %@" = "Bekräfta %@"; + +"Invalid data." = "Ogiltiga data."; + +"MM / YY" = "MM/ÅÅ"; + +"MM/YY" = "MM/ÅÅ"; + +"Postal Code" = "Postnummer"; + +"Shipping Address" = "Leveransadress"; + +"State / Province / Region" = "Stat/provins/region"; + +"The BSB you entered is invalid." = "Det BSB du angav är ogiltigt."; + +"The selected brand is not allowed" = "Valt kortmärke är inte tillåtet"; + +"To scan your card, allow camera access in Settings." = "Tillåt kameraåtkomst i Inställningar för att skanna kortet."; + +"Your ZIP is invalid." = "Ditt postnummer är ogiltigt."; + +"Your card has expired." = "Ditt kort har upphört att gälla."; + +"Your card number is incomplete." = "Ditt kortnummer är ofullständigt."; + +"Your card number is invalid." = "Ditt kortnummer är ogiltigt."; + +"Your card's expiration date is incomplete." = "Kortets utgångsdatum är ofullständigt."; + +"Your card's expiration date is invalid." = "Kortets utgångsdatum är ogiltigt."; + +"Your card's expiration month is invalid." = "Kortets utgångsmånad är ogiltig."; + +"Your card's expiration year is invalid." = "Kortets utgångsår är ogiltigt."; + +"Your card's security code is incomplete." = "Kortets säkerhetskod är ofullständig."; + +"Your card's security code is invalid." = "Kortets säkerhetskod är ogiltig."; + +"Your postal code is invalid." = "Ditt postnummer är ogiltigt."; + +"card number" = "kortnummer"; + +"example@example.com" = "exempel@exempel.com"; + +"expiration date" = "utgångsdatum"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/tk.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/tk.lproj/Localizable.strings new file mode 100644 index 00000000..e69de29b diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/tr.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/tr.lproj/Localizable.strings new file mode 100644 index 00000000..97f6f479 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/tr.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "%2$@ ile biten %1$@ hesabınız"; + +"%1$@ ending in %2$@" = "%2$@ İle Biten %1$@"; + +"%1$@ is not accepted" = "%1$@ kabul edilmiyor"; + +"Address line 2 (optional)" = "Adres satırı 2 (isteğe bağlı)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Banka Hesabı"; + +"Billing address" = "Fatura adresi"; + +"CVC" = "CVC"; + +"Card information" = "Kart bilgileri"; + +"Card number" = "Kart numarası"; + +"Confirm your %@" = "%@ kodunuzu onaylayın"; + +"Invalid data." = "Geçersiz veri."; + +"MM / YY" = "AA / YY"; + +"MM/YY" = "AA/YY"; + +"Postal Code" = "Posta Kodu"; + +"Shipping Address" = "Gönderi Adresi"; + +"State / Province / Region" = "İl/İlçe/Bölge"; + +"The BSB you entered is invalid." = "Girdiğiniz BSB geçersiz."; + +"The selected brand is not allowed" = "Seçilen markaya izin verilmiyor"; + +"To scan your card, allow camera access in Settings." = "Kartınızı taramak için Ayarlar'da kamera erişimine izin verin."; + +"Your ZIP is invalid." = "Posta kodunuz geçersiz."; + +"Your card has expired." = "Kartınızın süresi dolmuş."; + +"Your card number is incomplete." = "Kart numaranız eksik."; + +"Your card number is invalid." = "Kart numaranız geçersiz."; + +"Your card's expiration date is incomplete." = "Kartınızın son kullanma tarihi eksik."; + +"Your card's expiration date is invalid." = "Kartınızın son kullanma tarihi geçersiz."; + +"Your card's expiration month is invalid." = "Kartınızın son kullanma ayı geçersiz."; + +"Your card's expiration year is invalid." = "Kartınızın son kullanma yılı geçersiz."; + +"Your card's security code is incomplete." = "Kartınızın güvenlik kodu eksik."; + +"Your card's security code is invalid." = "Kartınızın güvenlik kodu geçersiz."; + +"Your postal code is invalid." = "Posta kodunuz geçersiz."; + +"card number" = "kart numarası"; + +"example@example.com" = "ornek@ornek.com"; + +"expiration date" = "son kullanma tarihi"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/vi.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/vi.lproj/Localizable.strings new file mode 100644 index 00000000..2adc5705 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/vi.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "Tài khoản %1$@ kết thúc bằng %2$@"; + +"%1$@ ending in %2$@" = "%1$@ kết thúc bằng %2$@"; + +"%1$@ is not accepted" = "%1$@ không được chấp nhận"; + +"Address line 2 (optional)" = "Dòng địa chỉ 2 (tùy chọn)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "Tài khoản ngân hàng"; + +"Billing address" = "Địa chỉ thanh toán"; + +"CVC" = "CVC"; + +"Card information" = "Thông tin thẻ"; + +"Card number" = "Số thẻ"; + +"Confirm your %@" = "Xác nhận %@ của bạn"; + +"Invalid data." = "Dữ liệu không hợp lệ."; + +"MM / YY" = "Tháng /Năm"; + +"MM/YY" = "Tháng/Năm"; + +"Postal Code" = "Mã bưu điện"; + +"Shipping Address" = "Địa chỉ vận chuyển"; + +"State / Province / Region" = "Tiểu bang/Tỉnh/Khu vực"; + +"The BSB you entered is invalid." = "BSB mà bạn nhập không hợp lệ."; + +"The selected brand is not allowed" = "Thương hiệu đã chọn không được cho phép"; + +"To scan your card, allow camera access in Settings." = "Để quét thẻ của bạn, hãy cho phép truy cập camera trong mục Cài đặt."; + +"Your ZIP is invalid." = "Mã ZIP không hợp lệ"; + +"Your card has expired." = "Thẻ đã hết hạn."; + +"Your card number is incomplete." = "Số thẻ chưa đầy đủ."; + +"Your card number is invalid." = "Số thẻ không hợp lệ."; + +"Your card's expiration date is incomplete." = "Ngày hết hạn trên thẻ không hợp lệ."; + +"Your card's expiration date is invalid." = "Ngày hết hạn trên thẻ không hợp lệ."; + +"Your card's expiration month is invalid." = "Tháng hết hạn trên thẻ của bạn không hợp lệ."; + +"Your card's expiration year is invalid." = "Năm hết hạn trên thẻ của bạn không hợp lệ."; + +"Your card's security code is incomplete." = "Mã bảo mật trên thẻ chưa đầy đủ."; + +"Your card's security code is invalid." = "Mã bảo mật trên thẻ không hợp lệ."; + +"Your postal code is invalid." = "Mã bưu điện không hợp lệ."; + +"card number" = "số thẻ"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "Ngày hết hạn"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-HK.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..2d73a7c7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-HK.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "尾號為 %2$@ 的 %1$@ 賬戶"; + +"%1$@ ending in %2$@" = "%1$@ - 尾號:%2$@"; + +"%1$@ is not accepted" = "不接受 %1$@"; + +"Address line 2 (optional)" = "地址第 2 行(可選)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "銀行賬戶"; + +"Billing address" = "賬單地址"; + +"CVC" = "安全碼"; + +"Card information" = "銀行卡資訊"; + +"Card number" = "卡號"; + +"Confirm your %@" = "確認您的 %@"; + +"Invalid data." = "數據無效。"; + +"MM / YY" = "月份/年份"; + +"MM/YY" = "月份/年份"; + +"Postal Code" = "郵區編號"; + +"Shipping Address" = "收貨地址"; + +"State / Province / Region" = "州/省/區"; + +"The BSB you entered is invalid." = "您輸入的 BSB 無效。"; + +"The selected brand is not allowed" = "不允許使用選定的品牌"; + +"To scan your card, allow camera access in Settings." = "要掃描您的銀行卡,在「設置」中啟用照相機。"; + +"Your ZIP is invalid." = "您的郵區編號無效。"; + +"Your card has expired." = "您的銀行卡已過期。"; + +"Your card number is incomplete." = "您的銀行卡號不完整。"; + +"Your card number is invalid." = "您的卡號無效。"; + +"Your card's expiration date is incomplete." = "您的銀行卡的有效期不完整。"; + +"Your card's expiration date is invalid." = "您的銀行卡的有效期無效。"; + +"Your card's expiration month is invalid." = "您的銀行卡的到期月份無效。"; + +"Your card's expiration year is invalid." = "您的銀行卡的到期年份無效。"; + +"Your card's security code is incomplete." = "您的銀行卡的安全碼不完整。"; + +"Your card's security code is invalid." = "您的銀行卡的安全碼無效。"; + +"Your postal code is invalid." = "您的郵區編號無效。"; + +"card number" = "卡號"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "到期日"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..dd008926 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "尾号为 %2$@ 的 %1$@ 账户"; + +"%1$@ ending in %2$@" = "%1$@ - 尾号:%2$@"; + +"%1$@ is not accepted" = "不接受 %1$@"; + +"Address line 2 (optional)" = "地址第 2 行(可选)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "银行账户"; + +"Billing address" = "账单地址"; + +"CVC" = "安全码"; + +"Card information" = "银行卡信息"; + +"Card number" = "卡号"; + +"Confirm your %@" = "确认您的 %@"; + +"Invalid data." = "数据无效。"; + +"MM / YY" = "月份/年份"; + +"MM/YY" = "月份/年份"; + +"Postal Code" = "邮编"; + +"Shipping Address" = "收货地址"; + +"State / Province / Region" = "州/省/地区"; + +"The BSB you entered is invalid." = "您输入的 BSB 无效。"; + +"The selected brand is not allowed" = "不允许使用选定的品牌"; + +"To scan your card, allow camera access in Settings." = "要扫描您的银行卡,在“设置”中启用照相机。"; + +"Your ZIP is invalid." = "您的邮编无效。"; + +"Your card has expired." = "您的银行卡已过期。"; + +"Your card number is incomplete." = "您的银行卡号不完整。"; + +"Your card number is invalid." = "您的卡号无效。"; + +"Your card's expiration date is incomplete." = "您的银行卡的有效期不完整。"; + +"Your card's expiration date is invalid." = "您的银行卡的有效期无效。"; + +"Your card's expiration month is invalid." = "您的银行卡的到期月份无效。"; + +"Your card's expiration year is invalid." = "您的银行卡的到期年份无效。"; + +"Your card's security code is incomplete." = "您的银行卡的安全码不完整。"; + +"Your card's security code is invalid." = "您的银行卡的安全码无效。"; + +"Your postal code is invalid." = "您的邮编无效。"; + +"card number" = "卡号"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "有效期"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-Hant.lproj/Localizable.strings b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..5d9070f3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/Localizations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,69 @@ +"%1$@ account ending in %2$@" = "尾號為 %2$@ 的 %1$@ 賬戶"; + +"%1$@ ending in %2$@" = "%1$@ - 尾號:%2$@"; + +"%1$@ is not accepted" = "不接受 %1$@"; + +"Address line 2 (optional)" = "地址第 2 行(可選)"; + +"Apple Pay" = "Apple Pay"; + +"BSB" = "BSB"; + +"Bank Account" = "銀行帳戶"; + +"Billing address" = "帳單地址"; + +"CVC" = "安全碼"; + +"Card information" = "金融卡資訊"; + +"Card number" = "卡號"; + +"Confirm your %@" = "確認您的 %@"; + +"Invalid data." = "數據無效。"; + +"MM / YY" = "月份/年份"; + +"MM/YY" = "月份/年份"; + +"Postal Code" = "郵遞區號"; + +"Shipping Address" = "收貨地址"; + +"State / Province / Region" = "州 / 省 / 地區"; + +"The BSB you entered is invalid." = "您輸入的 BSB 無效。"; + +"The selected brand is not allowed" = "不允許使用選定的品牌"; + +"To scan your card, allow camera access in Settings." = "要掃描您的金融卡,在「設定」中啟用網路攝影機。"; + +"Your ZIP is invalid." = "您的郵遞區號無效。"; + +"Your card has expired." = "您的金融卡已過期。"; + +"Your card number is incomplete." = "您的卡號不完整。"; + +"Your card number is invalid." = "您的卡號無效。"; + +"Your card's expiration date is incomplete." = "您的金融卡的有效期不完整。"; + +"Your card's expiration date is invalid." = "您的金融卡的有效期無效。"; + +"Your card's expiration month is invalid." = "您的金融卡的到期月份無效。"; + +"Your card's expiration year is invalid." = "您的金融卡的到期年份無效。"; + +"Your card's security code is incomplete." = "您的金融卡的安全碼不完整。"; + +"Your card's security code is invalid." = "您的金融卡的安全碼無效。"; + +"Your postal code is invalid." = "您的郵遞區號無效。"; + +"card number" = "卡號"; + +"example@example.com" = "example@example.com"; + +"expiration date" = "到期日"; diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/anz.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/anz.imageset/Contents.json new file mode 100644 index 00000000..6188f073 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/anz.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "anz.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/anz.imageset/anz.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/anz.imageset/anz.pdf new file mode 100644 index 00000000..398ef86d Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/anz.imageset/anz.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankofmelbourne.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankofmelbourne.imageset/Contents.json new file mode 100644 index 00000000..6460e3c3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankofmelbourne.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "bankofmelbourne.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankofmelbourne.imageset/bankofmelbourne.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankofmelbourne.imageset/bankofmelbourne.pdf new file mode 100644 index 00000000..b4cfd845 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankofmelbourne.imageset/bankofmelbourne.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/banksa.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/banksa.imageset/Contents.json new file mode 100644 index 00000000..6024b40f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/banksa.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "banksa.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/banksa.imageset/banksa.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/banksa.imageset/banksa.pdf new file mode 100644 index 00000000..1acfaa2e Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/banksa.imageset/banksa.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankwest.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankwest.imageset/Contents.json new file mode 100644 index 00000000..3549830d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankwest.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "bankwest.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankwest.imageset/bankwest.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankwest.imageset/bankwest.pdf new file mode 100644 index 00000000..4ef277a2 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/bankwest.imageset/bankwest.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/boq.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/boq.imageset/Contents.json new file mode 100644 index 00000000..231b72e7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/boq.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "boq.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/boq.imageset/boq.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/boq.imageset/boq.pdf new file mode 100644 index 00000000..e92bb981 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/boq.imageset/boq.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/cba.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/cba.imageset/Contents.json new file mode 100644 index 00000000..3f6334c7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/cba.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cba.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/cba.imageset/cba.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/cba.imageset/cba.pdf new file mode 100644 index 00000000..d47e85ad Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/cba.imageset/cba.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/nab.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/nab.imageset/Contents.json new file mode 100644 index 00000000..f8a515ab --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/nab.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "nab.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/nab.imageset/nab.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/nab.imageset/nab.pdf new file mode 100644 index 00000000..06acfb23 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/nab.imageset/nab.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stgeorges.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stgeorges.imageset/Contents.json new file mode 100644 index 00000000..86429996 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stgeorges.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stgeorges.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stgeorges.imageset/stgeorges.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stgeorges.imageset/stgeorges.pdf new file mode 100644 index 00000000..95ede2a4 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stgeorges.imageset/stgeorges.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stripe.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stripe.imageset/Contents.json new file mode 100644 index 00000000..513b8b8e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stripe.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stripe.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stripe.imageset/stripe.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stripe.imageset/stripe.pdf new file mode 100644 index 00000000..cea4db1b Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/stripe.imageset/stripe.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/suncorpmetway.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/suncorpmetway.imageset/Contents.json new file mode 100644 index 00000000..4cb084f3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/suncorpmetway.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "suncorpmetway.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/suncorpmetway.imageset/suncorpmetway.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/suncorpmetway.imageset/suncorpmetway.pdf new file mode 100644 index 00000000..899aa925 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/suncorpmetway.imageset/suncorpmetway.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/westpac.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/westpac.imageset/Contents.json new file mode 100644 index 00000000..5525bc17 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/westpac.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "westpac.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/westpac.imageset/westpac.pdf b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/westpac.imageset/westpac.pdf new file mode 100644 index 00000000..490f8d66 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/BECS/westpac.imageset/westpac.pdf differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex.imageset/Contents.json new file mode 100644 index 00000000..d8e252d1 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-amex.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex.imageset/icon-card-amex.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex.imageset/icon-card-amex.svg new file mode 100644 index 00000000..67b9388e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex.imageset/icon-card-amex.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex_template.imageset/Contents.json new file mode 100644 index 00000000..d30b973e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "vector (6).svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex_template.imageset/vector (6).svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex_template.imageset/vector (6).svg new file mode 100644 index 00000000..c8cb9e19 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_amex_template.imageset/vector (6).svg @@ -0,0 +1,11 @@ + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/Contents.json new file mode 100644 index 00000000..a559e97f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "stp_card_applepay.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_card_applepay@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_card_applepay@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay.png b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay.png new file mode 100644 index 00000000..8bde47c4 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay.png differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay@2x.png b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay@2x.png new file mode 100644 index 00000000..9696c999 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay@2x.png differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay@3x.png b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay@3x.png new file mode 100644 index 00000000..c0f37cda Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_applepay.imageset/stp_card_applepay@3x.png differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires.imageset/Contents.json new file mode 100644 index 00000000..a778c159 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-cartebancaire.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires.imageset/icon-card-cartebancaire.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires.imageset/icon-card-cartebancaire.svg new file mode 100644 index 00000000..b9826ed9 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires.imageset/icon-card-cartebancaire.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires_template.imageset/Contents.json new file mode 100644 index 00000000..94dcee4b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "vector (12).svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires_template.imageset/vector (12).svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires_template.imageset/vector (12).svg new file mode 100644 index 00000000..4787575b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cartes_bancaires_template.imageset/vector (12).svg @@ -0,0 +1,10 @@ + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/Contents.json new file mode 100644 index 00000000..b74b82f8 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "cbcLight.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "cbcDark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/cbcDark.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/cbcDark.svg new file mode 100644 index 00000000..019f30a0 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/cbcDark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/cbcLight.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/cbcLight.svg new file mode 100644 index 00000000..2277a5eb --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cbc.imageset/cbcLight.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/Contents.json new file mode 100644 index 00000000..0a381cb8 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "cvcLight.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "cvcDark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/cvcDark.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/cvcDark.svg new file mode 100644 index 00000000..2bbf2097 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/cvcDark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/cvcLight.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/cvcLight.svg new file mode 100644 index 00000000..e307ccc4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc.imageset/cvcLight.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/Contents.json new file mode 100644 index 00000000..9603c7bb --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "cvcAmexLight.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "cvcAmexDark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/cvcAmexDark.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/cvcAmexDark.svg new file mode 100644 index 00000000..aead2435 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/cvcAmexDark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/cvcAmexLight.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/cvcAmexLight.svg new file mode 100644 index 00000000..812aed3e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_cvc_amex.imageset/cvcAmexLight.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners.imageset/Contents.json new file mode 100644 index 00000000..afbe0ac7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-diners-club.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners.imageset/icon-card-diners-club.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners.imageset/icon-card-diners-club.svg new file mode 100644 index 00000000..1ced17fa --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners.imageset/icon-card-diners-club.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners_template.imageset/Contents.json new file mode 100644 index 00000000..41d3ad59 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Diners Club-bw.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners_template.imageset/Diners Club-bw.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners_template.imageset/Diners Club-bw.svg new file mode 100644 index 00000000..90134f66 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_diners_template.imageset/Diners Club-bw.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover.imageset/Contents.json new file mode 100644 index 00000000..4d0af291 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-discover.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover.imageset/icon-card-discover.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover.imageset/icon-card-discover.svg new file mode 100644 index 00000000..9e147c33 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover.imageset/icon-card-discover.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover_template.imageset/Contents.json new file mode 100644 index 00000000..b522156d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "vector (8).svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover_template.imageset/vector (8).svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover_template.imageset/vector (8).svg new file mode 100644 index 00000000..01015edd --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_discover_template.imageset/vector (8).svg @@ -0,0 +1,11 @@ + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_error.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_error.imageset/Contents.json new file mode 100644 index 00000000..6f4cd499 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_error.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "cardError.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_error.imageset/cardError.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_error.imageset/cardError.svg new file mode 100644 index 00000000..8bf2646c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_error.imageset/cardError.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb.imageset/Contents.json new file mode 100644 index 00000000..8ff2a092 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-jcb.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb.imageset/icon-card-jcb.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb.imageset/icon-card-jcb.svg new file mode 100644 index 00000000..f3a00151 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb.imageset/icon-card-jcb.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb_template.imageset/Contents.json new file mode 100644 index 00000000..c08531c2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "vector (9).svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb_template.imageset/vector (9).svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb_template.imageset/vector (9).svg new file mode 100644 index 00000000..53b57bc5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_jcb_template.imageset/vector (9).svg @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_maestro.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_maestro.imageset/Contents.json new file mode 100644 index 00000000..8c8cd0d8 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_maestro.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-maestro.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_maestro.imageset/icon-card-maestro.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_maestro.imageset/icon-card-maestro.svg new file mode 100644 index 00000000..ebb2145b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_maestro.imageset/icon-card-maestro.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard.imageset/Contents.json new file mode 100644 index 00000000..f3a47bcc --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-mastercard.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard.imageset/icon-card-mastercard.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard.imageset/icon-card-mastercard.svg new file mode 100644 index 00000000..fb6867aa --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard.imageset/icon-card-mastercard.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/Contents.json new file mode 100644 index 00000000..7cd8539d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "stp_card_mastercard_template.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "stp_card_mastercard_template@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "stp_card_mastercard_template@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template.png b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template.png new file mode 100644 index 00000000..a5f89274 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template.png differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template@2x.png b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template@2x.png new file mode 100644 index 00000000..bdee0f19 Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template@2x.png differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template@3x.png b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template@3x.png new file mode 100644 index 00000000..d95285ee Binary files /dev/null and b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_mastercard_template.imageset/stp_card_mastercard_template@3x.png differ diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay.imageset/Contents.json new file mode 100644 index 00000000..474d2bd8 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-unionpay.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay.imageset/icon-card-unionpay.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay.imageset/icon-card-unionpay.svg new file mode 100644 index 00000000..87ac638c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay.imageset/icon-card-unionpay.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay_template.imageset/Contents.json new file mode 100644 index 00000000..c8990068 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "vector (7).svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay_template.imageset/vector (7).svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay_template.imageset/vector (7).svg new file mode 100644 index 00000000..8e92b6b5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unionpay_template.imageset/vector (7).svg @@ -0,0 +1,11 @@ + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/Contents.json new file mode 100644 index 00000000..3b577c09 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "cardLight.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "cardDark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/cardDark.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/cardDark.svg new file mode 100644 index 00000000..e1562e81 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/cardDark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/cardLight.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/cardLight.svg new file mode 100644 index 00000000..68b3f27c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_unknown.imageset/cardLight.svg @@ -0,0 +1,4 @@ + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa.imageset/Contents.json new file mode 100644 index 00000000..49b9119f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon-card-visa.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa.imageset/icon-card-visa.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa.imageset/icon-card-visa.svg new file mode 100644 index 00000000..0246dda3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa.imageset/icon-card-visa.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa_template.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa_template.imageset/Contents.json new file mode 100644 index 00000000..2834a260 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa_template.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "vector (11).svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa_template.imageset/vector (11).svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa_template.imageset/vector (11).svg new file mode 100644 index 00000000..6c3a52a0 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Cards/stp_card_visa_template.imageset/vector (11).svg @@ -0,0 +1,11 @@ + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_amex.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_amex.imageset/Contents.json new file mode 100644 index 00000000..73addc46 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_amex.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_amex.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_amex.imageset/stp_card_unpadded_amex.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_amex.imageset/stp_card_unpadded_amex.svg new file mode 100644 index 00000000..b0c4f9f5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_amex.imageset/stp_card_unpadded_amex.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_cartes_bancaires.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_cartes_bancaires.imageset/Contents.json new file mode 100644 index 00000000..a414e0cf --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_cartes_bancaires.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cartesBancairesNoPadding.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_cartes_bancaires.imageset/cartesBancairesNoPadding.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_cartes_bancaires.imageset/cartesBancairesNoPadding.svg new file mode 100644 index 00000000..46344a7b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_cartes_bancaires.imageset/cartesBancairesNoPadding.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_diners_club.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_diners_club.imageset/Contents.json new file mode 100644 index 00000000..56a807fb --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_diners_club.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_diners_club.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_diners_club.imageset/stp_card_unpadded_diners_club.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_diners_club.imageset/stp_card_unpadded_diners_club.svg new file mode 100644 index 00000000..60d66a78 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_diners_club.imageset/stp_card_unpadded_diners_club.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_discover.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_discover.imageset/Contents.json new file mode 100644 index 00000000..86c56206 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_discover.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_discover.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_discover.imageset/stp_card_unpadded_discover.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_discover.imageset/stp_card_unpadded_discover.svg new file mode 100644 index 00000000..969683b3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_discover.imageset/stp_card_unpadded_discover.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_jcb.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_jcb.imageset/Contents.json new file mode 100644 index 00000000..0fe11ac1 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_jcb.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_jcb.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_jcb.imageset/stp_card_unpadded_jcb.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_jcb.imageset/stp_card_unpadded_jcb.svg new file mode 100644 index 00000000..96338fe5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_jcb.imageset/stp_card_unpadded_jcb.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_maestro.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_maestro.imageset/Contents.json new file mode 100644 index 00000000..e2245d84 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_maestro.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_maestro.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_maestro.imageset/stp_card_unpadded_maestro.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_maestro.imageset/stp_card_unpadded_maestro.svg new file mode 100644 index 00000000..c51eac4a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_maestro.imageset/stp_card_unpadded_maestro.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_mastercard.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_mastercard.imageset/Contents.json new file mode 100644 index 00000000..c2ec419d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_mastercard.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_mastercard.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_mastercard.imageset/stp_card_unpadded_mastercard.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_mastercard.imageset/stp_card_unpadded_mastercard.svg new file mode 100644 index 00000000..8216e4d4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_mastercard.imageset/stp_card_unpadded_mastercard.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_unionpay.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_unionpay.imageset/Contents.json new file mode 100644 index 00000000..2c948dd4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_unionpay.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "stp_card_unpadded_unionpay.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_unionpay.imageset/stp_card_unpadded_unionpay.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_unionpay.imageset/stp_card_unpadded_unionpay.svg new file mode 100644 index 00000000..b85f035d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_unionpay.imageset/stp_card_unpadded_unionpay.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_visa.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_visa.imageset/Contents.json new file mode 100644 index 00000000..4cf2424b --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_visa.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "visaNoPadding.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_visa.imageset/visaNoPadding.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_visa.imageset/visaNoPadding.svg new file mode 100644 index 00000000..88f1ebd7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/CardsNoPadding/stp_card_unpadded_visa.imageset/visaNoPadding.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/stp_icon_bank.imageset/Contents.json b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/stp_icon_bank.imageset/Contents.json new file mode 100644 index 00000000..26c6783f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/stp_icon_bank.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon-pm-bank-color.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/stp_icon_bank.imageset/icon-pm-bank-color.svg b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/stp_icon_bank.imageset/icon-pm-bank-color.svg new file mode 100644 index 00000000..d093fa60 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Resources/StripePaymentsUI.xcassets/stp_icon_bank.imageset/icon-pm-bank-color.svg @@ -0,0 +1,3 @@ + + + diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Categories/Enums+CustomStringConvertible.swift b/StripePaymentsUI/StripePaymentsUI/Source/Categories/Enums+CustomStringConvertible.swift new file mode 100644 index 00000000..8d310f9f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Categories/Enums+CustomStringConvertible.swift @@ -0,0 +1,49 @@ +// +// Enums+CustomStringConvertible.swift +// Stripe +// +// Autogenerated by generate_objc_enum_string_values.rb +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +/// :nodoc: +extension STPCardFieldType: CustomStringConvertible { + public var description: String { + switch self { + case .CVC: + return "CVC" + case .expiration: + return "expiration" + case .number: + return "number" + case .postalCode: + return "postalCode" + } + } +} + +/// :nodoc: +extension STPCardFormViewStyle: CustomStringConvertible { + public var description: String { + switch self { + case .borderless: + return "borderless" + case .standard: + return "standard" + } + } +} + +/// :nodoc: +extension STPPostalCodeIntendedUsage: CustomStringConvertible { + public var description: String { + switch self { + case .billingAddress: + return "billingAddress" + case .cardField: + return "cardField" + case .shippingAddress: + return "shippingAddress" + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/CardElementConfigService.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/CardElementConfigService.swift new file mode 100644 index 00000000..c3ceb972 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/CardElementConfigService.swift @@ -0,0 +1,83 @@ +// +// CardElementConfigService.swift +// StripePaymentsUI +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments + +private let CardElementConfigEndpoint = URL(string: "https://merchant-ui-api.stripe.com/elements/mobile-card-element-config")! + +class CardElementConfigService { + // The card element does not currently support non-singleton API clients, use the shared one for now. + var apiClient = STPAPIClient.shared + + static let shared = CardElementConfigService() + + struct CardElementConfig: Decodable { + struct CardBrandChoice: Decodable { + let eligible: Bool + } + let cardBrandChoice: CardBrandChoice + } + + enum CardElementConfigFetchState { + case fetching + case failed + case cached(CardElementConfig) + } + + // We only want to query once per process per PK, as the result should not + // change for an individual PK over the lifetime of a process. + private var _configsForPK: [String: CardElementConfigFetchState] = [:] + + func isCBCEligible(onBehalfOf: String? = nil) -> Bool { + guard let publishableKey = apiClient.publishableKey else { + // User has not yet initialized a PK, bail + return false + } + + let cacheKey = publishableKey + (onBehalfOf ?? "") + + if let fetchState = _configsForPK[cacheKey] { + switch fetchState { + case .fetching: + // Still waiting for a config, so we don't yet know if the user is CBC-eligible. + return false + case .failed: + // If something went wrong, don't fetch again for the life of the process. + return false + case .cached(let cardElementConfig): + return cardElementConfig.cardBrandChoice.eligible + } + } + + // Kick off a fetch request + _configsForPK[cacheKey] = .fetching + + let resultHandler: (Result) -> Void = { result in + DispatchQueue.main.async { + switch result { + case .success(let cardElementConfig): + // Cache the result for the next time the card element is presented + self._configsForPK[cacheKey] = .cached(cardElementConfig) + case .failure: + // Ignore failures, but send an analytic to the server + self._configsForPK[cacheKey] = .failed + STPAnalyticsClient.sharedClient.logCardElementConfigLoadFailed() + } + } + } + + var parameters: [String: Any] = [:] + if let onBehalfOf { + parameters["on_behalf_of"] = onBehalfOf + } + + apiClient.get(url: CardElementConfigEndpoint, parameters: parameters, ephemeralKeySecret: nil, completion: resultHandler) + + // No answer yet, so we don't know if the user is CBC-eligible + return false + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPBECSDebitAccountNumberValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPBECSDebitAccountNumberValidator.swift new file mode 100644 index 00000000..5895ef00 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPBECSDebitAccountNumberValidator.swift @@ -0,0 +1,84 @@ +// +// STPBECSDebitAccountNumberValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/13/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments + +class STPBECSDebitAccountNumberValidator: STPNumericStringValidator { + class func validationState( + forText text: String, + withBSBNumber bsbNumber: String?, + completeOnMaxLengthOnly: Bool + ) -> STPTextValidationState { + let numericText = self.sanitizedNumericString(for: text) + if numericText.count == 0 { + return .empty + } else { + let accountLengthRange = self._accountNumberLengthRange(forBSBNumber: bsbNumber) + if numericText.count < accountLengthRange.location { + return .incomplete + } else if !completeOnMaxLengthOnly + && (NSLocationInRange(numericText.count, accountLengthRange) + || numericText.count == NSMaxRange(accountLengthRange)) + { + return .complete + } else if completeOnMaxLengthOnly && numericText.count == NSMaxRange(accountLengthRange) + { + return .complete + } else if completeOnMaxLengthOnly + && NSLocationInRange(numericText.count, accountLengthRange) + { + return .incomplete + } else { + return .invalid + } + } + } + + class func formattedSanitizedText( + from string: String, + withBSBNumber bsbNumber: String? + ) + -> String? + { + let accountLengthRange = self._accountNumberLengthRange(forBSBNumber: bsbNumber) + return self.sanitizedNumericString(for: string).stp_safeSubstring( + to: NSMaxRange(accountLengthRange) + ) + } + + class func _accountNumberLengthRange(forBSBNumber bsbNumber: String?) -> NSRange { + // For a few banks we know how many digits the account number *should* have, + // but we still allow users to enter up to 9 digits just in case some bank + // decides to add more digits on. + let firstTwo = bsbNumber?.stp_safeSubstring(to: 2) + if firstTwo == "00" { + // Stripe + return NSRange(location: 9, length: 0) + } else if firstTwo == "06" { + // Commonwealth/CBA: 8 digits https://www.commbank.com.au/support.digital-banking.confirm-account-number-digits.html + return NSRange(location: 8, length: 1) + } else if (firstTwo == "03") || (firstTwo == "73") { + // Westpac/WBC: 6 digits + return NSRange(location: 6, length: 3) + } else if firstTwo == "01" { + // ANZ: 9 digits https://www.anz.com.au/support/help/ + return NSRange(location: 9, length: 0) + } else if firstTwo == "08" { + // NAB: 9 digits https://www.nab.com.au/business/accounts/business-accounts-online-application-help + return NSRange(location: 9, length: 0) + } else if firstTwo == "80" { + // Cuscal: 4 digits(?) https://groups.google.com/a/stripe.com/d/msg/au-becs-debits-archive/EERH5iITxQ4/Ksb84bV1AQAJ + return NSRange(location: 4, length: 5) + } else { + // Default 5-9 digits + return NSRange(location: 5, length: 4) + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPBSBNumberValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPBSBNumberValidator.swift new file mode 100644 index 00000000..b06e3d85 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPBSBNumberValidator.swift @@ -0,0 +1,143 @@ +// +// STPBSBNumberValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/5/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +class STPBSBNumberValidator: STPNumericStringValidator { + class func validationState(forText text: String) -> STPTextValidationState { + let numericText = self.sanitizedNumericString(for: text) + if numericText.count == 0 { + return .empty + } else if numericText.count > kBSBNumberLength { + return .invalid + } else { + if !self._isPossibleValidBSBNumber(numericText) { + return .invalid + } else { + return (numericText.count == kBSBNumberLength) ? .complete : .incomplete + } + } + } + + @objc(formattedSanitizedTextFromString:) class func formattedSanitizedText( + from string: String + ) + -> String? + { + var numericText = self.sanitizedNumericString(for: string).stp_safeSubstring( + to: kBSBNumberLength + ) + if numericText.count >= kBSBNumberDashIndex { + numericText.insert( + contentsOf: "-", + at: numericText.index(numericText.startIndex, offsetBy: kBSBNumberDashIndex) + ) + } + + return numericText + } + + class func identity(forText text: String) -> String? { + return self._data(forText: text)?["name"] as? String + } + + class func icon(forText text: String?) -> UIImage { + + let iconName = self._data(forText: text ?? "")?["icon"] as? String + if let iconName = iconName { + return StripeUICore.Image.brandImage(named: iconName)?.makeImage() + ?? STPImageLibrary.safeImageNamed(iconName, templateIfAvailable: false) + } else { + return STPImageLibrary.safeImageNamed("stp_icon_bank", templateIfAvailable: false) + } + } + + class func _isPossibleValidBSBNumber(_ text: String) -> Bool { + if text.count == 0 || self.identity(forText: text) != nil { + // this is faster than iterating through keys so try it first + return true + } else { + let bsbData = self._BSBData() + for key in bsbData.keys { + guard let key = key as? String else { + continue + } + if key.count > text.count && key.hasPrefix(text) { + return true + } + } + return false + } + + } + + static let _BSBDataSBSBData: [AnyHashable: Any] = { + var bsbData: [AnyHashable: Any] = [:] + if let url = StripePaymentsUIBundleLocator.resourcesBundle.url( + forResource: "au_becs_bsb", + withExtension: "json" + ), + let inputStream = InputStream.init(url: url) + { + inputStream.open() + if let jsonData = try? JSONSerialization.jsonObject(with: inputStream, options: []) + as? [AnyHashable: Any] + { + bsbData = jsonData + } + inputStream.close() + } + return bsbData + }() + + class func _BSBData() -> [AnyHashable: Any] { + if let key = STPAPIClient.shared.publishableKey, key.contains("_test_") { + var editedBSBData = _BSBDataSBSBData + // Add Stripe Test Bank + editedBSBData["00"] = [ + "name": "Stripe Test Bank", + "icon": "stripe", + ] + return editedBSBData + } + + return _BSBDataSBSBData + } + + static let _dataSBSBKeyLengths: [Int] = { + var keyLengths = Set() + for (bsbKey, _) in _BSBData() { + if let bsbKey = bsbKey as? String { + keyLengths.insert(bsbKey.count) + } + } + let orderedKeyLengths = [Int](keyLengths).sorted().reversed() + return [Int](orderedKeyLengths) + }() + + class func _data(forText text: String) -> [AnyHashable: Any]? { + + let bsbData = self._BSBData() + + for keyLength in _dataSBSBKeyLengths { + let subString = text.stp_safeSubstring(to: keyLength) + if let data = bsbData[subString] { + return data as? [AnyHashable: Any] + } + } + + return nil + } +} + +private let kBSBNumberLength = Int(6) +private let kBSBNumberDashIndex = Int(3) diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPCBCController.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPCBCController.swift new file mode 100644 index 00000000..0c50267c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPCBCController.swift @@ -0,0 +1,166 @@ +// +// STPCBCController.swift +// StripePaymentsUI +// +// Created by David Estes on 11/13/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +/// A controller to handle CBC state +/// Update the `cardNumber` as the card number changes. Brands will be fetched and returned via the `updateHandler()` automatically. +class STPCBCController { + /// Set this when the card number changes. + var cardNumber: String? { + didSet { + fetchCardBrands() + } + } + + var updateHandler: (() -> Void)? + + /// Initialize an STPCBCController. + /// updateHandler: A block that will be called when the list of available brands updates. Use this to update your UI. + init(updateHandler: (() -> Void)? = nil) { + self.updateHandler = updateHandler + } + + var selectedBrand: STPCardBrand? { + didSet { + self.updateHandler?() + } + } + + var cardBrands = Set() { + didSet { + // If the selected brand does not exist in the current list of brands, reset it + if let selectedBrand = selectedBrand, !cardBrands.contains(selectedBrand) { + self.selectedBrand = nil + } + // If the selected brand is nil and our preferred brand exists, set that as the selected brand + if let preferredNetworks = preferredNetworks, + selectedBrand == nil, + let preferredBrand = preferredNetworks.first(where: { cardBrands.contains($0) }) { + self.selectedBrand = preferredBrand + } + } + } + + var preferredNetworks: [STPCardBrand]? + + func fetchCardBrands() { + // Only fetch card brands if we have at least 8 digits in the pan + guard cbcEnabled, + let cardNumber = cardNumber, + cardNumber.count >= 8 else { + // Clear any previously fetched card brands from the dropdown + if self.cardBrands != Set() { + self.cardBrands = Set() + updateHandler?() + } + return + } + + var fetchedCardBrands = Set() + STPCardValidator.possibleBrands(forNumber: cardNumber) { [weak self] result in + switch result { + case .success(let brands): + fetchedCardBrands = brands + case .failure: + // If we fail to fetch card brands fall back to normal card brand detection + fetchedCardBrands = Set() + } + + if self?.cardBrands != fetchedCardBrands { + self?.cardBrands = fetchedCardBrands + self?.updateHandler?() + } + } + } + + var cbcEnabledOverride: Bool? + + var onBehalfOf: String? + + var cbcEnabled: Bool { + if let cbcEnabledOverride = cbcEnabledOverride { + return cbcEnabledOverride + } + return CardElementConfigService.shared.isCBCEligible(onBehalfOf: onBehalfOf) + } + + enum BrandState: Equatable { + case brand(STPCardBrand) + case cbcBrandSelected(STPCardBrand) + case unknown + case unknownMultipleOptions + + var isCBC: Bool { + switch self { + case .brand, .unknown: + return false + case .cbcBrandSelected, .unknownMultipleOptions: + return true + } + } + + var brand: STPCardBrand { + switch self { + case .brand(let brand): + return brand + case .cbcBrandSelected(let brand): + return brand + case .unknown, .unknownMultipleOptions: + return .unknown + } + } + } + + var brandState: BrandState { + if cbcEnabled { + if cardBrands.count > 1 { + if let selectedBrand = selectedBrand { + return .cbcBrandSelected(selectedBrand) + } + return .unknownMultipleOptions + } + if let cardBrand = cardBrands.first { + return .brand(cardBrand) + } + return .unknown + } else { + // Otherwise, return the brand for the number + return .brand(STPCardValidator.brand(forNumber: cardNumber ?? "")) + } + } + + // Instead of validating against the selected brand (for CBC purposes), + // validate CVCs against the default brand of the PAN. + // We can assume that the CVC length will not change based on the choice of card brand. + var brandForCVC: STPCardBrand { + return STPCardValidator.brand(forNumber: cardNumber ?? "") + } + + var contextMenuConfiguration: UIContextMenuConfiguration { + return UIContextMenuConfiguration(actionProvider: { _ in + let action = { (action: UIAction) in + let brand = STPCard.brand(from: action.identifier.rawValue) + // Set the selected brand if a brand is selected + self.selectedBrand = brand != .unknown ? brand : nil + } + let placeholderAction = UIAction(title: String.Localized.card_brand_dropdown_placeholder, attributes: .disabled, handler: action) + let menu = UIMenu(children: + [placeholderAction] + + self.cardBrands.enumerated().map { (_, brand) in + let brandString = STPCard.string(from: brand) + return UIAction(title: brandString, image: STPImageLibrary.unpaddedCardBrandImage(for: brand), identifier: .init(rawValue: brandString), state: self.selectedBrand == brand ? .on : .off, handler: action) + } + ) + return menu + }) + } + +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPImageLibrary.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPImageLibrary.swift new file mode 100644 index 00000000..ccacc626 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPImageLibrary.swift @@ -0,0 +1,181 @@ +// +// STPImageLibrary.swift +// StripePaymentsUI +// +// Created by Jack Flintermann on 6/30/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +/// This class lets you access card icons used by the Stripe SDK. All icons are 32 x 20 points. +public class STPImageLibrary: NSObject { + /// An icon representing Apple Pay. + @objc + public class func applePayCardImage() -> UIImage { + return self.safeImageNamed("stp_card_applepay") + } + + /// An icon representing American Express. + @objc + public class func amexCardImage() -> UIImage { + return self.cardBrandImage(for: .amex) + } + + /// An icon representing Diners Club. + @objc + public class func dinersClubCardImage() -> UIImage { + return self.cardBrandImage(for: .dinersClub) + } + + /// An icon representing Discover. + @objc + public class func discoverCardImage() -> UIImage { + return self.cardBrandImage(for: .discover) + } + + /// An icon representing JCB. + @objc + public class func jcbCardImage() -> UIImage { + return self.cardBrandImage(for: .JCB) + } + + /// An icon representing Mastercard. + @objc + public class func mastercardCardImage() -> UIImage { + return self.cardBrandImage(for: .mastercard) + } + + /// An icon representing UnionPay. + @objc + public class func unionPayCardImage() -> UIImage { + return self.cardBrandImage(for: .unionPay) + } + + /// An icon representing Visa. + @objc + public class func visaCardImage() -> UIImage { + return self.cardBrandImage(for: .visa) + } + + /// An icon to use when the type of the card is unknown. + @objc + public class func unknownCardCardImage() -> UIImage { + return self.cardBrandImage(for: .unknown) + } + + /// This returns the appropriate icon for the specified card brand. + @objc(brandImageForCardBrand:) public class func cardBrandImage( + for brand: STPCardBrand + ) + -> UIImage + { + return self.brandImage(for: brand, template: false) + } + + /// This returns an unpadded image for the specified card brand if available. + @_spi(STP) public class func unpaddedCardBrandImage( + for brand: STPCardBrand + ) + -> UIImage + { + switch brand { + case .cartesBancaires: + return safeImageNamed("stp_card_unpadded_cartes_bancaires") + case .visa: + return safeImageNamed("stp_card_unpadded_visa") + case .amex: + return safeImageNamed("stp_card_unpadded_amex") + case .mastercard: + return safeImageNamed("stp_card_unpadded_mastercard") + case .dinersClub: + return safeImageNamed("stp_card_unpadded_diners_club") + case .unionPay: + return safeImageNamed("stp_card_unpadded_unionpay") + case .discover: + return safeImageNamed("stp_card_unpadded_discover") + case .JCB: + return safeImageNamed("stp_card_unpadded_jcb") + case .unknown: + fallthrough + @unknown default: + return self.brandImage(for: brand, template: false) + } + } + + /// This returns the icon for an unselected brand when multiple card brands are available. + @objc(cardBrandChoiceImage) public class func cardBrandChoiceImage() + -> UIImage + { + return self.safeImageNamed("stp_card_cbc", templateIfAvailable: false) + } + + /// This returns the appropriate icon for the specified card brand as a + /// single color template that can be tinted + @objc(templatedBrandImageForCardBrand:) public class func templatedBrandImage( + for brand: STPCardBrand + ) -> UIImage { + return self.brandImage(for: brand, template: true) + } + + /// This returns a small icon indicating the CVC location for the given card brand. + @objc(cvcImageForCardBrand:) public class func cvcImage(for brand: STPCardBrand) -> UIImage { + let imageName = brand == .amex ? "stp_card_cvc_amex" : "stp_card_cvc" + return self.safeImageNamed(imageName) + } + + /// This returns a small icon indicating a card number error for the given card brand. + @objc(errorImageForCardBrand:) public class func errorImage(for brand: STPCardBrand) -> UIImage + { + return self.safeImageNamed("stp_card_error") + } + + @_spi(STP) public class func bankIcon() -> UIImage { + return self.safeImageNamed("stp_icon_bank", templateIfAvailable: true) + } + + class func brandImage( + for brand: STPCardBrand, + template shouldUseTemplate: Bool, + locale: Locale = .current + ) -> UIImage { + var imageName: String? + switch brand { + case .amex: + imageName = shouldUseTemplate ? "stp_card_amex_template" : "stp_card_amex" + case .dinersClub: + imageName = shouldUseTemplate ? "stp_card_diners_template" : "stp_card_diners" + case .discover: + imageName = shouldUseTemplate ? "stp_card_discover_template" : "stp_card_discover" + case .JCB: + imageName = shouldUseTemplate ? "stp_card_jcb_template" : "stp_card_jcb" + case .mastercard: + imageName = shouldUseTemplate ? "stp_card_mastercard_template" : "stp_card_mastercard" + case .unionPay: + imageName = shouldUseTemplate ? "stp_card_unionpay_template" : "stp_card_unionpay" + case .cartesBancaires: + imageName = shouldUseTemplate ? "stp_card_cartes_bancaires_template" : "stp_card_cartes_bancaires" + case .unknown: + imageName = "stp_card_unknown" + case .visa: + imageName = shouldUseTemplate ? "stp_card_visa_template" : "stp_card_visa" + @unknown default: + imageName = "stp_card_unknown" + } + let image = self.safeImageNamed( + imageName ?? "", + templateIfAvailable: shouldUseTemplate + ) + return image + } +} + +// MARK: - ImageMaker + +/// :nodoc: +@_spi(STP) extension STPImageLibrary: ImageMaker { + @_spi(STP) public typealias BundleLocator = StripePaymentsUIBundleLocator +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPLocalizedString.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPLocalizedString.swift new file mode 100644 index 00000000..e213a30c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPLocalizedString.swift @@ -0,0 +1,16 @@ +// +// STPLocalizedString.swift +// StripePaymentsUI +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@inline(__always) func STPLocalizedString(_ key: String, _ comment: String?) -> String { + return STPLocalizationUtils.localizedStripeString( + forKey: key, + bundleLocator: StripePaymentsUIBundleLocator.self + ) +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPhoneNumberValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPhoneNumberValidator.swift new file mode 100644 index 00000000..68c828ed --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPhoneNumberValidator.swift @@ -0,0 +1,91 @@ +// +// STPPhoneNumberValidator.swift +// StripePaymentsUI +// +// Created by Jack Flintermann on 10/16/15. +// Copyright © 2015 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments + +@_spi(STP) public class STPPhoneNumberValidator: NSObject { + + @objc(formattedSanitizedPhoneNumberForString:) class func formattedSanitizedPhoneNumber( + for string: String + ) -> String { + return self.formattedSanitizedPhoneNumber( + for: string, + forCountryCode: nil + ) + } + + @objc(formattedSanitizedPhoneNumberForString:forCountryCode:) + class func formattedSanitizedPhoneNumber( + for string: String, + forCountryCode nillableCode: String? + ) -> String { + let countryCode = self.countryCodeOrCurrentLocaleCountry(from: nillableCode) + let sanitized = STPCardValidator.sanitizedNumericString(for: string) + return self.formattedPhoneNumber( + for: sanitized, + forCountryCode: countryCode + ) + } + + @objc(formattedRedactedPhoneNumberForString:) class func formattedRedactedPhoneNumber( + for string: String + ) -> String { + return self.formattedRedactedPhoneNumber( + for: string, + forCountryCode: nil + ) + } + + @objc(formattedRedactedPhoneNumberForString:forCountryCode:) + class func formattedRedactedPhoneNumber( + for string: String, + forCountryCode nillableCode: String? + ) -> String { + let countryCode = self.countryCodeOrCurrentLocaleCountry(from: nillableCode) + let scanner = Scanner(string: string) + var prefix: NSString? = NSString() + prefix = scanner.scanUpToString("*") as NSString? + var number = (string as NSString).replacingOccurrences( + of: (prefix ?? "") as String, + with: "" + ) + number = number.replacingOccurrences(of: "*", with: "•") + number = self.formattedPhoneNumber( + for: number, + forCountryCode: countryCode + ) + return "\(prefix ?? "") \(number)" + } + + class func countryCodeOrCurrentLocaleCountry(from nillableCode: String?) -> String { + var countryCode = nillableCode + if countryCode == nil { + countryCode = NSLocale.autoupdatingCurrent.stp_regionCode + } + return countryCode ?? "" + } + + class func formattedPhoneNumber( + for string: String, + forCountryCode countryCode: String + ) -> String { + + if !(countryCode == "US") { + return string + } + if string.count >= 6 { + return + "(\(string.stp_safeSubstring(to: 3))) \(string.stp_safeSubstring(to: 6).stp_safeSubstring(from: 3))-\(string.stp_safeSubstring(to: 10).stp_safeSubstring(from: 6))" + } else if string.count >= 3 { + return "(\(string.stp_safeSubstring(to: 3))) \(string.stp_safeSubstring(from: 3))" + } + return string + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPostalCodeValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPostalCodeValidator.swift new file mode 100644 index 00000000..f8179ef5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPostalCodeValidator.swift @@ -0,0 +1,296 @@ +// +// STPPostalCodeValidator.swift +// StripePaymentsUI +// +// Created by Ben Guo on 4/14/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +@objc enum STPPostalCodeIntendedUsage: Int { + case billingAddress + case shippingAddress + case cardField +} + +@objc @_spi(STP) public enum STPPostalCodeRequirement: Int { + case standard + case upe +} + +@_spi(STP) public class STPPostalCodeValidator: NSObject { + @_spi(STP) public class func postalCodeIsRequired(forCountryCode countryCode: String?) -> Bool { + if countryCode == nil { + return true + } else { + return + !(self.countriesWithNoPostalCodes()?.contains(countryCode?.uppercased() ?? "") + ?? false) + } + } + + class func postalCodeIsRequiredForUPE(forCountryCode countryCode: String?) -> Bool { + guard let countryCode = countryCode else { return false } + return self.countriesWithPostalRequiredForUPE().contains(countryCode.uppercased()) + } + + class func postalCodeIsRequired( + forCountryCode countryCode: String?, + with postalRequirement: STPPostalCodeRequirement + ) -> Bool { + switch postalRequirement { + case .standard: + return postalCodeIsRequired(forCountryCode: countryCode) + case .upe: + return postalCodeIsRequiredForUPE(forCountryCode: countryCode) + } + } + + @_spi(STP) public class func validationState( + forPostalCode postalCode: String?, + countryCode: String?, + with postalRequirement: STPPostalCodeRequirement = .standard + ) -> STPCardValidationState { + let sanitizedCountryCode = countryCode?.uppercased() + if self.postalCodeIsRequired(forCountryCode: countryCode, with: postalRequirement) { + if sanitizedCountryCode == STPCountryCodeUnitedStates { + return self.validationState(forUSPostalCode: postalCode) + } else { + if (postalCode?.count ?? 0) > 0 { + return .valid + } else { + return .incomplete + } + } + } else { + return .valid + } + } + + @objc(formattedSanitizedPostalCodeFromString:countryCode:usage:) + class func formattedSanitizedPostalCode( + from postalCode: String?, + countryCode: String?, + usage: STPPostalCodeIntendedUsage + ) -> String? { + let sanitizedCountryCode = countryCode?.uppercased() + if usage != .cardField && (sanitizedCountryCode == STPCountryCodeUnitedStates) { + return self.formattedSanitizedUSZipCode( + from: postalCode, + usage: usage + ) + } else { + return self.formattedSanitizedPostalCode(from: postalCode) + } + + } + + class func countOfCharactersFromSetInString(_ string: String, _ cs: CharacterSet) -> Int { + var range = (string as NSString).rangeOfCharacter(from: cs) + var count = 0 + if range.location != NSNotFound { + var lastPosition = NSMaxRange(range) + count += range.length + while lastPosition < string.count { + range = (string as NSString).rangeOfCharacter( + from: cs, + options: [], + range: NSRange(location: lastPosition, length: string.count - lastPosition) + ) + if range.location == NSNotFound { + break + } else { + count += range.length + lastPosition = NSMaxRange(range) + } + } + } + + return count + } + + class func validationState(forUSPostalCode postalCode: String?) -> STPCardValidationState { + let firstFive = postalCode?.stp_safeSubstring(to: 5) + let firstFiveLength = firstFive?.count ?? 0 + let totalLength = postalCode?.count ?? 0 + + let firstFiveIsNumeric = STPCardValidator.stringIsNumeric(firstFive ?? "") + if !firstFiveIsNumeric { + // Non-numbers included in first five characters + return .invalid + } else if firstFiveLength < 5 { + // Incomplete ZIP with only numbers + return .incomplete + } else if totalLength == 5 { + // Valid 5 digit zip + return .valid + } else { + // ZIP+4 territory + let numberOfDigits = countOfCharactersFromSetInString( + postalCode ?? "", + CharacterSet.stp_asciiDigit + ) + + if numberOfDigits > 9 { + // Too many digits + return .invalid + } else if numberOfDigits == totalLength { + // All numeric postal code entered + if numberOfDigits == 9 { + return .valid + } else { + return .incomplete + } + } else if (numberOfDigits + 1) == totalLength { + // Possibly has a separator character for ZIP+4, check to see if + // its in the right place + + let separatorCharacter = (postalCode as NSString?)?.substring( + with: NSRange(location: 5, length: 1) + ) + if countOfCharactersFromSetInString( + separatorCharacter ?? "", + CharacterSet.stp_asciiDigit + ) + == 0 + { + // Non-digit is in right position to be separator + if numberOfDigits == 9 { + return .valid + } else { + return .incomplete + } + } else { + // Non-digit is in wrong position to be separator + return .invalid + } + } else { + // Not a valid zip code (too many non-numeric characters) + return .invalid + } + } + } + + class func formattedSanitizedPostalCode(from zipCode: String?) -> String? { + let formattedString = STPCardValidator.sanitizedPostalString(for: zipCode ?? "") + return formattedString.uppercased() + } + + class func formattedSanitizedUSZipCode( + from zipCode: String?, + usage: STPPostalCodeIntendedUsage + ) -> String? { + guard let zipCode = zipCode else { + return nil + } + var maxLength = 0 + switch usage { + case .billingAddress, .cardField: + maxLength = 5 + case .shippingAddress: + maxLength = 9 + } + + var formattedString = STPCardValidator.sanitizedNumericString(for: zipCode) + .stp_safeSubstring( + to: maxLength + ) + + // If the string is >5 numbers or == 5 and the last char of the unformatted + // string was already a hyphen, insert a hyphen at position 6 for ZIP+4 + if formattedString.count > 5 + || formattedString.count == 5 + && (zipCode as NSString).substring(from: zipCode.count - 1) == "-" + { + formattedString.insert( + contentsOf: "-", + at: formattedString.index(formattedString.startIndex, offsetBy: 5) + ) + } + + return formattedString + } + + class func countriesWithPostalRequiredForUPE() -> [AnyHashable] { + return ["CA", "GB", "US"] + } + + class func countriesWithNoPostalCodes() -> [AnyHashable]? { + return [ + "AE", + "AG", + "AN", + "AO", + "AW", + "BF", + "BI", + "BJ", + "BO", + "BS", + "BW", + "BZ", + "CD", + "CF", + "CG", + "CI", + "CK", + "CM", + "DJ", + "DM", + "ER", + "FJ", + "GD", + "GH", + "GM", + "GN", + "GQ", + "GY", + "HK", + "IE", + "JM", + "JP", + "KE", + "KI", + "KM", + "KN", + "KP", + "LC", + "ML", + "MO", + "MR", + "MS", + "MU", + "MW", + "NR", + "NU", + "PA", + "QA", + "RW", + "SB", + "SC", + "SL", + "SO", + "SR", + "ST", + "SY", + "TF", + "TK", + "TL", + "TO", + "TT", + "TV", + "TZ", + "UG", + "VU", + "YE", + "ZA", + "ZW", + ] + } +} + +private let STPCountryCodeUnitedStates = "US" diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift new file mode 100644 index 00000000..7e221a26 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift @@ -0,0 +1,126 @@ +// +// STPPromise.swift +// StripeiOS +// +// Created by Jack Flintermann on 4/20/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public class STPPromise: NSObject { + @_spi(STP) public typealias STPPromiseErrorBlock = (Error) -> Void + + @_spi(STP) public typealias STPPromiseValueBlock = (T) -> Void + + @_spi(STP) public typealias STPPromiseCompletionBlock = (T?, Error?) -> Void + + @_spi(STP) public typealias STPPromiseFlatMapBlock = (T) -> STPPromise + + @_spi(STP) public var completed: Bool { + return error != nil || value != nil + } + private(set) var value: T? + private(set) var error: Error? + + private var successCallbacks: [STPPromiseValueBlock] = [] + private var errorCallbacks: [STPPromiseErrorBlock] = [] + + convenience init( + error: Error + ) { + self.init() + self.fail(error) + } + + convenience init( + value: T + ) { + self.init() + self.succeed(value) + } + @_spi(STP) public func succeed(_ value: T) { + if completed { + return + } + self.value = value + stpDispatchToMainThreadIfNecessary({ + for valueBlock in self.successCallbacks { + valueBlock(value) + } + self.successCallbacks = [] + self.errorCallbacks = [] + }) + } + + @_spi(STP) public func fail(_ error: Error) { + if completed { + return + } + self.error = error + stpDispatchToMainThreadIfNecessary({ + for errorBlock in self.errorCallbacks { + errorBlock(error) + } + self.successCallbacks = [] + self.errorCallbacks = [] + }) + } + + @_spi(STP) public func complete(with promise: STPPromise) { + weak var weakSelf = self + promise.onSuccess({ value in + let strongSelf = weakSelf + strongSelf?.succeed(value) + }).onFailure({ error in + let strongSelf = weakSelf + strongSelf?.fail(error) + }) + } + + @discardableResult @_spi(STP) public func onSuccess(_ callback: @escaping STPPromiseValueBlock) -> Self { + if let value = value { + stpDispatchToMainThreadIfNecessary({ + callback(value) + }) + } else { + successCallbacks = successCallbacks + [callback] + } + return self + } + + @discardableResult @_spi(STP) public func onFailure(_ callback: @escaping STPPromiseErrorBlock) -> Self { + if let error = error { + stpDispatchToMainThreadIfNecessary({ + callback(error) + }) + } else { + errorCallbacks = errorCallbacks + [callback] + } + return self + } + + @discardableResult func onCompletion(_ callback: @escaping STPPromiseCompletionBlock) -> Self { + return onSuccess({ value in + callback(value, nil) + }).onFailure({ error in + callback(nil, error) + }) + } + + @discardableResult func flatMap(_ callback: @escaping STPPromiseFlatMapBlock) -> STPPromise { + let wrapper = STPPromise.init() + onSuccess({ value in + let `internal` = callback(value) + `internal`.onSuccess({ internalValue in + wrapper.succeed(internalValue) + }).onFailure({ internalError in + wrapper.fail(internalError) + }) + }).onFailure({ error in + wrapper.fail(error) + }) + return wrapper + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPStringUtils.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPStringUtils.swift new file mode 100644 index 00000000..b2f80c0c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPStringUtils.swift @@ -0,0 +1,69 @@ +// +// STPStringUtils.swift +// StripePaymentsUI +// +// Created by Brian Dorfman on 9/7/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public class STPStringUtils: NSObject { + // This code was adapted from Stripe.js + /// Reformats an expiration date with a four-digit year to one with a two digit year. + /// Ex: `01/2021` to `01/21`. + static let expirationDateStringRegex: NSRegularExpression = { + return try! NSRegularExpression( + pattern: "^(\\d{2}\\D{1,3})(\\d{1,4})?", + options: [] + ) + }() + + @objc(expirationDateStringFromString:) @_spi(STP) public class func expirationDateString( + from string: String? + ) + -> String? + { + guard let string = string else { + return nil + } + guard + let result = expirationDateStringRegex.matches( + in: string, + options: [], + range: NSRange(location: 0, length: string.count) + ).first + else { + return string + } + if result.numberOfRanges > 1 && result.range(at: 2).length == 4 { + // If a 4-digit year was pasted, shorten it to the last 2 digits + var range = result.range(at: 2) + range.length = 2 + range.location = (range.location) + 2 + let month = (string as NSString).substring(with: result.range(at: 1)) + let year = (string as NSString).substring(with: range) + return "\(month)\(year)" + } + + return string + } + + static let stringMayContainExpirationDateRegex: NSRegularExpression? = { + return try! NSRegularExpression( + pattern: "^(\\d{2}\\D{1,3})(\\d{1,4})?", + options: [] + ) + }() + + /// Returns YES if the string is likely to contain something formatted similar to an expiration date. + /// It doesn't confirm that the expiration date is valid, or that it is even a date. + @_spi(STP) public class func stringMayContainExpirationDate(_ string: String?) -> Bool { + let result = stringMayContainExpirationDateRegex?.matches( + in: string ?? "", + options: [], + range: NSRange(location: 0, length: string?.count ?? 0) + ).first + return result != nil && (result?.numberOfRanges ?? 0) > 0 + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift new file mode 100644 index 00000000..2caf73ce --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift @@ -0,0 +1,164 @@ +// +// String+Localized.swift +// StripePaymentsUI +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore + +// Localized strings that are used in multiple contexts. Collected here to avoid re-translation +// We use snake case to make long names easier to read. +extension String.Localized { + @_spi(STP) public static var bank_account: String { + STPLocalizedString("Bank Account", "Label for Bank Account selection or detail entry form") + } + + @_spi(STP) public static var card_number: String { + STPLocalizedString("Card number", "Label for card number entry text field") + } + + @_spi(STP) public static var card_brand_ending_in_last_4: String { + STPLocalizedString( + "%1$@ ending in %2$@", + "Details of a saved card. '{card brand} ending in {last 4}' e.g. 'VISA ending in 4242'" + ) + } + + @_spi(STP) public static var bank_name_account_ending_in_last_4: String { + STPLocalizedString( + "%1$@ account ending in %2$@", + "Details of a saved bank account. '{bank name} account ending in {last 4}' e.g. 'Chase account ending in 4242'" + ) + } + + @_spi(STP) public static var apple_pay: String { + STPLocalizedString("Apple Pay", "Text for Apple Pay payment method") + } + + @_spi(STP) public static var expiration_date_accessibility_label: String { + STPLocalizedString("expiration date", "accessibility label for text field") + } + + @_spi(STP) public static var allow_camera_access: String { + STPLocalizedString( + "To scan your card, allow camera access in Settings.", + "Error when the user hasn't allowed the current app to access the camera when scanning a payment card. 'Settings' is the localized name of the iOS Settings app." + ) + } + + @_spi(STP) public static var shipping_address: String { + STPLocalizedString("Shipping Address", "Title for shipping address entry section") + } + + @_spi(STP) public static var billing_address_lowercase: String { + STPLocalizedString("Billing address", "Billing address section title for card form entry.") + } + + @_spi(STP) public static var your_card_number_is_incomplete: String { + STPLocalizedString( + "Your card number is incomplete.", + "Error message for card form when card number is incomplete" + ) + } + + @_spi(STP) public static var your_card_number_is_invalid: String { + STPLocalizedString( + "Your card number is invalid.", + "Error message for card form when card number is invalid" + ) + } + @_spi(STP) public static var cvc_section_title: String { + STPLocalizedString( + "Confirm your %@", + "Section title for entering your CVC. e.g. 'Confirm your CVC'" + ) + } + + @_spi(STP) public static var cvc: String { + STPLocalizedString("CVC", "Label for entering CVC in text field") + } + + @_spi(STP) public static var card_information: String { + STPLocalizedString("Card information", "Card details entry form header title") + } + + @_spi(STP) public static var mm_yy: String { + STPLocalizedString("MM / YY", "label for text field to enter card expiry") + } + + @_spi(STP) public static var your_cards_security_code_is_incomplete: String { + STPLocalizedString( + "Your card's security code is incomplete.", + "Error message for card entry form when CVC is incomplete." + ) + } + + @_spi(STP) public static var your_cards_expiration_date_is_invalid: String { + STPLocalizedString( + "Your card's expiration date is invalid.", + "Error message for card details form when expiration date is invalid" + ) + } + + @_spi(STP) public static var your_card_has_expired: String { + STPLocalizedString( + "Your card has expired.", + "Error message for card details form when expiration date has passed" + ) + } + + @_spi(STP) public static var your_cards_expiration_date_is_incomplete: String { + STPLocalizedString( + "Your card's expiration date is incomplete.", + "Error message for card details form when expiration date isn't entered completely" + ) + } + + @_spi(STP) public static var your_cards_expiration_month_is_invalid: String { + STPLocalizedString( + "Your card's expiration month is invalid.", + "String to describe an invalid month in expiry date." + ) + } + + @_spi(STP) public static var your_cards_expiration_year_is_invalid: String { + STPLocalizedString( + "Your card's expiration year is invalid.", + "String to describe an invalid year in expiry date." + ) + } + + @_spi(STP) public static var brand_not_allowed: String { + STPLocalizedString( + "%1$@ is not accepted", + "String to inform a user that specific card brands are not accepted. E.g. American Express is not accepted" + ) + } + + @_spi(STP) public static var generic_brand_not_allowed: String { + STPLocalizedString( + "The selected brand is not allowed", + "String to inform a user that specific card brands are not accepted." + ) + } +} + +@_spi(STP) public struct StripeSharedStrings { + @_spi(STP) public static func localizedStateString(for countryCode: String?) -> String { + switch countryCode { + case "US": + return String.Localized.state + case "CA": + return String.Localized.province + case "GB": + return String.Localized.county + default: + return STPLocalizedString( + "State / Province / Region", + "Caption for generalized state/province/region field on address form (not tied to a specific country's format)" + ) + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/StripePayments+Export.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/StripePayments+Export.swift new file mode 100644 index 00000000..0e2731c2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/StripePayments+Export.swift @@ -0,0 +1,11 @@ +// +// StripePayments+Export.swift +// StripePaymentsUI +// +// Created by David Estes on 7/6/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_exported import StripeCore +@_exported import StripePayments diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/StripePaymentsBundleLocator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/StripePaymentsBundleLocator.swift new file mode 100644 index 00000000..ec1b8c05 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/StripePaymentsBundleLocator.swift @@ -0,0 +1,19 @@ +// +// StripePaymentsBundleLocator.swift +// StripePaymentsUI +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/// :nodoc: +@_spi(STP) public final class StripePaymentsUIBundleLocator: BundleLocatorProtocol { + public static let internalClass: AnyClass = StripePaymentsUIBundleLocator.self + public static let bundleName = "StripePaymentsUIBundle" + #if SWIFT_PACKAGE + public static let spmResourcesBundle = Bundle.module + #endif + public static let resourcesBundle = StripePaymentsUIBundleLocator.computeResourcesBundle() +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/NSAttributedString+Stripe.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/NSAttributedString+Stripe.swift new file mode 100644 index 00000000..a8c8a3d3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/NSAttributedString+Stripe.swift @@ -0,0 +1,19 @@ +// +// NSAttributedString+Stripe.swift +// StripePaymentsUI +// +// Created by Ramon Torres on 1/11/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +extension NSAttributedString { + + /// A range covering from the start to the end of the attributed string. + @_spi(STP) public var extent: NSRange { + return NSRange(location: 0, length: self.length) + } + +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/STPCardBrand+PaymentsUI.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/STPCardBrand+PaymentsUI.swift new file mode 100644 index 00000000..d9c3a7e4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/STPCardBrand+PaymentsUI.swift @@ -0,0 +1,42 @@ +// +// STPCardBrand+PaymentSheet.swift +// StripePaymentSheet +// +// Created by Nick Porter on 9/21/23. +// + +import Foundation +@_spi(STP) import StripeUICore +import UIKit + +extension STPCardBrand { + func brandIconAttributedString(theme: ElementsAppearance = .default, maxWidth: CGFloat? = nil) -> NSAttributedString { + let brandImageAttachment = NSTextAttachment() + let image: UIImage = self == .unknown ? STPImageLibrary.cardBrandChoiceImage() : STPImageLibrary.cardBrandImage(for: self) + brandImageAttachment.image = image + // TODO: -3 is a hack for proper vertical alignment, investigate this + let hackyVerticalInset: CGFloat = -3 + if let maxWidth = maxWidth { + let widthRatio = maxWidth / image.size.width + brandImageAttachment.bounds = .init(x: 0, y: hackyVerticalInset, width: maxWidth, height: image.size.height * widthRatio) + } else { + brandImageAttachment.bounds = .init(x: 0, y: hackyVerticalInset, width: image.size.width, height: image.size.height) + } + + return NSAttributedString(attachment: brandImageAttachment) + } + + func cardBrandItem(theme: ElementsAppearance = .default, maxWidth: CGFloat? = nil) -> DropdownFieldElement.DropdownItem { + let brandName = STPCardBrandUtilities.stringFrom(self) ?? "" + + let displayText = NSMutableAttributedString(attributedString: brandIconAttributedString(theme: theme)) + displayText.append(NSAttributedString(string: " " + brandName)) + + return DropdownFieldElement.DropdownItem( + pickerDisplayName: displayText, + labelDisplayName: brandIconAttributedString(theme: theme, maxWidth: maxWidth), + accessibilityValue: brandName, + rawData: STPCardBrandUtilities.apiValue(from: self) + ) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/UIButton+Stripe.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/UIButton+Stripe.swift new file mode 100644 index 00000000..5067b38a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/Categories/UIButton+Stripe.swift @@ -0,0 +1,47 @@ +// +// UIButton+Stripe.swift +// StripePaymentsUI +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +extension UIButton { + /// Sets the spacing between the button's image and title label. + /// + /// [UIButton: Padding Between Image and Text](https://noahgilmore.com/blog/uibutton-padding/) + /// + /// - Parameters: + /// - spacing: Space between image and title label. + /// - edgeInsets: Directional content edge insets. + @_spi(STP) public func setContentSpacing( + _ spacing: CGFloat, + withEdgeInsets edgeInsets: NSDirectionalEdgeInsets + ) { +// TODO: Rewrite this for visionOS & iOS 17. + #if canImport(CompositorServices) + #else + // UIButton doesn't have support for directional edge insets. We should + // apply insets depending on the layout direction. + if self.effectiveUserInterfaceLayoutDirection == .leftToRight { + self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing, bottom: 0, right: spacing) + self.contentEdgeInsets = UIEdgeInsets( + top: edgeInsets.top, + left: edgeInsets.leading + spacing, + bottom: edgeInsets.bottom, + right: edgeInsets.trailing + ) + } else { + self.imageEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: -spacing) + self.contentEdgeInsets = UIEdgeInsets( + top: edgeInsets.top, + left: edgeInsets.trailing, + bottom: edgeInsets.bottom, + right: edgeInsets.leading + spacing + ) + } +#endif + } + +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Elements/DropDownFieldElement+CardBrand.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Elements/DropDownFieldElement+CardBrand.swift new file mode 100644 index 00000000..c97de512 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Elements/DropDownFieldElement+CardBrand.swift @@ -0,0 +1,49 @@ +// +// DropDownFieldElement+CardBrand.swift +// StripeUICore +// +// Created by Nick Porter on 8/31/23. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +extension DropdownFieldElement { + + @_spi(STP) public static func makeCardBrandDropdown(cardBrands: Set = Set(), + theme: ElementsAppearance = .default, + includePlaceholder: Bool = true, + maxWidth: CGFloat? = nil, + hasPadding: Bool = true, + didPresent: DropdownFieldElement.DidPresent? = nil, + didTapClose: DropdownFieldElement.DidTapClose? = nil) -> DropdownFieldElement { + let dropDown = DropdownFieldElement( + items: items(from: cardBrands, theme: theme, includePlaceholder: includePlaceholder, maxWidth: maxWidth), + defaultIndex: 0, + label: nil, + theme: theme, + hasPadding: hasPadding, + isOptional: true, + didPresent: didPresent, + didTapClose: didTapClose + ) + dropDown.view.accessibilityIdentifier = "Card Brand Dropdown" + return dropDown + } + + @_spi(STP) public static func items(from cardBrands: Set, theme: ElementsAppearance, includePlaceholder: Bool = true, maxWidth: CGFloat? = nil) -> [DropdownItem] { + let placeholderItem = DropdownItem( + pickerDisplayName: NSAttributedString(string: .Localized.card_brand_dropdown_placeholder), + labelDisplayName: STPCardBrand.unknown.brandIconAttributedString(theme: theme, maxWidth: maxWidth), + accessibilityValue: .Localized.card_brand_dropdown_placeholder, + rawData: STPCardBrandUtilities.apiValue(from: .unknown), + isPlaceholder: true + ) + + let cardBrandItems = cardBrands.sorted().map { $0.cardBrandItem(theme: theme, maxWidth: maxWidth) } + return includePlaceholder ? [placeholderItem] + cardBrandItems : cardBrandItems + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift new file mode 100644 index 00000000..d0669b79 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift @@ -0,0 +1,262 @@ +// +// CardBrandView.swift +// StripePaymentsUI +// +// Created by Ramon Torres on 8/31/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +/// A view that displays a card brand icon/logo, or an icon to help the user locate the CVC +/// for a specific card brand. +/// For internal SDK use only +@objc(STP_Internal_CardBrandView) +@_spi(STP) public final class CardBrandView: UIView { + + // TODO(ramont): unify icon sizes. + + /// The size of legacy icons. + private static let legacyIconSize = CGSize(width: 29, height: 19) + + /// The target size of the rectangular part of the icon. + /// internal so we can reuse size with other icon views (see LinkPaymentMethodPicker-CellContentView) + @_spi(STP) public static let targetIconSize = CGSize(width: 24, height: 16) + + /// Padding built in to the icon. + /// + /// Card brand icons have baked-in top and right padding, so they align perfectly with the CVC icons. Bottom padding + /// is then added for vertically centering the icon; thus it must match the top padding. + /// + /// ``` + /// +----------------+ + /// | PADDING | + /// +------------+ | + /// | | | + /// | LOGO | | + /// | | | + /// +------------+---+ + /// | B. PADDING | + /// +----------------+ + /// ``` + /// + /// TODO(ramont): remove baked-in padding and perform alignment in code. + private static let iconPadding = UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 3) + private var centeringPadding: UIEdgeInsets { + return UIEdgeInsets( + top: 0, + left: centerHorizontally ? Self.iconPadding.right : 0, + bottom: Self.iconPadding.top, + right: 0 + ) + } + + lazy var cbcIndicatorView: UIImageView = { + let view = UIImageView(image: Image.icon_chevron_down.makeImage(template: true)) + view.tintColor = .placeholderText + return view + }() + + var cbcIndicatorSizeConstraint: NSLayoutConstraint? + + /// Card brand to display. + var cardBrandState: STPCBCController.BrandState = .unknown { + didSet { + updateIcon() + } + } + + /// If `true`, the view will display the CVC hint icon instead of the card brand image. + let showCVC: Bool + + /// If `true`, will center the card brand icon horizontally in the containing view + let centerHorizontally: Bool + + /// If `true`, show a CBC indicator arrow + var isShowingCBCIndicator: Bool = false { + didSet { + if oldValue != isShowingCBCIndicator { + // Gross, but we need to reach up to our top nested UIStackView to relayout with the new intrinsicContentSize: + self.superview?.superview?.setNeedsLayout() + self.invalidateIntrinsicContentSize() + cbcIndicatorView.isHidden = !isShowingCBCIndicator + self.cbcIndicatorSizeConstraint?.constant = isShowingCBCIndicator ? 9.0 : 0 + } + } + } + + @_spi(STP) public override var intrinsicContentSize: CGSize { + return size(for: Self.targetIconSize) + } + + @_spi(STP) public func size(for targetSize: CGSize) -> CGSize { + let padding = centeringPadding + + // The way this scaling works isn't perfect since the returned size has to map to a valid point value + // (see rounding) which can change the effects of scaleX/scaleY. In practice this is only 1 pixel off so + // acceptable for now. + let scaleX = + targetSize.width + / (Self.legacyIconSize.width - Self.iconPadding.left - Self.iconPadding.right) + let scaleY = + targetSize.height + / (Self.legacyIconSize.height - Self.iconPadding.top - Self.iconPadding.bottom) + // We could adapt this for multiple screens, but probably not worth it (better solution is to remove padding from images) + #if canImport(CompositorServices) + let screenScale = 1.0 + #else + let screenScale = UIScreen.main.scale + #endif + let extraWidth = isShowingCBCIndicator ? 9.0 : 0 + return CGSize( + width: (round(Self.legacyIconSize.width * scaleX * screenScale) / screenScale) + + padding.right + padding.left + extraWidth, + height: (round(Self.legacyIconSize.height * scaleY * screenScale) / screenScale) + + padding.top + padding.bottom + ) + } + + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + /// Creates and returns an initialized card brand view. + /// - Parameter showCVC: Whether or not to show the CVC hint icon instead of the card brand image. + @_spi(STP) public init( + showCVC: Bool = false, + centerHorizontally: Bool = false + ) { + self.showCVC = showCVC + self.centerHorizontally = centerHorizontally + super.init(frame: .zero) + + addSubview(imageView) + imageView.translatesAutoresizingMaskIntoConstraints = false + + addSubview(cbcIndicatorView) + cbcIndicatorView.translatesAutoresizingMaskIntoConstraints = false + + cbcIndicatorView.isHidden = true + let cbcIndicatorSizeConstraint = cbcIndicatorView.widthAnchor.constraint(equalToConstant: 0) + cbcIndicatorSizeConstraint.priority = .required + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: centeringPadding.top), + self.bottomAnchor.constraint( + equalTo: imageView.bottomAnchor, + constant: centeringPadding.bottom + ), + imageView.leftAnchor.constraint( + equalTo: self.leftAnchor, + constant: centeringPadding.left + ), + imageView.rightAnchor.constraint( + equalTo: cbcIndicatorView.leftAnchor, + constant: centeringPadding.right + ), + cbcIndicatorView.rightAnchor.constraint( + equalTo: self.rightAnchor, + constant: centeringPadding.right + ), + cbcIndicatorView.centerYAnchor.constraint( + equalTo: self.centerYAnchor + ), + cbcIndicatorView.heightAnchor.constraint( + equalToConstant: 9.0 + ), + cbcIndicatorSizeConstraint, + ]) + + self.cbcIndicatorSizeConstraint = cbcIndicatorSizeConstraint + updateIcon() + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + @_spi(STP) public func setCardBrand(_ brand: STPCardBrand) { + setCardBrand(.brand(brand), animated: false) + } + + /// Updates the card brand, optionally animating the transition. + /// - Parameters: + /// - newBrandState: New card brand state. + /// - animated: Whether or not to animate the transition. + func setCardBrand(_ newBrandState: STPCBCController.BrandState, animated: Bool) { + let newImage = image(for: newBrandState) + + // Image has changed and we're not switching between unknown option states + let canAnimateTransition = imageView.image != newImage && !(self.cardBrandState == .unknownMultipleOptions && newBrandState == .unknown) && !(self.cardBrandState == .unknown && newBrandState == .unknownMultipleOptions) + + self.cardBrandState = newBrandState + + if animated && canAnimateTransition { + performTransitionAnimation() + } + } + + // MARK: - Private Methods + + private func updateIcon() { + imageView.image = image(for: cardBrandState) + } + + private func performTransitionAnimation() { + // values match Elements animation + let timingFunction = CAMediaTimingFunction(controlPoints: 0.075, 0.82, 0.165, 1) + + let scaleAnimation = CABasicAnimation(keyPath: "transform") + scaleAnimation.duration = 0.4 + scaleAnimation.timingFunction = timingFunction + scaleAnimation.fromValue = CATransform3DScale(CATransform3DIdentity, 0.7, 0.7, 1) + scaleAnimation.toValue = CATransform3DIdentity + + let opacityAnimation = CABasicAnimation(keyPath: "opacity") + opacityAnimation.duration = 0.7 + opacityAnimation.timingFunction = timingFunction + opacityAnimation.fromValue = 0.0 + opacityAnimation.toValue = 1.0 + + let animationGroup = CAAnimationGroup() + animationGroup.animations = [scaleAnimation, opacityAnimation] + + self.imageView.layer.add(animationGroup, forKey: "transition") + } + + /// Returns the most appropriate icon/image for the given card brand. + /// - Parameter cardBrand: Card brand + /// - Returns: Image. + private func image(for brandState: STPCBCController.BrandState) -> UIImage { + if showCVC { + return STPImageLibrary.cvcImage(for: brandState.brand) + } + switch brandState { + case .brand(let brand): + return STPImageLibrary.cardBrandImage(for: brand) + case .cbcBrandSelected(let brand): + return STPImageLibrary.cardBrandImage(for: brand) + case .unknown: + return STPImageLibrary.cardBrandImage(for: .unknown) + case .unknownMultipleOptions: + return STPImageLibrary.cardBrandChoiceImage() + } + } + + // MARK: - Callbacks +#if !canImport(CompositorServices) + @_spi(STP) public override func traitCollectionDidChange( + _ previousTraitCollection: UITraitCollection? + ) { + super.traitCollectionDidChange(previousTraitCollection) + updateIcon() + } +#endif +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/DynamicImageView+Unknown.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/DynamicImageView+Unknown.swift new file mode 100644 index 00000000..f2eaab82 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/DynamicImageView+Unknown.swift @@ -0,0 +1,18 @@ +// +// DynamicImageView+Unknown.swift +// StripePaymentSheet +// +// Created by Nick Porter on 9/21/23. +// + +import Foundation +@_spi(STP) import StripeUICore + +@_spi(STP) public extension DynamicImageView { + static func makeUnknownCardImageView(theme: ElementsAppearance) -> DynamicImageView { + return DynamicImageView( + dynamicImage: STPImageLibrary.unknownCardCardImage(), + pairedColor: theme.colors.componentBackground + ) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextField.swift new file mode 100644 index 00000000..b5272465 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextField.swift @@ -0,0 +1,89 @@ +// +// STPCardCVCInputTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +@_spi(STP) public class STPCardCVCInputTextField: STPInputTextField { + + @_spi(STP) public var cardBrand: STPCardBrand = .unknown { + didSet { + cvcFormatter.cardBrand = cardBrand + cvcValidator.cardBrand = cardBrand + updateCVCImageAndPlaceholder() + truncateTextIfNeeded() + } + } + + var cvcFormatter: STPCardCVCInputTextFieldFormatter { + return formatter as! STPCardCVCInputTextFieldFormatter + } + + var cvcValidator: STPCardCVCInputTextFieldValidator { + return validator as! STPCardCVCInputTextFieldValidator + } + + let cvcHintView = CardBrandView(showCVC: true) + + public convenience init( + prefillDetails: STPCardFormView.PrefillDetails? = nil + ) { + self.init( + formatter: STPCardCVCInputTextFieldFormatter(), + validator: STPCardCVCInputTextFieldValidator() + ) + + // set card brand in a defer to ensure didSet is called updating the formatter & validator + // swiftlint:disable:next inert_defer + defer { + self.cardBrand = prefillDetails?.cardBrand ?? .unknown + } + } + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + assert(formatter.isKind(of: STPCardCVCInputTextFieldFormatter.self)) + assert(validator.isKind(of: STPCardCVCInputTextFieldValidator.self)) + super.init(formatter: formatter, validator: validator) + keyboardType = .asciiCapableNumberPad + } + + required init?( + coder: NSCoder + ) { + super.init(coder: coder) + } + + override func setupSubviews() { + super.setupSubviews() + accessibilityIdentifier = "CVC" + addAccessoryViews([cvcHintView]) + updateCVCImageAndPlaceholder() + } + + func updateCVCImageAndPlaceholder() { + cvcHintView.setCardBrand(.brand(cardBrand), animated: true) + + placeholder = String.Localized.cvc + } + + func truncateTextIfNeeded() { + guard let text = self.text else { + return + } + + let maxLength = Int(STPCardValidator.maxCVCLength(for: cardBrand)) + if text.count > maxLength { + self.text = text.stp_safeSubstring(to: maxLength) + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextFieldFormatter.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextFieldFormatter.swift new file mode 100644 index 00000000..f5b46971 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextFieldFormatter.swift @@ -0,0 +1,37 @@ +// +// STPCardCVCInputTextFieldFormatter.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +class STPCardCVCInputTextFieldFormatter: STPNumericDigitInputTextFormatter { + + var cardBrand: STPCardBrand = .unknown + + override func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool { + guard super.isAllowedInput(input, to: string, at: range) else { + return false + } + + let maxLength = STPCardValidator.maxCVCLength(for: cardBrand) + if string.count + input.count > maxLength { + return false + } + + return true + } + + override func formattedText( + from input: String, + with defaultAttributes: [NSAttributedString.Key: Any] + ) -> NSAttributedString { + let numeric = STPNumericStringValidator.sanitizedNumericString(for: input) + return NSAttributedString(string: numeric, attributes: defaultAttributes) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextFieldValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextFieldValidator.swift new file mode 100644 index 00000000..9a1946f8 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardCVCInputTextFieldValidator.swift @@ -0,0 +1,51 @@ +// +// STPCardCVCInputTextFieldValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +class STPCardCVCInputTextFieldValidator: STPInputTextFieldValidator { + + override var defaultErrorMessage: String? { + return STPLocalizedString( + "Your card's security code is invalid.", + "Error message for card entry form when CVC is invalid" + ) + } + + var cardBrand: STPCardBrand = .unknown { + didSet { + checkInputValidity() + } + } + + override public var inputValue: String? { + didSet { + checkInputValidity() + } + } + + private func checkInputValidity() { + guard let inputValue = inputValue else { + validationState = .incomplete(description: nil) + return + } + switch STPCardValidator.validationState(forCVC: inputValue, cardBrand: cardBrand) { + case .valid: + validationState = .valid(message: nil) + case .invalid: + validationState = .invalid(errorMessage: defaultErrorMessage) + case .incomplete: + validationState = .incomplete( + description: !inputValue.isEmpty + ? String.Localized.your_cards_security_code_is_incomplete : nil + ) + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextField.swift new file mode 100644 index 00000000..80988fad --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextField.swift @@ -0,0 +1,51 @@ +// +// STPCardExpiryInputTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +class STPCardExpiryInputTextField: STPInputTextField { + + public var expiryStrings: (month: String, year: String)? { + return (validator as! STPCardExpiryInputTextFieldValidator).expiryStrings + } + + public convenience init( + prefillDetails: STPCardFormView.PrefillDetails? = nil + ) { + self.init( + formatter: STPCardExpiryInputTextFieldFormatter(), + validator: STPCardExpiryInputTextFieldValidator() + ) + + self.text = prefillDetails?.formattedExpiry // pre-fill expiry if available + } + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + assert(formatter.isKind(of: STPCardExpiryInputTextFieldFormatter.self)) + assert(validator.isKind(of: STPCardExpiryInputTextFieldValidator.self)) + super.init(formatter: formatter, validator: validator) + keyboardType = .asciiCapableNumberPad + } + + required init?( + coder: NSCoder + ) { + super.init(coder: coder) + } + + override func setupSubviews() { + super.setupSubviews() + accessibilityIdentifier = "expiration date" + placeholder = String.Localized.mm_yy + accessibilityLabel = String.Localized.expiration_date_accessibility_label + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextFieldFormatter.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextFieldFormatter.swift new file mode 100644 index 00000000..d3c8fdd2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextFieldFormatter.swift @@ -0,0 +1,84 @@ +// +// STPCardExpiryInputTextFieldFormatter.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +class STPCardExpiryInputTextFieldFormatter: STPInputTextFieldFormatter { + + override func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool { + guard super.isAllowedInput(input, to: string, at: range), + let range = Range(range, in: string) + else { + return false + } + + let proposed = string.replacingCharacters(in: range, with: input) + .stp_stringByRemovingCharacters(from: .whitespaces) + var proposedComponents = proposed.split(separator: "/").map({ String($0) }).filter({ + !$0.isEmpty + }) + if let firstComponent = proposedComponents.first, + firstComponent == proposed + { + // we don't have a separator, so go by index + proposedComponents = [ + proposed.stp_safeSubstring(to: 2), proposed.stp_safeSubstring(from: 2), + ].filter({ !$0.isEmpty }) + } + + if proposedComponents.count > 2 { + return false + } else if proposedComponents.first(where: { + !STPNumericStringValidator.isStringNumeric(String($0)) + }) != nil { + return false + } else if let firstComponent = proposedComponents.first { + if proposedComponents.count > 1 && firstComponent.count > 2 { + return false + } else if firstComponent.count < 2 && proposedComponents.count > 1 { + return false + } else if proposedComponents.count > 1 { + let yearComponent = proposedComponents[1] + if yearComponent.count > 4 { + return false + } else if yearComponent.count > 2 && !string.isEmpty { + // we only allow pasting of 4 digit years, which will be formatted + return false + } + } + return true + } else { + return false + } + } + + override func formattedText( + from input: String, + with defaultAttributes: [NSAttributedString.Key: Any] + ) -> NSAttributedString { + var numericInput = STPNumericStringValidator.sanitizedNumericString(for: input) + + // A MM/YY starting with 2-9 must be a single digit month; prepend a 0 + if let firstNumber = numericInput.first?.wholeNumberValue, + (2...9).contains(firstNumber) + { + numericInput = "0".appending(numericInput) + } + + if numericInput.count > 2 { + numericInput = + numericInput.stp_safeSubstring(to: 2) + "/" + + numericInput.stp_safeSubstring(from: 2) + } + + let expirationString = STPStringUtils.expirationDateString(from: numericInput) + return NSAttributedString(string: expirationString ?? "", attributes: defaultAttributes) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextFieldValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextFieldValidator.swift new file mode 100644 index 00000000..1d21fe46 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardExpiryInputTextFieldValidator.swift @@ -0,0 +1,81 @@ +// +// STPCardExpiryInputTextFieldValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +class STPCardExpiryInputTextFieldValidator: STPInputTextFieldValidator { + + override var defaultErrorMessage: String? { + return String.Localized.your_cards_expiration_date_is_invalid + } + + public var expiryStrings: (month: String, year: String)? { + guard let inputValue = inputValue else { + return nil + } + let numericInput = STPNumericStringValidator.sanitizedNumericString(for: inputValue) + let monthString = numericInput.stp_safeSubstring(to: 2) + var yearString = numericInput.stp_safeSubstring(from: 2) + + // prepend "20" to ensure we provide a 4 digit year, this is to be consistent with Checkout + if yearString.count == 2 { + let centuryLeadingDigits = Int( + floor(Double(Calendar(identifier: .iso8601).component(.year, from: Date())) / 100) + ) + + yearString = "\(centuryLeadingDigits)\(yearString)" + } + + if monthString.count == 2 && yearString.count == 4 { + return (month: monthString, year: yearString) + } else { + return nil + } + } + + override public var inputValue: String? { + didSet { + guard let inputValue = inputValue else { + validationState = .incomplete(description: nil) + return + } + + let numericInput = STPNumericStringValidator.sanitizedNumericString(for: inputValue) + let monthString = numericInput.stp_safeSubstring(to: 2) + let yearString = numericInput.stp_safeSubstring(from: 2) + + let monthState = STPCardValidator.validationState(forExpirationMonth: monthString) + let yearState = STPCardValidator.validationState( + forExpirationYear: yearString, + inMonth: monthString + ) + + if monthState == .valid && yearState == .valid { + validationState = .valid(message: nil) + } else if monthState == .invalid && yearState == .invalid { + // TODO: We should be more specific here e.g. "Your card's expiration year is in the past." + validationState = .invalid(errorMessage: defaultErrorMessage) + } else if monthState == .invalid { + validationState = .invalid( + errorMessage: String.Localized.your_cards_expiration_month_is_invalid + ) + } else if yearState == .invalid { + validationState = .invalid( + errorMessage: String.Localized.your_cards_expiration_year_is_invalid + ) + } else { + validationState = .incomplete( + description: !inputValue.isEmpty + ? String.Localized.your_cards_expiration_date_is_incomplete : nil + ) + } + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextField.swift new file mode 100644 index 00000000..096c5bf0 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextField.swift @@ -0,0 +1,182 @@ +// +// STPCardNumberInputTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +@_spi(STP) public class STPCardNumberInputTextField: STPInputTextField { + + /// Describes which input fields can take input + @_spi(STP) public enum InputMode { + /// All input fields can be edited + case standard + // PAN field is locked, all others are editable + case panLocked + } + + struct LayoutConstants { + static let loadingIndicatorOffset: CGFloat = 4 + } + + var cardBrandState: STPCBCController.BrandState { + return (validator as! STPCardNumberInputTextFieldValidator).cardBrandState + } + + var brandForCVC: STPCardBrand { + return (validator as! STPCardNumberInputTextFieldValidator).cbcController.brandForCVC + } + + var preferredNetworks: [STPCardBrand]? { + get { + return (validator as? STPCardNumberInputTextFieldValidator)?.cbcController.preferredNetworks + } + set { + (validator as? STPCardNumberInputTextFieldValidator)?.cbcController.preferredNetworks = newValue + } + } + + open override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint { + let pointInBrandImageView = CGPoint(x: brandImageView.bounds.midX, y: brandImageView.bounds.maxY) + return self.convert(pointInBrandImageView, from: brandImageView) + } + + open override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + guard let validator = (validator as? STPCardNumberInputTextFieldValidator), validator.cbcController.brandState.isCBC else { + // Don't pop a menu if the CBC indicator isn't visible + return nil + } + + let targetPointInBrandView = brandImageView.convert(location, from: self) + let targetRect = brandImageView.bounds + if !targetRect.contains(targetPointInBrandView) { + // Don't pop a menu outside the brand selector area + return nil + } + return validator.cbcController.contextMenuConfiguration + } + + @_spi(STP) public convenience init( + inputMode: InputMode = .standard, + prefillDetails: STPCardFormView.PrefillDetails? = nil, + cbcEnabledOverride: Bool? = nil + ) { + let validator = STPCardNumberInputTextFieldValidator( + inputMode: inputMode, + cardBrand: prefillDetails?.cardBrand, + cbcEnabledOverride: cbcEnabledOverride + ) + // Don't format for panLocked input mode + self.init( + formatter: inputMode == .panLocked + ? STPInputTextFieldFormatter() : STPCardNumberInputTextFieldFormatter(), + validator: validator + ) + validator.cbcController.updateHandler = { [weak self] in + self?.updateRightView() + } + + self.text = prefillDetails?.formattedLast4 // pre-fill last 4 if available + } + + let brandImageView = CardBrandView() + + lazy var loadingIndicator: STPCardLoadingIndicator = { + let loadingIndicator = STPCardLoadingIndicator() + loadingIndicator.translatesAutoresizingMaskIntoConstraints = false + return loadingIndicator + }() + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + assert(validator.isKind(of: STPCardNumberInputTextFieldValidator.self)) + super.init(formatter: formatter, validator: validator) + keyboardType = .asciiCapableNumberPad + textContentType = .creditCardNumber + addAccessoryViews([brandImageView]) + updateRightView() + // Set up CBC menu interactions + if #available(iOS 14.0, *) { + self.showsMenuAsPrimaryAction = true + self.isContextMenuInteractionEnabled = true + } + } + + required init?( + coder: NSCoder + ) { + super.init(coder: coder) + } + + override func setupSubviews() { + super.setupSubviews() + accessibilityIdentifier = "Card number" + placeholder = STPLocalizedString("Card number", "Label for card number entry text field") + } + + func updateRightView() { + switch validator.validationState { + + case .unknown: + loadingIndicator.removeFromSuperview() + brandImageView.setCardBrand(.unknown, animated: true) + case .valid, .incomplete: + loadingIndicator.removeFromSuperview() + brandImageView.setCardBrand(cardBrandState, animated: true) + case .invalid: + loadingIndicator.removeFromSuperview() + brandImageView.setCardBrand(.unknown, animated: true) + case .processing: + if loadingIndicator.superview == nil { + brandImageView.setCardBrand(.unknown, animated: true) + // delay a bit before showing loading indicator because the response may come quickly + DispatchQueue.main.asyncAfter( + deadline: DispatchTime.now() + Double( + Int64(0.1 * Double(NSEC_PER_SEC)) + ) / Double(NSEC_PER_SEC), + execute: { + if case .processing = self.validator.validationState, + self.loadingIndicator.superview == nil + { + self.addSubview(self.loadingIndicator) + NSLayoutConstraint.activate( + [ + self.loadingIndicator.rightAnchor.constraint( + equalTo: self.brandImageView.rightAnchor, + constant: LayoutConstants.loadingIndicatorOffset + ), + self.loadingIndicator.topAnchor.constraint( + equalTo: self.brandImageView.topAnchor, + constant: -LayoutConstants.loadingIndicatorOffset + ), + ] + ) + } + } + ) + } + } + if let cardValidator = validator as? STPCardNumberInputTextFieldValidator { + let isCBC = cardValidator.cbcController.brandState.isCBC + brandImageView.isShowingCBCIndicator = isCBC + } + } + + override func validationDidUpdate( + to state: STPValidatedInputState, + from previousState: STPValidatedInputState, + for unformattedInput: String?, + in input: STPFormInput + ) { + super.validationDidUpdate(to: state, from: previousState, for: unformattedInput, in: input) + updateRightView() + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextFieldFormatter.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextFieldFormatter.swift new file mode 100644 index 00000000..573d9026 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextFieldFormatter.swift @@ -0,0 +1,82 @@ +// +// STPCardNumberInputTextFieldFormatter.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +class STPCardNumberInputTextFieldFormatter: STPNumericDigitInputTextFormatter { + + convenience init() { + self.init(allowedFormattingCharacterSet: CharacterSet.whitespaces) + } + + override func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool { + guard super.isAllowedInput(input, to: string, at: range), + let range = Range(range, in: string) + else { + return false + } + let proposed = string.replacingCharacters(in: range, with: input) + let unformattedProposed = STPNumericStringValidator.sanitizedNumericString(for: proposed) + + var maxLength = STPBINController.shared.maxCardNumberLength() + + let hasCompleteMetadataForCardNumber = STPBINController.shared.hasBINRanges( + forPrefix: unformattedProposed + ) + if hasCompleteMetadataForCardNumber { + let brand = STPCardValidator.brand(forNumber: unformattedProposed) + maxLength = STPCardValidator.maxLength(for: brand) + } + + if unformattedProposed.count > maxLength { + return false + } + + return true + } + + override func formattedText( + from input: String, + with defaultAttributes: [NSAttributedString.Key: Any] + ) -> NSAttributedString { + let numeric = STPNumericStringValidator.sanitizedNumericString(for: input) + let attributed = NSMutableAttributedString(string: numeric, attributes: defaultAttributes) + + let cardNumberFormat = STPCardValidator.cardNumberFormat(forCardNumber: attributed.string) + + var index = 0 + for segmentLength in cardNumberFormat { + var segmentIndex = 0 + + while index < (attributed.length) && segmentIndex < Int(segmentLength.uintValue) { + if index + 1 != attributed.length + && segmentIndex + 1 == Int(segmentLength.uintValue) + { + attributed.addAttribute( + .kern, + value: NSNumber(value: 5), + range: NSRange(location: index, length: 1) + ) + } else { + attributed.addAttribute( + .kern, + value: NSNumber(value: 0), + range: NSRange(location: index, length: 1) + ) + } + + index += 1 + segmentIndex += 1 + } + } + + return NSAttributedString(attributedString: attributed) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextFieldValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextFieldValidator.swift new file mode 100644 index 00000000..2ab6c8a3 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPCardNumberInputTextFieldValidator.swift @@ -0,0 +1,95 @@ +// +// STPCardNumberInputTextFieldValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +class STPCardNumberInputTextFieldValidator: STPInputTextFieldValidator { + private var inputMode = STPCardNumberInputTextField.InputMode.standard + + override var defaultErrorMessage: String? { + return String.Localized.your_card_number_is_invalid + } + + private var overridenCardBrand: STPCardBrand? + var cardBrandState: STPCBCController.BrandState { + if let overridenCardBrand = overridenCardBrand { + return .brand(overridenCardBrand) + } + + if cbcController.cbcEnabled { + return cbcController.brandState + } + + guard let inputValue = inputValue, + STPBINController.shared.hasBINRanges(forPrefix: inputValue) + else { + return .unknown + } + + return .brand(STPCardValidator.brand(forNumber: inputValue)) + } + + override public var inputValue: String? { + didSet { + cbcController.cardNumber = inputValue + guard let inputValue = inputValue else { + validationState = .incomplete(description: nil) + return + } + let updateValidationState = { + // Assume pan-locked is valid + if self.inputMode == .panLocked { + self.validationState = .valid(message: nil) + return + } + + switch STPCardValidator.validationState( + forNumber: inputValue, + validatingCardBrand: true + ) + { + + case .valid: + self.validationState = .valid(message: nil) + case .invalid: + self.validationState = .invalid(errorMessage: self.defaultErrorMessage) + case .incomplete: + self.validationState = .incomplete( + description: !inputValue.isEmpty + ? String.Localized.your_card_number_is_incomplete : nil + ) + } + } + if STPBINController.shared.hasBINRanges(forPrefix: inputValue) { + updateValidationState() + } else { + STPBINController.shared.retrieveBINRanges(forPrefix: inputValue) { (_) in + // Needs better error handling and analytics https://jira.corp.stripe.com/browse/MOBILESDK-110 + updateValidationState() + } + if STPBINController.shared.isLoadingCardMetadata(forPrefix: inputValue) { + validationState = .processing + } + } + } + } + + let cbcController = STPCBCController() + + init( + inputMode: STPCardNumberInputTextField.InputMode = .standard, + cardBrand: STPCardBrand? = nil, + cbcEnabledOverride: Bool? = nil + ) { + self.inputMode = inputMode + self.overridenCardBrand = cardBrand + self.cbcController.cbcEnabledOverride = cbcEnabledOverride + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextField.swift new file mode 100644 index 00000000..9dc1d6ec --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextField.swift @@ -0,0 +1,89 @@ +// +// STPPostalCodeInputTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/30/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +@_spi(STP) public class STPPostalCodeInputTextField: STPInputTextField { + + var countryCode: String? = Locale.autoupdatingCurrent.stp_regionCode { + didSet { + updatePlaceholder() + updateKeyboard() + (formatter as! STPPostalCodeInputTextFieldFormatter).countryCode = countryCode + (validator as! STPPostalCodeInputTextFieldValidator).countryCode = countryCode + clearIfInvalid() + } + } + + public var postalCode: String? { + return validator.inputValue + } + + public convenience init( + postalCodeRequirement: STPPostalCodeRequirement + ) { + self.init( + formatter: STPPostalCodeInputTextFieldFormatter(), + validator: STPPostalCodeInputTextFieldValidator( + postalCodeRequirement: postalCodeRequirement + ) + ) + } + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + assert(formatter.isKind(of: STPPostalCodeInputTextFieldFormatter.self)) + assert(validator.isKind(of: STPPostalCodeInputTextFieldValidator.self)) + super.init(formatter: formatter, validator: validator) + updateKeyboard() + textContentType = .postalCode + } + + required init?( + coder: NSCoder + ) { + super.init(coder: coder) + } + + override func setupSubviews() { + super.setupSubviews() + accessibilityIdentifier = "Postal Code" + updatePlaceholder() + } + + private func updatePlaceholder() { + guard STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: countryCode) else { + // don't update for countries that don't use postal codes (this helps with animations) + return + } + if countryCode == "US" { + placeholder = String.Localized.zip + } else { + placeholder = STPLocalizedString("Postal Code", "Postal code placeholder") + } + setNeedsLayout() + } + + private func updateKeyboard() { + if countryCode == "US" { + keyboardType = .asciiCapableNumberPad + } else { + keyboardType = .numbersAndPunctuation + } + } + + private func clearIfInvalid() { + if case .invalid = validator.validationState { + self.text = "" + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextFieldFormatter.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextFieldFormatter.swift new file mode 100644 index 00000000..ec8fa5e2 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextFieldFormatter.swift @@ -0,0 +1,48 @@ +// +// STPPostalCodeInputTextFieldFormatter.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/30/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class STPPostalCodeInputTextFieldFormatter: STPInputTextFieldFormatter { + + var countryCode: String? = Locale.autoupdatingCurrent.stp_regionCode + + override func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool { + guard super.isAllowedInput(input, to: string, at: range), + input.rangeOfCharacter(from: .stp_invertedPostalCode) == nil, + let range = Range(range, in: string) + else { + return false + } + + let proposed = string.replacingCharacters(in: range, with: input) + if countryCode == "US", proposed.count > 5 { + return false + } + return STPPostalCodeValidator.validationState( + forPostalCode: proposed, + countryCode: countryCode + ) != .invalid + } + + override func formattedText( + from input: String, + with defaultAttributes: [NSAttributedString.Key: Any] + ) -> NSAttributedString { + return NSAttributedString( + string: STPPostalCodeValidator.formattedSanitizedPostalCode( + from: input.trimmingCharacters(in: .whitespacesAndNewlines), + countryCode: countryCode, + usage: .billingAddress + ) ?? "", + attributes: defaultAttributes + ) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextFieldValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextFieldValidator.swift new file mode 100644 index 00000000..e67a68f9 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/Card/STPPostalCodeInputTextFieldValidator.swift @@ -0,0 +1,76 @@ +// +// STPPostalCodeInputTextFieldValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/30/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit + +class STPPostalCodeInputTextFieldValidator: STPInputTextFieldValidator { + + override var defaultErrorMessage: String? { + if countryCode?.uppercased() == "US" { + return STPLocalizedString( + "Your ZIP is invalid.", + "Error message for when postal code in form is invalid (US only)" + ) + } else { + return STPLocalizedString( + "Your postal code is invalid.", + "Error message for when postal code in form is invalid" + ) + } + } + + override public var inputValue: String? { + didSet { + updateValidationState() + } + } + + var countryCode: String? = Locale.autoupdatingCurrent.stp_regionCode { + didSet { + updateValidationState() + } + } + + let postalCodeRequirement: STPPostalCodeRequirement + + required init( + postalCodeRequirement: STPPostalCodeRequirement + ) { + self.postalCodeRequirement = postalCodeRequirement + super.init() + } + + private func updateValidationState() { + + switch STPPostalCodeValidator.validationState( + forPostalCode: inputValue, + countryCode: countryCode, + with: postalCodeRequirement + ) + { + case .valid: + validationState = .valid(message: nil) + case .invalid: + // Note: these don't actually happen (since we don't do offline validation, defaultErrorMessage is + // primarily a backup for missing api error strings) + validationState = .invalid(errorMessage: defaultErrorMessage) + case .incomplete: + var incompleteDescription: String? + if let inputValue = inputValue, !inputValue.isEmpty { + if countryCode?.uppercased() == "US" { + incompleteDescription = String.Localized.your_zip_is_incomplete + } else { + incompleteDescription = String.Localized.your_postal_code_is_incomplete + } + } + validationState = .incomplete(description: incompleteDescription) + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPCountryPickerInputField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPCountryPickerInputField.swift new file mode 100644 index 00000000..9b4fd2a4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPCountryPickerInputField.swift @@ -0,0 +1,135 @@ +// +// STPCountryPickerInputField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 11/16/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public class STPCountryPickerInputField: STPGenericInputPickerField { + + var countryPickerDataSource: CountryPickerDataSource { + wrappedDataSource.inputDataSource as! CountryPickerDataSource + } + + class CountryCodeValidator: STPInputTextFieldValidator { + override public var inputValue: String? { + didSet { + validationState = + inputValue?.count == 2 ? .valid(message: nil) : .incomplete(description: nil) + } + } + } + + override var wantsAutoFocus: Bool { + return false + } + + convenience init() { + self.init(dataSource: CountryPickerDataSource(), validator: CountryCodeValidator()) + } + + func select(countryCode: String) { + if let row = countryPickerDataSource.row(for: countryCode) { + select(row: row) + } + } + + func select(row: Int) { + pickerView.selectRow(row, inComponent: 0, animated: false) + updateValue() + } + + override func setupSubviews() { + super.setupSubviews() + // Default selection to the current country + pickerView.selectRow(0, inComponent: 0, animated: false) + // Set initial value + updateValue() + } +} + +/// :nodoc: +extension STPCountryPickerInputField { + @_spi(STP) public class CountryPickerDataSource: NSObject, STPGenericInputPickerFieldDataSource + { + + @_spi(STP) public let countries: [(code: String, displayName: String)] = { + + let currentCountryCode = Locale.autoupdatingCurrent.stp_regionCode + let locale = NSLocale.autoupdatingCurrent + + let unsorted = Locale.stp_isoRegionCodes.compactMap { (code) -> (String, String)? in + let identifier = Locale.identifier(fromComponents: [ + NSLocale.Key.countryCode.rawValue: code + ]) + if let countryName = (locale as NSLocale).displayName( + forKey: .identifier, + value: identifier + ) { + return (code, countryName) + } else { + return nil + } + } + + return unsorted.sorted { (a, b) -> Bool in + let code1 = a.0 + let code2 = b.0 + + if code1 == currentCountryCode { + return true + } else if code2 == currentCountryCode { + return false + } else { + let name1 = a.1 + let name2 = b.1 + return name1.compare(name2) == .orderedAscending ? true : false + } + } + }() + + func row(for countryCode: String) -> Int? { + return countries.firstIndex { (code: String, _) in + code == countryCode + } + } + + @_spi(STP) public func inputPickerField( + _ pickerField: STPGenericInputPickerField, + titleForRow row: Int + ) + -> String? + { + guard row >= 0, + row < countries.count + else { + return nil + } + + return countries[row].displayName + } + + @_spi(STP) public func inputPickerField( + _ pickerField: STPGenericInputPickerField, + inputValueForRow row: Int + ) + -> String? + { + guard row >= 0, + row < countries.count + else { + return nil + } + + return countries[row].code + } + + @_spi(STP) public func numberOfRows() -> Int { + return countries.count + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPGenericInputPickerField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPGenericInputPickerField.swift new file mode 100644 index 00000000..31e2ce27 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPGenericInputPickerField.swift @@ -0,0 +1,231 @@ +// +// STPGenericInputPickerField.swift +// StripePaymentsUI +// +// Created by Mel Ludowise on 2/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import UIKit + +@_spi(STP) public protocol STPGenericInputPickerFieldDataSource { + func numberOfRows() -> Int + func inputPickerField( + _ pickerField: STPGenericInputPickerField, + titleForRow row: Int + ) + -> String? + func inputPickerField( + _ pickerField: STPGenericInputPickerField, + inputValueForRow row: Int + ) + -> String? +} + +@_spi(STP) public class STPGenericInputPickerField: STPInputTextField { + /// Basic validator that sets `validationState` to `.valid` if there's an inputValue + class Validator: STPInputTextFieldValidator { + override var inputValue: String? { + didSet { + validationState = + (inputValue?.isEmpty != false) + ? .incomplete(description: nil) : .valid(message: nil) + } + } + } + + /// Formatter specific to `STPGenericInputPickerField`. + /// + /// Contains overrides to `UITextFieldDelegate` that ensure the textfield's text can't be + /// selected and the placeholder text displays correctly for a dropdown/picker style input. + /// + /// For internal SDK use only + @objc(STP_Internal_GenericInputPickerFieldFormatter) + class Formatter: STPInputTextFieldFormatter { + + override func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool + { + return false // no typing allowed + } + + // See extension for rest of implementation + } + + internal let wrappedDataSource: DataSourceWrapper + @_spi(STP) public let pickerView = UIPickerView() + + @_spi(STP) public var dataSource: STPGenericInputPickerFieldDataSource { + return wrappedDataSource.inputDataSource + } + + init( + dataSource: STPGenericInputPickerFieldDataSource, + formatter: STPGenericInputPickerField.Formatter = .init(), + validator: STPInputTextFieldValidator = Validator() + ) { + self.wrappedDataSource = DataSourceWrapper(inputDataSource: dataSource) + super.init(formatter: formatter, validator: validator) + } + + @objc public override var accessibilityAttributedValue: NSAttributedString? { + get { + return nil + } + set {} + } + + @objc public override var accessibilityAttributedLabel: NSAttributedString? { + get { + return nil + } + set {} + } + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + + fatalError("Use init(dataSource:formatter:validator:) instead") + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + override func setupSubviews() { + super.setupSubviews() + + pickerView.delegate = self + pickerView.dataSource = wrappedDataSource + inputView = pickerView + +#if !canImport(CompositorServices) + inputAccessoryView = DoneButtonToolbar(delegate: self) +#endif + + rightView = UIImageView(image: StripeUICore.Image.icon_chevron_down.makeImage()) + rightViewMode = .always + + // Prevents selection from flashing if the user double-taps on a word + tintColor = .clear + + // Prevents text from being highlighted red if the user double-taps a word the spell checker doesn't recognize + autocorrectionType = .no + } + + @_spi(STP) public override func resignFirstResponder() -> Bool { + // Update value right before resigning first responder (dismissing input view) + updateValue() + return super.resignFirstResponder() + } + + @_spi(STP) public override func caretRect(for position: UITextPosition) -> CGRect { + // hide the caret + return .zero + } + + override func textDidChange() { + // NOTE(mludowise): There's probably a more elegant solution than + // overriding this method, but this fixes a transcient bug where the + // validator's inputValue would temprorily get set to the display text + // (e.g. "United States") instead of value (e.g. "US") causing the field + // to display as invalid and postal code to sometimes not display when a + // valid country was selected. + // + // Override this method from `STPInputTextField` because... + // 1. We don't want to override validator.input with the display text. + // 2. The logic in `STPInputTextField` handles validation and formatting + // for cases when the user is typing in text into the text field, which + // we don't allow in this case since the value is determined from our + // data source. + } + + @_spi(STP) public func updateValue() { + let selectedRow = pickerView.selectedRow(inComponent: 0) + + text = dataSource.inputPickerField(self, titleForRow: selectedRow) + validator.inputValue = dataSource.inputPickerField(self, inputValueForRow: selectedRow) + + // Hide the placeholder so it behaves as though the placeholder is + // replaced with the selected value rather than displaying as a title + // label above the text. + placeholder = nil + } +} + +// MARK: - UIPickerViewDelegate + +/// :nodoc: +extension STPGenericInputPickerField: UIPickerViewDelegate { + @_spi(STP) public func pickerView( + _ pickerView: UIPickerView, + attributedTitleForRow row: Int, + forComponent component: Int + ) -> NSAttributedString? { + guard let title = dataSource.inputPickerField(self, titleForRow: row) else { + return nil + } + // Make sure the picker font matches our standard input font + return NSAttributedString( + string: title, + attributes: [.font: font ?? UIFont.preferredFont(forTextStyle: .body)] + ) + } +} + +// MARK: - Formatter + +extension STPGenericInputPickerField.Formatter { + func textFieldDidBeginEditing(_ textField: UITextField) { + guard let inputField = textField as? STPGenericInputPickerField else { + return + } + + // If this is the first time the picker displays, we need to display the + // current selection by manually calling the update method + inputField.updateValue() + UIAccessibility.post(notification: .layoutChanged, argument: inputField.pickerView) + } + + func textFieldDidChangeSelection(_ textField: UITextField) { + // Disable text selection + textField.selectedTextRange = textField.textRange( + from: textField.beginningOfDocument, + to: textField.beginningOfDocument + ) + } +} + +// MARK: - DoneButtonToolbarDelegate + +/// :nodoc: +extension STPGenericInputPickerField: DoneButtonToolbarDelegate { + @_spi(STP) public func didTapDone(_ toolbar: DoneButtonToolbar) { + _ = resignFirstResponder() + } +} + +/// Wraps `STPGenericInputPickerFieldDataSource` into `UIPickerViewDataSource` +/// For internal SDK use only +@objc(STP_Internal_DataSourceWrapper) +internal class DataSourceWrapper: NSObject, UIPickerViewDataSource { + let inputDataSource: STPGenericInputPickerFieldDataSource + + init( + inputDataSource: STPGenericInputPickerFieldDataSource + ) { + self.inputDataSource = inputDataSource + } + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return inputDataSource.numberOfRows() + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPGenericInputTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPGenericInputTextField.swift new file mode 100644 index 00000000..9731915d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPGenericInputTextField.swift @@ -0,0 +1,62 @@ +// +// STPGenericInputTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 11/19/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import UIKit + +class STPGenericInputTextField: STPInputTextField { + + class Validator: STPInputTextFieldValidator { + var optional: Bool = false { + didSet { + updateValidationState() + } + } + + override var inputValue: String? { + didSet { + updateValidationState() + } + } + + func updateValidationState() { + validationState = + (inputValue?.count ?? 0 > 0 || optional) + ? .valid(message: nil) : .incomplete(description: nil) + } + } + + public convenience init( + placeholder: String, + textContentType: UITextContentType? = nil, + keyboardType: UIKeyboardType = .default, + optional: Bool = false + ) { + let validator = STPGenericInputTextField.Validator() + validator.optional = optional + self.init(formatter: STPInputTextFieldFormatter(), validator: validator) + self.placeholder = placeholder + self.textContentType = textContentType + self.keyboardType = keyboardType + } + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + assert(formatter.isKind(of: STPInputTextFieldFormatter.self)) + assert(validator.isKind(of: STPGenericInputTextField.Validator.self)) + super.init(formatter: formatter, validator: validator) + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextField.swift new file mode 100644 index 00000000..ad8d7e9a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextField.swift @@ -0,0 +1,296 @@ +// +// STPInputTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/12/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import UIKit + +@_spi(STP) +public class STPInputTextField: STPFloatingPlaceholderTextField, STPFormInputValidationObserver { + let formatter: STPInputTextFieldFormatter + + let validator: STPInputTextFieldValidator + + weak var formContainer: STPFormContainer? + + var wantsAutoFocus: Bool { + return true + } + + let accessoryImageStackView: UIStackView = { + let stackView = UIStackView() + stackView.alignment = .center + stackView.distribution = .fillProportionally + stackView.spacing = 6 + return stackView + }() + let errorStateImageView: UIImageView = { + let imageView = UIImageView(image: Image.icon_error.makeImage()) + imageView.contentMode = UIView.ContentMode.scaleAspectFit + return imageView + }() + + required init( + formatter: STPInputTextFieldFormatter, + validator: STPInputTextFieldValidator + ) { + self.formatter = formatter + self.validator = validator + super.init(frame: .zero) + delegate = formatter + validator.textField = self + validator.addObserver(self) + addTarget(self, action: #selector(textDidChange), for: .editingChanged) + } + + override func setupSubviews() { + super.setupSubviews() + let fontMetrics = UIFontMetrics(forTextStyle: .body) + font = fontMetrics.scaledFont(for: UIFont.systemFont(ofSize: 14)) + placeholderLabel.font = font + defaultPlaceholderColor = .secondaryLabel + floatingPlaceholderColor = .secondaryLabel + rightView = accessoryImageStackView + rightViewMode = .always + errorStateImageView.alpha = 0 + accessoryImageStackView.addArrangedSubview(errorStateImageView) + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + @_spi(STP) public override var text: String? { + didSet { + textDidChange() + } + } + + internal func addAccessoryViews(_ accessoryViews: [UIView]) { + for view in accessoryViews { + accessoryImageStackView.addArrangedSubview(view) + } + } + + internal func removeAccessoryViews(_ accessoryViews: [UIView]) { + for view in accessoryViews { + accessoryImageStackView.removeArrangedSubview(view) + view.removeFromSuperview() + } + } + + @objc + func textDidChange() { + let text = self.text ?? "" + let formatted = formatter.formattedText(from: text, with: defaultTextAttributes) + if formatted != attributedText { + var updatedCursorPosition: UITextPosition? + if let selection = selectedTextRange { + let cursorPosition = offset(from: beginningOfDocument, to: selection.start) + updatedCursorPosition = position( + from: beginningOfDocument, + offset: cursorPosition - (text.count - formatted.length) + ) + + } + attributedText = formatted + sendActions(for: .valueChanged) + if let updatedCursorPosition = updatedCursorPosition { + selectedTextRange = textRange( + from: updatedCursorPosition, + to: updatedCursorPosition + ) + } + } + validator.inputValue = formatted.string + } + + @objc + override public func becomeFirstResponder() -> Bool { + self.formContainer?.inputTextFieldWillBecomeFirstResponder(self) + let ret = super.becomeFirstResponder() + updateTextColor() + return ret + } + + @objc + override public func resignFirstResponder() -> Bool { + let ret = super.resignFirstResponder() + if ret { + self.formContainer?.inputTextFieldDidResignFirstResponder(self) + } + updateTextColor() + return ret + } + + var isValid: Bool { + switch validator.validationState { + case .unknown, .valid, .processing: + return true + case .incomplete: + if isEditing { + return true + } else { + return false + } + case .invalid: + return false + } + } + + @objc + override public var isUserInteractionEnabled: Bool { + didSet { + if isUserInteractionEnabled { + updateTextColor() + defaultPlaceholderColor = .secondaryLabel + floatingPlaceholderColor = .secondaryLabel + } else { + textColor = InputFormColors.disabledTextColor + defaultPlaceholderColor = InputFormColors.disabledTextColor + floatingPlaceholderColor = InputFormColors.disabledTextColor + } + } + } + + func updateTextColor() { + switch validator.validationState { + + case .unknown: + textColor = InputFormColors.textColor + errorStateImageView.alpha = 0 + case .incomplete: + if isEditing || (validator.inputValue?.isEmpty ?? true) { + textColor = InputFormColors.textColor + errorStateImageView.alpha = 0 + } else { + textColor = InputFormColors.errorColor + errorStateImageView.alpha = 1 + } + case .invalid: + textColor = InputFormColors.errorColor + errorStateImageView.alpha = 1 + case .valid: + textColor = InputFormColors.textColor + errorStateImageView.alpha = 0 + case .processing: + textColor = InputFormColors.textColor + errorStateImageView.alpha = 0 + } + } + + @objc public override var accessibilityAttributedValue: NSAttributedString? { + get { + guard let text = text else { + return nil + } + let attributedString = NSMutableAttributedString(string: text) + attributedString.addAttribute( + .accessibilitySpeechSpellOut, + value: NSNumber(value: true), + range: attributedString.extent + ) + return attributedString + } + set { + // do nothing + } + } + + @objc public override var accessibilityAttributedLabel: NSAttributedString? { + get { + guard let accessibilityLabel = accessibilityLabel else { + return nil + } + let attributedString = NSMutableAttributedString(string: accessibilityLabel) + if !isValid { + let invalidData = STPLocalizedString( + "Invalid data.", + "Spoken during VoiceOver when a form field has failed validation." + ) + let failedString = NSMutableAttributedString( + string: invalidData, + attributes: [ + NSAttributedString.Key.accessibilitySpeechPitch: NSNumber(value: 0.6) + ] + ) + attributedString.append(NSAttributedString(string: " ")) + attributedString.append(failedString) + } + return attributedString + } + set { + // do nothing + } + } + + @objc + public override func deleteBackward() { + let deletingOnEmpty = (text?.count ?? 0) == 0 + super.deleteBackward() + if deletingOnEmpty { + formContainer?.inputTextFieldDidBackspaceOnEmpty(self) + } + } + + // Fixes a weird issue related to our custom override of deleteBackwards. This only affects the simulator and iPads with custom keyboards. + // copied from STPFormTextField + @objc + public override var keyCommands: [UIKeyCommand]? { + return [ + UIKeyCommand( + input: "\u{08}", + modifierFlags: .command, + action: #selector(commandDeleteBackwards) + ), + ] + } + + @objc + func commandDeleteBackwards() { + text = "" + } + + // MARK: - STPInputTextFieldValidationObserver + func validationDidUpdate( + to state: STPValidatedInputState, + from previousState: STPValidatedInputState, + for unformattedInput: String?, + in input: STPFormInput + ) { + + guard input == self, + unformattedInput == text + else { + return + } + updateTextColor() + } +} + +/// :nodoc: +extension STPInputTextField: STPFormInput { + + var validationState: STPValidatedInputState { + return validator.validationState + } + + var inputValue: String? { + return validator.inputValue + } + + func addObserver(_ validationObserver: STPFormInputValidationObserver) { + validator.addObserver(validationObserver) + } + + func removeObserver(_ validationObserver: STPFormInputValidationObserver) { + validator.removeObserver(validationObserver) + } + +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextFieldFormatter.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextFieldFormatter.swift new file mode 100644 index 00000000..8b86914e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextFieldFormatter.swift @@ -0,0 +1,62 @@ +// +// STPInputTextFieldFormatter.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import UIKit + +class STPInputTextFieldFormatter: NSObject { + func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool { + return true + } + + func formattedText( + from input: String, + with defaultAttributes: [NSAttributedString.Key: Any] + ) + -> NSAttributedString + { + return NSAttributedString(string: input, attributes: defaultAttributes) + } +} + +/// :nodoc: +extension STPInputTextFieldFormatter: UITextFieldDelegate { + public func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + + let insertingIntoEmptyField = + (textField.text?.count ?? 0) == 0 && range.location == 0 && range.length == 0 + let hasTextContentType = textField.textContentType != nil + + if hasTextContentType && insertingIntoEmptyField && string == " " { + // Observed behavior w/ iOS 11.0 through 11.2.0 (latest checked): + // + // 1. UITextContentType suggestions are only available when textField is empty + // 2. When user taps a QuickType suggestion for the `textContentType`, UIKit *first* + // calls this method with `range:{0, 0} replacementString:@" "` + // 3. If that succeeds (we return YES), this method is called again, this time with + // the actual content to insert (and a space at the end) + // + // Therefore, always allow entry of a single space in order to support `textContentType`. + return true + } + + // string.isEmpty check always allows deletions + return string.isEmpty || isAllowedInput(string, to: textField.text ?? "", at: range) + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField.returnKeyType == .done { + _ = textField.resignFirstResponder() + return false + } + return true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextFieldValidator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextFieldValidator.swift new file mode 100644 index 00000000..31a059a1 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPInputTextFieldValidator.swift @@ -0,0 +1,65 @@ +// +// STPInputTextFieldValidator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import UIKit + +enum STPValidatedInputState { + case unknown + case incomplete(description: String?) + case invalid(errorMessage: String?) + case valid(message: String?) + case processing +} + +/// :nodoc: +extension STPValidatedInputState: Equatable { + +} + +class STPInputTextFieldValidator: NSObject { + + var defaultErrorMessage: String? { + return nil + } + + var observersHash = NSHashTable.weakObjects() + + weak var textField: STPInputTextField? + + public var inputValue: String? + + var validationState: STPValidatedInputState = .unknown { + didSet { + updateObservers(with: validationState, previous: oldValue) + } + } + + public func addObserver(_ validationObserver: STPFormInputValidationObserver) { + // This is not thread safe: https://jira.corp.stripe.com/browse/MOBILESDK-108 + observersHash.add(validationObserver) + } + + public func removeObserver(_ validationObserver: STPFormInputValidationObserver) { + // This is not thread safe: https://jira.corp.stripe.com/browse/MOBILESDK-108 + observersHash.remove(validationObserver) + } + + func updateObservers(with state: STPValidatedInputState, previous: STPValidatedInputState) { + guard let textField = textField, + observersHash.count > 0 + else { + return + } + let observersCopy = observersHash.allObjects.compactMap({ + $0 as? STPFormInputValidationObserver + }) + for observer in observersCopy { + observer.validationDidUpdate(to: state, from: previous, for: inputValue, in: textField) + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPNumericDigitInputTextFormatter.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPNumericDigitInputTextFormatter.swift new file mode 100644 index 00000000..ae09ad23 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/Inputs/STPNumericDigitInputTextFormatter.swift @@ -0,0 +1,50 @@ +// +// STPNumericDigitInputTextFormatter.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +class STPNumericDigitInputTextFormatter: STPInputTextFieldFormatter { + let allowedFormattingCharacterSet: CharacterSet? + + internal init( + allowedFormattingCharacterSet: CharacterSet? + ) { + self.allowedFormattingCharacterSet = allowedFormattingCharacterSet + super.init() + } + + override convenience init() { + self.init(allowedFormattingCharacterSet: nil) + } + + override func isAllowedInput(_ input: String, to string: String, at range: NSRange) -> Bool { + guard super.isAllowedInput(input, to: string, at: range) else { + return false + } + + let unformattedInput: String + if let allowedFormattingCharacterSet = allowedFormattingCharacterSet { + unformattedInput = input.stp_stringByRemovingCharacters( + from: allowedFormattingCharacterSet + ) + } else { + unformattedInput = input + } + + return STPNumericStringValidator.isStringNumeric(unformattedInput) + } + + override func formattedText( + from input: String, + with defaultAttributes: [NSAttributedString.Key: Any] + ) -> NSAttributedString { + let numeric = STPNumericStringValidator.sanitizedNumericString(for: input) + return NSAttributedString(string: numeric, attributes: defaultAttributes) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPAUBECSFormViewModel.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPAUBECSFormViewModel.swift new file mode 100644 index 00000000..186395ec --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPAUBECSFormViewModel.swift @@ -0,0 +1,198 @@ +// +// STPAUBECSFormViewModel.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/12/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +enum STPAUBECSFormViewField: Int { + case name + case email + case BSBNumber + case accountNumber +} + +class STPAUBECSFormViewModel { + var name: String? + var email: String? + var bsbNumber: String? + var accountNumber: String? + + var becsDebitParams: STPPaymentMethodAUBECSDebitParams? { + guard areFieldsComplete(becsFieldsOnly: true) else { + return nil + } + + let params = STPPaymentMethodAUBECSDebitParams() + params.bsbNumber = STPBSBNumberValidator.sanitizedNumericString(for: bsbNumber ?? "") + params.accountNumber = STPBECSDebitAccountNumberValidator.sanitizedNumericString( + for: accountNumber ?? "" + ) + + return params + } + + var paymentMethodParams: STPPaymentMethodParams? { + guard areFieldsComplete(becsFieldsOnly: false), + let params = becsDebitParams + else { + return nil + } + + let billing = STPPaymentMethodBillingDetails() + billing.name = name + billing.email = email + + return STPPaymentMethodParams( + aubecsDebit: params, + billingDetails: billing, + metadata: nil + ) + } + + func formattedString(forInput input: String, in field: STPAUBECSFormViewField) -> String { + switch field { + case .name: + return input + case .email: + return input + case .BSBNumber: + return STPBSBNumberValidator.formattedSanitizedText(from: input) ?? "" + case .accountNumber: + return STPBECSDebitAccountNumberValidator.formattedSanitizedText( + from: input, + withBSBNumber: STPBSBNumberValidator.sanitizedNumericString(for: bsbNumber ?? "") + ) + ?? "" + } + } + + func bsbLabel( + forInput input: String?, + editing: Bool, + isErrorString: UnsafeMutablePointer + ) + -> String? + { + let state = STPBSBNumberValidator.validationState(forText: input ?? "") + if state == .invalid { + isErrorString.pointee = true + return STPLocalizedString( + "The BSB you entered is invalid.", + "Error string displayed to user when they enter in an invalid BSB number." + ) + } else if state == .incomplete && !editing { + isErrorString.pointee = true + return String.Localized.incompleteBSBEntered + } else { + isErrorString.pointee = false + return STPBSBNumberValidator.identity(forText: input ?? "") + } + } + + func bankIcon(forInput input: String?) -> UIImage { + return STPBSBNumberValidator.icon(forText: input) + } + + func isFieldComplete( + withInput input: String, + in field: STPAUBECSFormViewField, + editing: Bool + ) + -> Bool + { + switch field { + case .name: + return input.count > 0 + case .email: + return STPEmailAddressValidator.stringIsValidEmailAddress(input) + case .BSBNumber: + return STPBSBNumberValidator.validationState(forText: input) == .complete + case .accountNumber: + // If it's currently being edited, we won't consider the account number field complete until it reaches its + // maximum allowed length + return STPBECSDebitAccountNumberValidator.validationState( + forText: input, + withBSBNumber: STPBSBNumberValidator.sanitizedNumericString(for: bsbNumber ?? ""), + completeOnMaxLengthOnly: editing + ) == .complete + } + } + + func isInputValid(_ input: String, for field: STPAUBECSFormViewField, editing: Bool) -> Bool { + switch field { + case .name: + return true + case .email: + return input.count == 0 + || (editing && STPEmailAddressValidator.stringIsValidPartialEmailAddress(input)) + || (!editing && STPEmailAddressValidator.stringIsValidEmailAddress(input)) + case .BSBNumber: + let state = STPBSBNumberValidator.validationState(forText: input) + if editing { + return state != .invalid + } else { + return state != .invalid && state != .incomplete + } + case .accountNumber: + let state = STPBECSDebitAccountNumberValidator.validationState( + forText: input, + withBSBNumber: STPBSBNumberValidator.sanitizedNumericString(for: bsbNumber ?? ""), + completeOnMaxLengthOnly: editing + ) + if editing { + return state != .invalid + } else { + return state != .invalid && state != .incomplete + } + } + } + + private func areFieldsComplete(becsFieldsOnly: Bool) -> Bool { + var fields: [STPAUBECSFormViewField] + if becsFieldsOnly { + fields = [ + .BSBNumber, + .accountNumber, + ] + } else { + fields = [ + .name, + .email, + .BSBNumber, + .accountNumber, + ] + } + + for field in fields { + var input: String? + switch field { + case .name: + input = name + case .email: + input = email + case .BSBNumber: + input = bsbNumber + case .accountNumber: + input = accountNumber + } + + if let input = input { + if !isFieldComplete(withInput: input, in: field, editing: false) { + return false + } + } else { + return false + } + } + + return true + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPCardLoadingIndicator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPCardLoadingIndicator.swift new file mode 100644 index 00000000..3835b5c4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPCardLoadingIndicator.swift @@ -0,0 +1,102 @@ +// +// STPCardLoadingIndicator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 8/24/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import UIKit + +private let kCardLoadingIndicatorDiameter: CGFloat = 14.0 +private let kCardLoadingInnerCircleDiameter: CGFloat = 10.0 +private let kLoadingAnimationSpinDuration: CFTimeInterval = 0.6 +private let kLoadingAnimationIdentifier = "STPCardLoadingIndicator.spinning" + +class STPCardLoadingIndicator: UIView { + private var indicatorLayer: CALayer? + + override init( + frame: CGRect + ) { + super.init(frame: frame) + backgroundColor = UIColor( + red: 79.0 / 255.0, + green: 86.0 / 255.0, + blue: 107.0 / 255.0, + alpha: 1.0 + ) + + // Make us a circle + let shape = CAShapeLayer() + let path = UIBezierPath( + arcCenter: CGPoint( + x: 0.5 * kCardLoadingIndicatorDiameter, + y: 0.5 * kCardLoadingIndicatorDiameter + ), + radius: 0.5 * kCardLoadingIndicatorDiameter, + startAngle: 0.0, + endAngle: 2.0 * .pi, + clockwise: true + ) + shape.path = path.cgPath + layer.mask = shape + + // Add the inner circle + let innerCircle = CAShapeLayer() + innerCircle.anchorPoint = CGPoint(x: 0.5, y: 0.5) + innerCircle.position = CGPoint( + x: 0.5 * kCardLoadingIndicatorDiameter, + y: 0.5 * kCardLoadingIndicatorDiameter + ) + + let indicatorPath = UIBezierPath( + arcCenter: CGPoint(x: 0.0, y: 0.0), + radius: 0.5 * kCardLoadingInnerCircleDiameter, + startAngle: 0.0, + endAngle: 9.0 * .pi / 6.0, + clockwise: true + ) + innerCircle.path = indicatorPath.cgPath + innerCircle.strokeColor = UIColor(white: 1.0, alpha: 0.8).cgColor + innerCircle.fillColor = UIColor.clear.cgColor + layer.addSublayer(innerCircle) + indicatorLayer = innerCircle + } + + override var intrinsicContentSize: CGSize { + return CGSize(width: kCardLoadingIndicatorDiameter, height: kCardLoadingIndicatorDiameter) + } + + override func systemLayoutSizeFitting( + _ targetSize: CGSize, + withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, + verticalFittingPriority: UILayoutPriority + ) -> CGSize { + return intrinsicContentSize + } + + override func didMoveToWindow() { + super.didMoveToWindow() + startAnimating() + } + + func startAnimating() { + let spinAnimation = CABasicAnimation(keyPath: "transform.rotation") + spinAnimation.byValue = NSNumber(value: Float(2.0 * .pi)) + spinAnimation.duration = kLoadingAnimationSpinDuration + spinAnimation.repeatCount = .infinity + + indicatorLayer?.add(spinAnimation, forKey: kLoadingAnimationIdentifier) + } + + func stopAnimating() { + indicatorLayer?.removeAnimation(forKey: kLoadingAnimationIdentifier) + } + + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPFormTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPFormTextField.swift new file mode 100644 index 00000000..288dde6a --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPFormTextField.swift @@ -0,0 +1,484 @@ +// +// STPFormTextField.swift +// StripePaymentsUI +// +// Created by Jack Flintermann on 7/16/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +import UIKit + +@_spi(STP) public enum STPFormTextFieldAutoFormattingBehavior: Int { + case none + case phoneNumbers + case cardNumbers + case expiration + case bsbNumber +} + +@objc protocol STPFormTextFieldDelegate: UITextFieldDelegate { + // Note, post-Swift conversion: + // In lieu of a real delegate proxy, this should always be implemented and call: + // if let textField = textField as? STPFormTextField, let delegateProxy = textField.delegateProxy { + // return delegateProxy.textField(textField, shouldChangeCharactersIn: range, replacementString: string) + // } + // return true + @objc(textField:shouldChangeCharactersInRange:replacementString:) func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool + + @objc optional func formTextFieldDidBackspace(onEmpty formTextField: STPFormTextField) + @objc optional func formTextField( + _ formTextField: STPFormTextField, + modifyIncomingTextChange input: NSAttributedString + ) -> NSAttributedString + @objc optional func formTextFieldTextDidChange(_ textField: STPFormTextField) +} + +@objc @_spi(STP) public class STPFormTextField: STPValidatedTextField { + + private var _selectionEnabled = false + @_spi(STP) public var selectionEnabled: Bool { + get { + _selectionEnabled + } + set(selectionEnabled) { + _selectionEnabled = selectionEnabled + delegateProxy?.selectionEnabled = selectionEnabled + } + } + // defaults to NO + @_spi(STP) public var preservesContentsOnPaste = false + // defaults to NO + private var _compressed = false + var compressed: Bool { + get { + _compressed + } + set(compressed) { + if compressed != _compressed { + _compressed = compressed + // reset text values as needed + _didSetText(text: self.text ?? "") + _didSetAttributedPlaceholder(attributedPlaceholder: attributedPlaceholder) + } + } + } + // defaults to NO + private var _autoFormattingBehavior: STPFormTextFieldAutoFormattingBehavior = .none + @_spi(STP) public var autoFormattingBehavior: STPFormTextFieldAutoFormattingBehavior { + get { + _autoFormattingBehavior + } + set(autoFormattingBehavior) { + _autoFormattingBehavior = autoFormattingBehavior + delegateProxy?.autoformattingBehavior = autoFormattingBehavior + switch autoFormattingBehavior { + case .none, .expiration: + textFormattingBlock = nil + case .cardNumbers: + textFormattingBlock = { inputString in + guard let inputString = inputString else { + return NSAttributedString() + } + if !STPCardValidator.stringIsNumeric(inputString.string) { + return inputString + } + let attributedString = NSMutableAttributedString(attributedString: inputString) + let cardNumberFormat = STPCardValidator.cardNumberFormat( + forCardNumber: attributedString.string + ) + var index = 0 + for segmentLength in cardNumberFormat { + var segmentIndex = 0 + + while index < (attributedString.length) + && segmentIndex < Int(segmentLength.uintValue) + { + if index + 1 != attributedString.length + && segmentIndex + 1 == Int(segmentLength.uintValue) + { + attributedString.addAttribute( + .kern, + value: NSNumber(value: 5), + range: NSRange(location: index, length: 1) + ) + } else { + attributedString.addAttribute( + .kern, + value: NSNumber(value: 0), + range: NSRange(location: index, length: 1) + ) + } + + index += 1 + segmentIndex += 1 + } + } + return attributedString + } + case .phoneNumbers: + weak var weakSelf = self + textFormattingBlock = { inputString in + if !STPCardValidator.stringIsNumeric(inputString?.string ?? "") { + return inputString! + } + guard let strongSelf = weakSelf else { + return inputString! + } + let phoneNumber = STPPhoneNumberValidator.formattedSanitizedPhoneNumber( + for: inputString?.string ?? "" + ) + let attributes = type(of: strongSelf).attributes(for: inputString) + return NSAttributedString( + string: phoneNumber, + attributes: attributes as? [NSAttributedString.Key: Any] + ) + } + case .bsbNumber: + weak var weakSelf = self + textFormattingBlock = { inputString in + guard let inputString = inputString else { + return NSAttributedString() + } + if !STPBSBNumberValidator.isStringNumeric(inputString.string) { + return inputString + } + guard let strongSelf = weakSelf else { + return NSAttributedString() + } + let bsbNumber = STPBSBNumberValidator.formattedSanitizedText( + from: inputString.string + ) + let attributes = type(of: strongSelf).attributes(for: inputString) + return NSAttributedString( + string: bsbNumber ?? "", + attributes: attributes as? [NSAttributedString.Key: Any] + ) + } + } + } + } + + private weak var _formDelegate: STPFormTextFieldDelegate? + weak var formDelegate: STPFormTextFieldDelegate? { + get { + _formDelegate + } + set(formDelegate) { + _formDelegate = formDelegate + delegate = formDelegate + } + } + var delegateProxy: STPTextFieldDelegateProxy? + private var textFormattingBlock: STPFormTextTransformationBlock? + + class func attributes(for attributedString: NSAttributedString?) -> [AnyHashable: Any]? { + if attributedString?.length == 0 { + return [:] + } + return attributedString?.attributes( + at: 0, + longestEffectiveRange: nil, + in: NSRange(location: 0, length: attributedString?.length ?? 0) + ) + } + + /// :nodoc: + @objc + public override func insertText(_ text: String) { + self.text = self.text ?? "" + text + } + + /// :nodoc: + @objc + public override func deleteBackward() { + super.deleteBackward() + if (text?.count ?? 0) == 0 { + if formDelegate?.responds( + to: #selector(STPPaymentCardTextField.formTextFieldDidBackspace(onEmpty:)) + ) ?? false { + formDelegate?.formTextFieldDidBackspace?(onEmpty: self) + } + } + } + + /// :nodoc: + @objc public override var text: String? { + get { + return super.text + } + set(text) { + let nonNilText = text ?? "" + _didSetText(text: nonNilText) + } + } + func _didSetText(text: String) { + let attributed = NSAttributedString(string: text, attributes: defaultTextAttributes) + attributedText = attributed + } + + /// :nodoc: + @objc public override var attributedText: NSAttributedString? { + get { + return super.attributedText + } + set(attributedText) { + let oldValue = self.attributedText + let shouldModify = + formDelegate != nil + && formDelegate?.responds( + to: #selector( + STPFormTextFieldDelegate.formTextField(_:modifyIncomingTextChange:)) + ) + ?? false + var modified: NSAttributedString? + if let attributedText = attributedText { + modified = + shouldModify + ? formDelegate?.formTextField?(self, modifyIncomingTextChange: attributedText) + : attributedText + } + let transformed = textFormattingBlock != nil ? textFormattingBlock?(modified) : modified + super.attributedText = transformed + sendActions(for: .editingChanged) + if formDelegate?.responds( + to: #selector(STPPaymentCardTextField.formTextFieldTextDidChange(_:)) + ) ?? false { + if let oldValue = oldValue { + if !(transformed?.isEqual(to: oldValue) ?? false) { + formDelegate?.formTextFieldTextDidChange?(self) + } + } + } + } + } + + @objc public override var accessibilityAttributedValue: NSAttributedString? { + get { + guard let text = text else { + return nil + } + let attributedString = NSMutableAttributedString(string: text) + attributedString.addAttribute( + .accessibilitySpeechSpellOut, + value: NSNumber(value: true), + range: attributedString.extent + ) + return attributedString + } + set { + // do nothing + } + } + + @objc public override var accessibilityAttributedLabel: NSAttributedString? { + get { + guard let accessibilityLabel = accessibilityLabel else { + return nil + } + let attributedString = NSMutableAttributedString(string: accessibilityLabel) + if !validText { + let invalidData = STPLocalizedString( + "Invalid data.", + "Spoken during VoiceOver when a form field has failed validation." + ) + let failedString = NSMutableAttributedString( + string: invalidData, + attributes: [ + NSAttributedString.Key.accessibilitySpeechPitch: NSNumber(value: 0.6) + ] + ) + attributedString.append(NSAttributedString(string: " ")) + attributedString.append(failedString) + } + return attributedString + } + set { + // do nothing + } + } + + /// :nodoc: + @objc public override var attributedPlaceholder: NSAttributedString? { + get { + return super.attributedPlaceholder + } + set(attributedPlaceholder) { + _didSetAttributedPlaceholder(attributedPlaceholder: attributedPlaceholder) + } + } + func _didSetAttributedPlaceholder(attributedPlaceholder: NSAttributedString?) { + let transformed = + textFormattingBlock != nil + ? textFormattingBlock?(attributedPlaceholder) : attributedPlaceholder + super.attributedPlaceholder = transformed + } + + // Fixes a weird issue related to our custom override of deleteBackwards. This only affects the simulator and iPads with custom keyboards. + /// :nodoc: + @objc public override var keyCommands: [UIKeyCommand]? { + return [ + UIKeyCommand( + input: "\u{08}", + modifierFlags: .command, + action: #selector(commandDeleteBackwards) + ), + ] + } + + @objc func commandDeleteBackwards() { + text = "" + } + + /// :nodoc: + @objc + public override func closestPosition(to point: CGPoint) -> UITextPosition? { + if selectionEnabled { + return super.closestPosition(to: point) + } + return position(from: beginningOfDocument, offset: text?.count ?? 0) + } + + /// :nodoc: + @objc + public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + return super.canPerformAction(action, withSender: sender) && action == #selector(paste(_:)) + } + + /// :nodoc: + @objc + public override func paste(_ sender: Any?) { + if preservesContentsOnPaste { + super.paste(sender) + } else if autoFormattingBehavior == .expiration { + text = STPStringUtils.expirationDateString(from: UIPasteboard.general.string) + } else { + text = UIPasteboard.general.string + } + } + + /// :nodoc: + @objc public override weak var delegate: UITextFieldDelegate? { + get { + super.delegate + } + set { + let dProxy = STPTextFieldDelegateProxy() + dProxy.autoformattingBehavior = autoFormattingBehavior + dProxy.selectionEnabled = selectionEnabled + self.delegateProxy = dProxy + super.delegate = newValue + } + } +} + +class STPTextFieldDelegateProxy: NSObject, UITextFieldDelegate { + internal var inShouldChangeCharactersInRange = false + @_spi(STP) public var autoformattingBehavior: STPFormTextFieldAutoFormattingBehavior = .none + @_spi(STP) public var selectionEnabled = false + + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + if inShouldChangeCharactersInRange { + // This guards against infinite recursion that happens when moving the cursor + return true + } + inShouldChangeCharactersInRange = true + + let insertingIntoEmptyField = + (textField.text?.count ?? 0) == 0 && range.location == 0 && range.length == 0 + let hasTextContentType = textField.textContentType != nil + + if hasTextContentType && insertingIntoEmptyField && (string == " ") { + // Observed behavior w/iOS 11.0 through 11.2.0 (latest): + // + // 1. UITextContentType suggestions are only available when textField is empty + // 2. When user taps a QuickType suggestion for the `textContentType`, UIKit *first* + // calls this method with `range:{0, 0} replacementString:@" "` + // 3. If that succeeds (we return YES), this method is called again, this time with + // the actual content to insert (and a space at the end) + // + // Therefore, always allow entry of a single space in order to support `textContentType`. + // + // Warning: This bypasses `setText:`, and subsequently `setAttributedText:` and the + // formDelegate methods: `formTextField:modifyIncomingTextChange:` & `formTextFieldTextDidChange:` + // That's acceptable for a single space. + inShouldChangeCharactersInRange = false + return true + } + + let deleting = + range.location == (textField.text?.count ?? 0) - 1 && range.length == 1 + && (string == "") + var inputText: String? + if deleting { + if let sanitized = unformattedString(for: textField.text) { + inputText = sanitized.stp_safeSubstring(to: sanitized.count - 1) + } + } else { + let newString = (textField.text as NSString?)?.replacingCharacters( + in: range, + with: string + ) + // Removes any disallowed characters from the whole string. + // If we (incorrectly) allowed a space to start the text entry hoping it would be a + // textContentType completion, this will remove it. + let sanitized = unformattedString(for: newString) + inputText = sanitized + } + + let beginning = textField.beginningOfDocument + let start = textField.position(from: beginning, offset: range.location) + + if textField.text == inputText { + inShouldChangeCharactersInRange = false + return false + } + + textField.text = inputText + + if autoformattingBehavior == .none && selectionEnabled { + + // this will be the new cursor location after insert/paste/typing + var cursorOffset: Int? + if let start = start { + cursorOffset = textField.offset(from: beginning, to: start) + string.count + } + + let newCursorPosition = textField.position( + from: textField.beginningOfDocument, + offset: cursorOffset ?? 0 + ) + var newSelectedRange: UITextRange? + if let newCursorPosition = newCursorPosition { + newSelectedRange = textField.textRange( + from: newCursorPosition, + to: newCursorPosition + ) + } + textField.selectedTextRange = newSelectedRange + } + + inShouldChangeCharactersInRange = false + return false + } + + func unformattedString(for string: String?) -> String? { + switch autoformattingBehavior { + case .none: + return string + case .cardNumbers, .phoneNumbers, .expiration, .bsbNumber: + return STPCardValidator.sanitizedNumericString(for: string ?? "") + } + } +} + +typealias STPFormTextTransformationBlock = (NSAttributedString?) -> NSAttributedString diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPLabeledFormTextFieldView.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPLabeledFormTextFieldView.swift new file mode 100644 index 00000000..badd26f7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPLabeledFormTextFieldView.swift @@ -0,0 +1,115 @@ +// +// STPLabeledFormTextFieldView.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/12/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +let kLabeledFormFieldHeight: CGFloat = 44.0 +let kLabeledFormVeriticalMargin: CGFloat = 4.0 +let kLabeledFormHorizontalMargin: CGFloat = 12.0 + +class STPLabeledFormTextFieldView: STPViewWithSeparator { + private var formLabel: UILabel? + + @objc init( + formLabel formLabelText: String, + textField: STPFormTextField + ) { + super.init(frame: CGRect.zero) + let formLabel = UILabel() + formLabel.text = formLabelText + formLabel.font = textField.font + formLabel.textColor = textField.defaultColor + // We want the textField to fill additional space so set the label's contentHuggingPriority to high + formLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + formLabel.translatesAutoresizingMaskIntoConstraints = false + textField.translatesAutoresizingMaskIntoConstraints = false + addSubview(formLabel) + addSubview(textField) + + var constraints = [ + formLabel.widthAnchor.constraint( + lessThanOrEqualTo: layoutMarginsGuide.widthAnchor, + multiplier: 0.5 + ), + heightAnchor.constraint(greaterThanOrEqualToConstant: kLabeledFormFieldHeight), + textField.centerYAnchor.constraint(equalTo: centerYAnchor), + formLabel.centerYAnchor.constraint(equalTo: centerYAnchor), + topAnchor.anchorWithOffset(to: textField.topAnchor).constraint( + greaterThanOrEqualToConstant: kLabeledFormVeriticalMargin + ), + topAnchor.anchorWithOffset(to: formLabel.topAnchor).constraint( + greaterThanOrEqualToConstant: kLabeledFormVeriticalMargin + ), + // constraining the height here works around an issue where UITextFields without a border style + // change height slightly when they become or resign first responder + textField.heightAnchor.constraint( + equalToConstant: textField.systemLayoutSizeFitting(UIView.layoutFittingExpandedSize) + .height + ), + ] + + constraints.append( + contentsOf: [ + formLabel.leadingAnchor.constraint( + equalToSystemSpacingAfter: layoutMarginsGuide.leadingAnchor, + multiplier: 1.0 + ), + textField.leadingAnchor.constraint( + equalToSystemSpacingAfter: formLabel.trailingAnchor, + multiplier: 2.0 + ), + layoutMarginsGuide.trailingAnchor.constraint( + equalToSystemSpacingAfter: textField.trailingAnchor, + multiplier: 1.0 + ), + ]) + + labelWidthDimension = formLabel.widthAnchor + self.formLabel = formLabel + + NSLayoutConstraint.activate(constraints) + } + + @objc var formBackgroundColor: UIColor? { + get { + return backgroundColor ?? UIColor.clear + } + set(formBackgroundColor) { + backgroundColor = formBackgroundColor + } + } + // Initializes to textField.defaultColor + + var formLabelTextColor: UIColor? { + get { + return formLabel?.textColor ?? UIColor.clear + } + set(formLabelTextColor) { + formLabel?.textColor = formLabelTextColor + } + } + // Initializes to textField.font + + var formLabelFont: UIFont? { + get { + return (formLabel?.font)! + } + set(formLabelFont) { + formLabel?.font = formLabelFont + } + } + private(set) var labelWidthDimension = NSLayoutDimension() + + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPLabeledMultiFormTextFieldView.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPLabeledMultiFormTextFieldView.swift new file mode 100644 index 00000000..9fc79279 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPLabeledMultiFormTextFieldView.swift @@ -0,0 +1,122 @@ +// +// STPLabeledMultiFormTextFieldView.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/12/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import UIKit + +class STPLabeledMultiFormTextFieldView: UIView { + private var fieldContainer: STPViewWithSeparator? + + init( + formLabel formLabelText: String, + firstTextField textField1: STPFormTextField, + secondTextField textField2: STPFormTextField + ) { + super.init(frame: CGRect.zero) + let formLabel = UILabel() + formLabel.text = formLabelText + formLabel.font = UIFont.preferredFont(forTextStyle: .caption1) + formLabel.textColor = UIColor.secondaryLabel + formLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(formLabel) + + let fieldContainer = STPViewWithSeparator() + fieldContainer.backgroundColor = UIColor.systemBackground + + textField1.translatesAutoresizingMaskIntoConstraints = false + textField2.translatesAutoresizingMaskIntoConstraints = false + fieldContainer.addSubview(textField1) + fieldContainer.addSubview(textField2) + + fieldContainer.translatesAutoresizingMaskIntoConstraints = false + addSubview(fieldContainer) + + var constraints = [ + formLabel.topAnchor.constraint( + equalTo: topAnchor, + constant: kLabeledFormVeriticalMargin + ), + fieldContainer.topAnchor.constraint( + equalTo: formLabel.bottomAnchor, + constant: kLabeledFormVeriticalMargin + ), + fieldContainer.heightAnchor.constraint( + greaterThanOrEqualToConstant: kLabeledFormFieldHeight + ), + bottomAnchor.constraint(equalTo: fieldContainer.bottomAnchor), + fieldContainer.leadingAnchor.constraint(equalTo: leadingAnchor), + fieldContainer.trailingAnchor.constraint(equalTo: trailingAnchor), + textField1.centerYAnchor.constraint(equalTo: fieldContainer.centerYAnchor), + textField2.centerYAnchor.constraint(equalTo: fieldContainer.centerYAnchor), + textField1.trailingAnchor.constraint( + equalTo: centerXAnchor, + constant: -0.5 * kLabeledFormHorizontalMargin + ), + textField2.leadingAnchor.constraint( + equalTo: centerXAnchor, + constant: 0.5 * kLabeledFormHorizontalMargin + ), + fieldContainer.topAnchor.anchorWithOffset(to: textField1.topAnchor).constraint( + greaterThanOrEqualToConstant: kLabeledFormVeriticalMargin + ), + fieldContainer.topAnchor.anchorWithOffset(to: textField2.topAnchor).constraint( + greaterThanOrEqualToConstant: kLabeledFormVeriticalMargin + ), + // constraining the height here works around an issue where UITextFields without a border style + // change height slightly when they become or resign first responder + textField1.heightAnchor.constraint( + equalToConstant: textField1.systemLayoutSizeFitting( + UIView.layoutFittingExpandedSize + ).height + ), + textField2.heightAnchor.constraint( + equalToConstant: textField2.systemLayoutSizeFitting( + UIView.layoutFittingExpandedSize + ).height + ), + ] + + constraints.append( + contentsOf: [ + formLabel.leadingAnchor.constraint( + equalToSystemSpacingAfter: layoutMarginsGuide.leadingAnchor, + multiplier: 1.0 + ), + layoutMarginsGuide.trailingAnchor.constraint( + greaterThanOrEqualToSystemSpacingAfter: formLabel.trailingAnchor, + multiplier: 1.0 + ), + textField1.leadingAnchor.constraint( + equalToSystemSpacingAfter: layoutMarginsGuide.leadingAnchor, + multiplier: 1.0 + ), + layoutMarginsGuide.trailingAnchor.constraint( + equalToSystemSpacingAfter: textField2.trailingAnchor, + multiplier: 1.0 + ), + ]) + + NSLayoutConstraint.activate(constraints) + + self.fieldContainer = fieldContainer + } + + @objc var formBackgroundColor: UIColor? { + get { + return fieldContainer?.backgroundColor ?? UIColor.clear + } + set(formBackgroundColor) { + fieldContainer?.backgroundColor = formBackgroundColor + } + } + + required init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPPaymentCardTextFieldViewModel.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPPaymentCardTextFieldViewModel.swift new file mode 100644 index 00000000..bd3f4402 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPPaymentCardTextFieldViewModel.swift @@ -0,0 +1,260 @@ +// +// STPPaymentCardTextFieldViewModel.swift +// StripePaymentsUI +// +// Created by Jack Flintermann on 7/21/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripePayments +import UIKit + +@objc enum STPCardFieldType: Int { + case number + case expiration + case CVC + case postalCode +} + +class STPPaymentCardTextFieldViewModel: NSObject { + init(brandUpdateHandler: @escaping () -> Void) { + self.cbcController = STPCBCController(updateHandler: brandUpdateHandler) + } + + private var _cardNumber: String? + @objc dynamic var cardNumber: String? { + get { + _cardNumber + } + set(cardNumber) { + let sanitizedNumber = STPCardValidator.sanitizedNumericString(for: cardNumber ?? "") + hasCompleteMetadataForCardNumber = STPBINController.shared.hasBINRanges( + forPrefix: sanitizedNumber + ) + if hasCompleteMetadataForCardNumber { + let brand = STPCardValidator.brand(forNumber: sanitizedNumber) + let maxLength = STPCardValidator.maxLength(for: brand) + _cardNumber = sanitizedNumber.stp_safeSubstring(to: maxLength) + } else { + _cardNumber = sanitizedNumber.stp_safeSubstring( + to: Int(STPBINController.shared.maxCardNumberLength()) + ) + } + cbcController.cardNumber = _cardNumber + } + } + + @objc var rawExpiration: String? { + get { + var array: [String] = [] + if expirationMonth != nil && !(expirationMonth == "") { + array.append(expirationMonth ?? "") + } + + if STPCardValidator.validationState(forExpirationMonth: expirationMonth ?? "") == .valid + { + array.append(expirationYear ?? "") + } + return array.joined(separator: "/") + } + set(expiration) { + let sanitizedExpiration = STPCardValidator.sanitizedNumericString(for: expiration ?? "") + expirationMonth = sanitizedExpiration.stp_safeSubstring(to: 2) + expirationYear = sanitizedExpiration.stp_safeSubstring(from: 2).stp_safeSubstring(to: 2) + } + } + + private var _cvc: String? + @objc dynamic var cvc: String? { + get { + _cvc + } + set(cvc) { + let maxLength = STPCardValidator.maxCVCLength(for: brand) + _cvc = STPCardValidator.sanitizedNumericString(for: cvc ?? "").stp_safeSubstring( + to: Int(maxLength) + ) + } + } + @objc dynamic var postalCodeRequested = false + + var postalCodeRequired: Bool { + return postalCodeRequested + && STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: postalCodeCountryCode) + } + + private var _postalCode: String? + var postalCode: String? { + get { + _postalCode + } + set(postalCode) { + _postalCode = STPPostalCodeValidator.formattedSanitizedPostalCode( + from: postalCode, + countryCode: postalCodeCountryCode, + usage: .cardField + ) + } + } + + private var _postalCodeCountryCode: String? + @objc dynamic var postalCodeCountryCode: String? { + get { + _postalCodeCountryCode + } + set(postalCodeCountryCode) { + _postalCodeCountryCode = postalCodeCountryCode + postalCode = STPPostalCodeValidator.formattedSanitizedPostalCode( + from: postalCode, + countryCode: postalCodeCountryCode, + usage: .cardField + ) + } + } + + let cbcController: STPCBCController + + @objc dynamic var brand: STPCardBrand { + switch cbcController.brandState { + case .brand(let brand): + return brand + case .cbcBrandSelected(let brand): + return brand + case .unknown, .unknownMultipleOptions: + return .unknown + } + } + + @objc dynamic var isValid: Bool { + return STPCardValidator.validationState( + forNumber: cardNumber ?? "", + validatingCardBrand: true + ) + == .valid && hasCompleteMetadataForCardNumber + && validationStateForExpiration() == .valid + && validationStateForCVC() == .valid + && (!postalCodeRequired || validationStateForPostalCode() == .valid) + } + @objc dynamic private(set) var hasCompleteMetadataForCardNumber = false + + var isNumberMaxLength: Bool { + return (cardNumber?.count ?? 0) == STPBINController.shared.maxCardNumberLength() + } + + func defaultPlaceholder() -> String { + return "4242424242424242" + } + + func compressedCardNumber(withPlaceholder placeholder: String?) -> String? { + var cardNumber = self.cardNumber + if (cardNumber?.count ?? 0) == 0 { + cardNumber = placeholder ?? defaultPlaceholder() + } + + // use the card number format + let cardNumberFormat = STPCardValidator.cardNumberFormat(forCardNumber: cardNumber ?? "") + + var index = 0 + for segment in cardNumberFormat { + let segmentLength = Int(segment.uintValue) + if index + segmentLength >= (cardNumber?.count ?? 0) { + return cardNumber?.stp_safeSubstring(from: index) + } + index += segmentLength + } + + let length = Int(cardNumberFormat.last?.uintValue ?? 0) + index = (cardNumber?.count ?? 0) - length + + if index < (cardNumber?.count ?? 0) { + return cardNumber?.stp_safeSubstring(from: index) + } + + return nil + } + + func validationStateForExpiration() -> STPCardValidationState { + let monthState = STPCardValidator.validationState(forExpirationMonth: expirationMonth ?? "") + let yearState = STPCardValidator.validationState( + forExpirationYear: expirationYear ?? "", + inMonth: expirationMonth ?? "" + ) + if monthState == .valid && yearState == .valid { + return .valid + } else if monthState == .invalid || yearState == .invalid { + return .invalid + } else { + return .incomplete + } + } + + func validationStateForCVC() -> STPCardValidationState { + return STPCardValidator.validationState(forCVC: cvc ?? "", cardBrand: cbcController.brandForCVC) + } + + func validationStateForPostalCode() -> STPCardValidationState { + if (postalCode?.count ?? 0) > 0 { + return .valid + } else { + return .incomplete + } + } + + func validationStateForCardNumber(handler: @escaping (STPCardValidationState) -> Void) { + STPBINController.shared.retrieveBINRanges(forPrefix: cardNumber ?? "") { _ in + self.hasCompleteMetadataForCardNumber = STPBINController.shared.hasBINRanges( + forPrefix: self.cardNumber ?? "" + ) + handler( + STPCardValidator.validationState( + forNumber: self.cardNumber ?? "", + validatingCardBrand: true + ) + ) + } + } + + private var _expirationMonth: String? + @objc private(set) var expirationMonth: String? { + get { + _expirationMonth + } + set { + // This might contain slashes. + var sanitizedExpiration = STPCardValidator.sanitizedNumericString(for: newValue ?? "") + if sanitizedExpiration.count == 1 && !(sanitizedExpiration == "0") + && !(sanitizedExpiration == "1") + { + sanitizedExpiration = "0" + sanitizedExpiration + } + _expirationMonth = sanitizedExpiration.stp_safeSubstring(to: 2) + } + } + private var _expirationYear: String? + @objc private(set) dynamic var expirationYear: String? { + get { + _expirationYear + } + set { + _expirationYear = STPCardValidator.sanitizedNumericString(for: newValue ?? "") + .stp_safeSubstring(to: 2) + + } + } + + @objc + public class func keyPathsForValuesAffectingIsValid() -> Set { + return Set([ + "cardNumber", + "expirationMonth", + "expirationYear", + "cvc", + "brand", + "postalCode", + "postalCodeRequested", + "postalCodeCountryCode", + "hasCompleteMetadataForCardNumber", + ]) + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPValidatedTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPValidatedTextField.swift new file mode 100644 index 00000000..b966abe5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPValidatedTextField.swift @@ -0,0 +1,99 @@ +// +// STPValidatedTextField.swift +// StripePaymentsUI +// +// Created by Daniel Jackson on 12/14/17. +// Copyright © 2017 Stripe, Inc. All rights reserved. +// + +import UIKit + +/// A UITextField that changes the text color, based on the validity of +/// its contents. +/// This does *not* (currently?) have any logic or hooks for determining whether +/// the contents are valid, that must be done by something else. +@_spi(STP) public class STPValidatedTextField: UITextField { + + // MARK: - Property Overrides + private var _defaultColor: UIColor? + /// color to use for `text` when `validText` is YES + @_spi(STP) public var defaultColor: UIColor? { + get { + _defaultColor + } + set(defaultColor) { + _defaultColor = defaultColor + updateColor() + } + } + + private var _errorColor: UIColor? + /// color to use for `text` when `validText` is NO + @_spi(STP) public var errorColor: UIColor? { + get { + _errorColor + } + set(errorColor) { + _errorColor = errorColor + updateColor() + } + } + + private var _placeholderColor: UIColor? + /// color to use for `placeholderText`, displayed when `text` is empty + @objc @_spi(STP) public var placeholderColor: UIColor? { + get { + _placeholderColor + } + set(placeholderColor) { + _placeholderColor = placeholderColor + self._updateAttributedPlaceholder() + } + } + + private var _validText = false + /// flag to indicate whether the contents are valid or not. + @objc @_spi(STP) public var validText: Bool { + get { + _validText + } + set(validText) { + _validText = validText + updateColor() + } + } + + func _updateAttributedPlaceholder() { + let nonNilPlaceholder = placeholder ?? "" + let attributedPlaceholder = NSAttributedString( + string: nonNilPlaceholder, + attributes: placeholderTextAttributes() as? [NSAttributedString.Key: Any] + ) + self.attributedPlaceholder = attributedPlaceholder + } + + // MARK: - UITextField overrides + /// :nodoc: + @objc public override var placeholder: String? { + get { + return super.placeholder + } + set(placeholder) { + super.placeholder = placeholder + self._updateAttributedPlaceholder() + } + } + + // MARK: - Private Methods + func updateColor() { + textColor = validText ? defaultColor : errorColor + } + + func placeholderTextAttributes() -> [AnyHashable: Any]? { + var defaultAttributes = defaultTextAttributes + if let placeholderColor = placeholderColor { + defaultAttributes[NSAttributedString.Key.foregroundColor] = placeholderColor + } + return defaultAttributes + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPViewWithSeparator.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPViewWithSeparator.swift new file mode 100644 index 00000000..1fd92ddf --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/STPViewWithSeparator.swift @@ -0,0 +1,89 @@ +// +// STPViewWithSeparator.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/11/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import UIKit + +class STPViewWithSeparator: UIView { + private var topSeparator = UIView() + private var bottomSeparator = UIView() + private var separatorHeightConstraint = NSLayoutConstraint() + + @objc public var topSeparatorHidden: Bool { + get { + return topSeparator.isHidden + } + set(topSeparatorHidden) { + topSeparator.isHidden = topSeparatorHidden + } + } + + required init?( + coder: NSCoder + ) { + super.init(coder: coder) + _addSeparators() + } + + override init( + frame: CGRect + ) { + super.init(frame: frame) + _addSeparators() + } + + func _addSeparators() { + topSeparator.backgroundColor = UIColor.opaqueSeparator + + topSeparator.translatesAutoresizingMaskIntoConstraints = false + addSubview(topSeparator) + + separatorHeightConstraint = topSeparator.heightAnchor.constraint( + equalToConstant: _currentPixelHeight() + ) + + bottomSeparator.backgroundColor = UIColor.opaqueSeparator + + bottomSeparator.translatesAutoresizingMaskIntoConstraints = false + addSubview(bottomSeparator) + + NSLayoutConstraint.activate( + [ + topSeparator.leadingAnchor.constraint(equalTo: leadingAnchor), + topSeparator.trailingAnchor.constraint(equalTo: trailingAnchor), + topSeparator.topAnchor.constraint(equalTo: topAnchor), + separatorHeightConstraint, + bottomSeparator.leadingAnchor.constraint(equalTo: leadingAnchor), + bottomSeparator.trailingAnchor.constraint(equalTo: trailingAnchor), + bottomSeparator.bottomAnchor.constraint(equalTo: bottomAnchor), + bottomSeparator.heightAnchor.constraint( + equalTo: topSeparator.heightAnchor, + multiplier: 1.0 + ), + ]) + } + + /// :nodoc: + @objc + public override func didMoveToWindow() { + super.didMoveToWindow() + separatorHeightConstraint.constant = _currentPixelHeight() + } + + func _currentPixelHeight() -> CGFloat { + #if canImport(CompositorServices) + return 1.0 + #else + let screen = window?.screen ?? UIScreen.main + if screen.nativeScale > 0 { + return 1.0 / screen.nativeScale + } else { + return 0.5 + } + #endif + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/PaymentMethodMessagingView+Configuration.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/PaymentMethodMessagingView+Configuration.swift new file mode 100644 index 00000000..82630a5f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/PaymentMethodMessagingView+Configuration.swift @@ -0,0 +1,86 @@ +// +// PaymentMethodMessagingView+Configuration.swift +// StripeiOS +// +// Created by Yuki Tokuhiro on 9/29/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) extension PaymentMethodMessagingView { + /// 🏗 Under construction + /// + /// Configuration for the `PaymentMethodMessagingView` class. + public struct Configuration { + /// Initializes a `PaymentMethodMessagingView.Configuration` + public init( + apiClient: STPAPIClient = .shared, + paymentMethods: [PaymentMethodMessagingView.Configuration.PaymentMethod], + currency: String, + amount: Int, + locale: Locale = Locale.current, + countryCode: String = Locale.current.stp_regionCode ?? "", + font: UIFont = .preferredFont(forTextStyle: .footnote), + textColor: UIColor = .label, + imageColor: ( + userInterfaceStyleLight: PaymentMethodMessagingView.Configuration.ImageColor, + userInterfaceStyleDark: PaymentMethodMessagingView.Configuration.ImageColor + ) = (userInterfaceStyleLight: .dark, userInterfaceStyleDark: .light) + ) { + self.apiClient = apiClient + self.paymentMethods = paymentMethods + self.currency = currency + self.amount = amount + self.locale = locale + self.countryCode = countryCode + self.font = font + self.textColor = textColor + self.imageColor = imageColor + } + + /// Payment methods that can be displayed by `PaymentMethodMessagingView` + public enum PaymentMethod: CaseIterable { + case klarna + case afterpayClearpay + } + /// The APIClient instance used to make requests to Stripe + public var apiClient: STPAPIClient = .shared + /// The payment methods to display messaging for. + public var paymentMethods: [PaymentMethod] + /// The currency, as a three-letter ISO currency code. + public var currency: String + /// The purchase amount, in the smallest currency unit. e.g. 100 for $1 USD. + public var amount: Int + /// The customer's locale. Defaults to the device locale. + public var locale: Locale = Locale.current + /// The customer's country as a two-letter string. Defaults to their device's country. + public var countryCode: String = Locale.current.stp_regionCode ?? "" + /// The font of text displayed in the view. Defaults to the system font. + public var font: UIFont = .preferredFont(forTextStyle: .footnote) + /// The color of text displayed in the view. Defaults to `UIColor.labelColor`. + public var textColor: UIColor = .label + + /// The colors of the image + public enum ImageColor { + case light + case dark + case color + } + /// The color of the images displayed in the view as a tuple specifying the color to use in light and dark mode. + /// Defaults to `(.dark, .light)`. + public var imageColor: + (userInterfaceStyleLight: ImageColor, userInterfaceStyleDark: ImageColor) = ( + userInterfaceStyleLight: .dark, userInterfaceStyleDark: .light + ) + } +} + +extension PaymentMethodMessagingView { + /// PaymentMethodMessagingView errors + enum Error: Swift.Error { + /// The view failed to initialize the attributed string + case failedToInitializeAttributedString + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/PaymentMethodMessagingView.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/PaymentMethodMessagingView.swift new file mode 100644 index 00000000..515d011d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/PaymentMethodMessagingView.swift @@ -0,0 +1,355 @@ +// +// PaymentMethodMessagingView.swift +// StripeiOS +// +// Created by Yuki Tokuhiro on 9/26/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import SafariServices +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore +import UIKit +import WebKit + +/// 🏗 Under construction +/// +/// A view that displays promotional text and images for payment methods like Afterpay and Klarna. For example, "As low as 4 interest-free payments of $9.75". When tapped, this view presents a full-screen SFSafariViewController to the customer with additional information about the payment methods being displayed. +/// +/// You can embed this into your checkout or product screens to promote payment method options to your customer. +/// +/// - Note: You must initialize this class by calling `PaymentMethodMessagingView.create(configuration:completion:)` +@objc(STP_Internal_PaymentMethodMessagingView) +@_spi(STP) public final class PaymentMethodMessagingView: UIView { + // MARK: Initializers + + /// Asynchronously creates a `PaymentMethodMessagingView` for the given configuration. + /// - Parameter configuration: A Configuration object containing details like the payment methods to display and the purchase amount. + /// - Parameter completion: A completion block called when the view is loaded or failed to load. + /// - Note: You must use this method to initialize a `PaymentMethodMessagingView`. + public static func create( + configuration: Configuration, + completion: @escaping (Result) -> Void + ) { + assert(!configuration.paymentMethods.isEmpty) + assert(configuration.apiClient.publishableKey?.nonEmpty != nil) + assert(!configuration.countryCode.isEmpty) + let loadStartTime = Date() + let parameters = Self.makeMessagingContentEndpointParams(configuration: configuration) + var request = configuration.apiClient.configuredRequest( + for: APIEndpoint, + additionalHeaders: [:] + ) + request.stp_addParameters(toURL: parameters) + Task { + do { + let response = try await loadContent(configuration: configuration) + let attributedString = try await makeAttributedString( + from: response.display_l_html, + configuration: configuration + ) + let view = PaymentMethodMessagingView( + attributedString: attributedString, + modalURL: response.learn_more_modal_url, + configuration: configuration + ) + let loadDuration = Date().timeIntervalSince(loadStartTime) + Self.analyticsClient.log( + analytic: Analytic.loadSucceeded(duration: loadDuration), + apiClient: configuration.apiClient + ) + completion(.success(view)) + } catch { + let loadDuration = Date().timeIntervalSince(loadStartTime) + Self.analyticsClient.log( + analytic: Analytic.loadFailed(duration: loadDuration), + apiClient: configuration.apiClient + ) + completion(.failure(error)) + } + } + } + + init( + attributedString: NSAttributedString, + modalURL: String, + configuration: Configuration + ) { + Self.analyticsClient.addClass(toProductUsageIfNecessary: PaymentMethodMessagingView.self) + self.modalURL = URL(string: "https://" + modalURL) + self.configuration = configuration + super.init(frame: .zero) + directionalLayoutMargins = .init(top: 12, leading: 12, bottom: 12, trailing: 12) + backgroundColor = UIColor.systemBackground + label.attributedText = attributedString + label.textColor = configuration.textColor + label.translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), + label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), + label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + ]) + addSubview(dummyLabelForDynamicType) + let tap = UITapGestureRecognizer(target: self, action: #selector(didTap)) + addGestureRecognizer(tap) + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Overrides + +#if !canImport(CompositorServices) + // Overriden so we can respond to changing dark mode by updating the image color + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + Task { + var attributedString = label.attributedText + if previousTraitCollection?.isDarkMode != traitCollection.isDarkMode { + // Update images by reloading content from the server + let content = try await Self.loadContent(configuration: configuration) + attributedString = try await Self.makeAttributedString( + from: content.display_l_html, + configuration: configuration + ) + } + if adjustsFontForContentSizeCategory { + // Adjust the font size + let adjustedFontSize = + dummyLabelForDynamicType.font?.pointSize ?? configuration.font.pointSize + attributedString = attributedString?.withFontSize(adjustedFontSize) + } + label.attributedText = attributedString + label.textColor = configuration.textColor + } + } +#endif + + // MARK: Internal + + static let APIEndpoint: URL = URL(string: "https://ppm.stripe.com/content")! + let modalURL: URL? + let configuration: Configuration + static var analyticsClient: STPAnalyticsClientProtocol = STPAnalyticsClient.sharedClient + + lazy var label: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + return label + }() + + @objc func didTap() { + guard let modalURL = modalURL else { return } + let config = SFSafariViewController.Configuration() + let safariController = SFSafariViewController(url: modalURL, configuration: config) + safariController.modalPresentationStyle = .overCurrentContext + window?.findTopMostPresentedViewController()?.present(safariController, animated: true) + log(analytic: .tapped) + } + + // MARK: - UIContentSizeCategoryAdjusting + + public var adjustsFontForContentSizeCategory: Bool = false + /// Some notes on supporting `UIContentSizeCategoryAdjusting` and Dynamic Type: + /// 1. Our textView's `adjustsFontForContentSizeCategory` property doesn't work b/c we have to specify font via CSS (see `makeAttributedString`) + /// 2. There's no API to get the size of a font adjusted for content size category + /// As a workaround, we'll set `configuration.font` on this label and use its font size + private lazy var dummyLabelForDynamicType: UILabel = { + let view = UILabel() + view.text = "Dummy text" + view.font = configuration.font + view.adjustsFontForContentSizeCategory = true + view.isHidden = true + return view + }() +} + +// MARK: - Helpers + +extension PaymentMethodMessagingView { + /// A regex that matches tags and captures its url + static let imgTagRegex = try! NSRegularExpression(pattern: "") + + /// - Returns: The given `html` string with tags replaced with tags, and a list of the URLs contained in the replaced tags + static func htmlReplacingImageTags(html: String) -> (String, [URL]) { + var html = html + var images = [URL]() + for match in imgTagRegex.matches(in: html, range: NSRange(html.startIndex..., in: html)).reversed() { + guard + let rangeOfImgTag = Range(match.range, in: html), + let rangeOfURL = Range(match.range(at: 1), in: html) + else { + continue + } + let url = html[rangeOfURL] + html.replaceSubrange(rangeOfImgTag, with: "_") + // Fetch the image + guard let URL = URL(string: String(url)) else { continue } + images.append(URL) + } + return (html, images) + } + + static func makeCSS(for font: UIFont) -> String { + // To set the font, we prepend CSS to the given `html` before converting it to an NSAttributedString + let isSystemFont = font.familyName == UIFont.systemFont(ofSize: font.pointSize).familyName + // If the specified font is the same family as the system font, use "-apple-system" as the family name. Otherwise, the html renderer will only use the non-bold variation of the system font, breaking any bold font configurations. + let fontFamily = isSystemFont ? "-apple-system" : font.familyName + return """ + + """ + } + + static func makeAttributedString( + from html: String, + configuration: Configuration + ) async throws -> NSAttributedString { + // tags don't work with `NSAttributedString.loadFromHTML` on iOS 13/14. As a workaround, we'll replace with in this String, and manually replace them with images later: + // 1. Replace the tags with and pull out the image URLs + let (html, imageURLs) = htmlReplacingImageTags(html: html) + // 2. Construct the attributed string + let css = makeCSS(for: configuration.font) + let (attributedString, _) = try await NSAttributedString.fromHTML(css + html, options: [:]) + // 3. Fetch the images + var images = [URL: UIImage]() + for imageURL in imageURLs { + images[imageURL] = try await loadImage(url: imageURL, apiClient: configuration.apiClient) + } + // 4. Replace the links in the attributed string with image attachments + let mAttributedString = NSMutableAttributedString(attributedString: attributedString) + mAttributedString.enumerateAttribute(.link, in: NSRange(0.. PaymentMethodMessagingContentResponse { + let parameters = Self.makeMessagingContentEndpointParams(configuration: configuration) + var request = configuration.apiClient.configuredRequest( + for: APIEndpoint, + additionalHeaders: [:] + ) + request.stp_addParameters(toURL: parameters) + return try await withCheckedThrowingContinuation { continuation in + configuration.apiClient.get( + url: APIEndpoint, + parameters: parameters + ) { (result: Result) in + continuation.resume(with: result) + } + } + } + + static func makeMessagingContentEndpointParams(configuration: Configuration) -> [String: Any] { + let logoColor: String + switch UITraitCollection.current.isDarkMode + ? configuration.imageColor.userInterfaceStyleDark + : configuration.imageColor.userInterfaceStyleLight + { + case .light: + logoColor = "white" + case .dark: + logoColor = "black" + case .color: + logoColor = "color" + } + return [ + "payment_methods": configuration.paymentMethods.map { (paymentMethod) -> String in + switch paymentMethod { + case .klarna: return "klarna" + case .afterpayClearpay: return "afterpay_clearpay" + } + }, + "currency": configuration.currency, + "amount": configuration.amount, + "country": configuration.countryCode, + "client": "ios", + "logo_color": logoColor, + "locale": Locale.canonicalLanguageIdentifier(from: configuration.locale.identifier), + ] + } + + static func loadImage(url: URL, apiClient: STPAPIClient) async throws -> UIImage? { + let request = apiClient.configuredRequest(for: url) + let (data, _) = try await apiClient.urlSession.data(for: request) + return UIImage(data: data, scale: 3)?.withRenderingMode(.alwaysOriginal) + } +} + +// MARK: - STPAnalyticsProtocol +extension PaymentMethodMessagingView: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier = "PaymentMethodMessagingView" +} + +extension PaymentMethodMessagingView { + func log(analytic: Analytic) { + Self.analyticsClient.log(analytic: analytic, apiClient: configuration.apiClient) + } + + enum Analytic: StripeCore.Analytic { + case loadFailed(duration: TimeInterval) + case loadSucceeded(duration: TimeInterval) + case tapped + var event: StripeCore.STPAnalyticEvent { + switch self { + case .loadFailed: return .paymentMethodMessagingViewLoadFailed + case .loadSucceeded: return .paymentMethodMessagingViewLoadSucceeded + case .tapped: return .paymentMethodMessagingViewTapped + } + } + var params: [String: Any] { + switch self { + case .loadFailed(let duration), .loadSucceeded(let duration): + return [ + "duration": duration + ] + case .tapped: + return [:] + } + } + } +} + +// MARK: - NSAttributedString helpers +extension NSAttributedString { + func withFontSize(_ size: CGFloat) -> NSAttributedString { + let mutable = NSMutableAttributedString(attributedString: self) + mutable.enumerateAttributes(in: NSRange(0.. STPFormTextField { + let textField = STPFormTextField(frame: CGRect.zero) + textField.keyboardType = .asciiCapableNumberPad + textField.textAlignment = .natural + + textField.font = formFont + textField.defaultColor = formTextColor + textField.errorColor = formTextErrorColor + textField.placeholderColor = formPlaceholderColor + textField.keyboardAppearance = formKeyboardAppearance + + textField.validText = true + textField.selectionEnabled = true + return textField + } + + class func _nameTextFieldLabel() -> String { + return String.Localized.name + } + + class func _emailTextFieldLabel() -> String { + return String.Localized.email + } + + class func _bsbNumberTextFieldLabel() -> String { + return String.Localized.bank_account + } + + class func _accountNumberTextFieldLabel() -> String { + return self._bsbNumberTextFieldLabel() // same label + } + + func _updateValidText(for formTextField: STPFormTextField) { + if formTextField == _bsbNumberTextField { + formTextField.validText = + viewModel.isInputValid( + formTextField.text ?? "", + for: .BSBNumber, + editing: formTextField.isFirstResponder + ) + } else if formTextField == _accountNumberTextField { + formTextField.validText = + viewModel.isInputValid( + formTextField.text ?? "", + for: .accountNumber, + editing: formTextField.isFirstResponder + ) + } else if formTextField == _nameTextField { + formTextField.validText = + viewModel.isInputValid( + formTextField.text ?? "", + for: .name, + editing: formTextField.isFirstResponder + ) + } else if formTextField == _emailTextField { + formTextField.validText = + viewModel.isInputValid( + formTextField.text ?? "", + for: .email, + editing: formTextField.isFirstResponder + ) + } else { + assert( + false, + "Shouldn't call for text field not managed by \(NSStringFromClass(STPAUBECSDebitFormView.self))" + ) + } + } + + /// :nodoc: + @objc + public override func systemLayoutSizeFitting( + _ targetSize: CGSize, + withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, + verticalFittingPriority: UILayoutPriority + ) -> CGSize { + // UITextViews don't play nice with autolayout, so we have to add a temporary height constraint + // to get this method to account for the full, non-scrollable size of _mandateLabel + layoutIfNeeded() + let tempConstraint = mandateLabel.heightAnchor.constraint( + equalToConstant: mandateLabel.contentSize.height + ) + tempConstraint.isActive = true + let size = super.systemLayoutSizeFitting( + targetSize, + withHorizontalFittingPriority: horizontalFittingPriority, + verticalFittingPriority: verticalFittingPriority + ) + tempConstraint.isActive = false + return size + + } + + func _defaultBSBLabelTextColor() -> UIColor { + return UIColor.secondaryLabel + } + + func _updateBSBLabel() { + var isErrorString = false + bsbLabel.text = viewModel.bsbLabel( + forInput: _bsbNumberTextField.text, + editing: _bsbNumberTextField.isFirstResponder, + isErrorString: &isErrorString + ) + bsbLabel.textColor = isErrorString ? formTextErrorColor : _defaultBSBLabelTextColor() + } + + // MARK: - STPMultiFormFieldDelegate + func formTextFieldDidStartEditing( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) { + _updateValidText(for: formTextField) + if formTextField == _bsbNumberTextField { + _updateBSBLabel() + } + } + + func formTextFieldDidEndEditing( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) { + _updateValidText(for: formTextField) + if formTextField == _bsbNumberTextField { + _updateBSBLabel() + } + } + + func modifiedIncomingTextChange( + _ input: NSAttributedString, + for formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) -> NSAttributedString { + if formTextField == _bsbNumberTextField { + return NSAttributedString( + string: viewModel.formattedString(forInput: input.string, in: .BSBNumber), + attributes: _bsbNumberTextField.defaultTextAttributes + ) + } else if formTextField == _accountNumberTextField { + return NSAttributedString( + string: viewModel.formattedString(forInput: input.string, in: .accountNumber), + attributes: _accountNumberTextField.defaultTextAttributes + ) + } else if formTextField == _nameTextField { + return NSAttributedString( + string: viewModel.formattedString(forInput: input.string, in: .name), + attributes: _nameTextField.defaultTextAttributes + ) + } else if formTextField == _emailTextField { + return NSAttributedString( + string: viewModel.formattedString(forInput: input.string, in: .email), + attributes: _emailTextField.defaultTextAttributes + ) + } else { + assert( + false, + "Shouldn't call for text field not managed by \(NSStringFromClass(STPAUBECSDebitFormView.self))" + ) + return input + } + } + + func formTextFieldTextDidChange( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) { + _updateValidText(for: formTextField) + + let hadCompletePaymentMethod = viewModel.paymentMethodParams != nil + + if formTextField == _bsbNumberTextField { + viewModel.bsbNumber = formTextField.text + + _updateBSBLabel() + bankIconView.image = viewModel.bankIcon(forInput: formTextField.text) + + // Since BSB number affects validity for the account number as well, we also need to update that field + _updateValidText(for: _accountNumberTextField) + + if viewModel.isFieldComplete( + withInput: formTextField.text ?? "", + in: .BSBNumber, + editing: formTextField.isFirstResponder + ) { + focusNextForm() + } + } else if formTextField == _accountNumberTextField { + viewModel.accountNumber = formTextField.text + if viewModel.isFieldComplete( + withInput: formTextField.text ?? "", + in: .accountNumber, + editing: formTextField.isFirstResponder + ) { + focusNextForm() + } + } else if formTextField == _nameTextField { + viewModel.name = formTextField.text + } else if formTextField == _emailTextField { + viewModel.email = formTextField.text + } else { + assert( + false, + "Shouldn't call for text field not managed by \(NSStringFromClass(STPAUBECSDebitFormView.self))" + ) + } + + let nowHasCompletePaymentMethod = viewModel.paymentMethodParams != nil + if hadCompletePaymentMethod != nowHasCompletePaymentMethod { + becsDebitFormDelegate?.auBECSDebitForm( + self, + didChangeToStateComplete: nowHasCompletePaymentMethod + ) + } + } + + func isFormFieldComplete( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) -> Bool { + if formTextField == _bsbNumberTextField { + return viewModel.isFieldComplete( + withInput: formTextField.text ?? "", + in: .BSBNumber, + editing: false + ) + } else if formTextField == _accountNumberTextField { + return viewModel.isFieldComplete( + withInput: formTextField.text ?? "", + in: .accountNumber, + editing: false + ) + } else if formTextField == _nameTextField { + return viewModel.isFieldComplete( + withInput: formTextField.text ?? "", + in: .name, + editing: false + ) + } else if formTextField == _emailTextField { + return viewModel.isFieldComplete( + withInput: formTextField.text ?? "", + in: .email, + editing: false + ) + } else { + assert( + false, + "Shouldn't call for text field not managed by \(NSStringFromClass(STPAUBECSDebitFormView.self))" + ) + return false + } + } + + // MARK: - UITextViewDelegate + /// :nodoc: +#if !canImport(CompositorServices) + @objc + public func textView( + _ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction + ) -> Bool { + return true + } +#endif + + // MARK: - STPFormTextFieldContainer (Overrides) + /// :nodoc: + @objc public override var formFont: UIFont { + get { + super.formFont + } + set { + super.formFont = newValue + labeledNameField.formLabelFont = newValue + labeledEmailField.formLabelFont = newValue + } + } + + /// :nodoc: + @objc public override var formTextColor: UIColor { + get { + super.formTextColor + } + set { + super.formTextColor = newValue + labeledNameField.formLabelTextColor = newValue + labeledEmailField.formLabelTextColor = newValue + } + } +} + +extension STPAUBECSDebitFormView { + func nameTextField() -> STPFormTextField { + return _nameTextField + } + + func emailTextField() -> STPFormTextField { + return _emailTextField + } + + func bsbNumberTextField() -> STPFormTextField { + return _bsbNumberTextField + } + + func accountNumberTextField() -> STPFormTextField { + return _accountNumberTextField + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPCardFormView+SwiftUI.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPCardFormView+SwiftUI.swift new file mode 100644 index 00000000..ace69f53 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPCardFormView+SwiftUI.swift @@ -0,0 +1,66 @@ +// +// STPCardFormView+SwiftUI.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripePayments +import SwiftUI + +extension STPCardFormView { + + /// A SwiftUI representation of STPCardFormView + public struct Representable: UIViewRepresentable { + @Binding var paymentMethodParams: STPPaymentMethodParams + @Binding var isComplete: Bool + + let cardFormViewStyle: STPCardFormViewStyle + + /// Initialize a SwiftUI representation of an STPCardFormView. + /// - Parameter style: The visual style to apply to the STPCardFormView. @see STPCardFormViewStyle + /// - Parameter paymentMethodParams: A binding to the payment card text field's contents. + /// The STPPaymentMethodParams will be `nil` if the card form view's contents are invalid or incomplete. + public init( + _ style: STPCardFormViewStyle = .standard, + paymentMethodParams: Binding, + isComplete: Binding + ) { + cardFormViewStyle = style + _paymentMethodParams = paymentMethodParams + _isComplete = isComplete + } + + public func makeCoordinator() -> Coordinator { + return Coordinator(parent: self) + } + + public func makeUIView(context: Context) -> STPCardFormView { + let cardFormView = STPCardFormView(style: cardFormViewStyle) + cardFormView.delegate = context.coordinator + cardFormView.cardParams = paymentMethodParams + return cardFormView + } + + public func updateUIView(_ cardFormView: STPCardFormView, context: Context) { + cardFormView.cardParams = paymentMethodParams + } + } + + /// :nodoc: + public class Coordinator: NSObject, STPCardFormViewDelegate { + + var parent: Representable + init( + parent: Representable + ) { + self.parent = parent + } + + /// :no-doc: + public func cardFormView(_ form: STPCardFormView, didChangeToStateComplete complete: Bool) { + parent.isComplete = complete + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPCardFormView.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPCardFormView.swift new file mode 100644 index 00000000..3b6ac536 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPCardFormView.swift @@ -0,0 +1,831 @@ +// +// STPCardFormView.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +/// Options for configuring the display of an `STPCardFormView` instance. +@objc +public enum STPCardFormViewStyle: Int { + /// Draws the form in a rounded rect with full separators between + /// each input field. + case standard + + /// Draws the form without an outer border and underlines under + /// each input field. + case borderless +} + +/// `STPCardFormViewDelegate` defines the interface that should be adopted to receive +/// updates from `STPCardFormView` instances. +@objc +public protocol STPCardFormViewDelegate: NSObjectProtocol { + /// Delegate method that is called when all of the form view's required inputs + /// are complete or transition away from all being complete. These transitions + /// correspond to `cardForView.cardParams` returning a nil value or not. + func cardFormView(_ form: STPCardFormView, didChangeToStateComplete complete: Bool) +} + +/// Internal only delegate methods for STPCardFormView +@_spi(STP) public protocol STPCardFormViewInternalDelegate { + /// Delegate method that is called when the selected country is changed. + func cardFormView(_ form: STPCardFormView, didUpdateSelectedCountry countryCode: String?) +} + +/// `STPCardFormView` provides a multiline interface for users to input their +/// credit card details as well as billing postal code and provides an interface to access +/// the created `STPPaymentMethodParams`. +/// `STPCardFormView` includes both the input fields as well as an error label that +/// is displayed when invalid input is detected. +public class STPCardFormView: STPFormView { + + let numberField: STPCardNumberInputTextField + let cvcField: STPCardCVCInputTextField + let expiryField: STPCardExpiryInputTextField + + let billingAddressSubForm: BillingAddressSubForm + let postalCodeRequirement: STPPostalCodeRequirement + let inputMode: STPCardNumberInputTextField.InputMode + + @_spi(STP) public var countryField: STPCountryPickerInputField { + return billingAddressSubForm.countryPickerField + } + + @_spi(STP) public var postalCodeField: STPPostalCodeInputTextField { + return billingAddressSubForm.postalCodeField + } + + var stateField: STPGenericInputTextField? { + return billingAddressSubForm.stateField + } + + @_spi(STP) public var countryCode: String? { + didSet { + updateCountryCodeValues() + } + } + + private func updateCountryCodeValues() { + postalCodeField.countryCode = countryCode + set( + textField: postalCodeField, + isHidden: !STPPostalCodeValidator.postalCodeIsRequired( + forCountryCode: countryCode, + with: postalCodeRequirement + ), + animated: window != nil + ) + stateField?.placeholder = StripeSharedStrings.localizedStateString(for: countryCode) + } + + var hideShadow: Bool = false { + didSet { + sectionViews.forEach { (sectionView) in + sectionView.stackView.hideShadow = hideShadow + } + } + } + + /// The delegate to notify when the card form transitions to or from being complete. + /// - seealso: STPCardFormViewDelegate + @objc + public weak var delegate: STPCardFormViewDelegate? + + internal var internalDelegate: STPCardFormViewInternalDelegate? { + return delegate as? STPCardFormViewInternalDelegate + } + + var _backgroundColor: UIColor? + + /// :nodoc: + @objc + public override var backgroundColor: UIColor? { + get { + switch style { + + case .standard: + return sectionViews.first?.stackView.customBackgroundColor + + case .borderless: + return _backgroundColor + + } + } + set { + switch style { + + case .standard: + super.backgroundColor = nil + sectionViews.forEach({ $0.stackView.customBackgroundColor = newValue }) + + case .borderless: + _backgroundColor = newValue + super.backgroundColor = backgroundColor + + } + } + } + + var _disabledBackgroundColor: UIColor? + + /// The background color that is automatically applied to the input fields when `isUserInteractionEnabled` is set to `false. + /// @note `STPCardFormView` uses text colors, most of which are iOS system colors, that are designed to be as + /// accessible as possible, so any customization should avoid decreasing contrast between the text and background. + @objc + public var disabledBackgroundColor: UIColor? { + get { + switch style { + + case .standard: + return sectionViews.first?.stackView.customBackgroundDisabledColor + + case .borderless: + return _disabledBackgroundColor + + } + } + set { + switch style { + + case .standard: + sectionViews.forEach({ $0.stackView.customBackgroundDisabledColor = newValue }) + + case .borderless: + _disabledBackgroundColor = disabledBackgroundColor + } + } + } + + /// A configured `STPPaymentMethodParams` with the entered card number, expiration date, cvc, and + /// postal code (if applicable). If any field is invalid or incomplete then this property will return `nil`. + /// You can monitor when `STPCardFormView` has complete details by implementing + /// `STPFormViewDelegate` and setting the `STPCardFormView's` `delegate` + /// property. + @objc + public internal(set) var cardParams: STPPaymentMethodParams? { + get { + guard case .valid = numberField.validator.validationState, + let cardNumber = numberField.validator.inputValue, + case .valid = cvcField.validator.validationState, + let cvc = cvcField.validator.inputValue, + case .valid = expiryField.validator.validationState, + let expiryStrings = expiryField.expiryStrings, + let monthInt = Int(expiryStrings.month), + let yearInt = Int(expiryStrings.year), + let billingDetails = billingAddressSubForm.billingDetails + else { + return nil + } + + if let bindedPaymentMethodParams = _bindedPaymentMethodParams { + updateBindedPaymentMethodParams() + return bindedPaymentMethodParams + } + + let cardParams = STPPaymentMethodCardParams() + cardParams.number = cardNumber + cardParams.cvc = cvc + cardParams.expMonth = NSNumber(value: monthInt) + cardParams.expYear = NSNumber(value: yearInt) + + // If CBC is enabled, set the selected card brand + if let cardValidator = (numberField.validator as? STPCardNumberInputTextFieldValidator), + let selectedBrand = cardValidator.cbcController.selectedBrand { + cardParams.networks = STPPaymentMethodCardNetworksParams(preferred: STPCardBrandUtilities.apiValue(from: selectedBrand)) + } + + return STPPaymentMethodParams( + card: cardParams, + billingDetails: billingDetails, + metadata: nil + ) + } + set { + if let card = newValue?.card { + if let number = card.number { + numberField.text = number + } + if let expMonth = card.expMonth, let expYear = card.expYear { + let expText = String( + format: "%02lu%02lu", + Int(truncating: expMonth), + Int(truncating: expYear) % 100 + ) + expiryField.text = expText + } + if let cvc = card.cvc { + cvcField.text = cvc + } + // If a card brand is explicitly selected, retain that information + if let preferredBrandString = card.networks?.preferred, + let cardValidator = (numberField.validator as? STPCardNumberInputTextFieldValidator) + { + cardValidator.cbcController.selectedBrand = STPCard.brand(from: preferredBrandString) + } + } + + billingAddressSubForm.billingDetails = newValue?.billingDetails + // MUST be called after setting field values + _bindedPaymentMethodParams = newValue + } + } + + @_spi(STP) public func _stpinternal_setCardParams(_ params: STPPaymentMethodParams?) { + self.cardParams = params + } + + var _bindedPaymentMethodParams: STPPaymentMethodParams? { + didSet { + updateBindedPaymentMethodParams() + } + } + + func updateBindedPaymentMethodParams() { + guard let bindedPaymentMethodParams = _bindedPaymentMethodParams else { + return + } + + let cardParams = bindedPaymentMethodParams.card ?? STPPaymentMethodCardParams() + bindedPaymentMethodParams.card = cardParams + cardParams.number = numberField.inputValue + cardParams.cvc = cvcField.inputValue + if let expiryStrings = expiryField.expiryStrings, + let monthInt = Int(expiryStrings.month), + let yearInt = Int(expiryStrings.year) + { + cardParams.expMonth = NSNumber(value: monthInt) + cardParams.expYear = NSNumber(value: yearInt) + } else { + cardParams.expMonth = nil + cardParams.expYear = nil + } + + let billingDetails = + bindedPaymentMethodParams.billingDetails ?? STPPaymentMethodBillingDetails() + bindedPaymentMethodParams.billingDetails = billingDetails + billingAddressSubForm.updateBindedBillingDetails(billingDetails) + } + + func updateCurrentBackgroundColor() { + switch style { + + case .standard: + break // no-op, switching background color is handled at the section view layer + + case .borderless: + // if there's a backgroundColor set but no disabledBackgroundColor + // assume no color change for disabled state + super.backgroundColor = + isUserInteractionEnabled + ? backgroundColor : (disabledBackgroundColor ?? backgroundColor) + } + } + + @objc + public override var isUserInteractionEnabled: Bool { + didSet { + updateCurrentBackgroundColor() + if inputMode == .panLocked { + self.numberField.isUserInteractionEnabled = false + } + } + } + + let style: STPCardFormViewStyle + + /// Public initializer for `STPCardFormView`. + /// @param style The visual style to use for this instance. @see STPCardFormViewStyle + @objc + public convenience init( + style: STPCardFormViewStyle = .standard + ) { + self.init( + billingAddressCollection: .automatic, + style: style, + prefillDetails: nil + ) + } + + @_spi(STP) public convenience init( + billingAddressCollection: BillingAddressCollectionLevel, + style: STPCardFormViewStyle = .standard, + postalCodeRequirement: STPPostalCodeRequirement = .standard, + prefillDetails: PrefillDetails? = nil, + inputMode: STPCardNumberInputTextField.InputMode = .standard, + cbcEnabledOverride: Bool? = nil + ) { + self.init( + numberField: STPCardNumberInputTextField( + inputMode: inputMode, + prefillDetails: prefillDetails, + cbcEnabledOverride: cbcEnabledOverride + ), + cvcField: STPCardCVCInputTextField(prefillDetails: prefillDetails), + expiryField: STPCardExpiryInputTextField(prefillDetails: prefillDetails), + billingAddressSubForm: BillingAddressSubForm( + billingAddressCollection: billingAddressCollection, + postalCodeRequirement: postalCodeRequirement + ), + style: style, + postalCodeRequirement: postalCodeRequirement, + prefillDetails: prefillDetails, + inputMode: inputMode + ) + } + + required init( + numberField: STPCardNumberInputTextField, + cvcField: STPCardCVCInputTextField, + expiryField: STPCardExpiryInputTextField, + billingAddressSubForm: BillingAddressSubForm, + style: STPCardFormViewStyle = .standard, + postalCodeRequirement: STPPostalCodeRequirement = .standard, + prefillDetails: PrefillDetails? = nil, + inputMode: STPCardNumberInputTextField.InputMode = .standard + ) { + self.numberField = numberField + self.cvcField = cvcField + self.expiryField = expiryField + self.billingAddressSubForm = billingAddressSubForm + self.style = style + self.postalCodeRequirement = postalCodeRequirement + self.inputMode = inputMode + + if inputMode == .panLocked { + self.numberField.isUserInteractionEnabled = false + } + + var rows: [[STPFormInput]] = [ + [numberField], + [expiryField, cvcField], + ] + rows.append(contentsOf: billingAddressSubForm.formSection.rows) + + let cardParamsSection = STPFormView.Section( + rows: rows, + title: nil, + accessoryButton: nil + ) + + super.init( + sections: [cardParamsSection] + ) + numberField.addObserver(self) + cvcField.addObserver(self) + expiryField.addObserver(self) + billingAddressSubForm.formSection.rows.forEach({ $0.forEach({ $0.addObserver(self) }) }) + countryCode = countryField.inputValue + updateCountryCodeValues() + + switch style { + + case .standard: + break + + case .borderless: + sectionViews.forEach { (sectionView) in + sectionView.stackView.separatorStyle = .partial + sectionView.stackView.drawBorder = false + sectionView.insetFooterLabel = true + } + } + + hideShadow = true + // manually call the didSet behavior of hideShadow since that's not triggered in initializers + sectionViews.forEach { (sectionView) in + sectionView.stackView.hideShadow = hideShadow + // remove default background coloring + sectionView.stackView.customBackgroundColor = nil + } + + STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPCardFormView.self) + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + required init( + sections: [Section] + ) { + fatalError("init(sections:) has not been implemented") + } + + override func shouldAutoAdvance( + for input: STPInputTextField, + with validationState: STPValidatedInputState, + from previousState: STPValidatedInputState + ) -> Bool { + if input == numberField { + if case .valid = validationState { + if case .processing = previousState { + return false + } else { + return true + } + } else { + return false + } + } else if input == postalCodeField { + if case .valid = validationState { + if countryCode == "US" { + return true + } + } else { + return false + } + } else if input == cvcField { + if case .valid = validationState { + return (input.validator.inputValue?.count ?? 0) + >= STPCardValidator.maxCVCLength(for: cvcField.cardBrand) + } else { + return false + } + } else if billingAddressSubForm.formSection.contains(input) { + return false + } + return super.shouldAutoAdvance(for: input, with: validationState, from: previousState) + } + + override func validationDidUpdate( + to state: STPValidatedInputState, + from previousState: STPValidatedInputState, + for unformattedInput: String?, + in input: STPFormInput + ) { + guard let textField = input as? STPInputTextField else { + return + } + + if textField == numberField { + cvcField.cardBrand = numberField.brandForCVC + } else if textField == countryField { + let countryChanged = textField.inputValue != countryCode + + countryCode = countryField.inputValue + + let shouldFocusOnPostalCode = + countryChanged + && STPPostalCodeValidator.postalCodeIsRequired( + forCountryCode: countryCode, + with: postalCodeRequirement + ) + + if shouldFocusOnPostalCode { + _ = postalCodeField.becomeFirstResponder() + } + + if countryChanged { + self.internalDelegate?.cardFormView(self, didUpdateSelectedCountry: countryCode) + } + } + super.validationDidUpdate( + to: state, + from: previousState, + for: unformattedInput, + in: textField + ) + if case .valid = state, state != previousState { + if cardParams != nil { + // we transitioned to complete + delegate?.cardFormView(self, didChangeToStateComplete: true) + formViewInternalDelegate?.formView(self, didChangeToStateComplete: true) + } + } else if case .valid = previousState, state != previousState { + delegate?.cardFormView(self, didChangeToStateComplete: false) + formViewInternalDelegate?.formView(self, didChangeToStateComplete: false) + } + + updateBindedPaymentMethodParams() + } + + @objc func scanButtonTapped(sender: UIButton) { + self.formViewInternalDelegate?.formView(self, didTapAccessoryButton: sender) + } + + /// Returns true iff the form can mark the error to one of its fields + @_spi(STP) public func markFormErrors(for apiError: Error) -> Bool { + let error = apiError as NSError + guard let errorCode = error.userInfo[STPError.stripeErrorCodeKey] as? String else { + return false + } + switch errorCode { + case "incorrect_number", "invalid_number": + numberField.validator.validationState = .invalid( + errorMessage: error.userInfo[NSLocalizedDescriptionKey] as? String + ?? numberField.validator.defaultErrorMessage + ) + return true + + case "invalid_expiry_month", "invalid_expiry_year", "expired_card": + expiryField.validator.validationState = .invalid( + errorMessage: error.userInfo[NSLocalizedDescriptionKey] as? String + ?? expiryField.validator.defaultErrorMessage + ) + return true + + case "invalid_cvc", "incorrect_cvc": + cvcField.validator.validationState = .invalid( + errorMessage: error.userInfo[NSLocalizedDescriptionKey] as? String + ?? cvcField.validator.defaultErrorMessage + ) + return true + + case "incorrect_zip": + postalCodeField.validator.validationState = .invalid( + errorMessage: error.userInfo[NSLocalizedDescriptionKey] as? String + ?? postalCodeField.validator.defaultErrorMessage + ) + return true + + default: + return false + } + } + + // MARK: CBC + /// The list of preferred networks that should be used to process + /// payments made with a co-branded card if your user hasn't selected a + /// network themselves. + /// + /// The first preferred network that matches any available network will + /// be offered to the customer. If no preferred network is applicable, the + /// customer will select the network. + open var preferredNetworks: [STPCardBrand]? { + get { + numberField.preferredNetworks + } + set { + numberField.preferredNetworks = newValue + } + } + + /// The list of preferred networks that should be used to process + /// payments made with a co-branded card if your user hasn't selected a + /// network themselves. + /// + /// The first preferred network that matches any available network will + /// be offered to the customer. If no preferred network is applicable, the + /// customer will select the network. + /// + /// In Objective-C, this is an array of NSNumbers representing STPCardBrands. + /// For example: + /// [textField setPreferredNetworks:@[[NSNumber numberWithInt:STPCardBrandVisa]]]; + @available(swift, obsoleted: 1.0) + @objc(preferredNetworks) open func preferredNetworks_objc() -> [NSNumber]? { + guard let preferredNetworks = self.preferredNetworks else { + return nil + } + return preferredNetworks.map { NSNumber(value: $0.rawValue) } + } + + /// The list of preferred networks that should be used to process + /// payments made with a co-branded card if your user hasn't selected a + /// network themselves. + /// + /// The first preferred network that matches any available network will + /// be offered to the customer. If no preferred network is applicable, the + /// customer will select the network. + /// + /// In Objective-C, this is an array of NSNumbers representing STPCardBrands. + /// For example: + /// [textField setPreferredNetworks:@[[NSNumber numberWithInt:STPCardBrandVisa]]]; + @available(swift, obsoleted: 1.0) + @objc(setPreferredNetworks:) open func setPreferredNetworks_objc(preferredNetworks: [NSNumber]?) { + guard let preferredNetworks = preferredNetworks else { + self.preferredNetworks = nil + return + } + self.preferredNetworks = preferredNetworks.map { STPCardBrand(rawValue: $0.intValue) ?? .unknown } + } + + /// The account (if any) for which the funds of the intent are intended. + /// The Stripe account ID (if any) which is the business of record. + /// See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. + /// This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + /// provided on the Intent used when confirming payment. + public var onBehalfOf: String? { + didSet { + if let cardValidator = (numberField.validator as? STPCardNumberInputTextFieldValidator) { + cardValidator.cbcController.onBehalfOf = onBehalfOf + } + } + } +} + +/// :nodoc: +extension STPCardFormView { + class BillingAddressSubForm: NSObject { + let formSection: STPFormView.Section + + let postalCodeField: STPPostalCodeInputTextField + let countryPickerField: STPCountryPickerInputField = STPCountryPickerInputField() + let stateField: STPGenericInputTextField? + + let line1Field: STPGenericInputTextField? + let line2Field: STPGenericInputTextField? + let cityField: STPGenericInputTextField? + + var billingDetails: STPPaymentMethodBillingDetails? { + get { + let billingDetails = STPPaymentMethodBillingDetails() + let address = STPPaymentMethodAddress() + + if !postalCodeField.isHidden { + if case .valid = postalCodeField.validationState { + address.postalCode = postalCodeField.postalCode + } else { + return nil + } + } + + if case .valid = countryPickerField.validationState { + address.country = countryPickerField.inputValue + } else { + return nil + } + + billingDetails.address = address + return billingDetails + } + + set { + let address = newValue?.address + + // MUST set country code before postal code + if let countryCode = address?.country { + countryPickerField.select(countryCode: countryCode) + } + + postalCodeField.text = address?.postalCode + + if let stateField = stateField { + stateField.text = address?.state + } + + if let line1Field = line1Field { + line1Field.text = address?.line1 + } + + if let line2Field = line2Field { + line2Field.text = address?.line2 + } + + if let cityField = cityField { + cityField.text = address?.city + } + } + } + + func updateBindedBillingDetails(_ billingDetails: STPPaymentMethodBillingDetails) { + let address = billingDetails.address ?? STPPaymentMethodAddress() + + if !postalCodeField.isHidden { + address.postalCode = postalCodeField.postalCode + } else { + address.postalCode = nil + } + + address.country = countryPickerField.inputValue + + if let stateField = stateField { + address.state = stateField.inputValue + } + + if let line1Field = line1Field { + address.line1 = line1Field.inputValue + } + + if let line2Field = line2Field { + address.line2 = line2Field.inputValue + } + + if let cityField = cityField { + address.city = cityField.inputValue + } + + billingDetails.address = address + } + + required init( + billingAddressCollection: BillingAddressCollectionLevel, + postalCodeRequirement: STPPostalCodeRequirement + ) { + postalCodeField = STPPostalCodeInputTextField( + postalCodeRequirement: postalCodeRequirement + ) + + let rows: [[STPInputTextField]] + let title: String + switch billingAddressCollection { + + case .automatic: + stateField = nil + line1Field = nil + line2Field = nil + cityField = nil + rows = [ + [countryPickerField], + [postalCodeField], + ] + title = String.Localized.country_or_region + + case .required: + stateField = STPGenericInputTextField( + placeholder: StripeSharedStrings.localizedStateString( + for: Locale.autoupdatingCurrent.stp_regionCode + ), + textContentType: .addressState + ) + line1Field = STPGenericInputTextField( + placeholder: String.Localized.address_line1, + textContentType: .streetAddressLine1, + keyboardType: .numbersAndPunctuation + ) + line2Field = STPGenericInputTextField( + placeholder: STPLocalizedString( + "Address line 2 (optional)", + "Address line 2 placeholder for billing address form." + ), + textContentType: .streetAddressLine2, + keyboardType: .numbersAndPunctuation, + optional: true + ) + cityField = STPGenericInputTextField( + placeholder: String.Localized.city, + textContentType: .addressCity + ) + rows = [ + // Country selector + [countryPickerField], + // Address line 1 + [line1Field!], + // Address line 2 + [line2Field!], + // City, Postal code + [cityField!, postalCodeField], + // State + [stateField!], + ] + title = String.Localized.billing_address_lowercase + } + + formSection = STPFormView.Section(rows: rows, title: title, accessoryButton: nil) + } + + } +} + +/// :nodoc: +@_spi(STP) extension STPCardFormView: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier: String = "STPCardFormView" +} + +extension STPCardFormView { + + @_spi(STP) public struct PrefillDetails { + @_spi(STP) public let last4: String + @_spi(STP) public let expiryMonth: Int + @_spi(STP) public let expiryYear: Int + @_spi(STP) public let cardBrand: STPCardBrand + + @_spi(STP) public var formattedLast4: String { + return "•••• \(last4)" + } + + @_spi(STP) public var formattedExpiry: String { + let paddedZero = expiryMonth < 10 + return "\(paddedZero ? "0" : "")\(expiryMonth)/\(expiryYear)" + } + + @_spi(STP) public init( + last4: String, + expiryMonth: Int, + expiryYear: Int, + cardBrand: STPCardBrand + ) { + self.last4 = last4 + self.expiryMonth = expiryMonth + self.expiryYear = expiryYear + self.cardBrand = cardBrand + } + } +} + +/// Billing address collection modes for PaymentSheet +@_spi(STP) public enum BillingAddressCollectionLevel { + /// (Default) PaymentSheet will only collect the necessary billing address information + case automatic + + /// PaymentSheet will always collect full billing address details + case required +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFloatingPlaceholderTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFloatingPlaceholderTextField.swift new file mode 100644 index 00000000..642fb028 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFloatingPlaceholderTextField.swift @@ -0,0 +1,422 @@ +// +// STPFloatingPlaceholderTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/7/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import UIKit + +/// A `UITextField` subclass that moves the placeholder text to the top leading side of the field +/// instead of hiding it upon text entry or editing. +@_spi(STP) public class STPFloatingPlaceholderTextField: UITextField { + + struct LayoutConstants { + static let defaultHeight: CGFloat = 40 + + static let horizontalMargin: CGFloat = 11 + static let horizontalSpacing: CGFloat = 4 + + static let floatingPlaceholderScale: CGFloat = 0.75 + + static let defaultPlaceholderColor: UIColor = .secondaryLabel + + static let floatingPlaceholderColor: UIColor = .secondaryLabel + } + + let placeholderLabel: UILabel = { + let label = UILabel() + label.textColor = STPFloatingPlaceholderTextField.LayoutConstants.defaultPlaceholderColor + label.adjustsFontForContentSizeCategory = true + return label + }() + + var lastAnimator: UIViewPropertyAnimator? + + var changingFirstResponderStatus = false + + var defaultPlaceholderColor: UIColor = STPFloatingPlaceholderTextField.LayoutConstants + .defaultPlaceholderColor + var floatingPlaceholderColor: UIColor = STPFloatingPlaceholderTextField.LayoutConstants + .floatingPlaceholderColor + + var placeholderColor: UIColor { + get { + return placeholderLabel.textColor + } + set { + placeholderLabel.textColor = newValue + } + } + + override init( + frame: CGRect + ) { + super.init(frame: frame) + setupSubviews() + } + + required init?( + coder: NSCoder + ) { + super.init(coder: coder) + setupSubviews() + } + + func setupSubviews() { + // even though the default font value for UITextFields is body, on iOS 13 at least they do not respect + // the font size settings. Resetting here fixes + font = UIFont.preferredFont(forTextStyle: .body) + adjustsFontForContentSizeCategory = true + + placeholderLabel.font = font + placeholderLabel.textAlignment = textAlignment + placeholderColor = defaultPlaceholderColor + addSubview(placeholderLabel) + } + + func floatingPlaceholderHeight() -> CGFloat { + let placeholderLabelHeight = placeholderLabel.textRect( + forBounds: CGRect( + x: 0, + y: 0, + width: CGFloat.greatestFiniteMagnitude, + height: CGFloat.greatestFiniteMagnitude + ), + limitedToNumberOfLines: 1 + ).height + return placeholderLabelHeight + * STPFloatingPlaceholderTextField.LayoutConstants.floatingPlaceholderScale + } + + func contentPadding() -> UIEdgeInsets { + + let floatingPlaceholderLabelHeight = floatingPlaceholderHeight() + let availableHeight = bounds.height + + // + // |----------------------------------------------------| + // |_______|vMargin_____________________________________| + // | | | + // |_______|floatingPlacholderLabelHeight_______________| + // | | | + // | | | availableHeight + // | | | + // |_______|textEntryHeight_____________________________| + // |_______|vMargin_____________________________________| + // + // vMargin is calculated as follows: + // + // We want the text content to be vertically centered, giving the equation: + // (floatingPlaceholderLabelHeight + textEntryHeight)/2 = availableHeight/2 + // + // We want the distance from the top to the midpoint of the floating placeholder to + // be the same as the distance from the bottom to the center of the text entry rect, + // but scaled by floatingPlaceholderScale giving: + // floatingPlaceholderScale * (textEntryHeight/2 + vMargin) = floatingPlacholderLabelHeight/2 + vMargin + // + + let vMargin = + floatingPlaceholderLabelHeight > 0 + ? max( + 0, + STPFloatingPlaceholderTextField.LayoutConstants.floatingPlaceholderScale + * (availableHeight - floatingPlaceholderLabelHeight + - (floatingPlaceholderLabelHeight + / STPFloatingPlaceholderTextField.LayoutConstants + .floatingPlaceholderScale)) + / CGFloat(2) + ) : 0 + + var leftMargin = STPFloatingPlaceholderTextField.LayoutConstants.horizontalMargin + if leftView != nil, + leftViewMode == .always + { + leftMargin = + leftMargin + self.leftViewRect(forBounds: bounds).width + + STPFloatingPlaceholderTextField.LayoutConstants.horizontalSpacing + } + + var rightMargin = STPFloatingPlaceholderTextField.LayoutConstants.horizontalMargin + if rightView != nil, + rightViewMode == .always + { + rightMargin = + rightMargin + self.rightViewRect(forBounds: bounds).width + + STPFloatingPlaceholderTextField.LayoutConstants.horizontalSpacing + } + + let isRTL = traitCollection.layoutDirection == .rightToLeft + + return UIEdgeInsets( + top: vMargin, + left: isRTL ? rightMargin : leftMargin, + bottom: vMargin, + right: isRTL ? leftMargin : rightMargin + ) + } + + func textEntryFieldInset() -> UIEdgeInsets { + var inset = contentPadding() + if isEditing || !(text?.isEmpty ?? true) { + // contentPadding pads the top to the floating placeholder so for text + // entry we need to offset past that + let floatingPlaceholderLabelHeight = floatingPlaceholderHeight() + inset.top = inset.top + floatingPlaceholderLabelHeight + } + return inset + } + + func textEntryFrame() -> CGRect { + return bounds.inset(by: textEntryFieldInset()) + } + + func layoutPlaceholder(animated: Bool) { + guard !(placeholder?.isEmpty ?? true) else { + return + } + layoutIfNeeded() + + var placeholderFrame = textEntryFrame() + placeholderFrame.size.width = min( + placeholderFrame.size.width, + placeholderLabel.textRect(forBounds: placeholderFrame, limitedToNumberOfLines: 1).width + ) + if traitCollection.layoutDirection == .rightToLeft { + placeholderFrame.origin.x = textEntryFrame().maxX - placeholderFrame.width + } + var placeholderTransform = CGAffineTransform.identity + var placeholderColor: UIColor = defaultPlaceholderColor + + let minimized = isEditing || !(text?.isEmpty ?? true) + + if minimized { + let scale = STPFloatingPlaceholderTextField.LayoutConstants.floatingPlaceholderScale + + placeholderFrame.origin.y = self.contentPadding().top + if traitCollection.layoutDirection == .rightToLeft { + // shift origin to the right by the amount the text is compressed horizontally + placeholderFrame.origin.x = + placeholderFrame.origin.x + (1 - scale) * placeholderFrame.width + } + // scaling the width here leads to a clean up and down animation + placeholderFrame.size.width = placeholderFrame.width * scale + placeholderFrame.size.height = + placeholderLabel.textRect(forBounds: placeholderFrame, limitedToNumberOfLines: 1) + .height * scale + placeholderTransform = placeholderTransform.scaledBy(x: scale, y: scale) + placeholderColor = floatingPlaceholderColor + } + + if animated { + // Stop any in-flight animations + lastAnimator?.stopAnimation(true) + let params = UISpringTimingParameters( + mass: 1.0, + dampingRatio: 0.93, + frequencyResponse: 0.22 + ) + let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) + animator.isInterruptible = true + animator.addAnimations { + self.placeholderLabel.transform = placeholderTransform + self.placeholderLabel.frame = placeholderFrame + if !minimized { + // when we are animating back to center, change color immediately + self.placeholderColor = placeholderColor + } + } + animator.addCompletion { (_) in + if minimized { + // when animating away from center, change color at end of animation + self.placeholderColor = placeholderColor + } + } + animator.startAnimation() + self.lastAnimator = animator + } else { + placeholderLabel.transform = placeholderTransform + placeholderLabel.frame = placeholderFrame + self.placeholderColor = placeholderColor + } + } + +} + +// MARK: UITextField Overrides +extension STPFloatingPlaceholderTextField { + + /// :nodoc: + @objc public override var placeholder: String? { + get { + return placeholderLabel.text + } + set { + placeholderLabel.text = newValue + self.accessibilityLabel = newValue + invalidateIntrinsicContentSize() + setNeedsLayout() + } + } + + /// :nodoc: + @objc public override var attributedPlaceholder: NSAttributedString? { + get { + return placeholderLabel.attributedText + } + set { + placeholderLabel.attributedText = newValue + self.accessibilityLabel = newValue?.string + invalidateIntrinsicContentSize() + setNeedsLayout() + } + } + + /// :nodoc: + @objc public override var font: UIFont? { + didSet { + placeholderLabel.font = font + } + } + + /// :nodoc: + @objc public override var textAlignment: NSTextAlignment { + didSet { + placeholderLabel.textAlignment = textAlignment + } + } + + /// :nodoc: + @objc public override var leftViewMode: UITextField.ViewMode { + get { + return super.leftViewMode + } + set { + if newValue != .always && newValue != .never { + assert(false, "Only .always or .never are supported") + super.leftViewMode = .never + } else { + super.leftViewMode = newValue + } + } + } + + /// :nodoc: + @objc public override var rightViewMode: UITextField.ViewMode { + get { + return super.rightViewMode + } + set { + if newValue != .always && newValue != .never { + assert(false, "Only .always or .never are supported") + super.rightViewMode = .never + } else { + super.rightViewMode = newValue + } + } + } + + /// :nodoc: + @objc public override func layoutSubviews() { + super.layoutSubviews() + // internally, becoming first responder triggers a layout which we want to suppress + // so we can animate + if !changingFirstResponderStatus { + layoutPlaceholder(animated: false) + } + } + + /// :nodoc: + @objc public override func becomeFirstResponder() -> Bool { + changingFirstResponderStatus = true + let ret = super.becomeFirstResponder() + layoutPlaceholder(animated: true) + changingFirstResponderStatus = false + return ret + } + + /// :nodoc: + @objc public override func resignFirstResponder() -> Bool { + changingFirstResponderStatus = true + let ret = super.resignFirstResponder() + layoutPlaceholder(animated: true) + changingFirstResponderStatus = false + return ret + } + + /// :nodoc: + @objc public override func textRect(forBounds bounds: CGRect) -> CGRect { + // N.B. The bounds passed here are not the same as self.bounds + // which is why we don't just use textEntryFrame() + return bounds.inset(by: textEntryFieldInset()) + } + + /// :nodoc: + @objc public override func placeholderRect(forBounds bounds: CGRect) -> CGRect { + // N.B. The bounds passed here are not the same as self.bounds + // which is why we don't just use textEntryFrame() + return bounds.inset(by: textEntryFieldInset()) + } + + /// :nodoc: + @objc public override func editingRect(forBounds bounds: CGRect) -> CGRect { + // N.B. The bounds passed here are not the same as self.bounds + // which is why we don't just use textEntryFrame() + return bounds.inset(by: textEntryFieldInset()) + } + + /// :nodoc: + @objc public override func leftViewRect(forBounds bounds: CGRect) -> CGRect { + var leftViewRect = super.leftViewRect(forBounds: bounds) + leftViewRect.origin.x = + leftViewRect.origin.x + STPFloatingPlaceholderTextField.LayoutConstants.horizontalMargin + return leftViewRect + } + + /// :nodoc: + @objc public override func rightViewRect(forBounds bounds: CGRect) -> CGRect { + var rightViewRect = super.rightViewRect(forBounds: bounds) + rightViewRect.origin.x = + rightViewRect.origin.x + - STPFloatingPlaceholderTextField.LayoutConstants.horizontalMargin + return rightViewRect + } + + /// :nodoc: + @objc public override var intrinsicContentSize: CGSize { + let height = UIFontMetrics.default.scaledValue( + for: STPFloatingPlaceholderTextField.LayoutConstants.defaultHeight + ) + let contentPadding = self.contentPadding() + return CGSize( + width: placeholderLabel.intrinsicContentSize.width + contentPadding.left + + contentPadding.right, + height: height + ) + } + + /// :nodoc: + @objc public override func sizeThatFits(_ size: CGSize) -> CGSize { + var size = super.sizeThatFits(size) + size.height = max(size.height, intrinsicContentSize.height) + size.width = max(size.width, intrinsicContentSize.width) + return size + } + + /// :nodoc: + @objc public override func systemLayoutSizeFitting( + _ targetSize: CGSize, + withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, + verticalFittingPriority: UILayoutPriority + ) -> CGSize { + var size = super.systemLayoutSizeFitting( + targetSize, + withHorizontalFittingPriority: horizontalFittingPriority, + verticalFittingPriority: verticalFittingPriority + ) + size.width = max(size.width, intrinsicContentSize.width) + return size + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFormTextFieldContainer.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFormTextFieldContainer.swift new file mode 100644 index 00000000..cd136ee0 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFormTextFieldContainer.swift @@ -0,0 +1,33 @@ +// +// STPFormTextFieldContainer.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/12/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/// STPFormTextFieldContainer is a protocol that views can conform to to provide customization properties for the field form views that they contain. +@objc public protocol STPFormTextFieldContainer: NSObjectProtocol { + /// The font used in each child field. Default is `.body`. + dynamic var formFont: UIFont { get set } + /// The text color to be used when entering valid text. Default is `.label` on iOS 13.0 and later and `.darkText` on earlier versions. + dynamic var formTextColor: UIColor { get set } + /// The text color to be used when the user has entered invalid information, + /// such as an invalid card number. + /// Default is `.red`. + dynamic var formTextErrorColor: UIColor { get set } + /// The text placeholder color used in each child field. + /// This will also set the color of the card placeholder icon. + /// Default is `.placeholderText` on iOS 13.0 and `.lightGray` on earlier versions. + dynamic var formPlaceholderColor: UIColor { get set } + /// The cursor color for the field. + /// This is a proxy for the view's tintColor property, exposed for clarity only + /// (in other words, calling setCursorColor is identical to calling setTintColor). + dynamic var formCursorColor: UIColor { get set } + /// The keyboard appearance for the field. + /// Default is `.default`. + dynamic var formKeyboardAppearance: UIKeyboardAppearance { get set } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFormView.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFormView.swift new file mode 100644 index 00000000..7e3e51e4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPFormView.swift @@ -0,0 +1,695 @@ +// +// STPFormView.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import UIKit + +/// Base protocol to support manually backspacing between form inputs and +/// responding to different inputs receiving/losing focus. +protocol STPFormContainer: NSObjectProtocol { + func inputTextFieldDidBackspaceOnEmpty(_ textField: STPInputTextField) + func inputTextFieldWillBecomeFirstResponder(_ textField: STPInputTextField) + func inputTextFieldDidResignFirstResponder(_ textField: STPInputTextField) +} + +/// Internal version of `STPFormViewDelegate` that also includes additional methods for controlling +/// form view interactions. +@_spi(STP) public protocol STPFormViewInternalDelegate: NSObjectProtocol { + func formView(_ form: STPFormView, didChangeToStateComplete complete: Bool) + func formViewWillBecomeFirstResponder(_ form: STPFormView) + func formView(_ form: STPFormView, didTapAccessoryButton button: UIButton) +} + +/// Protocol for observing the state of a specific input field within an `STPFormView`. +protocol STPFormInputValidationObserver: NSObjectProtocol { + func validationDidUpdate( + to state: STPValidatedInputState, + from previousState: STPValidatedInputState, + for unformattedInput: String?, + in input: STPFormInput + ) +} + +/// Protocol for various input types that may be in an `STPFormView`. +protocol STPFormInput where Self: UIView { + + var formContainer: STPFormContainer? { get set } + + var validationState: STPValidatedInputState { get } + var inputValue: String? { get } + + func addObserver(_ validationObserver: STPFormInputValidationObserver) + func removeObserver(_ validationObserver: STPFormInputValidationObserver) + + var wantsAutoFocus: Bool { get } + +} + +/// `STPFormView` is a base class for the Stripe SDK's form input UI. You should use one of the available subclasses +/// (`STPCardFormView`) rather than instantiating an `STPFormView` instance directly. +public class STPFormView: UIView, STPFormInputValidationObserver { + + static let borderlessInset: CGFloat = StackViewWithSeparator.borderlessInset + + let sections: [Section] + let sectionViews: [SectionView] + + let vStack: UIStackView + + static let borderWidth: CGFloat = 1 + static let cornerRadius: CGFloat = 6 + static let interSectionSpacing: CGFloat = 7 + + @_spi(STP) public weak var formViewInternalDelegate: STPFormViewInternalDelegate? + + required init( + sections: [Section] + ) { + self.sections = sections + + vStack = UIStackView() + var sectionViews = [SectionView]() + for section in sections { + let sectionView = SectionView(section: section) + sectionViews.append(sectionView) + sectionView.translatesAutoresizingMaskIntoConstraints = false + vStack.addArrangedSubview(sectionView) + } + + self.sectionViews = sectionViews + super.init(frame: .zero) + + vStack.axis = .vertical + vStack.distribution = .fillProportionally + vStack.spacing = STPFormView.interSectionSpacing + vStack.translatesAutoresizingMaskIntoConstraints = false + + sequentialFields.forEach({ $0.formContainer = self }) + addSubview(vStack) + NSLayoutConstraint.activate([ + vStack.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: vStack.trailingAnchor), + vStack.topAnchor.constraint(equalTo: topAnchor), + bottomAnchor.constraint(equalTo: vStack.bottomAnchor), + ]) + } + + required init?( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + func shouldAutoAdvance( + for input: STPInputTextField, + with validationState: STPValidatedInputState, + from previousState: STPValidatedInputState + ) -> Bool { + if case .valid = validationState { + return true + } + return false + } + + func sectionView(for input: STPInputTextField) -> SectionView? { + return sectionViews.first { (sectionView) -> Bool in + sectionView.section.contains(input) + } + } + + func set(textField: STPInputTextField, isHidden: Bool, animated: Bool) { + guard isHidden != textField.isHidden, + let rowView = textField.superview as? UIStackView, + let sectionView = sectionView(for: textField) + else { + return + } + var hideContainer = true + for input in rowView.arrangedSubviews { + if input == textField { + if !isHidden { + hideContainer = false + break + } + } else if !input.isHidden { + hideContainer = false + break + } + } + + if animated { + + if hideContainer != rowView.isHidden { + if textField.isHidden { + textField.alpha = 0 + textField.isHidden = isHidden + } else { + textField.alpha = 1 + } + } + + self.setNeedsLayout() + self.layoutIfNeeded() + + rowView.invalidateIntrinsicContentSize() + sectionView.stackView.invalidateIntrinsicContentSize() + UIView.animate(withDuration: 0.2) { + textField.alpha = isHidden ? 0 : 1 + if hideContainer == rowView.isHidden { + textField.isHidden = isHidden + } + rowView.isHidden = hideContainer + + rowView.layoutIfNeeded() + self.setNeedsLayout() + self.layoutIfNeeded() + + } completion: { (_) in + textField.isHidden = isHidden + } + + } else { + textField.isHidden = isHidden + rowView.isHidden = hideContainer + } + } + + // MARK: - UIResponder + /// :nodoc: + @objc + public override var canResignFirstResponder: Bool { + if let currentFirstResponderField = currentFirstResponderField() { + return currentFirstResponderField.canResignFirstResponder + } else { + return true + } + } + + /// :nodoc: + @objc + public override func resignFirstResponder() -> Bool { + let ret = super.resignFirstResponder() + if let currentFirstResponderField = currentFirstResponderField() { + return currentFirstResponderField.resignFirstResponder() + } else { + return ret + } + } + + /// :nodoc: + @objc + public override var isFirstResponder: Bool { + return super.isFirstResponder || currentFirstResponderField()?.isFirstResponder ?? false + } + + /// :nodoc: + @objc + public override var canBecomeFirstResponder: Bool { + return sequentialFields.count > 0 + } + + /// :nodoc: + @objc + public override func becomeFirstResponder() -> Bool { + // grab the next first responder before calling super (which will cause any current first responder to resign) + var firstResponder: STPFormInput? + if currentFirstResponderField() != nil { + // we are already first responder, move to next field sequentially + firstResponder = nextInSequenceFirstResponderField() ?? sequentialFields.first + } else { + // Default to the first nonvalid subfield when becoming first responder + firstResponder = firstNonValidSubField() + } + + self.formViewInternalDelegate?.formViewWillBecomeFirstResponder(self) + let ret = super.becomeFirstResponder() + if let firstResponder = firstResponder { + return firstResponder.becomeFirstResponder() + } else { + return ret + } + } + + /// :nodoc: + @objc + public override var isUserInteractionEnabled: Bool { + didSet { + for sectionView in sectionViews { + sectionView.isUserInteractionEnabled = isUserInteractionEnabled + } + } + } + + // MARK: - Helpers + + var sequentialFields: [STPFormInput] { + return sections.reduce(into: [STPFormInput]()) { (result, section) in + result.append( + contentsOf: section.rows.reduce(into: [STPInputTextField]()) { (_, row) in + for input in row { + if !input.isHidden { + result.append(input) + } + } + } + ) + } + } + + func currentFirstResponderField() -> STPFormInput? { + for field in sequentialFields { + if field.isFirstResponder { + return field + } + } + return nil + } + + func previousField(_ wantsAutoFocusOnly: Bool = false) -> STPFormInput? { + if let currentFirstResponder = currentFirstResponderField() { + for (index, field) in sequentialFields.enumerated() { + if field == currentFirstResponder { + var i = index - 1 + while i >= 0 { + let input = sequentialFields[i] + if !wantsAutoFocusOnly || (wantsAutoFocusOnly && input.wantsAutoFocus) { + return input + } + i -= 1 + } + return nil + } + } + } + return nil + } + + @_spi(STP) public func nextFirstResponderFieldBecomeFirstResponder() { + nextFirstResponderField()?.becomeFirstResponder() + } + + func nextFirstResponderField(_ wantsAutoFocusOnly: Bool = false) -> STPFormInput? { + if let nextField = nextInSequenceFirstResponderField(wantsAutoFocusOnly) { + return nextField + } else { + if currentFirstResponderField() == nil { + // if we don't currently have a first responder, consider the first non-valid field the next one + return firstNonValidSubField(wantsAutoFocusOnly) + } else { + return lastSubField(wantsAutoFocusOnly) + } + } + } + + func nextInSequenceFirstResponderField(_ wantsAutoFocusOnly: Bool = false) -> STPFormInput? { + if let currentFirstResponder = currentFirstResponderField() { + for (index, field) in sequentialFields.enumerated() { + if field == currentFirstResponder { + var i = index + 1 + while i < sequentialFields.count { + let input = sequentialFields[i] + if !wantsAutoFocusOnly || (wantsAutoFocusOnly && input.wantsAutoFocus) { + return input + } + i += 1 + } + return nil + } + } + } + return nil + } + + func firstNonValidSubField(_ wantsAutoFocusOnly: Bool = false) -> STPFormInput? { + for field in sequentialFields { + if case .valid = field.validationState { + // this field is valid + } else { + if !wantsAutoFocusOnly || (wantsAutoFocusOnly && field.wantsAutoFocus) { + return field + } + } + } + return nil + } + + func lastSubField(_ wantsAutoFocusOnly: Bool = false) -> STPFormInput? { + for field in sequentialFields.reversed() { + if !wantsAutoFocusOnly || (wantsAutoFocusOnly && field.wantsAutoFocus) { + return field + } + } + return nil + } + + func configureFooter(in sectionView: STPFormView.SectionView) { + let fields = sectionView.sequentialFields + let invalidFields = fields.filter { (field) -> Bool in + if case .invalid = field.validationState { + return true + } else { + return false + } + } + if let firstInvalid = invalidFields.first, + case .invalid(let errorMessage) = firstInvalid.validationState, + let nonNilErrorMessage = errorMessage + { + sectionView.footerTextColor = InputFormColors.errorColor + sectionView.footerText = nonNilErrorMessage + return + } + + let incompleteFields = fields.filter { (field) -> Bool in + if case .incomplete = field.validationState, !field.isFirstResponder, + !(field.inputValue?.isEmpty ?? true) + { + return true + } else { + return false + } + } + let incompleteFieldsWithMessages = incompleteFields.filter { (field) -> Bool in + if case .incomplete(let description) = field.validationState, description != nil { + return true + } else { + return false + } + } + + if let firstIncomplete = incompleteFieldsWithMessages.first, + case .incomplete(let description) = firstIncomplete.validationState, + let nonNilDescription = description + { + sectionView.footerTextColor = InputFormColors.errorColor + sectionView.footerText = nonNilDescription + return + } + + if let firstCompleteWithMessageField = fields.first(where: { (field) -> Bool in + if case .valid(let message) = field.validationState, message != nil { + return true + } else { + return false + } + }), + case .valid(let message) = firstCompleteWithMessageField.validationState, + let nonNilMessage = message + { + sectionView.footerTextColor = .label + sectionView.footerText = nonNilMessage + return + } + + sectionView.footerTextColor = .label + sectionView.footerText = nil + } + + // MARK: - STPInputTextFieldValidationObserver + + func validationDidUpdate( + to state: STPValidatedInputState, + from previousState: STPValidatedInputState, + for unformattedInput: String?, + in input: STPFormInput + ) { + guard let textField = input as? STPInputTextField, + let sectionView = sectionView(for: textField) + else { + assertionFailure("Should not receive updates for uncontained inputs") + return + } + + let fieldsInSection = sectionView.sequentialFields + + if fieldsInSection.first(where: { + if case .invalid = $0.validationState { + return true + } else { + return false + } + }) != nil { + sectionView.separatorColor = InputFormColors.errorColor + } else { + sectionView.separatorColor = InputFormColors.outlineColor + } + + configureFooter(in: sectionView) + + if textField == currentFirstResponderField() + && shouldAutoAdvance(for: textField, with: state, from: previousState) + { + if let nextField = nextFirstResponderField(true) { + _ = nextField.becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nextField) + } + } + } +} + +/// :nodoc: +extension STPFormView: STPFormContainer { + func inputTextFieldDidBackspaceOnEmpty(_ textField: STPInputTextField) { + guard textField == currentFirstResponderField() else { + return + } + + let previous = previousField(true) + _ = previous?.becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: previous) + if let previousTextField = previous as? STPInputTextField, + previousTextField.hasText + { + previousTextField.deleteBackward() + } + } + + func inputTextFieldWillBecomeFirstResponder(_ textField: STPInputTextField) { + self.formViewInternalDelegate?.formViewWillBecomeFirstResponder(self) + + // Always update on become firstResponder in case some fields + // were hidden or unhidden + if textField == lastSubField() { + textField.returnKeyType = .done + } else { + // Note that observed on iOS 14 that setting .next here + // sometimes messes up the keyboardType for asciiCapableNumberPad + textField.returnKeyType = .default + } + + if let sectionView = sectionView(for: textField) { + configureFooter(in: sectionView) + } + } + + func inputTextFieldDidResignFirstResponder(_ textField: STPInputTextField) { + if let sectionView = sectionView(for: textField) { + configureFooter(in: sectionView) + } + } +} + +/// Internal types +extension STPFormView { + struct Section { + let rows: [[STPFormInput]] + let title: String? + let accessoryButton: UIButton? + + func contains(_ input: STPInputTextField) -> Bool { + for row in rows.compactMap({ $0 as? [STPInputTextField] }) { + if row.contains(input) { + return true + } + } + return false + } + } + + class SectionView: UIView { + let section: Section + + let stackView: StackViewWithSeparator = StackViewWithSeparator() + + static let titleVerticalMargin: CGFloat = 4 + + let footerLabel = UILabel() + + var footerTextColor: UIColor { + get { + return footerLabel.textColor + } + set { + footerLabel.textColor = newValue + } + } + + var footerText: String? { + get { + return footerLabel.text + } + set { + if let newValue = newValue, !newValue.isEmpty { + footerLabel.text = newValue + } else { + // We don't want this to ever be empty for sizing reasons + footerLabel.text = " " + } + } + } + + var insetFooterLabel: Bool = false { + didSet { + footerLabelLeadingConstraint.constant = + insetFooterLabel ? STPFormView.borderlessInset : 0 + } + } + + lazy var footerLabelLeadingConstraint: NSLayoutConstraint = { + return footerLabel.leadingAnchor.constraint(equalTo: leadingAnchor) + }() + + @objc + override var isUserInteractionEnabled: Bool { + didSet { + stackView.isUserInteractionEnabled = isUserInteractionEnabled + for field in sequentialFields { + field.isUserInteractionEnabled = isUserInteractionEnabled + } + } + } + + required init( + section: Section + ) { + self.section = section + let rows = section.rows + + let rowViews = rows.map { (row) -> StackViewWithSeparator in + let stackView = StackViewWithSeparator(arrangedSubviews: row) + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.spacing = STPFormView.borderWidth + stackView.separatorColor = InputFormColors.outlineColor + return stackView + } + + super.init(frame: .zero) + for rowView in rowViews { + stackView.addArrangedSubview(rowView) + } + + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.spacing = STPFormView.borderWidth + stackView.separatorColor = InputFormColors.outlineColor + + stackView.drawBorder = true + stackView.borderCornerRadius = STPFormView.cornerRadius + + stackView.translatesAutoresizingMaskIntoConstraints = false + addSubview(stackView) + var constraints: [NSLayoutConstraint] = [ + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: stackView.trailingAnchor), + ] + + if let title = section.title { + let titleLabel = UILabel() + titleLabel.text = title + let fontMetrics = UIFontMetrics(forTextStyle: .body) + titleLabel.font = fontMetrics.scaledFont( + for: UIFont.systemFont(ofSize: 13, weight: .semibold) + ) + titleLabel.textColor = .secondaryLabel + titleLabel.accessibilityTraits = [.header] + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.setContentHuggingPriority(.required, for: .vertical) + + var arrangedSubviews: [UIView] = [titleLabel] + + if let button = section.accessoryButton { + button.setContentHuggingPriority(.defaultLow + 1, for: .horizontal) + titleLabel.setContentCompressionResistancePriority( + .defaultHigh + 1, + for: .horizontal + ) + button.translatesAutoresizingMaskIntoConstraints = false + arrangedSubviews.append(button) + } + + let headerView = UIStackView(arrangedSubviews: arrangedSubviews) + headerView.translatesAutoresizingMaskIntoConstraints = false + addSubview(headerView) + constraints.append(contentsOf: [ + headerView.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: headerView.trailingAnchor), + headerView.topAnchor.constraint(equalTo: topAnchor), + stackView.topAnchor.constraint( + equalTo: headerView.bottomAnchor, + constant: SectionView.titleVerticalMargin + ), + ]) + } else { + constraints.append(stackView.topAnchor.constraint(equalTo: topAnchor)) + } + + footerLabel.translatesAutoresizingMaskIntoConstraints = false + footerLabel.font = .preferredFont(forTextStyle: .caption1) + addSubview(footerLabel) + footerText = " " + constraints.append(contentsOf: [ + footerLabelLeadingConstraint, + trailingAnchor.constraint(equalTo: footerLabel.trailingAnchor), + footerLabel.topAnchor.constraint( + equalTo: stackView.bottomAnchor, + constant: SectionView.titleVerticalMargin + ), + bottomAnchor.constraint(equalTo: footerLabel.bottomAnchor), + ]) + + // the initial layout of a SectionView will log constraint errors if it has a row with multiple + // inputs because the non-zero spacing conflicts with the default 0 horizontal size. Mark the + // constraints as priority required-1 to avoid those unhelpful logs + constraints.forEach({ + $0.priority = UILayoutPriority(rawValue: UILayoutPriority.required.rawValue - 1) + }) + NSLayoutConstraint.activate(constraints) + setContentHuggingPriority(.required, for: .vertical) + } + + required init( + coder: NSCoder + ) { + fatalError("init(coder:) has not been implemented") + } + + var separatorColor: UIColor = InputFormColors.outlineColor { + didSet { + stackView.separatorColor = separatorColor + for rowView in stackView.arrangedSubviews.compactMap({ + $0 as? StackViewWithSeparator + }) { + rowView.separatorColor = separatorColor + } + } + } + + var sequentialFields: [STPFormInput] { + return section.rows.reduce(into: [STPFormInput]()) { (result, row) in + for input in row { + if !input.isHidden { + result.append(input) + } + } + } + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPMultiFormTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPMultiFormTextField.swift new file mode 100644 index 00000000..be7cc5c7 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPMultiFormTextField.swift @@ -0,0 +1,337 @@ +// +// STPMultiFormTextField.swift +// StripePaymentsUI +// +// Created by Cameron Sabol on 3/4/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +/// STPMultiFormFieldDelegate provides methods for a delegate to respond to editing and text changes. +@objc protocol STPMultiFormFieldDelegate: NSObjectProtocol { + /// Called when the text field becomes the first responder. + func formTextFieldDidStartEditing( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) + /// Called when the text field resigns from being the first responder. + func formTextFieldDidEndEditing( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) + /// Called when the text within the form text field changes. + func formTextFieldTextDidChange( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) + /// Called to get any additional formatting from the delegate for the string input to the form text field. + func modifiedIncomingTextChange( + _ input: NSAttributedString, + for formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) -> NSAttributedString + /// Delegates should implement this method so that STPMultiFormTextField when the contents of the form text field renders it complete. + func isFormFieldComplete( + _ formTextField: STPFormTextField, + inMultiForm multiFormField: STPMultiFormTextField + ) -> Bool +} + +/// STPMultiFormTextField is a lightweight UIView that wraps a collection of STPFormTextFields and can automatically move to the next form field when one is completed. +public class STPMultiFormTextField: UIView, STPFormTextFieldContainer, UITextFieldDelegate, + STPFormTextFieldDelegate +{ + /// :nodoc: + @objc + public func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + if let textField = textField as? STPFormTextField, + let delegateProxy = textField.delegateProxy + { + return delegateProxy.textField( + textField, + shouldChangeCharactersIn: range, + replacementString: string + ) + } + return true + } + + /// The collection of STPFormTextFields that this instance manages. + + private var _formTextFields: [STPFormTextField]? + var formTextFields: [STPFormTextField]? { + get { + _formTextFields + } + set(formTextFields) { + _formTextFields = formTextFields + for field in formTextFields ?? [] { + field.formDelegate = self + } + } + } + /// The STPMultiFormTextField's delegate. + @objc weak var multiFormFieldDelegate: STPMultiFormFieldDelegate? + + /// Calling this method will make the next incomplete STPFormTextField in `formTextFields` become the first responder. + /// If all of the form text fields are already complete, then the last field in `formTextFields` will become the first responder. + @objc public func focusNextForm() { + let nextField = _nextFirstResponderField() + if nextField == _currentFirstResponderField() { + // If this doesn't actually advance us, resign first responder + nextField?.resignFirstResponder() + } else { + nextField?.becomeFirstResponder() + } + } + + // MARK: - UIResponder + /// :nodoc: + @objc public override var canResignFirstResponder: Bool { + if _currentFirstResponderField() != nil { + return _currentFirstResponderField()?.canResignFirstResponder ?? false + } else { + return true + } + } + + /// :nodoc: + @objc + public override func resignFirstResponder() -> Bool { + super.resignFirstResponder() + if _currentFirstResponderField() != nil { + return _currentFirstResponderField()?.resignFirstResponder() ?? false + } else { + return true + } + } + + /// :nodoc: + @objc public override var isFirstResponder: Bool { + return super.isFirstResponder || _currentFirstResponderField()?.isFirstResponder ?? false + } + + /// :nodoc: + @objc public override var canBecomeFirstResponder: Bool { + return (formTextFields?.count ?? 0) > 0 + } + + /// :nodoc: + @objc + public override func becomeFirstResponder() -> Bool { + // grab the next first responder before calling super (which will cause any current first responder to resign) + var firstResponder: STPFormTextField? + if _currentFirstResponderField() != nil { + // we are already first responder, move to next field sequentially + firstResponder = _nextInSequenceFirstResponderField() ?? formTextFields?.first + } else { + // Default to the first invalid subfield when becoming first responder + firstResponder = _firstInvalidSubField() + } + + super.becomeFirstResponder() + return firstResponder?.becomeFirstResponder() ?? false + } + + // MARK: - UITextFieldDelegate + /// :nodoc: + @objc + public func textFieldDidEndEditing(_ textField: UITextField) { + let formTextField = (textField is STPFormTextField) ? textField as? STPFormTextField : nil + textField.layoutIfNeeded() + + if let formTextField = formTextField { + multiFormFieldDelegate?.formTextFieldDidEndEditing(formTextField, inMultiForm: self) + } + } + + /// :nodoc: + @objc + public func textFieldDidBeginEditing(_ textField: UITextField) { + let formTextField = (textField is STPFormTextField) ? textField as? STPFormTextField : nil + if let formTextField = formTextField { + multiFormFieldDelegate?.formTextFieldDidStartEditing(formTextField, inMultiForm: self) + } + } + + /// :nodoc: + @objc + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + let nextInSequence = _nextInSequenceFirstResponderField() + if let nextInSequence = nextInSequence { + nextInSequence.becomeFirstResponder() + return false + } else { + textField.resignFirstResponder() + return true + } + } + + // MARK: - STPFormTextFieldDelegate + @objc func formTextFieldDidBackspace(onEmpty formTextField: STPFormTextField) { + let previous = _previousField() + previous?.becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + if previous?.hasText ?? false { + previous?.deleteBackward() + } + } + + @objc func formTextField( + _ formTextField: STPFormTextField, + modifyIncomingTextChange input: NSAttributedString + ) -> NSAttributedString { + return + (multiFormFieldDelegate?.modifiedIncomingTextChange( + input, + for: formTextField, + inMultiForm: self + ))! + } + + @objc func formTextFieldTextDidChange(_ formTextField: STPFormTextField) { + multiFormFieldDelegate?.formTextFieldTextDidChange( + formTextField, + inMultiForm: self + ) + } + + // MARK: - Helpers + func _currentFirstResponderField() -> STPFormTextField? { + for textField in formTextFields ?? [] { + if textField.isFirstResponder { + return textField + } + } + return nil + } + + func _previousField() -> STPFormTextField? { + let currentSubResponder = _currentFirstResponderField() + if let currentSubResponder = currentSubResponder { + let index = formTextFields?.firstIndex(of: currentSubResponder) ?? NSNotFound + if index != NSNotFound && index > 0 { + return formTextFields?[index - 1] + } + } + return nil + } + + func _nextFirstResponderField() -> STPFormTextField? { + let nextField = _nextInSequenceFirstResponderField() + if let nextField = nextField { + return nextField + } else { + if _currentFirstResponderField() == nil { + // if we don't currently have a first responder, consider the first invalid field the next one + return _firstInvalidSubField() + } else { + return _lastSubField() + } + } + } + + func _nextInSequenceFirstResponderField() -> STPFormTextField? { + let currentFirstResponder = _currentFirstResponderField() + if let currentFirstResponder = currentFirstResponder { + let index = formTextFields?.firstIndex(of: currentFirstResponder) ?? NSNotFound + if index != NSNotFound { + let nextField = + formTextFields!.stp_boundSafeObject(at: index + 1) + if let nextField = nextField { + return nextField + } + } + } + + return nil + } + + func _firstInvalidSubField() -> STPFormTextField? { + for textField in formTextFields ?? [] { + if !(multiFormFieldDelegate?.isFormFieldComplete(textField, inMultiForm: self) ?? false) + { + return textField + } + } + return nil + } + + func _lastSubField() -> STPFormTextField { + return (formTextFields?.last)! + } + + // MARK: - STPFormTextFieldContainer + @objc public var formFont: UIFont = UIFont.preferredFont(forTextStyle: .body) { + didSet { + if formFont != oldValue { + for textField in formTextFields ?? [] { + textField.font = formFont + } + } + } + } + + @objc public var formTextColor: UIColor = .label + { + didSet { + if oldValue != formTextColor { + for textField in formTextFields ?? [] { + textField.defaultColor = formTextColor + } + } + } + } + + @objc public var formTextErrorColor: UIColor = .systemRed + { + didSet { + if oldValue != formTextErrorColor { + for textField in formTextFields ?? [] { + textField.errorColor = formTextErrorColor + } + } + } + } + + @objc public var formPlaceholderColor: UIColor = .placeholderText + { + didSet { + if oldValue != formPlaceholderColor { + for textField in formTextFields ?? [] { + textField.placeholderColor = formPlaceholderColor + } + } + } + } + + @objc public var formCursorColor: UIColor { + get { + self.tintColor + } + set { + if newValue != tintColor { + tintColor = newValue + for textField in formTextFields ?? [] { + textField.tintColor = tintColor + } + } + } + } + + @objc public var formKeyboardAppearance: UIKeyboardAppearance = .default { + didSet { + if oldValue != formKeyboardAppearance { + for textField in formTextFields ?? [] { + textField.keyboardAppearance = formKeyboardAppearance + } + } + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPPaymentCardTextField+SwiftUI.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPPaymentCardTextField+SwiftUI.swift new file mode 100644 index 00000000..f7e2fca5 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPPaymentCardTextField+SwiftUI.swift @@ -0,0 +1,66 @@ +// +// STPPaymentCardTextField+SwiftUI.swift +// StripePaymentsUI +// +// Created by David Estes on 2/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripePayments +import SwiftUI + +extension STPPaymentCardTextField { + + /// A SwiftUI representation of an STPPaymentCardTextField. + public struct Representable: UIViewRepresentable { + @Binding var paymentMethodParams: STPPaymentMethodParams? + + /// Initialize a SwiftUI representation of an STPPaymentCardTextField. + /// - Parameter paymentMethodParams: A binding to the payment card text field's contents. + /// The STPPaymentMethodParams will be `nil` if the payment card text field's contents are invalid. + public init( + paymentMethodParams: Binding + ) { + _paymentMethodParams = paymentMethodParams + } + + public func makeCoordinator() -> Coordinator { + return Coordinator(parent: self) + } + + public func makeUIView(context: Context) -> STPPaymentCardTextField { + let paymentCardField = STPPaymentCardTextField() + if let paymentMethodParams = paymentMethodParams { + paymentCardField.paymentMethodParams = paymentMethodParams + } + paymentCardField.delegate = context.coordinator + paymentCardField.setContentHuggingPriority(.required, for: .vertical) + + return paymentCardField + } + + public func updateUIView(_ paymentCardField: STPPaymentCardTextField, context: Context) { + if let paymentMethodParams = paymentMethodParams { + paymentCardField.paymentMethodParams = paymentMethodParams + } + } + + public class Coordinator: NSObject, STPPaymentCardTextFieldDelegate { + var parent: Representable + init( + parent: Representable + ) { + self.parent = parent + } + + public func paymentCardTextFieldDidChange(_ cardField: STPPaymentCardTextField) { + let paymentMethodParams = cardField.paymentMethodParams + if !cardField.isValid { + parent.paymentMethodParams = nil + return + } + parent.paymentMethodParams = paymentMethodParams + } + } + } +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPPaymentCardTextField.swift b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPPaymentCardTextField.swift new file mode 100644 index 00000000..2f3dbac9 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/Source/UI Components/STPPaymentCardTextField.swift @@ -0,0 +1,2484 @@ +// +// STPPaymentCardTextField.swift +// StripePaymentsUI +// +// Created by Jack Flintermann on 7/16/15. +// Copyright (c) 2015 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore +import UIKit + +/// STPPaymentCardTextField is a text field with similar properties to UITextField, +/// but specialized for credit/debit card information. It manages +/// multiple UITextFields under the hood to collect this information. It's +/// designed to fit on a single line, and from a design perspective can be used +/// anywhere a UITextField would be appropriate. +@IBDesignable +@objc(STPPaymentCardTextField) +open class STPPaymentCardTextField: UIControl, UIKeyInput, STPFormTextFieldDelegate { + /// :nodoc: + @objc + open func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + if let textField = textField as? STPFormTextField, + let delegateProxy = textField.delegateProxy + { + return delegateProxy.textField( + textField, + shouldChangeCharactersIn: range, + replacementString: string + ) + } + return true + } + + private var metadataLoadingIndicator: STPCardLoadingIndicator? + + /// - seealso: STPPaymentCardTextFieldDelegate + @IBOutlet open weak var delegate: STPPaymentCardTextFieldDelegate? + + /// The font used in each child field. Default is `UIFont.systemFont(ofSize:18)`. + @objc open var font: UIFont = { + return UIFontMetrics.default.scaledFont(for: UIFont.systemFont(ofSize: 18)) + }() + { + didSet { + for field in allFields { + field.font = font + } + + sizingField.font = font + clearSizingCache() + + setNeedsLayout() + } + } + + /// The text color to be used when entering valid text. Default is `.label`. + @objc open var textColor: UIColor = .label + { + didSet { + for field in allFields { + field.defaultColor = textColor + } + } + } + + /// The text color to be used when the user has entered invalid information, + /// such as an invalid card number. + /// Default is `.red`. + @objc open var textErrorColor: UIColor = .systemRed + { + didSet { + for field in allFields { + field.errorColor = textErrorColor + } + } + } + + /// The text placeholder color used in each child field. + /// This will also set the color of the card placeholder icon. + /// Default is `.systemGray2`. + @objc open var placeholderColor: UIColor = placeholderGrayColor { + didSet { + brandImageView.tintColor = placeholderColor + cbcIndicatorView.tintColor = placeholderColor + + for field in allFields { + field.placeholderColor = placeholderColor + } + } + } + + @IBInspectable private var _numberPlaceholder: String? + /// The placeholder for the card number field. + /// Default is "4242424242424242". + /// If this is set to something that resembles a card number, it will automatically + /// format it as such (in other words, you don't need to add spaces to this string). + @IBInspectable open var numberPlaceholder: String? { + get { + _numberPlaceholder + } + set(numberPlaceholder) { + _numberPlaceholder = numberPlaceholder + numberField.placeholder = _numberPlaceholder + } + } + + @IBInspectable private var _expirationPlaceholder: String? + /// The placeholder for the expiration field. Defaults to "MM/YY". + @IBInspectable open var expirationPlaceholder: String? { + get { + _expirationPlaceholder + } + set(expirationPlaceholder) { + _expirationPlaceholder = expirationPlaceholder + expirationField.placeholder = _expirationPlaceholder + } + } + + @IBInspectable private var _cvcPlaceholder: String? + /// The placeholder for the cvc field. Defaults to "CVC". + @IBInspectable open var cvcPlaceholder: String? { + get { + _cvcPlaceholder + } + set(cvcPlaceholder) { + _cvcPlaceholder = cvcPlaceholder + cvcField.placeholder = _cvcPlaceholder + } + } + + @IBInspectable private var _postalCodePlaceholder: String? + /// The placeholder for the postal code field. Defaults to "ZIP" for United States + /// or @"Postal" for all other country codes. + @IBInspectable open var postalCodePlaceholder: String? { + get { + _postalCodePlaceholder + } + set(postalCodePlaceholder) { + _postalCodePlaceholder = postalCodePlaceholder + updatePostalFieldPlaceholder() + } + } + /// The cursor color for the field. + /// This is a proxy for the view's tintColor property, exposed for clarity only + /// (in other words, calling setCursorColor is identical to calling setTintColor). + @objc open var cursorColor: UIColor { + get { + tintColor + } + set { + self.tintColor = newValue + } + } + + var _borderColor: UIColor? = placeholderGrayColor + /// The border color for the field. + /// Can be nil (in which case no border will be drawn). + /// Default is .systemGray2. + @objc open var borderColor: UIColor? { + get { + _borderColor + } + set { + _borderColor = newValue + if let borderColor = newValue { + self.layer.borderColor = (borderColor.copy() as! UIColor).cgColor + } else { + self.layer.borderColor = UIColor.clear.cgColor + } + } + } + + var _borderWidth: CGFloat = 1.0 + /// The width of the field's border. + /// Default is 1.0. + @objc open var borderWidth: CGFloat { + get { + _borderWidth + } + set { + _borderWidth = newValue + layer.borderWidth = borderWidth + } + } + + var _cornerRadius: CGFloat = 5.0 + /// The corner radius for the field's border. + /// Default is 5.0. + @objc open var cornerRadius: CGFloat { + get { + _cornerRadius + } + set { + _cornerRadius = cornerRadius + layer.cornerRadius = newValue + } + } + + /// The keyboard appearance for the field. + /// Default is UIKeyboardAppearanceDefault. + @objc open var keyboardAppearance: UIKeyboardAppearance = .default { + didSet { + for field in allFields { + field.keyboardAppearance = keyboardAppearance + } + } + } + + private var _inputView: UIView? + /// This behaves identically to setting the inputView for each child text field. + @objc open override var inputView: UIView? { + get { + _inputView + } + set(inputView) { + _inputView = inputView + + for field in allFields { + field.inputView = inputView + } + } + } + +#if !canImport(CompositorServices) + private var _inputAccessoryView: UIView? + /// This behaves identically to setting the inputAccessoryView for each child text field. + @objc open override var inputAccessoryView: UIView? { + get { + _inputAccessoryView + } + set(inputAccessoryView) { + _inputAccessoryView = inputAccessoryView + + for field in allFields { + field.inputAccessoryView = inputAccessoryView + } + } + } +#endif + + /// The curent brand image displayed in the receiver. + @objc open private(set) var brandImage: UIImage? + /// Whether or not the form currently contains a valid card number, + /// expiration date, CVC, and postal code (if required). + /// - seealso: STPCardValidator + + @objc dynamic open var isValid: Bool { + return viewModel.isValid + } + /// Enable/disable selecting or editing the field. Useful when submitting card details to Stripe. + + @objc open override var isEnabled: Bool { + get { + super.isEnabled + } + set(enabled) { + super.isEnabled = enabled + for textField in allFields { + textField.isEnabled = enabled + } + } + } + /// The current card number displayed by the field. + /// May or may not be valid, unless `isValid` is true, in which case it is guaranteed + /// to be valid. + @objc open var cardNumber: String? { + return viewModel.cardNumber + } + /// The current expiration month displayed by the field (1 = January, etc). + /// May or may not be valid, unless `isValid` is true, in which case it is + /// guaranteed to be valid. + @objc open var expirationMonth: Int { + if let monthString = viewModel.expirationMonth, let month = Int(monthString) { + return month + } + return 0 + } + /// The current expiration month displayed by the field, as a string. T + /// This may or may not be a valid entry (i.e. "0") unless `isValid` is true. + /// It may be also 0-prefixed (i.e. "01" for January). + @objc open var formattedExpirationMonth: String? { + return viewModel.expirationMonth + } + /// The current expiration year displayed by the field, modulo 100 + /// (e.g. the year 2015 will be represented as 15). + /// May or may not be valid, unless `isValid` is true, in which case it is + /// guaranteed to be valid. + + @objc open var expirationYear: Int { + if let yearString = viewModel.expirationYear, let year = Int(yearString) { + return year + } + return 0 + } + /// The current expiration year displayed by the field, as a string. + /// This is a 2-digit year (i.e. "15"), and may or may not be a valid entry + /// unless `isValid` is true. + + @objc open var formattedExpirationYear: String? { + return viewModel.expirationYear + } + /// The current card CVC displayed by the field. + /// May or may not be valid, unless `isValid` is true, in which case it + /// is guaranteed to be valid. + + @objc open var cvc: String? { + return viewModel.cvc + } + + /// The current card ZIP or postal code displayed by the field. + @objc open var postalCode: String? { + get { + if postalCodeEntryEnabled { + return viewModel.postalCode + } else { + return nil + } + } + set { + if postalCodeEntryEnabled { + if newValue != postalCode { + setText(newValue, inField: .postalCode) + } + } + } + } + /// Controls if a postal code entry field can be displayed to the user. + /// Default is YES. + /// If YES, the type of code entry shown is controlled by the set `countryCode` + /// value. Some country codes may result in no postal code entry being shown if + /// those countries do not commonly use postal codes. + /// If NO, no postal code entry will ever be displayed. + @objc open var postalCodeEntryEnabled: Bool { + get { + return viewModel.postalCodeRequired + } + set(postalCodeEntryEnabled) { + viewModel.postalCodeRequested = postalCodeEntryEnabled + } + } + /// The two-letter ISO country code that corresponds to the user's billing address. + /// If `postalCodeEntryEnabled` is YES, this controls which type of entry is allowed. + /// If `postalCodeEntryEnabled` is NO, this property currently has no effect. + /// If set to nil and postal code entry is enabled, the country from the user's current + /// locale will be filled in. Otherwise the specific country code set will be used. + /// By default this will fetch the user's current country code from NSLocale. + + @objc open var countryCode: String? { + get { + return viewModel.postalCodeCountryCode + } + set(cCode) { + if viewModel.postalCodeCountryCode == cCode { + return + } + let countryCode = (cCode ?? Locale.autoupdatingCurrent.stp_regionCode) + viewModel.postalCodeCountryCode = countryCode + updatePostalFieldPlaceholder() + + // This will revalidate and reformat + setText(postalCode, inField: .postalCode) + } + } + /// Convenience property for creating an `STPPaymentMethodCardParams` from the currently entered information + /// or programmatically setting the field's contents. For example, if you're using another library + /// to scan your user's credit card with a camera, you can assemble that data into an `STPPaymentMethodCardParams` + /// object and set this property to that object to prefill the fields you've collected. + /// Accessing this property returns a *copied* `cardParams`. The only way to change properties in this + /// object is to make changes to a `STPPaymentMethodCardParams` you own (retrieved from this text field if desired), + /// and then set this property to the new value. + /// + /// - Warning: Deprecated. Use `.paymentMethodParams` instead. If you must access the STPPaymentMethodCardParams, use `.paymentMethodParams.card`. + @available( + *, + deprecated, + message: + "Use .paymentMethodParams instead. If you must access the STPPaymentMethodCardParams, use .paymentMethodParams.card." + ) + @objc open var cardParams: STPPaymentMethodCardParams { + get { + // `card` will always exist + return paymentMethodParams.card! + } + set { + paymentMethodParams = STPPaymentMethodParams( + card: newValue, + billingDetails: nil, + metadata: nil + ) + } + } + + /// Convenience property for creating an `STPPaymentMethodParams` from the currently entered information + /// or programmatically setting the field's contents. For example, if you're using another library + /// to scan your user's credit card with a camera, you can assemble that data into an `STPPaymentMethodParams` + /// object and set this property to that object to prefill the fields you've collected. + /// Accessing this property returns a *copied* `paymentMethodParams`. The only way to change properties in this + /// object is to make changes to a `STPPaymentMethodParams` you own (retrieved from this text field if desired), + /// and then set this property to the new value. + @objc open var paymentMethodParams: STPPaymentMethodParams { + get { + let newParams = internalCardParams + newParams.number = cardNumber + if let monthString = viewModel.expirationMonth, let month = Int(monthString) { + newParams.expMonth = NSNumber(value: month) + } + if let yearString = viewModel.expirationYear, let year = Int(yearString) { + newParams.expYear = NSNumber(value: year) + } + newParams.cvc = cvc + internalCardParams = newParams + let cardToReturn = newParams.copy() as! STPPaymentMethodCardParams + var billingDetails = internalBillingDetails?.copy() as? STPPaymentMethodBillingDetails + if let postalCode = self.postalCode, !postalCode.isEmpty { + // If we don't have an internal billing details, create a new one to populate the postal code + billingDetails = billingDetails ?? STPPaymentMethodBillingDetails() + let address = STPPaymentMethodAddress() + address.postalCode = postalCode + address.country = countryCode ?? Locale.autoupdatingCurrent.stp_regionCode + billingDetails!.address = address // billingDetails will always be non-nil + } + + // If CBC is enabled, set the selected card brand + if let selectedBrand = viewModel.cbcController.selectedBrand { + cardToReturn.networks = STPPaymentMethodCardNetworksParams(preferred: STPCardBrandUtilities.apiValue(from: selectedBrand)) + } + + return STPPaymentMethodParams( + card: cardToReturn, + billingDetails: billingDetails, + metadata: internalMetadata + ) + } + set(callersCardParams) { + guard case .card = callersCardParams.type, + callersCardParams.card != nil + else { + assertionFailure("\(type(of: self)) only supports Card STPPaymentMethodParams") + return + } + + // Always set the metadata + internalMetadata = callersCardParams.metadata + + let currentPaymentMethodParams = self.paymentMethodParams + if (callersCardParams.card ?? STPPaymentMethodCardParams()).isEqual( + currentPaymentMethodParams.card + ) && callersCardParams.billingDetails == currentPaymentMethodParams.billingDetails { + // These are identical card params: Don't take any action. + return + } + // Due to the way this class is written, programmatically setting field text + // behaves identically to user entering text (and will have the same forwarding + // on to next responder logic). + // + // We have some custom logic here in the main accesible programmatic setter + // to dance around this a bit. First we save what is the current responder + // at the time this method was called. Later logic after text setting should be: + // 1. If we were not first responder, we should still not be first responder + // (but layout might need updating depending on PAN validity) + // 2. If original field is still not valid, it is still first responder + // (manually reset it back to first responder) + // 3. Otherwise the first subfield with invalid text should now be first responder + let originalSubResponder = currentFirstResponderField() + + // #1031 small footgun hiding here. Use copies to protect from mutations of + // `internalCardParams` in the `cardParams` property accessor and any mutations + // the app code might make to their `callersCardParams` object. + let desiredCardParams = + (callersCardParams.card ?? STPPaymentMethodCardParams()).copy() + as! STPPaymentMethodCardParams + internalCardParams = desiredCardParams.copy() as! STPPaymentMethodCardParams + + if let newBillingDetails = callersCardParams.billingDetails { + // If we receive billing details, set a copy of these as our internal billing details + internalBillingDetails = newBillingDetails.copy() as? STPPaymentMethodBillingDetails + } else { + // Otherwise, unset billing details + internalBillingDetails = nil + } + // Set the postal code, unsetting if nil + postalCode = internalBillingDetails?.address?.postalCode + + // If an explicit country code is passed, set it. Otherwise use the default behavior (NSLocale.current) + if let countryCode = callersCardParams.billingDetails?.address?.country { + self.countryCode = countryCode + } + + // If a card brand is explicitly selected, retain that information + if let preferredBrandString = callersCardParams.card?.networks?.preferred { + viewModel.cbcController.selectedBrand = STPCard.brand(from: preferredBrandString) + } + + setText(desiredCardParams.number, inField: .number) + let expirationPresent = + desiredCardParams.expMonth != nil && desiredCardParams.expYear != nil + if expirationPresent { + let text = String( + format: "%02lu%02lu", + UInt(desiredCardParams.expMonth?.intValue ?? 0), + UInt(desiredCardParams.expYear?.intValue ?? 0) % 100 + ) + setText(text, inField: .expiration) + } else { + setText("", inField: .expiration) + } + setText(desiredCardParams.cvc, inField: .CVC) + + if isFirstResponder { + var fieldType = STPCardFieldType.number + if let originalSubResponderTag = originalSubResponder?.tag, + let lastFieldType = STPCardFieldType(rawValue: originalSubResponderTag) + { + fieldType = lastFieldType + } + var state: STPCardValidationState = .incomplete + + switch fieldType { + case .number: + state = + viewModel.hasCompleteMetadataForCardNumber + ? STPCardValidator.validationState( + forNumber: viewModel.cardNumber ?? "", + validatingCardBrand: true + ) + : .incomplete + case .expiration: + state = viewModel.validationStateForExpiration() + case .CVC: + state = viewModel.validationStateForCVC() + case .postalCode: + state = viewModel.validationStateForPostalCode() + } + + if state == .valid { + let nextField = _firstInvalidAutoAdvanceField() + if let nextField = nextField { + nextField.becomeFirstResponder() + } else { + resignFirstResponder() + } + } else { + originalSubResponder?.becomeFirstResponder() + } + } else { + layoutViews( + toFocus: nil, + becomeFirstResponder: true, + animated: false, + completion: nil + ) + } + + // update the card image, falling back to the number field image if not editing + if expirationField.isFirstResponder { + updateImage(for: .expiration) + } else if cvcField.isFirstResponder { + updateImage(for: .CVC) + } else { + updateImage(for: .number) + } + updateCVCPlaceholder() + } + } + + /// Causes the text field to begin editing. Presents the keyboard. + /// - Returns: Whether or not the text field successfully began editing. + /// - seealso: UIResponder + @objc @discardableResult open override func becomeFirstResponder() -> Bool { + let firstResponder = currentFirstResponderField() ?? nextFirstResponderField() + return firstResponder.becomeFirstResponder() + } + + /// Causes the text field to stop editing. Dismisses the keyboard. + /// - Returns: Whether or not the field successfully stopped editing. + /// - seealso: UIResponder + @discardableResult open override func resignFirstResponder() -> Bool { + super.resignFirstResponder() + let success = currentFirstResponderField()?.resignFirstResponder() ?? false + layoutViews( + toFocus: nil, + becomeFirstResponder: false, + animated: true, + completion: nil + ) + updateImage(for: .number) + return success + } + + /// Resets all of the contents of all of the fields. If the field is currently being edited, the number field will become selected. + @objc open func clear() { + for field in allFields { + field.text = "" + } + let postalCodeRequested = viewModel.postalCodeRequested + viewModel = STPPaymentCardTextFieldViewModel(brandUpdateHandler: { [weak self] in + guard let self else { return } + self.updateImage(for: .number) + self.onChange() + }) + viewModel.postalCodeRequested = postalCodeRequested + onChange() + updateImage(for: .number) + updateCVCPlaceholder() + weak var weakSelf = self + layoutViews( + toFocus: NSNumber(value: STPCardFieldType.postalCode.rawValue), + becomeFirstResponder: true, + animated: true + ) { _ in + guard let strongSelf = weakSelf else { + return + } + if strongSelf.isFirstResponder { + strongSelf.numberField.becomeFirstResponder() + } + } + } + + /// Returns the cvc image used for a card brand. + /// Override this method in a subclass if you would like to provide custom images. + /// - Parameter cardBrand: The brand of card entered. + /// - Returns: The cvc image used for a card brand. + @objc(cvcImageForCardBrand:) open class func cvcImage(for cardBrand: STPCardBrand) -> UIImage? { + return STPImageLibrary.cvcImage(for: cardBrand) + } + + /// Returns the image used for a card when no brand is selected but + /// multiple brands are available. + /// Override this method in a subclass if you would like to provide custom images. + /// - Returns: The image used for a card when no brand is selected. + @objc(cardBrandChoiceImage) open class func cardBrandChoiceImage() -> UIImage? { + return STPImageLibrary.cardBrandChoiceImage() + } + + /// Returns the brand image used for a card brand. + /// Override this method in a subclass if you would like to provide custom images. + /// - Parameter cardBrand: The brand of card entered. + /// - Returns: The brand image used for a card brand. + @objc(brandImageForCardBrand:) open class func brandImage( + for cardBrand: STPCardBrand + ) + -> UIImage? + { + return STPImageLibrary.cardBrandImage(for: cardBrand) + } + + /// Returns the error image used for a card brand. + /// Override this method in a subclass if you would like to provide custom images. + /// - Parameter cardBrand: The brand of card entered. + /// - Returns: The error image used for a card brand. + @objc(errorImageForCardBrand:) open class func errorImage( + for cardBrand: STPCardBrand + ) + -> UIImage? + { + return STPImageLibrary.errorImage(for: cardBrand) + } + + /// Returns the rectangle in which the receiver draws its brand image. + /// - Parameter bounds: The bounding rectangle of the receiver. + /// - Returns: the rectangle in which the receiver draws its brand image. + @objc(brandImageRectForBounds:) open func brandImageRect(forBounds bounds: CGRect) -> CGRect { + let brandIconSize: CGSize = .init(width: 29.0, height: 19.0) + let height = CGFloat(min(bounds.size.height, brandIconSize.height)) + return CGRect( + x: STPPaymentCardTextFieldDefaultPadding, + y: 0.5 * bounds.size.height - 0.5 * height, + width: brandIconSize.width, + height: height + ) + } + + func cbcIndicatorRect(forBounds bounds: CGRect) -> CGRect { + let brandImageRect = brandImageRect(forBounds: bounds) + let width: CGFloat = 9 + let height: CGFloat = 9 + return CGRect( + x: brandImageRect.maxX, + y: brandImageRect.midY - (height / 2.0), + width: width, + height: height + ) + } + + /// Returns the rectangle in which the receiver draws the text fields. + /// - Parameter bounds: The bounding rectangle of the receiver. + /// - Returns: The rectangle in which the receiver draws the text fields. + @objc(fieldsRectForBounds:) open func fieldsRect(forBounds bounds: CGRect) -> CGRect { + let brandRect = self.brandImageRect(forBounds: bounds) + // Add a little padding for the CBC arrow + let minX = brandRect.maxX + 2.0 + return CGRect( + x: minX, + y: 0, + width: bounds.width - minX, + height: bounds.height + ) + } + + @objc internal lazy var brandImageView: UIImageView = UIImageView( + image: Self.brandImage(for: .unknown) + ) + + @objc internal lazy var cbcIndicatorView: UIImageView = UIImageView( + image: Image.icon_chevron_down.makeImage(template: true) + ) + + @objc internal lazy var fieldsView: UIView = UIView() + @objc internal lazy var numberField: STPFormTextField = { + return build() + }() + @objc internal lazy var expirationField: STPFormTextField = { + return build() + }() + @objc internal lazy var cvcField: STPFormTextField = { + return build() + }() + @objc internal lazy var postalCodeField: STPFormTextField = { + return build() + }() + + @objc internal lazy var viewModel: STPPaymentCardTextFieldViewModel = { + STPPaymentCardTextFieldViewModel(brandUpdateHandler: { [weak self] in + guard let self else { return } + self.updateImage(for: .number) + onChange() + }) + }() + + @objc internal var internalCardParams = STPPaymentMethodCardParams() + @objc internal var internalBillingDetails: STPPaymentMethodBillingDetails? + @objc internal var internalMetadata: [String: String]? + + @objc @_spi(STP) public var allFields: [STPFormTextField] = [] + private lazy var sizingField: STPFormTextField = { + let field = build() + field.formDelegate = nil + return field + }() + private lazy var sizingLabel: UILabel = { + let label = UILabel() + label.adjustsFontForContentSizeCategory = true + return label + }() + // These track the input parameters to the brand image setter so that we can + // later perform proper transition animations when new values are set + private var currentBrandImageFieldType: STPCardFieldType = .number + private var currentBrandImageBrand: STPCardBrand = .unknown + /// This is a number-wrapped STPCardFieldType (or nil) that layout uses + /// to determine how it should move/animate its subviews so that the chosen + /// text field is fully visible. + @objc internal var focusedTextFieldForLayout: NSNumber? + // Creating and measuring the size of attributed strings is expensive so + // cache the values here. + private var textToWidthCache: [String: NSNumber] = [:] + private var numberToWidthCache: [String: NSNumber] = [:] + /// These bits lets us track beginEditing and endEditing for payment text field + /// as a whole (instead of on a per-subview basis). + /// DO NOT read this values directly. Use the return value from + /// `getAndUpdateSubviewEditingTransitionStateFromCall:` which updates them all + /// and returns you the correct current state for the method you are in. + /// The state transitons in the should/did begin/end editing callbacks for all + /// our subfields. If we get a shouldEnd AND a shouldBegin before getting either's + /// matching didEnd/didBegin, then we are transitioning focus between our subviews + /// (and so we ourselves should not consider us to have begun or ended editing). + /// But if we get a should and did called on their own without a matching opposite + /// pair (shouldBegin/didBegin or shouldEnd/didEnd) then we are transitioning + /// into/out of our subviews from/to outside of ourselves + private var isMidSubviewEditingTransitionInternal = false + private var receivedUnmatchedShouldBeginEditing = false + private var receivedUnmatchedShouldEndEditing = false + private var lastShouldBeginField: STPCardFieldType? + + let STPPaymentCardTextFieldDefaultPadding: CGFloat = 13 + + let STPPaymentCardTextFieldDefaultInsets: CGFloat = 13 + + let STPPaymentCardTextFieldMinimumPadding: CGFloat = 10 + + // MARK: initializers + /// :nodoc: + required public init?( + coder aDecoder: NSCoder + ) { + super.init(coder: aDecoder) + commonInit() + } + + /// :nodoc: + public override init( + frame: CGRect + ) { + super.init(frame: frame) + commonInit() + } + + func commonInit() { + STPAnalyticsClient.sharedClient.addClass( + toProductUsageIfNecessary: STPPaymentCardTextField.self + ) + + // We're using ivars here because UIAppearance tracks when setters are + // called, and won't override properties that have already been customized + layer.borderColor = _borderColor?.cgColor + layer.cornerRadius = _cornerRadius + layer.borderWidth = _borderWidth + + clipsToBounds = true + + brandImageView.contentMode = .scaleAspectFit + brandImageView.backgroundColor = UIColor.clear + brandImageView.tintColor = placeholderColor + + cbcIndicatorView.contentMode = .scaleAspectFit + cbcIndicatorView.backgroundColor = UIColor.clear + cbcIndicatorView.tintColor = placeholderColor + cbcIndicatorView.alpha = 0.0 // Hide by default + + // This does not offer quick-type suggestions (as iOS 11.2), but does pick + // the best keyboard (maybe other, hidden behavior?) + numberField.textContentType = .creditCardNumber + numberField.autoFormattingBehavior = .cardNumbers + numberField.tag = STPCardFieldType.number.rawValue + numberField.accessibilityLabel = STPLocalizedString( + "card number", + "accessibility label for text field" + ) + numberPlaceholder = viewModel.defaultPlaceholder() + + expirationField.autoFormattingBehavior = .expiration + expirationField.tag = STPCardFieldType.expiration.rawValue + expirationField.alpha = 0 + expirationField.isAccessibilityElement = false + expirationField.accessibilityLabel = STPLocalizedString( + "expiration date", + "accessibility label for text field" + ) + expirationPlaceholder = STPLocalizedString( + "MM/YY", + "label for text field to enter card expiry" + ) + + cvcField.tag = STPCardFieldType.CVC.rawValue + cvcField.alpha = 0 + cvcField.isAccessibilityElement = false + cvcPlaceholder = nil + cvcField.accessibilityLabel = defaultCVCPlaceholder() + + postalCodeField.textContentType = .postalCode + postalCodeField.tag = STPCardFieldType.postalCode.rawValue + postalCodeField.alpha = 0 + postalCodeField.isAccessibilityElement = false + postalCodeField.keyboardType = .numbersAndPunctuation + // Placeholder is set by country code setter + + fieldsView.clipsToBounds = true + fieldsView.backgroundColor = UIColor.clear + + allFields = [numberField, expirationField, cvcField, postalCodeField].compactMap { $0 } + + addSubview(self.fieldsView) + for field in allFields { + self.fieldsView.addSubview(field) + } + + addSubview(brandImageView) + // On small screens, the number field fits ~4 numbers, and the brandImage is just as large. + // Previously, taps on the brand image would *dismiss* the keyboard. Make it move to the numberField instead + brandImageView.isUserInteractionEnabled = true + + addSubview(cbcIndicatorView) + cbcIndicatorView.isUserInteractionEnabled = true + + setupBrandTapGestureRecognizers() + + focusedTextFieldForLayout = nil + updateCVCPlaceholder() + resetSubviewEditingTransitionState() + + viewModel.postalCodeRequested = true + countryCode = Locale.autoupdatingCurrent.stp_regionCode + + sizingField.formDelegate = nil + + // We need to add sizingField and sizingLabel to the view + // hierarchy so they can accurately size for dynamic font + // sizes. + // Set them to hidden and send to back + sizingField.isHidden = true + sizingLabel.isHidden = true + addSubview(sizingField) + addSubview(sizingLabel) + sendSubviewToBack(sizingLabel) + sendSubviewToBack(sizingField) + + } + + func setupBrandTapGestureRecognizers() { + if #available(iOS 14.0, *) { + self.showsMenuAsPrimaryAction = true + self.isContextMenuInteractionEnabled = true + } + brandImageView.addGestureRecognizer( + UITapGestureRecognizer( + target: self, + action: #selector(brandViewTapped) + ) + ) + cbcIndicatorView.addGestureRecognizer( + UITapGestureRecognizer( + target: self, + action: #selector(brandViewTapped) + ) + ) + } + + @objc func brandViewTapped() { + if !self.viewModel.cbcController.brandState.isCBC { + self.numberField.becomeFirstResponder() + } + } + + var isShowingCBCIndicator: Bool { + // The brand state is CBC + return self.viewModel.cbcController.brandState.isCBC && + // And the CVC field isn't selected + currentBrandImageFieldType != .CVC && + // And the card is not invalid (we're not showing an error image) + STPCardValidator.validationState( + forNumber: viewModel.cardNumber ?? "", + validatingCardBrand: true + ) != .invalid + } + + open override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint { + let brandImageRect = self.brandImageRect(forBounds: self.bounds) + // TODO: Figure out actual padding (not 14px) + return CGPoint(x: brandImageRect.minX + 14, y: brandImageRect.maxY + 4) + } + + open override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { + if !isShowingCBCIndicator { + // Don't pop a menu if the CBC indicator isn't visible + return nil + } + + var targetRect = self.brandImageRect(forBounds: self.bounds) + // Add a little padding to include the arrow view + targetRect.size.width += self.cbcIndicatorRect(forBounds: self.bounds).width + if !targetRect.contains(location) { + // Don't pop a menu outside the brand selector area + return nil + } + + return viewModel.cbcController.contextMenuConfiguration + } + + // MARK: appearance properties + func clearSizingCache() { + textToWidthCache = [:] + numberToWidthCache = [:] + } + + static let placeholderGrayColor: UIColor = .systemGray2 + +#if !canImport(CompositorServices) + open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if previousTraitCollection?.preferredContentSizeCategory + != traitCollection.preferredContentSizeCategory + { + clearSizingCache() + setNeedsLayout() + } + } +#endif + + /// :nodoc: + @objc open override var backgroundColor: UIColor? { + get { + let defaultColor = UIColor.systemBackground + + return super.backgroundColor ?? defaultColor + } + set { + super.backgroundColor = newValue + self.numberField.backgroundColor = newValue + } + } + + /// :nodoc: + @objc open override var contentVerticalAlignment: UIControl.ContentVerticalAlignment { + get { + return super.contentVerticalAlignment + } + set(contentVerticalAlignment) { + super.contentVerticalAlignment = contentVerticalAlignment + for field in allFields { + field.contentVerticalAlignment = contentVerticalAlignment + } + switch contentVerticalAlignment { + case .center: + brandImageView.contentMode = .center + case .bottom: + brandImageView.contentMode = .bottom + case .fill: + brandImageView.contentMode = .top + case .top: + brandImageView.contentMode = .top + @unknown default: + break + } + } + } + + func updatePostalFieldPlaceholder() { + if postalCodePlaceholder == nil { + let placeholder = defaultPostalFieldPlaceholder(forCountryCode: countryCode) + postalCodeField.placeholder = placeholder + postalCodeField.accessibilityLabel = placeholder + } else { + postalCodeField.placeholder = postalCodePlaceholder + postalCodeField.accessibilityLabel = postalCodePlaceholder + } + } + + func defaultPostalFieldPlaceholder(forCountryCode countryCode: String?) -> String? { + if countryCode?.uppercased() == "US" { + return String.Localized.zip + } else { + return String.Localized.postal_code + } + } + + // MARK: UIControl + + // MARK: UIResponder & related methods + /// :nodoc: + @objc open override var isFirstResponder: Bool { + return currentFirstResponderField() != nil + } + + /// :nodoc: + @objc open override var canBecomeFirstResponder: Bool { + let firstResponder = currentFirstResponderField() ?? nextFirstResponderField() + return firstResponder.canBecomeFirstResponder + } + + /// Returns the next text field to be edited, in priority order: + /// 1. If we're currently in a text field, returns the next one (ignoring postalCodeField if postalCodeEntryEnabled == NO) + /// 2. Otherwise, returns the first invalid field (either cycling back from the end or as it gains 1st responder) + /// 3. As a final fallback, just returns the last field + func nextFirstResponderField() -> STPFormTextField { + let currentFirstResponder = currentFirstResponderField() + if let currentFirstResponder = currentFirstResponder { + let index = allFields.firstIndex(of: currentFirstResponder) ?? NSNotFound + if index != NSNotFound { + let nextField = + allFields.stp_boundSafeObject(at: index + 1) + if nextField != nil && (postalCodeEntryEnabled || nextField != postalCodeField) { + return nextField! + } + } + } + + if (numberField.text?.count ?? 0) == 0 { + return numberField + } + + return _firstInvalidAutoAdvanceField() ?? lastSubField() + } + + func _firstInvalidAutoAdvanceField() -> STPFormTextField? { + if viewModel.validationStateForExpiration() != .valid { + return expirationField + } else if viewModel.validationStateForCVC() != .valid { + return cvcField + } else if postalCodeEntryEnabled && viewModel.validationStateForPostalCode() != .valid { + return postalCodeField + } else { + return nil + } + } + + func lastSubField() -> STPFormTextField { + return (postalCodeEntryEnabled ? postalCodeField : cvcField) + } + + @objc func currentFirstResponderField() -> STPFormTextField? { + for textField in allFields { + if textField.isFirstResponder { + return textField + } + } + return nil + } + + /// :nodoc: + @objc open override var canResignFirstResponder: Bool { + return currentFirstResponderField()?.canResignFirstResponder ?? false + } + + func previousField() -> STPFormTextField? { + let currentSubResponder = currentFirstResponderField() + if let currentSubResponder = currentSubResponder { + let index = allFields.firstIndex(of: currentSubResponder) ?? NSNotFound + if index != NSNotFound && index > 0 { + return allFields[index - 1] + } + } + return nil + } + + // MARK: public convenience methods + + @objc func valid() -> Bool { + return isValid + } + + // MARK: readonly variables + + func setText(_ text: String?, inField field: STPCardFieldType) { + let nonNilText = text ?? "" + var textField: STPFormTextField? + switch field { + case .number: + textField = numberField + case .expiration: + textField = expirationField + case .CVC: + textField = cvcField + case .postalCode: + textField = postalCodeField + } + textField?.text = nonNilText + } + + func numberFullWidth() -> CGFloat { + return CGFloat( + max( + width(forCardNumber: viewModel.cardNumber), + width(forCardNumber: viewModel.defaultPlaceholder()) + ) + ) + } + + func numberCompressedWidth() -> CGFloat { + + var cardNumber = self.cardNumber + if (cardNumber?.count ?? 0) == 0 { + cardNumber = viewModel.defaultPlaceholder() + } + + let currentBrand = STPCardValidator.brand(forNumber: cardNumber ?? "") + let sortedCardNumberFormat = + (STPCardValidator.cardNumberFormat(forCardNumber: cardNumber ?? "") as NSArray) + .sortedArray( + using: #selector(getter: NSNumber.uintValue) + ) as! [NSNumber] + let fragmentLength = STPCardValidator.fragmentLength(for: currentBrand) + let maxLength: Int = max(Int(fragmentLength), sortedCardNumberFormat.last!.intValue) + + let maxCompressedString = "".padding(toLength: maxLength, withPad: "8", startingAt: 0) + return width(forText: maxCompressedString) + } + + func cvcFieldWidth() -> CGFloat { + if focusedTextFieldForLayout != NSNumber(value: STPCardFieldType.CVC.rawValue) + && viewModel.validationStateForCVC() == .valid + { + // If we're not focused and have valid text, size exactly to what is entered + return width(forText: viewModel.cvc) + } else { + // Otherwise size to fit our placeholder or what is likely to be the + // largest possible string enterable (whichever is larger) + let maxCvcLength = Int(STPCardValidator.maxCVCLength(for: viewModel.cbcController.brandForCVC)) + var longestCvc = "888" + if maxCvcLength == 4 { + longestCvc = "8888" + } + + return CGFloat(max(width(forText: cvcField.placeholder), width(forText: longestCvc))) + } + } + + func expirationFieldWidth() -> CGFloat { + if focusedTextFieldForLayout == nil && viewModel.validationStateForExpiration() == .valid { + // If we're not focused and have valid text, size exactly to what is entered + return width(forText: viewModel.rawExpiration) + } else { + // Otherwise size to fit our placeholder or what is likely to be the + // largest possible string enterable (whichever is larger) + return CGFloat( + max(width(forText: expirationField.placeholder), width(forText: "88/88")) + ) + } + } + + func postalCodeFieldFullWidth() -> CGFloat { + let compressedWidth = postalCodeFieldCompressedWidth() + let currentTextWidth = width(forText: viewModel.postalCode) + + if currentTextWidth <= compressedWidth { + return compressedWidth + } else if countryCode?.uppercased() == "US" { + // This format matches ZIP+4 which is currently disabled since it is + // not used for billing, but could be useful for future shipping addr purposes + return width(forText: "88888-8888 ") + } else { + // This format more closely matches the typical max UK/Canadian size which is our most common non-US market currently + return width(forText: "888 8888 ") + } + } + + func postalCodeFieldCompressedWidth() -> CGFloat { + var maxTextWidth: CGFloat = 0 + if countryCode?.uppercased() == "US" { + // The QuickType ZIP suggestion adds a space at the end, so we will too for calculating our bounds + maxTextWidth = width(forText: "88888 ") + } else { + // This format more closely matches the typical max UK/Canadian size which is our most common non-US market currently + maxTextWidth = width(forText: "888 8888 ") + } + + let placeholderWidth = width( + forText: defaultPostalFieldPlaceholder(forCountryCode: countryCode) + ) + return CGFloat(max(maxTextWidth, placeholderWidth)) + } + + /// :nodoc: + @objc open override var intrinsicContentSize: CGSize { + + let imageSize = brandImageView.image?.size + + sizingField.text = viewModel.defaultPlaceholder() + sizingField.sizeToFit() + let textHeight = sizingField.frame.height + let imageHeight = (imageSize?.height ?? 0.0) + (STPPaymentCardTextFieldDefaultInsets) + let height = ceil(CGFloat((max(max(imageHeight, textHeight), 44)))) + + var width = + STPPaymentCardTextFieldDefaultInsets + (imageSize?.width ?? 0.0) + + STPPaymentCardTextFieldDefaultInsets + numberFullWidth() + + STPPaymentCardTextFieldDefaultInsets + + width = ceil(width) + + return CGSize(width: width, height: height) + } + + enum STPCardTextFieldState: Int { + case visible + case compressed + case hidden + } + + func minimumPaddingForViews( + withWidth width: CGFloat, + pan panVisibility: STPCardTextFieldState, + expiry expiryVisibility: STPCardTextFieldState, + cvc cvcVisibility: STPCardTextFieldState, + postal postalVisibility: STPCardTextFieldState + ) -> CGFloat { + + var requiredWidth: CGFloat = 0 + var paddingsRequired: CGFloat = -1 + + if panVisibility != .hidden { + paddingsRequired += 1 + requiredWidth += + (panVisibility == .compressed) ? numberCompressedWidth() : numberFullWidth() + } + + if expiryVisibility != .hidden { + paddingsRequired += 1 + requiredWidth += expirationFieldWidth() + } + + if cvcVisibility != .hidden { + paddingsRequired += 1 + requiredWidth += cvcFieldWidth() + } + + if postalVisibility != .hidden && postalCodeEntryEnabled { + paddingsRequired += 1 + requiredWidth += + (postalVisibility == .compressed) + ? postalCodeFieldCompressedWidth() : postalCodeFieldFullWidth() + } + + if paddingsRequired > 0 { + return ceil((width - requiredWidth) / paddingsRequired) + } else { + return STPPaymentCardTextFieldMinimumPadding + } + } + + /// :nodoc: + @objc + open override func layoutSubviews() { + super.layoutSubviews() + recalculateSubviewLayout() + } + + func recalculateSubviewLayout() { + + let bounds = self.bounds + + brandImageView.frame = brandImageRect(forBounds: bounds) + cbcIndicatorView.frame = cbcIndicatorRect(forBounds: bounds) + + let fieldsViewRect = fieldsRect(forBounds: bounds) + fieldsView.frame = fieldsViewRect + + let availableFieldsWidth = fieldsViewRect.width - (2 * STPPaymentCardTextFieldDefaultInsets) + + // These values are filled in via the if statements and then used + // to do the proper layout at the end + let fieldsHeight = fieldsViewRect.height + var hPadding = STPPaymentCardTextFieldDefaultPadding + var panVisibility: STPCardTextFieldState = .visible + var expiryVisibility: STPCardTextFieldState = .visible + var cvcVisibility: STPCardTextFieldState = .visible + var postalVisibility: STPCardTextFieldState = postalCodeEntryEnabled ? .visible : .hidden + + let calculateMinimumPaddingWithLocalVars: (() -> CGFloat) = { + return self.minimumPaddingForViews( + withWidth: availableFieldsWidth, + pan: panVisibility, + expiry: expiryVisibility, + cvc: cvcVisibility, + postal: postalVisibility + ) + } + + hPadding = calculateMinimumPaddingWithLocalVars() + + if hPadding >= STPPaymentCardTextFieldMinimumPadding { + // Can just render everything at full size + // Do Nothing + } else { + // Need to do selective view compression/hiding + + if focusedTextFieldForLayout == nil { + // No field is currently being edited - + // + // Render all fields visible: + // Show compressed PAN, visible CVC and expiry, fill remaining space + // with postal if necessary + // + // The most common way to be in this state is the user finished entry + // and has moved on to another field (so we want to show summary) + // but possibly some fields are invalid + while hPadding < STPPaymentCardTextFieldMinimumPadding { + // Try hiding things in this order + if panVisibility == .visible { + panVisibility = .compressed + } else if postalVisibility == .visible { + postalVisibility = .compressed + } else { + // Can't hide anything else, set to minimum and stop + hPadding = STPPaymentCardTextFieldMinimumPadding + break + } + hPadding = calculateMinimumPaddingWithLocalVars() + } + } else { + switch STPCardFieldType(rawValue: focusedTextFieldForLayout?.intValue ?? 0)! { + case .number: + // The user is entering PAN + // + // It must be fully visible. Everything else is optional + + while hPadding < STPPaymentCardTextFieldMinimumPadding { + if postalVisibility == .visible { + postalVisibility = .compressed + } else if postalVisibility == .compressed { + postalVisibility = .hidden + } else if cvcVisibility == .visible { + cvcVisibility = .hidden + } else if expiryVisibility == .visible { + expiryVisibility = .hidden + } else { + hPadding = STPPaymentCardTextFieldMinimumPadding + break + } + hPadding = calculateMinimumPaddingWithLocalVars() + } + case .expiration: + // The user is entering expiration date + // + // It must be fully visible, and the next and previous fields + // must be visible so they can be tapped over to + while hPadding < STPPaymentCardTextFieldMinimumPadding { + if panVisibility == .visible { + panVisibility = .compressed + } else if postalVisibility == .visible { + postalVisibility = .compressed + } else if postalVisibility == .compressed { + postalVisibility = .hidden + } else { + hPadding = STPPaymentCardTextFieldMinimumPadding + break + } + hPadding = calculateMinimumPaddingWithLocalVars() + } + case .CVC: + // The user is entering CVC + // + // It must be fully visible, and the next and previous fields + // must be visible so they can be tapped over to (although + // there might not be a next field) + while hPadding < STPPaymentCardTextFieldMinimumPadding { + if panVisibility == .visible { + panVisibility = .compressed + } else if postalVisibility == .visible { + postalVisibility = .compressed + } else if panVisibility == .compressed { + panVisibility = .hidden + } else { + hPadding = STPPaymentCardTextFieldMinimumPadding + break + } + hPadding = calculateMinimumPaddingWithLocalVars() + } + case .postalCode: + // The user is entering postal code + // + // It must be fully visible, and the previous field must + // be visible + while hPadding < STPPaymentCardTextFieldMinimumPadding { + if panVisibility == .visible { + panVisibility = .compressed + } else if panVisibility == .compressed { + panVisibility = .hidden + } else if expiryVisibility == .visible { + expiryVisibility = .hidden + } else { + hPadding = STPPaymentCardTextFieldMinimumPadding + break + } + hPadding = calculateMinimumPaddingWithLocalVars() + } + } + } + } + + // -- Do layout here -- + var xOffset = STPPaymentCardTextFieldDefaultInsets + var width: CGFloat = 0 + + // Make all fields actually slightly wider than needed so that when the + // cursor is at the end position the contents aren't clipped off to the left side + let additionalWidth = self.width(forText: "8") + + if panVisibility == .compressed { + // Need to lower xOffset so pan is partially off-screen + + let hasEnteredCardNumber = (cardNumber?.count ?? 0) > 0 + let compressedCardNumber = + viewModel.compressedCardNumber(withPlaceholder: numberPlaceholder) ?? "" + let cardNumberToHide = (hasEnteredCardNumber ? cardNumber : numberPlaceholder)? + .stp_string( + byRemovingSuffix: compressedCardNumber + ) + + if (cardNumberToHide?.count ?? 0) > 0 + && STPCardValidator.stringIsNumeric(cardNumberToHide ?? "") + { + width = + hasEnteredCardNumber ? self.width(forCardNumber: cardNumber) : numberFullWidth() + + let hiddenWidth = self.width(forCardNumber: cardNumberToHide) + xOffset -= hiddenWidth + let maskView = UIView( + frame: CGRect( + x: hiddenWidth, + y: 0, + width: width - hiddenWidth, + height: fieldsHeight + ) + ) + maskView.backgroundColor = UIColor.label + maskView.isOpaque = true + maskView.isUserInteractionEnabled = false + UIView.performWithoutAnimation({ + self.numberField.mask = maskView + }) + } else { + width = numberCompressedWidth() + UIView.performWithoutAnimation({ + self.numberField.mask = nil + }) + } + } else { + width = numberFullWidth() + UIView.performWithoutAnimation({ + self.numberField.mask = nil + }) + + if panVisibility == .hidden { + // Need to lower xOffset so pan is fully off screen + xOffset = xOffset - width - hPadding + } + } + + numberField.frame = CGRect( + x: xOffset, + y: 0, + width: CGFloat(min(width + additionalWidth, fieldsView.frame.width - additionalWidth)), + height: fieldsHeight + ) + xOffset += width + hPadding + + width = expirationFieldWidth() + expirationField.frame = CGRect( + x: xOffset, + y: 0, + width: width + additionalWidth, + height: fieldsHeight + ) + // If the field isn't visible, we don't want to move the xOffset forward. + if expiryVisibility != .hidden { + xOffset += width + hPadding + } + + width = cvcFieldWidth() + cvcField.frame = CGRect( + x: xOffset, + y: 0, + width: width + additionalWidth, + height: fieldsHeight + ) + if cvcVisibility != .hidden { + xOffset += width + hPadding + } + + if postalCodeEntryEnabled { + width = fieldsView.frame.size.width - xOffset - STPPaymentCardTextFieldDefaultInsets + postalCodeField.frame = CGRect( + x: xOffset, + y: 0, + width: width + additionalWidth, + height: fieldsHeight + ) + } + + let updateFieldVisibility: ((STPFormTextField?, STPCardTextFieldState) -> Void)? = { + field, + fieldState in + if fieldState == .hidden { + field?.alpha = 0.0 + field?.isAccessibilityElement = false + } else { + field?.alpha = 1.0 + field?.isAccessibilityElement = true + } + } + + updateFieldVisibility?(numberField, panVisibility) + updateFieldVisibility?(expirationField, expiryVisibility) + updateFieldVisibility?(cvcField, cvcVisibility) + updateFieldVisibility?(postalCodeField, postalCodeEntryEnabled ? postalVisibility : .hidden) + } + + // MARK: - private helper methods + func build() -> STPFormTextField { + let textField = STPFormTextField(frame: CGRect.zero) + textField.backgroundColor = UIColor.clear + textField.adjustsFontForContentSizeCategory = true + // setCountryCode: updates the postalCodeField keyboardType, this is safe + textField.keyboardType = .asciiCapableNumberPad + textField.textAlignment = .left + textField.font = font + textField.defaultColor = textColor + textField.errorColor = textErrorColor + textField.placeholderColor = placeholderColor + textField.formDelegate = self + textField.validText = true + return textField + } + + typealias STPLayoutAnimationCompletionBlock = (Bool) -> Void + + func layoutViews( + toFocus focusedField: NSNumber?, + becomeFirstResponder shouldBecomeFirstResponder: Bool, + animated: Bool, + completion: STPLayoutAnimationCompletionBlock? + ) { + + var fieldtoFocus = focusedField + + if fieldtoFocus == nil + && !(focusedTextFieldForLayout == NSNumber(value: STPCardFieldType.number.rawValue)) + { + fieldtoFocus = NSNumber(value: STPCardFieldType.number.rawValue) + if shouldBecomeFirstResponder { + numberField.becomeFirstResponder() + } + } + + if (fieldtoFocus == nil && focusedTextFieldForLayout == nil) + || (fieldtoFocus != nil && (focusedTextFieldForLayout == fieldtoFocus)) + { + if let completion = completion { + completion(true) + } + return + } + + focusedTextFieldForLayout = fieldtoFocus + + let animations: (() -> Void)? = { + self.recalculateSubviewLayout() + } + + if animated { + let duration: TimeInterval = 0.3 + if let animations = animations { + UIView.animate( + withDuration: duration, + delay: 0, + usingSpringWithDamping: 0.85, + initialSpringVelocity: 0, + options: [], + animations: animations, + completion: completion + ) + } + } else { + animations?() + } + } + + func width(forAttributedText attributedText: NSAttributedString?) -> CGFloat { + // UITextField doesn't seem to size correctly here for unknown reasons + // But UILabel reliably calculates size correctly using this method + sizingLabel.attributedText = attributedText + sizingLabel.sizeToFit() + return ceil(sizingLabel.bounds.width) + + } + + func width(forText text: String?) -> CGFloat { + guard let text = text, text.count > 0 else { + return 0 + } + + if let cachedValue = textToWidthCache[text] { + return CGFloat(cachedValue.doubleValue) + } + sizingField.autoFormattingBehavior = .none + sizingField.text = STPNonLocalizedString(text) + let cachedValue = NSNumber( + value: Float(width(forAttributedText: sizingField.attributedText)) + ) + textToWidthCache[text] = cachedValue + return CGFloat(cachedValue.doubleValue) + } + + func width(forCardNumber cardNumber: String?) -> CGFloat { + guard let cardNumber = cardNumber, cardNumber.count > 0 else { + return 0 + } + + if let cachedValue = numberToWidthCache[cardNumber] { + return CGFloat(cachedValue.doubleValue) + } + sizingField.autoFormattingBehavior = .cardNumbers + sizingField.text = cardNumber + let cachedValue = NSNumber( + value: Float(width(forAttributedText: sizingField.attributedText)) + ) + numberToWidthCache[cardNumber] = cachedValue + return CGFloat(cachedValue.doubleValue) + } + + // MARK: STPFormTextFieldDelegate + @objc(formTextFieldDidBackspaceOnEmpty:) func formTextFieldDidBackspace( + onEmpty formTextField: STPFormTextField + ) { + let previous = previousField() + previous?.becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + if let previous = previous, previous.hasText, let previousText = previous.text { + // `UITextField.deleteBackwards` doesn't update the `text` property directly, and we depend on the `didSet` + // call on it to update our backing store. + // To get around this we manually remove the last character instead of calling `deleteBackwards` in the + // previous field. + previous.text = String(previousText.dropLast()) + } + } + + @objc(formTextField:modifyIncomingTextChange:) func formTextField( + _ formTextField: STPFormTextField, + modifyIncomingTextChange input: NSAttributedString + ) -> NSAttributedString { + guard let fieldType = STPCardFieldType(rawValue: formTextField.tag) else { + return NSAttributedString(string: "") + } + switch fieldType { + case .number: + viewModel.cardNumber = input.string + setNeedsLayout() + case .expiration: + viewModel.rawExpiration = input.string + case .CVC: + viewModel.cvc = input.string + case .postalCode: + viewModel.postalCode = input.string + setNeedsLayout() + } + + switch fieldType { + case .number: + return NSAttributedString( + string: viewModel.cardNumber ?? "", + attributes: numberField.defaultTextAttributes + ) + case .expiration: + return NSAttributedString( + string: viewModel.rawExpiration ?? "", + attributes: expirationField.defaultTextAttributes + ) + case .CVC: + return NSAttributedString( + string: viewModel.cvc ?? "", + attributes: cvcField.defaultTextAttributes + ) + case .postalCode: + return NSAttributedString( + string: viewModel.postalCode ?? "", + attributes: cvcField.defaultTextAttributes + ) + } + } + + @objc(formTextFieldTextDidChange:) func formTextFieldTextDidChange( + _ formTextField: STPFormTextField + ) { + guard let fieldType = STPCardFieldType(rawValue: formTextField.tag) else { + return + } + + formTextField.validText = true + + switch fieldType { + case .number: + let number = viewModel.cardNumber + + // Changing the card number field can invalidate the cvc, e.g. going from 4 digit Amex cvc to 3 digit Visa + // it is not expected that the brand will change based on network response so we update this immediately + // as well as in the completion just in case + updateCVCPlaceholder() + cvcField.validText = viewModel.validationStateForCVC() != .invalid + updateImage(for: fieldType) + + if viewModel.hasCompleteMetadataForCardNumber { + let state = STPCardValidator.validationState( + forNumber: viewModel.cardNumber ?? "", + validatingCardBrand: true + ) + updateCVCPlaceholder() + cvcField.validText = viewModel.validationStateForCVC() != .invalid + formTextField.validText = state != .invalid + + if state == .valid { + // auto-advance + nextFirstResponderField().becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + } + } else { + viewModel.validationStateForCardNumber(handler: { state in + if self.viewModel.cardNumber == number { + self.updateCVCPlaceholder() + self.cvcField.validText = self.viewModel.validationStateForCVC() != .invalid + formTextField.validText = state != .invalid + if state == .valid { + // log that user entered full complete PAN before we got a network response + STPAnalyticsClient.sharedClient + .logUserEnteredCompletePANBeforeMetadataLoaded() + } + self.onChange() + } + // Update image on response because we may want to remove the loading indicator + if let tag = (self.currentFirstResponderField() ?? self.numberField)?.tag, + let current = STPCardFieldType(rawValue: tag) + { + self.updateImage(for: current) + } + // no auto-advance + }) + + if viewModel.isNumberMaxLength { + let isValidLuhn = STPCardValidator.stringIsValidLuhn(viewModel.cardNumber ?? "") + formTextField.validText = isValidLuhn + if isValidLuhn { + // auto-advance + nextFirstResponderField().becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + } + } + } + case .expiration: + let state = viewModel.validationStateForExpiration() + formTextField.validText = state != .invalid + if state == .valid { + // auto-advance + nextFirstResponderField().becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + } + case .CVC: + let state = viewModel.validationStateForCVC() + formTextField.validText = state != .invalid + if state == .valid { + // Even though any CVC longer than the min required CVC length + // is valid, we don't want to forward on to the next field + // unless it is actually >= the max cvc length (otherwise when + // postal code is showing, you can't easily enter CVCs longer than + // the minimum. + let sanitizedCvc = STPCardValidator.sanitizedNumericString( + for: formTextField.text ?? "" + ) + if sanitizedCvc.count >= STPCardValidator.maxCVCLength(for: viewModel.cbcController.brandForCVC) { + // auto-advance + nextFirstResponderField().becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + } + } + case .postalCode: + formTextField.validText = viewModel.validationStateForPostalCode() != .invalid + // no auto-advance + // Similar to the UX problems on CVC, since our Postal Code validation + // is pretty light, we want to block auto-advance here. In the US, this + // allows users to enter 9 digit zips if they want, and as many as they + // need in non-US countries (where >0 characters is "valid") + } + + onChange() + } + + enum STPFieldEditingTransitionCallSite: Int { + case shouldBegin + case shouldEnd + case didBegin + case didEnd + } + + // Explanation of the logic here is with the definition of these properties + // at the top of this file + @discardableResult func getAndUpdateSubviewEditingTransitionState( + fromCall sendingMethod: STPFieldEditingTransitionCallSite + ) -> Bool { + var stateToReturn: Bool + switch sendingMethod { + case .shouldBegin: + receivedUnmatchedShouldBeginEditing = true + if receivedUnmatchedShouldEndEditing { + isMidSubviewEditingTransitionInternal = true + } + stateToReturn = isMidSubviewEditingTransitionInternal + case .shouldEnd: + receivedUnmatchedShouldEndEditing = true + if receivedUnmatchedShouldBeginEditing { + isMidSubviewEditingTransitionInternal = true + } + stateToReturn = isMidSubviewEditingTransitionInternal + case .didBegin: + stateToReturn = isMidSubviewEditingTransitionInternal + receivedUnmatchedShouldBeginEditing = false + if receivedUnmatchedShouldEndEditing == false { + isMidSubviewEditingTransitionInternal = false + } + case .didEnd: + stateToReturn = isMidSubviewEditingTransitionInternal + receivedUnmatchedShouldEndEditing = false + if receivedUnmatchedShouldBeginEditing == false { + isMidSubviewEditingTransitionInternal = false + } + } + + return stateToReturn + } + + func resetSubviewEditingTransitionState() { + isMidSubviewEditingTransitionInternal = false + receivedUnmatchedShouldBeginEditing = false + receivedUnmatchedShouldEndEditing = false + } + + /// :nodoc: + @objc + open func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + // On iOS 17 this delegate method gets called multiple times for the same field, this messes up our transition + // state, which in turn causes the paymentCardTextFieldDidEndEditing delegate method to not be called. + // To fix this, we keep track of the last field this delegate method was called for and ignore subsequent calls. + guard lastShouldBeginField != STPCardFieldType(rawValue: textField.tag) else { return true } + + getAndUpdateSubviewEditingTransitionState(fromCall: .shouldBegin) + lastShouldBeginField = STPCardFieldType(rawValue: textField.tag) + return true + } + + /// :nodoc: + @objc + open func textFieldDidBeginEditing(_ textField: UITextField) { + let isMidSubviewEditingTransition = getAndUpdateSubviewEditingTransitionState( + fromCall: .didBegin + ) + + layoutViews( + toFocus: NSNumber(value: textField.tag), + becomeFirstResponder: true, + animated: true, + completion: nil + ) + + if !isMidSubviewEditingTransition { + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidBeginEditing(_:)) + ) + ?? false + { + delegate?.paymentCardTextFieldDidBeginEditing?(self) + } + } + + guard let cardType = STPCardFieldType(rawValue: textField.tag) else { + return + } + switch cardType { + case .number: + (textField as? STPFormTextField)?.validText = true + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidBeginEditingNumber(_:)) + ) ?? false { + delegate?.paymentCardTextFieldDidBeginEditingNumber?(self) + } + case .CVC: + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidBeginEditingCVC(_:)) + ) + ?? false + { + delegate?.paymentCardTextFieldDidBeginEditingCVC?(self) + } + case .expiration: + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidBeginEditingExpiration( + _: + )) + ) + ?? false + { + delegate?.paymentCardTextFieldDidBeginEditingExpiration?(self) + } + case .postalCode: + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidBeginEditingPostalCode( + _: + )) + ) + ?? false + { + delegate?.paymentCardTextFieldDidBeginEditingPostalCode?(self) + } + } + updateImage(for: cardType) + } + + /// :nodoc: + @objc + open func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + getAndUpdateSubviewEditingTransitionState(fromCall: .shouldEnd) + updateImage(for: .number) + return true + } + + /// :nodoc: + @objc + open func textFieldDidEndEditing(_ textField: UITextField) { + let isMidSubviewEditingTransition = getAndUpdateSubviewEditingTransitionState( + fromCall: .didEnd + ) + + guard let cardType = STPCardFieldType(rawValue: textField.tag) else { + return + } + + switch cardType { + case .number: + viewModel.validationStateForCardNumber(handler: { state in + if state == .incomplete && !textField.isEditing { + (textField as? STPFormTextField)?.validText = false + } + }) + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidEndEditingNumber(_:)) + ) + ?? false + { + delegate?.paymentCardTextFieldDidEndEditingNumber?(self) + } + case .CVC: + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidEndEditingCVC(_:)) + ) + ?? false + { + delegate?.paymentCardTextFieldDidEndEditingCVC?(self) + } + case .expiration: + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidEndEditingExpiration(_:)) + ) ?? false { + delegate?.paymentCardTextFieldDidEndEditingExpiration?(self) + } + case .postalCode: + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidEndEditingPostalCode(_:)) + ) ?? false { + delegate?.paymentCardTextFieldDidEndEditingPostalCode?(self) + } + } + + if !isMidSubviewEditingTransition { + layoutViews( + toFocus: nil, + becomeFirstResponder: false, + animated: true, + completion: nil + ) + updateImage(for: .number) + if delegate?.responds( + to: #selector(STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidEndEditing(_:)) + ) + ?? false + { + delegate?.paymentCardTextFieldDidEndEditing?(self) + } + + // Clear this when the whole field ends editing, so the next call to should begin is not ignored. + lastShouldBeginField = nil + } + } + + /// :nodoc: + @objc + open func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if textField == lastSubField() && _firstInvalidAutoAdvanceField() == nil { + // User pressed return in the last field, and all fields are valid + if delegate?.responds( + to: #selector( + STPPaymentCardTextFieldDelegate.paymentCardTextFieldWillEndEditing(forReturn:)) + ) + ?? false + { + delegate?.paymentCardTextFieldWillEndEditing?(forReturn: self) + } + resignFirstResponder() + } else { + // otherwise, move to the next field + nextFirstResponderField().becomeFirstResponder() + UIAccessibility.post(notification: .screenChanged, argument: nil) + } + + return false + } + + @objc internal func brandImage( + for fieldType: STPCardFieldType, + validationState: STPCardValidationState + ) -> UIImage? { + let brandImage = { + switch self.viewModel.cbcController.brandState { + case .brand(let brand): + return Self.brandImage(for: brand) + case .cbcBrandSelected(let brand): + return Self.brandImage(for: brand) + case .unknown: + return Self.brandImage(for: .unknown) + case .unknownMultipleOptions: + return Self.cardBrandChoiceImage() + } + }() + switch fieldType { + case .number: + if validationState == .invalid { + return Self.errorImage(for: viewModel.brand) + } else { + if viewModel.hasCompleteMetadataForCardNumber { + return brandImage + } else { + return Self.brandImage(for: .unknown) + } + } + case .CVC: + return Self.cvcImage(for: viewModel.brand) + case .expiration: + return brandImage + case .postalCode: + return brandImage + } + } + + func brandImageAnimationOptions( + forNewType newType: STPCardFieldType, + newBrand: STPCardBrand, + oldType: STPCardFieldType, + oldBrand: STPCardBrand + ) -> UIView.AnimationOptions { + + if newType == .CVC && oldType != .CVC { + // Transitioning to show CVC + + if newBrand != .amex { + // CVC is on the back + return [.curveEaseInOut, .transitionFlipFromRight] + } + } else if newType != .CVC && oldType == .CVC { + // Transitioning to stop showing CVC + + if oldBrand != .amex { + // CVC was on the back + return [.curveEaseInOut, .transitionFlipFromLeft] + } + } + + // All other cases just cross dissolve + return [.curveEaseInOut, .transitionCrossDissolve] + + } + + func updateImage(for fieldType: STPCardFieldType) { + let addLoadingIndicator: (() -> Void)? = { + if self.metadataLoadingIndicator == nil { + self.metadataLoadingIndicator = STPCardLoadingIndicator() + + self.metadataLoadingIndicator?.translatesAutoresizingMaskIntoConstraints = false + if let metadataLoadingIndicator = self.metadataLoadingIndicator { + self.addSubview(metadataLoadingIndicator) + } + NSLayoutConstraint.activate( + [ + self.metadataLoadingIndicator?.rightAnchor.constraint( + equalTo: self.brandImageView.rightAnchor + ), + self.metadataLoadingIndicator?.topAnchor.constraint( + equalTo: self.brandImageView.topAnchor + ), + ].compactMap { $0 } + ) + } + + let loadingIndicator = self.metadataLoadingIndicator + if !(loadingIndicator?.isHidden ?? false) { + return + } + + loadingIndicator?.alpha = 0.0 + loadingIndicator?.isHidden = false + UIView.animate( + withDuration: 0.6, + delay: 0, + options: .curveEaseInOut, + animations: { + loadingIndicator?.alpha = 1.0 + } + ) { _ in + loadingIndicator?.alpha = 1.0 + } + } + + let removeLoadingIndicator: (() -> Void)? = { + if self.metadataLoadingIndicator != nil + && !(self.metadataLoadingIndicator?.isHidden ?? false) + { + let loadingIndicator = self.metadataLoadingIndicator + + UIView.animate( + withDuration: 0.6, + delay: 0, + options: .curveEaseInOut, + animations: { + loadingIndicator?.alpha = 0.0 + } + ) { _ in + loadingIndicator?.alpha = 0.0 + loadingIndicator?.isHidden = true + } + } + } + + let applyBrandImage: ((STPCardFieldType, STPCardValidationState) -> Void)? = { + applyFieldType, + validationState in + let image = self.brandImage(for: applyFieldType, validationState: validationState) + if !(image == self.brandImageView.image) { + + let newBrand = self.viewModel.brand + let imageAnimationOptions = self.brandImageAnimationOptions( + forNewType: fieldType, + newBrand: newBrand, + oldType: self.currentBrandImageFieldType, + oldBrand: self.currentBrandImageBrand + ) + + self.currentBrandImageFieldType = applyFieldType + self.currentBrandImageBrand = newBrand + + UIView.transition( + with: self.brandImageView, + duration: 0.2, + options: imageAnimationOptions, + animations: { + self.brandImageView.image = image + } + ) + } + } + + if !(viewModel.hasCompleteMetadataForCardNumber) + && STPBINController.shared.isLoadingCardMetadata(forPrefix: viewModel.cardNumber ?? "") + { + applyBrandImage?(.number, .incomplete) + // delay a bit before showing loading indicator because the response may come quickly + DispatchQueue.main.asyncAfter( + deadline: DispatchTime.now() + Double( + Int64(kCardLoadingAnimationDelay * Double(NSEC_PER_SEC)) + ) + / Double(NSEC_PER_SEC), + execute: { + if !(self.viewModel.hasCompleteMetadataForCardNumber) + && STPBINController.shared.isLoadingCardMetadata( + forPrefix: self.viewModel.cardNumber ?? "" + ) + { + addLoadingIndicator?() + } + } + ) + } else { + removeLoadingIndicator?() + + switch fieldType { + case .number: + applyBrandImage?( + .number, + STPCardValidator.validationState( + forNumber: viewModel.cardNumber ?? "", + validatingCardBrand: true + ) + ) + case .expiration: + applyBrandImage?(fieldType, (viewModel.validationStateForExpiration())) + case .CVC: + applyBrandImage?(fieldType, (viewModel.validationStateForCVC())) + case .postalCode: + applyBrandImage?(fieldType, (viewModel.validationStateForPostalCode())) + } + } + UIView.transition( + with: self.cbcIndicatorView, + duration: 0.2, + options: [.curveEaseInOut, .transitionCrossDissolve], + animations: { + self.cbcIndicatorView.alpha = self.isShowingCBCIndicator ? 1.0 : 0.0 + } + ) + } + + // MARK: Card brand choice + // For internal testing + @_spi(STP) public var cbcEnabledOverride: Bool? { + get { + viewModel.cbcController.cbcEnabledOverride + } + set { + viewModel.cbcController.cbcEnabledOverride = newValue + } + } + + func defaultCVCPlaceholder() -> String? { + return String.Localized.cvc + } + + func updateCVCPlaceholder() { + if let cvcPlaceholder = cvcPlaceholder { + cvcField.placeholder = cvcPlaceholder + cvcField.accessibilityLabel = cvcPlaceholder + } else { + cvcField.placeholder = defaultCVCPlaceholder() + cvcField.accessibilityLabel = defaultCVCPlaceholder() + } + } + + func onChange() { + if delegate?.responds( + to: #selector(STPPaymentCardTextFieldDelegate.paymentCardTextFieldDidChange(_:)) + ) + ?? false + { + delegate?.paymentCardTextFieldDidChange?(self) + } + sendActions(for: .valueChanged) + } + + // MARK: UIKeyInput + /// :nodoc: + @objc open var hasText: Bool { + return numberField.hasText || expirationField.hasText || cvcField.hasText + } + + /// :nodoc: + @objc + open func insertText(_ text: String) { + currentFirstResponderField()?.insertText(text) + } + + /// :nodoc: + @objc + open func deleteBackward() { + currentFirstResponderField()?.deleteBackward() + } + + /// :nodoc: + @objc + open class func keyPathsForValuesAffectingIsValid() -> Set { + return Set([ + "viewModel.isValid", + "viewModel.hasCompleteMetadataForCardNumber", + ]) + } + + /// The list of preferred networks that should be used to process + /// payments made with a co-branded card if your user hasn't selected a + /// network themselves. + /// + /// The first preferred network that matches any available network will + /// be offered to the customer. If no preferred network is applicable, the + /// customer will select the network. + open var preferredNetworks: [STPCardBrand]? { + didSet { + viewModel.cbcController.preferredNetworks = preferredNetworks + } + } + + /// The list of preferred networks that should be used to process + /// payments made with a co-branded card if your user hasn't selected a + /// network themselves. + /// + /// The first preferred network that matches any available network will + /// be offered to the customer. If no preferred network is applicable, the + /// customer will select the network. + /// + /// In Objective-C, this is an array of NSNumbers representing STPCardBrands. + /// For example: + /// [textField setPreferredNetworks:@[[NSNumber numberWithInt:STPCardBrandVisa]]]; + @available(swift, obsoleted: 1.0) + @objc(preferredNetworks) open func preferredNetworks_objc() -> [NSNumber]? { + guard let preferredNetworks = self.preferredNetworks else { + return nil + } + return preferredNetworks.map { NSNumber(value: $0.rawValue) } + } + + /// The list of preferred networks that should be used to process + /// payments made with a co-branded card if your user hasn't selected a + /// network themselves. + /// + /// The first preferred network that matches any available network will + /// be offered to the customer. If no preferred network is applicable, the + /// customer will select the network. + /// + /// In Objective-C, this is an array of NSNumbers representing STPCardBrands. + /// For example: + /// [textField setPreferredNetworks:@[[NSNumber numberWithInt:STPCardBrandVisa]]]; + @available(swift, obsoleted: 1.0) + @objc(setPreferredNetworks:) open func setPreferredNetworks_objc(preferredNetworks: [NSNumber]?) { + guard let preferredNetworks = preferredNetworks else { + self.preferredNetworks = nil + return + } + self.preferredNetworks = preferredNetworks.map { STPCardBrand(rawValue: $0.intValue) ?? .unknown } + } + + /// The account (if any) for which the funds of the intent are intended. + /// The Stripe account ID (if any) which is the business of record. + /// See [use cases](https://docs.stripe.com/connect/charges#on_behalf_of) to determine if this option is relevant for your integration. + /// This should match the [on_behalf_of](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-on_behalf_of) + /// provided on the Intent used when confirming payment. + public var onBehalfOf: String? { + didSet { + viewModel.cbcController.onBehalfOf = onBehalfOf + } + } +} + +/// This protocol allows a delegate to be notified when a payment text field's +/// contents change, which can in turn be used to take further actions depending +/// on the validity of its contents. +@objc public protocol STPPaymentCardTextFieldDelegate: NSObjectProtocol { + /// Called when either the card number, expiration, or CVC changes. At this point, + /// one can call `isValid` on the text field to determine, for example, + /// whether or not to enable a button to submit the form. Example: + /// - (void)paymentCardTextFieldDidChange:(STPPaymentCardTextField *)textField { + /// self.paymentButton.enabled = textField.isValid; + /// } + /// - Parameter textField: the text field that has changed + @objc optional func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) + /// Called when editing begins in the text field as a whole. + /// After receiving this callback, you will always also receive a callback for which + /// specific subfield of the view began editing. + @objc optional func paymentCardTextFieldDidBeginEditing(_ textField: STPPaymentCardTextField) + /// Notification that the user pressed the `return` key after completely filling + /// out the STPPaymentCardTextField with data that passes validation. + /// The Stripe SDK is going to `resignFirstResponder` on the `STPPaymentCardTextField` + /// to dismiss the keyboard after this delegate method returns, however if your app wants + /// to do something more (ex: move first responder to another field), this is a good + /// opportunity to do that. + /// This is delivered *before* the corresponding `paymentCardTextFieldDidEndEditing:` + /// - Parameter textField: The STPPaymentCardTextField that was being edited when the user pressed return + @objc optional func paymentCardTextFieldWillEndEditing( + forReturn textField: STPPaymentCardTextField + ) + /// Called when editing ends in the text field as a whole. + /// This callback is always preceded by an callback for which + /// specific subfield of the view ended its editing. + @objc optional func paymentCardTextFieldDidEndEditing(_ textField: STPPaymentCardTextField) + /// Called when editing begins in the payment card field's number field. + @objc optional func paymentCardTextFieldDidBeginEditingNumber( + _ textField: STPPaymentCardTextField + ) + /// Called when editing ends in the payment card field's number field. + @objc optional func paymentCardTextFieldDidEndEditingNumber( + _ textField: STPPaymentCardTextField + ) + /// Called when editing begins in the payment card field's CVC field. + @objc optional func paymentCardTextFieldDidBeginEditingCVC(_ textField: STPPaymentCardTextField) + /// Called when editing ends in the payment card field's CVC field. + @objc optional func paymentCardTextFieldDidEndEditingCVC(_ textField: STPPaymentCardTextField) + /// Called when editing begins in the payment card field's expiration field. + @objc optional func paymentCardTextFieldDidBeginEditingExpiration( + _ textField: STPPaymentCardTextField + ) + /// Called when editing ends in the payment card field's expiration field. + @objc optional func paymentCardTextFieldDidEndEditingExpiration( + _ textField: STPPaymentCardTextField + ) + /// Called when editing begins in the payment card field's ZIP/postal code field. + @objc optional func paymentCardTextFieldDidBeginEditingPostalCode( + _ textField: STPPaymentCardTextField + ) + /// Called when editing ends in the payment card field's ZIP/postal code field. + @objc optional func paymentCardTextFieldDidEndEditingPostalCode( + _ textField: STPPaymentCardTextField + ) +} + +private let kCardLoadingAnimationDelay: TimeInterval = 0.1 + +/// :nodoc: +@_spi(STP) extension STPPaymentCardTextField: STPAnalyticsProtocol { + @_spi(STP) public static var stp_analyticsIdentifier = "STPPaymentCardTextField" +} diff --git a/StripePaymentsUI/StripePaymentsUI/StripePaymentsUI.h b/StripePaymentsUI/StripePaymentsUI/StripePaymentsUI.h new file mode 100644 index 00000000..bc8b2c3e --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUI/StripePaymentsUI.h @@ -0,0 +1,18 @@ +// +// StripePaymentsUI.h +// StripePaymentsUI +// +// Created by David Estes on 6/30/22. +// + +#import + +//! Project version number for StripePaymentsUI. +FOUNDATION_EXPORT double StripePaymentsUIVersionNumber; + +//! Project version string for StripePaymentsUI. +FOUNDATION_EXPORT const unsigned char StripePaymentsUIVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripePaymentsUI/StripePaymentsUITests/CardElementConfigServiceTests.swift b/StripePaymentsUI/StripePaymentsUITests/CardElementConfigServiceTests.swift new file mode 100644 index 00000000..a0d2d72d --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUITests/CardElementConfigServiceTests.swift @@ -0,0 +1,103 @@ +// +// CardElementConfigServiceTests.swift +// StripePaymentsUITests +// + +import Foundation + +import OHHTTPStubs +import OHHTTPStubsSwift +@_spi(STP) @testable import StripeCoreTestUtils +@_spi(STP) @testable import StripePaymentsUI +import XCTest + +class CardElementConfigServiceTests: APIStubbedTestCase { + + func testSuccessfullyFetchesConfig() throws { + let exp = expectation(description: "fetched config") + let cecs = CardElementConfigService() + cecs.apiClient = stubbedAPIClient() + cecs.apiClient.publishableKey = "pk_test_123abc" + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/mobile-card-element-config") ?? false + } response: { _ in + let responseData = """ + {"card_brand_choice":{"eligible":true}} + """.data(using: .utf8)! + defer { + exp.fulfill() + } + return HTTPStubsResponse(data: responseData, statusCode: 200, headers: nil) + } + // Returns false at first... + XCTAssertFalse(cecs.isCBCEligible()) + + waitForExpectations(timeout: 3.0) + // But after waiting for the response (and another turn of the runloop), it returns true! + let exp2 = expectation(description: "processed and checked response") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + XCTAssertTrue(cecs.isCBCEligible()) + exp2.fulfill() + } + waitForExpectations(timeout: 1.0) + } + + func testSuccessfullyFetchesConfigForOnBehalfOf() throws { + let exp = expectation(description: "fetched config") + let cecs = CardElementConfigService() + cecs.apiClient = stubbedAPIClient() + cecs.apiClient.publishableKey = "pk_test_123abc" + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/mobile-card-element-config?on_behalf_of=acct_abc123") ?? false + } response: { _ in + let responseData = """ + {"card_brand_choice":{"eligible":true}} + """.data(using: .utf8)! + defer { + exp.fulfill() + } + return HTTPStubsResponse(data: responseData, statusCode: 200, headers: nil) + } + // Returns false at first... + XCTAssertFalse(cecs.isCBCEligible(onBehalfOf: "acct_abc123")) + + waitForExpectations(timeout: 3.0) + // But after waiting for the response (and another turn of the runloop), it returns true! + let exp2 = expectation(description: "processed and checked response") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + XCTAssertTrue(cecs.isCBCEligible(onBehalfOf: "acct_abc123")) + exp2.fulfill() + } + waitForExpectations(timeout: 1.0) + } + + func testNothingBadHappensOnInvalidData() throws { + let exp = expectation(description: "fetched config") + let cecs = CardElementConfigService() + cecs.apiClient = stubbedAPIClient() + cecs.apiClient.publishableKey = "pk_test_123abc" + stub { urlRequest in + return urlRequest.url?.absoluteString.contains("/mobile-card-element-config") ?? false + } response: { _ in + let responseData = """ + {"card_brand_choice":{"eligible":"chicken"}} + """.data(using: .utf8)! + defer { + exp.fulfill() + } + return HTTPStubsResponse(data: responseData, statusCode: 200, headers: nil) + } + // Returns false at first... + XCTAssertFalse(cecs.isCBCEligible()) + + waitForExpectations(timeout: 3.0) + // But after waiting for the response (and another turn of the runloop), it still returns false (as the response was invalid) + let exp2 = expectation(description: "processed and checked response") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + XCTAssertFalse(cecs.isCBCEligible()) + exp2.fulfill() + } + waitForExpectations(timeout: 1.0) + } + +} diff --git a/StripePaymentsUI/StripePaymentsUITests/Info.plist b/StripePaymentsUI/StripePaymentsUITests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripePaymentsUI/StripePaymentsUITests/STPAnalyticsClient+PaymentsUITests.swift b/StripePaymentsUI/StripePaymentsUITests/STPAnalyticsClient+PaymentsUITests.swift new file mode 100644 index 00000000..110e2e1f --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUITests/STPAnalyticsClient+PaymentsUITests.swift @@ -0,0 +1,32 @@ +// +// STPAnalyticsClient+PaymentsUITests.swift +// StripePaymentsUITests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import XCTest + +// swift-format-ignore +@_spi(STP) @testable import StripeCore + +// swift-format-ignore +@_spi(STP) @testable import StripePayments + +// swift-format-ignore +@_spi(STP) @testable import StripePaymentsUI + +class STPAnalyticsClientPaymentsUITest: XCTestCase { + func testPaymentsUISDKVariantPayload() throws { + // setup + let analytic = GenericPaymentAnalytic( + event: .paymentMethodCreation, + paymentConfiguration: nil, + additionalParams: [:] + ) + let client = STPAnalyticsClient() + let payload = client.payload(from: analytic) + XCTAssertEqual("payments-ui", payload["pay_var"] as? String) + } +} diff --git a/StripePaymentsUI/StripePaymentsUITests/STPPaymentCardTextFieldSnapshotTests.swift b/StripePaymentsUI/StripePaymentsUITests/STPPaymentCardTextFieldSnapshotTests.swift new file mode 100644 index 00000000..c342ce9c --- /dev/null +++ b/StripePaymentsUI/StripePaymentsUITests/STPPaymentCardTextFieldSnapshotTests.swift @@ -0,0 +1,58 @@ +// +// STPPaymentCardTextFieldSnapshotTests.swift +// StripePaymentsUI +// +// Created by David Estes on 9/26/23. +// + +import iOSSnapshotTestCase +@_spi(STP)@testable import StripeCore +import StripeCoreTestUtils +import StripePaymentsTestUtils +@_spi(STP)@testable import StripePaymentsUI +@_spi(STP)@testable import StripeUICore + +class STPPaymentCardTextFieldSnapshotTests: STPSnapshotTestCase { + + var paymentCardTextField: STPPaymentCardTextField { + return STPPaymentCardTextField(frame: CGRect(x: 0, y: 0, width: 400, height: 50)) + } + + func testPaymentCardTextField() { + let pctf = paymentCardTextField + STPSnapshotVerifyView(pctf) + } + + func testPaymentCardTextFieldWithNumber() { + let pctf = paymentCardTextField + let card = STPPaymentMethodCardParams() + card.number = "4242424242424242" + card.expMonth = 12 + card.expYear = 43 + // dear future engineer in 2043: i'm sorry + card.cvc = "123" + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + pctf.paymentMethodParams = params + STPSnapshotVerifyView(pctf) + } + + func testPaymentCardTextFieldCBC() { + STPAPIClient.shared.publishableKey = STPTestingDefaultPublishableKey + let pctf = paymentCardTextField + pctf.cbcEnabledOverride = true + let card = STPPaymentMethodCardParams() + card.number = "4973019750239993" + card.expMonth = 12 + card.expYear = 43 + card.cvc = "123" + let params = STPPaymentMethodParams(card: card, billingDetails: nil, metadata: nil) + pctf.paymentMethodParams = params + let exp = expectation(description: "Wait for CBC load") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + self.STPSnapshotVerifyView(pctf) + exp.fulfill() + } + waitForExpectations(timeout: 3.0) + } + +} diff --git a/StripeUICore/StripeUICore.xcodeproj/project.pbxproj b/StripeUICore/StripeUICore.xcodeproj/project.pbxproj new file mode 100644 index 00000000..226b3249 --- /dev/null +++ b/StripeUICore/StripeUICore.xcodeproj/project.pbxproj @@ -0,0 +1,1180 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 0019E30E7B8189C3DEA719DC /* STPVPANumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D634B04FC897DFB74B81DED6 /* STPVPANumberValidator.swift */; }; + 019C76A03A30A67AE9F1FAEE /* PickerFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F3046A526D3A4CE402FB83 /* PickerFieldView.swift */; }; + 02B503A3227EF1409ABF53F1 /* UIColor+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4AEB27F9734178E20836A /* UIColor+StripeUICore.swift */; }; + 073D41F1EC0560423FEF87AA /* AddressSpecProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83BC5E25F42045563F4A660B /* AddressSpecProviderTest.swift */; }; + 08C773D1E6A5452B7BD7CF81 /* TextFieldElement+AddressFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE57C3D398C94604889D9F61 /* TextFieldElement+AddressFactoryTest.swift */; }; + 0A3130227F7602524C9824D3 /* TextFieldElement+AddressFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD7C1AAB9D26E00526AFB26 /* TextFieldElement+AddressFactory.swift */; }; + 0BA1E7C26903E45912FDE25A /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D558F65E4C23971C94BAD3E /* String+Localized.swift */; }; + 0CB675370A06DEC6F23608C4 /* AddressSpec+ElementFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4D27EC1A9207F327B1BF20 /* AddressSpec+ElementFactory.swift */; }; + 0D8AE1CEFBE7FA004A3DD62D /* CheckboxButtonSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D917F78E89D67691EF760D14 /* CheckboxButtonSnapshotTests.swift */; }; + 0FBBB5C7FD2DA9A11E7FE2BC /* FloatingPlaceholderTextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD764D16A365FD46700FB25 /* FloatingPlaceholderTextFieldView.swift */; }; + 11BD8DFB36FACB6966D0236F /* String+CountryEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73D88DFAB03D663217399D0 /* String+CountryEmoji.swift */; }; + 11C99D83A1DA88996A45BA47 /* DynamicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE775CAF20488D81C9167B4 /* DynamicImageView.swift */; }; + 1424B1529E24582122F86149 /* PhoneNumberElementSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7796F7211034806DA24D1710 /* PhoneNumberElementSnapshotTests.swift */; }; + 159D7B9A2960A1CD7E661191 /* UIStackView+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5855EA2F4F38847E3E47935D /* UIStackView+StripeUICore.swift */; }; + 163A77E9E4C40AFFA5226E23 /* TestFieldElement+AccountFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83462E27CEA3580E1BD3E2CD /* TestFieldElement+AccountFactoryTest.swift */; }; + 1EB16D8F60923EE9C890CE43 /* STPEmailAddressValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE233EED40F9D3FADDFE5951 /* STPEmailAddressValidator.swift */; }; + 225B20CEF547BB1F6C6D447E /* BSBNumberProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3DD58BEF337017E7286459 /* BSBNumberProvider.swift */; }; + 2AA023A5031CFF0E56C7A14E /* DateFieldElementTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B0431535F20802E386624B /* DateFieldElementTest.swift */; }; + 2ADEF1482E62A173A0C7F7AD /* au_becs_bsb.json in Resources */ = {isa = PBXBuildFile; fileRef = 6E66B0CF81D4F8EDBB642C9F /* au_becs_bsb.json */; }; + 2F1D03471202E25FD7682148 /* Locale+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36342ED77935BE9A32B082EC /* Locale+StripeUICore.swift */; }; + 2FD444AC5FC064EFCF6259ED /* STPVPANumberValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53AA9FCEF2640C493D140033 /* STPVPANumberValidatorTest.swift */; }; + 30972A45F8A32DEEC17DA4F6 /* UITraitCollection+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA22461E52111D9A4E8B133 /* UITraitCollection+StripeUICore.swift */; }; + 32971AFF5D0DF9B28E9C464C /* SectionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C0A3DE4BCF8DA03AD17144 /* SectionContainerView.swift */; }; + 32D69CBB3CE780A29AA553AB /* BSBNumberProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0408AC0298D342DADD1F84A5 /* BSBNumberProviderTest.swift */; }; + 336B882978CA47EE46260774 /* SectionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BB17B284B6D063AF0329DAD /* SectionElement.swift */; }; + 343DD093A6095DEAA06D245E /* InputFormColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB5A05E653BFD01FF65E193C /* InputFormColors.swift */; }; + 36376E12AE7ABA21FFC474E6 /* Events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4844F0B5436706225DE5A176 /* Events.swift */; }; + 377058C2363FA0348AFBD32E /* AddressSectionElement+DummyAddressLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B66D666BB3EA733B92A60AA /* AddressSectionElement+DummyAddressLine.swift */; }; + 39E61B5E6A88E5AF1922EE62 /* TextFieldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34178B51599FDCB0D0D7475A /* TextFieldFormatter.swift */; }; + 4755A24604B396D5A25058CD /* StripeUICore.h in Headers */ = {isa = PBXBuildFile; fileRef = CB8A9F7B4B2E8AA5A7E4FE98 /* StripeUICore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 48D3B7C2983A8A25C6599119 /* OneTimeCodeTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2BFFB32C50AB4EECA90326 /* OneTimeCodeTextField.swift */; }; + 4A5EADAF2F6514299BA4B8D8 /* FormElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1FE2B6BCD6FC77117A2521 /* FormElement.swift */; }; + 4B05CF7F485F0AED498DAE49 /* OneTimeCodeTextField-TextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 815DB8DE48AE3CE1609C8316 /* OneTimeCodeTextField-TextStorage.swift */; }; + 4B414F0A0E46D914C89B3741 /* StaticElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 583B93BE0152CDF7383A37E7 /* StaticElement.swift */; }; + 4C519A87445AB16A55FE2408 /* PhoneNumberElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794D2AA5365F9DE155083927 /* PhoneNumberElementTests.swift */; }; + 4C98A44C61BC71CABF8A9BF2 /* StripeCoreTestUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40DF1A7FC5D84F937042D172 /* StripeCoreTestUtils.framework */; }; + 4CD207111219BF250A400ACC /* UIWindow+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59215CEEBA0D56FF41C3E412 /* UIWindow+StripeUICore.swift */; }; + 504FA2DE5FE66FDE90842019 /* STPLocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4F4CE2A0B16DA57DB249227 /* STPLocalizedString.swift */; }; + 57C288DFD2CC2CFC216E47CC /* PickerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0355EA21D04DDC29E620CEA /* PickerTextField.swift */; }; + 5936629C4665BC698C3458B1 /* TextFieldElementConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8367A676E35A663E320E1B37 /* TextFieldElementConfiguration.swift */; }; + 5A9B21FE5A6941713087B94B /* SectionElement+MultiElementRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6737026DF19C8D10AE8114A /* SectionElement+MultiElementRow.swift */; }; + 62601F856C41CFA1C7A8B18F /* UIView+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBBC76FD31F07EC67C52075 /* UIView+StripeUICore.swift */; }; + 63632799CD2134991E0EA510 /* iOSSnapshotTestCase in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = 9B701A244243959A191FF16F /* iOSSnapshotTestCase */; }; + 64E61A5E0A705F1C4582381A /* PhoneNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7E56A83CFA4BA8385638 /* PhoneNumberTests.swift */; }; + 65B9A839BD4AAC315231B421 /* TextFieldElement+AccountFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69C3FF914611057972ABA41 /* TextFieldElement+AccountFactory.swift */; }; + 6606DC43D230ADD183AEF5DA /* RegionCodeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA00332BBDCE27F6F5A615C4 /* RegionCodeProvider.swift */; }; + 67216EB4E004BDB1D2E49BD4 /* IDNumberTextFieldConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92623B785C43F351D8A944D1 /* IDNumberTextFieldConfiguration.swift */; }; + 67FCE4493235656689E915F6 /* UIBarButtonItem+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321D6CCBA2AA9162A96080C /* UIBarButtonItem+StripeUICore.swift */; }; + 68F7D5EEB894A68DDC184ADA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20209CDCC3856CE548DA4D25 /* Image.swift */; }; + 69B082B15479DCC4560E3D92 /* UIColor+StripeUICoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48C9A13C4663EA8ACA6EA2E5 /* UIColor+StripeUICoreTests.swift */; }; + 6CB223E48029E6BA6ED48041 /* LinkOpeningTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80F72A0A72DDC5F246ED51F /* LinkOpeningTextView.swift */; }; + 710D7FB87C39EEB0DB1F3E75 /* STPBlikCodeValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99B2D80F3FBAC287CBF86B0 /* STPBlikCodeValidator.swift */; }; + 717D176DAF084461C18F2A09 /* StripeUICore.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69B1E4AE618E237C0EE5036F /* StripeUICore.xcassets */; }; + 7272A43410D4BA1365D71E70 /* UIButton+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A60A0D2417D3A70D500EE30 /* UIButton+StripeUICore.swift */; }; + 74F65B6435E46B0FC8386FBB /* BSBNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CCBFC1830139737F2E947F1 /* BSBNumberTests.swift */; }; + 778BACD1A29BEDEE21ED3FBE /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8678CFB3968AE5232932C461 /* ActivityIndicator.swift */; }; + 7B90479C19C30407FC21B228 /* ImageMaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A681C4DEAE7B35B4DC0BD0FB /* ImageMaker.swift */; }; + 7E0A56FBC86BCBB58D0440AE /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8DC1E33CDF3B3FE549A7210 /* StripeCore.framework */; }; + 80B0519BC9CC21D9B650FC88 /* UISpringTimingParameters+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A81696ECEEF728F25202B6 /* UISpringTimingParameters+StripeUICore.swift */; }; + 824858D45F9D952BBDF822E2 /* CALayer+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8B1E38153B0C5ED6CD459C /* CALayer+StripeUICore.swift */; }; + 86427678E119E4AD22410E30 /* PhoneNumberElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D0478259F9F7A04A8CF43BB /* PhoneNumberElement.swift */; }; + 88F53AB8F31B1DA2187E5740 /* IDNumberTextFieldConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8995A59E3BC6D26B8030C874 /* IDNumberTextFieldConfigurationTest.swift */; }; + 8B8D3BD090415EFF9948D2C2 /* ContainerElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4893E4B43FEFCE920D432F0C /* ContainerElement.swift */; }; + 900EFF5918D96B9716CFB673 /* UIFont+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43943E8FF7CD279F8D8605D3 /* UIFont+StripeUICore.swift */; }; + 93CF3CE90B520B4A25208E95 /* SectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3C9C0D2302B8E7F421EFBD /* SectionView.swift */; }; + 976AFE0D02FE65BFA1757E4E /* NSAttributedString+StripeUICoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982AA7FCDBD1D5264A5FB040 /* NSAttributedString+StripeUICoreTests.swift */; }; + 986924FDA1EEF4146CD81B50 /* TextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A312A55A629CED2C6404F1 /* TextFieldView.swift */; }; + 993E51173490AAE5D7AF4C4E /* TextFieldElementTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9131CB11D2A0898B16D44C4E /* TextFieldElementTest.swift */; }; + 9DBFEB7045692EE931CE014D /* Locale+StripeUICoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2749BB93AD1B5A9FB3B9B97 /* Locale+StripeUICoreTests.swift */; }; + 9DEAD347B741A53E2F1764B4 /* TextFieldElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = A004788FA286E5ED22334238 /* TextFieldElement.swift */; }; + 9E308BC63E9FB185619E5859 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4621DA31A1A5FC51F76E562 /* XCTest.framework */; }; + A21277FDCDD0C6BFB73A4B51 /* SectionElementTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62539E4F0904670C008DB18E /* SectionElementTest.swift */; }; + A29B5AC2F03116E2F48970EE /* TextOrDropdownElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD864C601BFB09A851715FA /* TextOrDropdownElement.swift */; }; + A3F0D42EB3A3FF2299F2F473 /* BankRoutingNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312236317D0DA79F2D32CBE2 /* BankRoutingNumber.swift */; }; + A5C379C7D1EEF497FD845306 /* STPEmailAddressValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312E77021827300B2504F016 /* STPEmailAddressValidatorTest.swift */; }; + A5E59185A9708613676988C6 /* DateFieldElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B274D6A71A11DA7B98D502AA /* DateFieldElement.swift */; }; + AA8F938C3A8B7BC7F11B5048 /* Enums+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EECCC1035F00C122B2B1ED /* Enums+CustomStringConvertible.swift */; }; + B106FCB3D7C3DE7C40F0AE5C /* AddressSectionElementTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55F7532752871E779F47278 /* AddressSectionElementTest.swift */; }; + B1B177526E04EE65A9D1C64A /* DynamicHeightContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9E7CFED5747279A5976D7BA /* DynamicHeightContainerView.swift */; }; + B6107E5E2D6E116417D22DD9 /* StripeUICoreBundleLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E68363D1C6BB6468AC1DDA4C /* StripeUICoreBundleLocator.swift */; }; + BC40443B2A2F7130351589A7 /* NSAttributedString+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B8AFA3891DA214D9FBEF650 /* NSAttributedString+StripeUICore.swift */; }; + BE42104922DCA4DCB3919DD4 /* AddressSectionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5CCD5D11A49D48D8D21C62 /* AddressSectionElement.swift */; }; + BEB4F0E3B6218CA3E5DE95F9 /* STPBlikCodeValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2055BFB32FE9CF519DC25C8F /* STPBlikCodeValidatorTest.swift */; }; + C1CA6209591EDDBBE019FF22 /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558DF5BAEF6E23E0C0F2CFF1 /* FormView.swift */; }; + C23C78D87D8E6682F31345CB /* DropdownFieldElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAD4C8EE686B0F9E86DBC8D /* DropdownFieldElement.swift */; }; + C3D6D899B671398717F22520 /* CheckboxElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1EF8010A475D99C7190FF9 /* CheckboxElement.swift */; }; + C44A57646A325EE26B75E6BF /* CheckboxButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E799C8CB484FE83CB87E0 /* CheckboxButton.swift */; }; + C6B11F4F219F7ED04321A33F /* DateFieldElementSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48E2652D09C1A4EBBDE2FB61 /* DateFieldElementSnapshotTest.swift */; }; + CF9D4CC40A7008DD1E8136A3 /* DoneButtonToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19F61D5356203ED977ED42E /* DoneButtonToolbar.swift */; }; + D01976C07EB39B2BED64CCAC /* ButtonSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5E2F9A5B1A3E8E7B765E4F /* ButtonSnapshotTest.swift */; }; + D083BAAF86707F9865AF2AF4 /* String+RegionCodeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA1688A2CE2037846F8E3937 /* String+RegionCodeProvider.swift */; }; + D47D77A0B82DC6AE15E0A74E /* UIViewController+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12973742CA1AA39D3B84F072 /* UIViewController+StripeUICore.swift */; }; + D6AEB6D5567AAD1B44E4AF70 /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 868B2E8EDC242DB5AFFB3D0C /* StripeUICore.framework */; }; + D6D8BCCF86C964B40D2FA58E /* UIKeyboardType+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE651DD3E5873538C7CE743 /* UIKeyboardType+StripeUICore.swift */; }; + D7DB5C5724CD47E33245B25A /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB02AB36074791D39F0CDC1 /* Element.swift */; }; + D912BF580DBAB7416040B637 /* localized_address_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 4E25905FCD05DF8B72888AAF /* localized_address_data.json */; }; + DA6550F1FFA1376DB656D6E0 /* NSDirectionalEdgeInsets+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB52FF6CAC981EFA60A81AFF /* NSDirectionalEdgeInsets+StripeUICore.swift */; }; + E40999CDEDEA23451CA89707 /* TextFieldElement+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFB80CA75F00847E4D74F821 /* TextFieldElement+Validation.swift */; }; + E94BA0179485AED17D412865 /* AddressSpecProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B844EED0578B990F4772CD01 /* AddressSpecProvider.swift */; }; + E9CA12DAB591AC834CE9539A /* AddressSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC72344B4B313CE07A8AA33 /* AddressSpec.swift */; }; + ECCA9BD118D763DBF658E5FD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 71C8163AEB97D2FF8BB3A1C8 /* Localizable.strings */; }; + EDDE1C83333AB1A1F2BD0F3E /* StackViewWithSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72394C7783071B1C6ED82A48 /* StackViewWithSeparator.swift */; }; + EE28852FFCF42A8C47098051 /* TextFieldFormatterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B32FABC26C892C74FD444E88 /* TextFieldFormatterTest.swift */; }; + F0EB247FEFF4600CD44B8261 /* DropdownFieldElementTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3B67DE90B0C1D71257D333 /* DropdownFieldElementTest.swift */; }; + F86769C5CFD9AD3732127951 /* DropdownFieldElement+AddressFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374D0BE980D58B75FA04DE66 /* DropdownFieldElement+AddressFactory.swift */; }; + F901303E0B78F2D8E4C8A2F1 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF05370E4825D3225D9A6910 /* PhoneNumber.swift */; }; + FAD790056C7A9E645A6B2C74 /* ElementsUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC8D5E9700C8471D225C22 /* ElementsUI.swift */; }; + FB33F2F446570394AABB7EC7 /* CompatibleColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E657F1ABE46BA5C0692D9D41 /* CompatibleColor.swift */; }; + FC1F8C4DC70C8212B507AE7E /* AddressSectionElementSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7836BA51814D0841B688E1 /* AddressSectionElementSnapshotTest.swift */; }; + FC48FCDC5FD43E5E8AFC32D2 /* TextFieldElement+Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D4BD46908791D2E4150C4DC /* TextFieldElement+Factory.swift */; }; + FDF52A43A01CD72D4B5A2CA9 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB48FCA3B2447A5F4CCEC69 /* Button.swift */; }; + FF4E844383E5A5FD5C099B41 /* DropdownFieldElementSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62575FAAAAEFF31406C9B417 /* DropdownFieldElementSnapshotTest.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 980541F3E23EAD7E20DBCA47 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A39BBBD15F0F6B54725E52AF /* Project object */; + proxyType = 1; + remoteGlobalIDString = DE3C3F3D3BB67DD660A44B1E; + remoteInfo = StripeUICore; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 7823EBE0BD66DC6070DC1530 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 8B58BADB5D787E66A0A50257 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 00455FBF8F3D7C9B0E65DA54 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = ""; }; + 011872F74559CB0D0D61170C /* sk-SK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sk-SK"; path = "sk-SK.lproj/Localizable.strings"; sourceTree = ""; }; + 02DA3E661669718BD61C702D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 0408AC0298D342DADD1F84A5 /* BSBNumberProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BSBNumberProviderTest.swift; sourceTree = ""; }; + 08B0431535F20802E386624B /* DateFieldElementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFieldElementTest.swift; sourceTree = ""; }; + 0D558F65E4C23971C94BAD3E /* String+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; + 0ED24D748AEB8B1E0BA1FBAD /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + 11C37DE7B064DC9E3F7E55CD /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; + 12973742CA1AA39D3B84F072 /* UIViewController+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+StripeUICore.swift"; sourceTree = ""; }; + 18C5B42A6D5AF223F0CFB23F /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 1A50D68D24D1DE0459ED9529 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 20209CDCC3856CE548DA4D25 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; }; + 2055BFB32FE9CF519DC25C8F /* STPBlikCodeValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBlikCodeValidatorTest.swift; sourceTree = ""; }; + 263B7E56A83CFA4BA8385638 /* PhoneNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberTests.swift; sourceTree = ""; }; + 2AAE278AD27DB71B88C97B1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 2BAA056C8170A4B0E8C763AD /* sl-SI */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sl-SI"; path = "sl-SI.lproj/Localizable.strings"; sourceTree = ""; }; + 2D9B5640C88DD94D17666E0E /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 312236317D0DA79F2D32CBE2 /* BankRoutingNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankRoutingNumber.swift; sourceTree = ""; }; + 312E77021827300B2504F016 /* STPEmailAddressValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEmailAddressValidatorTest.swift; sourceTree = ""; }; + 34178B51599FDCB0D0D7475A /* TextFieldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFormatter.swift; sourceTree = ""; }; + 34EC2F4C025AD1B1DD44CF78 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 36342ED77935BE9A32B082EC /* Locale+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeUICore.swift"; sourceTree = ""; }; + 374D0BE980D58B75FA04DE66 /* DropdownFieldElement+AddressFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DropdownFieldElement+AddressFactory.swift"; sourceTree = ""; }; + 3A99D0F01D29E302C52217FE /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; + 3B8AFA3891DA214D9FBEF650 /* NSAttributedString+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+StripeUICore.swift"; sourceTree = ""; }; + 40DF1A7FC5D84F937042D172 /* StripeCoreTestUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCoreTestUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 40E0822DE6CFA0D3CCD5D513 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 43943E8FF7CD279F8D8605D3 /* UIFont+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+StripeUICore.swift"; sourceTree = ""; }; + 4844F0B5436706225DE5A176 /* Events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Events.swift; sourceTree = ""; }; + 4893E4B43FEFCE920D432F0C /* ContainerElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerElement.swift; sourceTree = ""; }; + 48C9A13C4663EA8ACA6EA2E5 /* UIColor+StripeUICoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+StripeUICoreTests.swift"; sourceTree = ""; }; + 48E2652D09C1A4EBBDE2FB61 /* DateFieldElementSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFieldElementSnapshotTest.swift; sourceTree = ""; }; + 4B66D666BB3EA733B92A60AA /* AddressSectionElement+DummyAddressLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressSectionElement+DummyAddressLine.swift"; sourceTree = ""; }; + 4CBAE2B07C5575F19470464D /* StripeiOS-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Release.xcconfig"; sourceTree = ""; }; + 4D4BD46908791D2E4150C4DC /* TextFieldElement+Factory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+Factory.swift"; sourceTree = ""; }; + 4E25905FCD05DF8B72888AAF /* localized_address_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = localized_address_data.json; sourceTree = ""; }; + 4EE651DD3E5873538C7CE743 /* UIKeyboardType+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKeyboardType+StripeUICore.swift"; sourceTree = ""; }; + 4F37C42EA28BA31EC5CCDC7C /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/Localizable.strings"; sourceTree = ""; }; + 4FD764D16A365FD46700FB25 /* FloatingPlaceholderTextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPlaceholderTextFieldView.swift; sourceTree = ""; }; + 51C4AEB27F9734178E20836A /* UIColor+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+StripeUICore.swift"; sourceTree = ""; }; + 5321D6CCBA2AA9162A96080C /* UIBarButtonItem+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+StripeUICore.swift"; sourceTree = ""; }; + 53900894BD0E2FF59029D2B6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 53AA9FCEF2640C493D140033 /* STPVPANumberValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPVPANumberValidatorTest.swift; sourceTree = ""; }; + 558DF5BAEF6E23E0C0F2CFF1 /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; + 583B93BE0152CDF7383A37E7 /* StaticElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticElement.swift; sourceTree = ""; }; + 5855EA2F4F38847E3E47935D /* UIStackView+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+StripeUICore.swift"; sourceTree = ""; }; + 59215CEEBA0D56FF41C3E412 /* UIWindow+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindow+StripeUICore.swift"; sourceTree = ""; }; + 5B5CCD5D11A49D48D8D21C62 /* AddressSectionElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSectionElement.swift; sourceTree = ""; }; + 5BAD4C8EE686B0F9E86DBC8D /* DropdownFieldElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownFieldElement.swift; sourceTree = ""; }; + 62539E4F0904670C008DB18E /* SectionElementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionElementTest.swift; sourceTree = ""; }; + 62575FAAAAEFF31406C9B417 /* DropdownFieldElementSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownFieldElementSnapshotTest.swift; sourceTree = ""; }; + 64EECCC1035F00C122B2B1ED /* Enums+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Enums+CustomStringConvertible.swift"; sourceTree = ""; }; + 67C454346BE566F9F689543B /* Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Debug.xcconfig"; sourceTree = ""; }; + 68F3046A526D3A4CE402FB83 /* PickerFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerFieldView.swift; sourceTree = ""; }; + 699ED5466892D95ADC151B64 /* ro-RO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ro-RO"; path = "ro-RO.lproj/Localizable.strings"; sourceTree = ""; }; + 69B1E4AE618E237C0EE5036F /* StripeUICore.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = StripeUICore.xcassets; sourceTree = ""; }; + 6A4D27EC1A9207F327B1BF20 /* AddressSpec+ElementFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressSpec+ElementFactory.swift"; sourceTree = ""; }; + 6E66B0CF81D4F8EDBB642C9F /* au_becs_bsb.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = au_becs_bsb.json; sourceTree = ""; }; + 6F6E799C8CB484FE83CB87E0 /* CheckboxButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxButton.swift; sourceTree = ""; }; + 70B4BE2ACC46DF88949121CD /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = ""; }; + 71A81696ECEEF728F25202B6 /* UISpringTimingParameters+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISpringTimingParameters+StripeUICore.swift"; sourceTree = ""; }; + 71DCDDEE36E2CD98E4A03752 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 72394C7783071B1C6ED82A48 /* StackViewWithSeparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackViewWithSeparator.swift; sourceTree = ""; }; + 72EB2DE23B3740D8DA3081E5 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 74EDF4CC65F409F55E14EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 7796F7211034806DA24D1710 /* PhoneNumberElementSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberElementSnapshotTests.swift; sourceTree = ""; }; + 794D2AA5365F9DE155083927 /* PhoneNumberElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberElementTests.swift; sourceTree = ""; }; + 7B899349F7EF770D8F19509D /* ca-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ca-ES"; path = "ca-ES.lproj/Localizable.strings"; sourceTree = ""; }; + 7B8B1E38153B0C5ED6CD459C /* CALayer+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+StripeUICore.swift"; sourceTree = ""; }; + 7BB17B284B6D063AF0329DAD /* SectionElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionElement.swift; sourceTree = ""; }; + 7D4E60C42E09A23ACD60BCD8 /* bg-BG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "bg-BG"; path = "bg-BG.lproj/Localizable.strings"; sourceTree = ""; }; + 7DC72344B4B313CE07A8AA33 /* AddressSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSpec.swift; sourceTree = ""; }; + 7EB48FCA3B2447A5F4CCEC69 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + 815DB8DE48AE3CE1609C8316 /* OneTimeCodeTextField-TextStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OneTimeCodeTextField-TextStorage.swift"; sourceTree = ""; }; + 81A4AE07CB9049B6D8C8D7D2 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 8278020A829AE8C5CB8B6A9C /* ms-MY */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ms-MY"; path = "ms-MY.lproj/Localizable.strings"; sourceTree = ""; }; + 83462E27CEA3580E1BD3E2CD /* TestFieldElement+AccountFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestFieldElement+AccountFactoryTest.swift"; sourceTree = ""; }; + 8367A676E35A663E320E1B37 /* TextFieldElementConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldElementConfiguration.swift; sourceTree = ""; }; + 83BC5E25F42045563F4A660B /* AddressSpecProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSpecProviderTest.swift; sourceTree = ""; }; + 848196B6CCE964A735BFCD46 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 85AC8D5E9700C8471D225C22 /* ElementsUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementsUI.swift; sourceTree = ""; }; + 8678CFB3968AE5232932C461 /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; + 868B2E8EDC242DB5AFFB3D0C /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8995A59E3BC6D26B8030C874 /* IDNumberTextFieldConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDNumberTextFieldConfigurationTest.swift; sourceTree = ""; }; + 8A60A0D2417D3A70D500EE30 /* UIButton+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+StripeUICore.swift"; sourceTree = ""; }; + 8D0478259F9F7A04A8CF43BB /* PhoneNumberElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberElement.swift; sourceTree = ""; }; + 8F1EF8010A475D99C7190FF9 /* CheckboxElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxElement.swift; sourceTree = ""; }; + 9131CB11D2A0898B16D44C4E /* TextFieldElementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldElementTest.swift; sourceTree = ""; }; + 92623B785C43F351D8A944D1 /* IDNumberTextFieldConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IDNumberTextFieldConfiguration.swift; sourceTree = ""; }; + 94FB3B96E7884F5B5C1C3EA8 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Localizable.strings; sourceTree = ""; }; + 9559041BED6315C71F796A5F /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; + 982AA7FCDBD1D5264A5FB040 /* NSAttributedString+StripeUICoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+StripeUICoreTests.swift"; sourceTree = ""; }; + 9CCBFC1830139737F2E947F1 /* BSBNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BSBNumberTests.swift; sourceTree = ""; }; + 9E3254388F5DAB8E8C76AE04 /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = ""; }; + A004788FA286E5ED22334238 /* TextFieldElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldElement.swift; sourceTree = ""; }; + A032BB17A6FA85EE4E3D00AE /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + A1A02A2319937F5033495984 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + A360D5AE016620B560FB8A77 /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = ""; }; + A4F4CE2A0B16DA57DB249227 /* STPLocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPLocalizedString.swift; sourceTree = ""; }; + A681C4DEAE7B35B4DC0BD0FB /* ImageMaker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageMaker.swift; sourceTree = ""; }; + A73D88DFAB03D663217399D0 /* String+CountryEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+CountryEmoji.swift"; sourceTree = ""; }; + AB2C786B198F91AE124C790A /* lt-LT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lt-LT"; path = "lt-LT.lproj/Localizable.strings"; sourceTree = ""; }; + AC2BFFB32C50AB4EECA90326 /* OneTimeCodeTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneTimeCodeTextField.swift; sourceTree = ""; }; + AE4AB8A1DB70CDE06D9887CF /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + B081CD063903B2FDBE327A15 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = ""; }; + B2161853A89C273B745B8A60 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = ""; }; + B274D6A71A11DA7B98D502AA /* DateFieldElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFieldElement.swift; sourceTree = ""; }; + B32FABC26C892C74FD444E88 /* TextFieldFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFormatterTest.swift; sourceTree = ""; }; + B6737026DF19C8D10AE8114A /* SectionElement+MultiElementRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SectionElement+MultiElementRow.swift"; sourceTree = ""; }; + B844EED0578B990F4772CD01 /* AddressSpecProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSpecProvider.swift; sourceTree = ""; }; + B84B32F86F684EA5233DDED5 /* nn-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nn-NO"; path = "nn-NO.lproj/Localizable.strings"; sourceTree = ""; }; + B871CED2EB8A1F7EEC0FE087 /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = ""; }; + B8DC1E33CDF3B3FE549A7210 /* StripeCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B9CAB799E5645867F2400F0F /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; + B9E7CFED5747279A5976D7BA /* DynamicHeightContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicHeightContainerView.swift; sourceTree = ""; }; + BA1688A2CE2037846F8E3937 /* String+RegionCodeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+RegionCodeProvider.swift"; sourceTree = ""; }; + BA366F6105D04C9136240341 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; + BB7836BA51814D0841B688E1 /* AddressSectionElementSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSectionElementSnapshotTest.swift; sourceTree = ""; }; + BD52DCDA490D452AAA4B5E44 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + C0355EA21D04DDC29E620CEA /* PickerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerTextField.swift; sourceTree = ""; }; + C19F61D5356203ED977ED42E /* DoneButtonToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoneButtonToolbar.swift; sourceTree = ""; }; + C55F7532752871E779F47278 /* AddressSectionElementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSectionElementTest.swift; sourceTree = ""; }; + C85C015B47A8EBF87E5F662E /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = ""; }; + CB52FF6CAC981EFA60A81AFF /* NSDirectionalEdgeInsets+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDirectionalEdgeInsets+StripeUICore.swift"; sourceTree = ""; }; + CB8A9F7B4B2E8AA5A7E4FE98 /* StripeUICore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StripeUICore.h; sourceTree = ""; }; + CC3DD58BEF337017E7286459 /* BSBNumberProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BSBNumberProvider.swift; sourceTree = ""; }; + CDE775CAF20488D81C9167B4 /* DynamicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicImageView.swift; sourceTree = ""; }; + CF3B67DE90B0C1D71257D333 /* DropdownFieldElementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownFieldElementTest.swift; sourceTree = ""; }; + CF3C9C0D2302B8E7F421EFBD /* SectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionView.swift; sourceTree = ""; }; + CFB80CA75F00847E4D74F821 /* TextFieldElement+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+Validation.swift"; sourceTree = ""; }; + D1FB364DF60C1DEE0DA8EE75 /* Project-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Project-Release.xcconfig"; sourceTree = ""; }; + D2749BB93AD1B5A9FB3B9B97 /* Locale+StripeUICoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+StripeUICoreTests.swift"; sourceTree = ""; }; + D4621DA31A1A5FC51F76E562 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D59C1B7B69C92DFC9A25DAB2 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; + D5A312A55A629CED2C6404F1 /* TextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldView.swift; sourceTree = ""; }; + D634B04FC897DFB74B81DED6 /* STPVPANumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPVPANumberValidator.swift; sourceTree = ""; }; + D69C3FF914611057972ABA41 /* TextFieldElement+AccountFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+AccountFactory.swift"; sourceTree = ""; }; + D917F78E89D67691EF760D14 /* CheckboxButtonSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxButtonSnapshotTests.swift; sourceTree = ""; }; + DE233EED40F9D3FADDFE5951 /* STPEmailAddressValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPEmailAddressValidator.swift; sourceTree = ""; }; + E0C8DFA9A617506071C59815 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = ""; }; + E242FECC90FE2644CF99692D /* StripeiOS Tests-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Release.xcconfig"; sourceTree = ""; }; + E395C593AC970A8C79A8109C /* StripeiOS-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS-Debug.xcconfig"; sourceTree = ""; }; + E522C47E0874F612E330DA38 /* StripeUICoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeUICoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E657F1ABE46BA5C0692D9D41 /* CompatibleColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibleColor.swift; sourceTree = ""; }; + E68363D1C6BB6468AC1DDA4C /* StripeUICoreBundleLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripeUICoreBundleLocator.swift; sourceTree = ""; }; + E80F72A0A72DDC5F246ED51F /* LinkOpeningTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkOpeningTextView.swift; sourceTree = ""; }; + E99B2D80F3FBAC287CBF86B0 /* STPBlikCodeValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPBlikCodeValidator.swift; sourceTree = ""; }; + EA00332BBDCE27F6F5A615C4 /* RegionCodeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionCodeProvider.swift; sourceTree = ""; }; + EAB02AB36074791D39F0CDC1 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = ""; }; + EB1FE2B6BCD6FC77117A2521 /* FormElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormElement.swift; sourceTree = ""; }; + EB5E2F9A5B1A3E8E7B765E4F /* ButtonSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonSnapshotTest.swift; sourceTree = ""; }; + EB942257C8B6B357AD0F6FC9 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + EDD7C1AAB9D26E00526AFB26 /* TextFieldElement+AddressFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+AddressFactory.swift"; sourceTree = ""; }; + EDD864C601BFB09A851715FA /* TextOrDropdownElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextOrDropdownElement.swift; sourceTree = ""; }; + F1C0A3DE4BCF8DA03AD17144 /* SectionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionContainerView.swift; sourceTree = ""; }; + FAA22461E52111D9A4E8B133 /* UITraitCollection+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITraitCollection+StripeUICore.swift"; sourceTree = ""; }; + FB5A05E653BFD01FF65E193C /* InputFormColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputFormColors.swift; sourceTree = ""; }; + FDBBC76FD31F07EC67C52075 /* UIView+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+StripeUICore.swift"; sourceTree = ""; }; + FE57C3D398C94604889D9F61 /* TextFieldElement+AddressFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+AddressFactoryTest.swift"; sourceTree = ""; }; + FF05370E4825D3225D9A6910 /* PhoneNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0D674E67240745BB10E9C307 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E0A56FBC86BCBB58D0440AE /* StripeCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B361F19E111D84FD84927757 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9E308BC63E9FB185619E5859 /* XCTest.framework in Frameworks */, + 4C98A44C61BC71CABF8A9BF2 /* StripeCoreTestUtils.framework in Frameworks */, + D6AEB6D5567AAD1B44E4AF70 /* StripeUICore.framework in Frameworks */, + 63632799CD2134991E0EA510 /* iOSSnapshotTestCase in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0737F3733178B902986E6128 /* Form */ = { + isa = PBXGroup; + children = ( + EB1FE2B6BCD6FC77117A2521 /* FormElement.swift */, + 558DF5BAEF6E23E0C0F2CFF1 /* FormView.swift */, + ); + path = Form; + sourceTree = ""; + }; + 14BA0999EF98D5C19F7A0018 /* Elements */ = { + isa = PBXGroup; + children = ( + C55F7532752871E779F47278 /* AddressSectionElementTest.swift */, + 83BC5E25F42045563F4A660B /* AddressSpecProviderTest.swift */, + 0408AC0298D342DADD1F84A5 /* BSBNumberProviderTest.swift */, + 08B0431535F20802E386624B /* DateFieldElementTest.swift */, + CF3B67DE90B0C1D71257D333 /* DropdownFieldElementTest.swift */, + 8995A59E3BC6D26B8030C874 /* IDNumberTextFieldConfigurationTest.swift */, + 794D2AA5365F9DE155083927 /* PhoneNumberElementTests.swift */, + 62539E4F0904670C008DB18E /* SectionElementTest.swift */, + 83462E27CEA3580E1BD3E2CD /* TestFieldElement+AccountFactoryTest.swift */, + FE57C3D398C94604889D9F61 /* TextFieldElement+AddressFactoryTest.swift */, + 9131CB11D2A0898B16D44C4E /* TextFieldElementTest.swift */, + B32FABC26C892C74FD444E88 /* TextFieldFormatterTest.swift */, + ); + path = Elements; + sourceTree = ""; + }; + 17217EBC2C89A90814900B85 /* Checkbox */ = { + isa = PBXGroup; + children = ( + 6F6E799C8CB484FE83CB87E0 /* CheckboxButton.swift */, + 8F1EF8010A475D99C7190FF9 /* CheckboxElement.swift */, + ); + path = Checkbox; + sourceTree = ""; + }; + 254AA347B50AF1B8370A43DF /* Elements */ = { + isa = PBXGroup; + children = ( + 17217EBC2C89A90814900B85 /* Checkbox */, + F08533861A4AC1BB01315CCE /* Factories */, + 0737F3733178B902986E6128 /* Form */, + CF2B7051099FA08B7F0C446F /* PhoneNumber */, + B15F8A0D7BC00200896F00FD /* PickerField */, + B3A1C7E17AE91C2352DE42DB /* Section */, + 82B232DB32C55ABC81F03EEA /* TextField */, + 4893E4B43FEFCE920D432F0C /* ContainerElement.swift */, + B274D6A71A11DA7B98D502AA /* DateFieldElement.swift */, + 5BAD4C8EE686B0F9E86DBC8D /* DropdownFieldElement.swift */, + EAB02AB36074791D39F0CDC1 /* Element.swift */, + 85AC8D5E9700C8471D225C22 /* ElementsUI.swift */, + 583B93BE0152CDF7383A37E7 /* StaticElement.swift */, + EDD864C601BFB09A851715FA /* TextOrDropdownElement.swift */, + ); + path = Elements; + sourceTree = ""; + }; + 361DA94D6B150221B8694910 /* Categories */ = { + isa = PBXGroup; + children = ( + D2749BB93AD1B5A9FB3B9B97 /* Locale+StripeUICoreTests.swift */, + 982AA7FCDBD1D5264A5FB040 /* NSAttributedString+StripeUICoreTests.swift */, + 48C9A13C4663EA8ACA6EA2E5 /* UIColor+StripeUICoreTests.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 36DAB16F61F10767C1FAA6CB /* Resources */ = { + isa = PBXGroup; + children = ( + A33F87F760EA97E2416B5E3F /* JSON */, + 4EE613673FA66E06A8963989 /* Localizations */, + 69B1E4AE618E237C0EE5036F /* StripeUICore.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + 3F7B82C31FE216F26BAA22BD /* Views */ = { + isa = PBXGroup; + children = ( + C19F61D5356203ED977ED42E /* DoneButtonToolbar.swift */, + B9E7CFED5747279A5976D7BA /* DynamicHeightContainerView.swift */, + CDE775CAF20488D81C9167B4 /* DynamicImageView.swift */, + E80F72A0A72DDC5F246ED51F /* LinkOpeningTextView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 47D21B13FCCDA3A340E0E45B /* Source */ = { + isa = PBXGroup; + children = ( + 4FBEFD693426C6259B6BF7D6 /* Categories */, + 91389CA21E7E02309F469572 /* Controls */, + 254AA347B50AF1B8370A43DF /* Elements */, + 6409D732BE9F9BD02673B79B /* Helpers */, + 7C026500149D281CB84ECA33 /* Validators */, + 3F7B82C31FE216F26BAA22BD /* Views */, + 4844F0B5436706225DE5A176 /* Events.swift */, + 20209CDCC3856CE548DA4D25 /* Image.swift */, + ); + path = Source; + sourceTree = ""; + }; + 4EE613673FA66E06A8963989 /* Localizations */ = { + isa = PBXGroup; + children = ( + 71C8163AEB97D2FF8BB3A1C8 /* Localizable.strings */, + ); + path = Localizations; + sourceTree = ""; + }; + 4FBEFD693426C6259B6BF7D6 /* Categories */ = { + isa = PBXGroup; + children = ( + 7B8B1E38153B0C5ED6CD459C /* CALayer+StripeUICore.swift */, + 64EECCC1035F00C122B2B1ED /* Enums+CustomStringConvertible.swift */, + 36342ED77935BE9A32B082EC /* Locale+StripeUICore.swift */, + 3B8AFA3891DA214D9FBEF650 /* NSAttributedString+StripeUICore.swift */, + CB52FF6CAC981EFA60A81AFF /* NSDirectionalEdgeInsets+StripeUICore.swift */, + 5321D6CCBA2AA9162A96080C /* UIBarButtonItem+StripeUICore.swift */, + 8A60A0D2417D3A70D500EE30 /* UIButton+StripeUICore.swift */, + 51C4AEB27F9734178E20836A /* UIColor+StripeUICore.swift */, + 43943E8FF7CD279F8D8605D3 /* UIFont+StripeUICore.swift */, + 4EE651DD3E5873538C7CE743 /* UIKeyboardType+StripeUICore.swift */, + 71A81696ECEEF728F25202B6 /* UISpringTimingParameters+StripeUICore.swift */, + 5855EA2F4F38847E3E47935D /* UIStackView+StripeUICore.swift */, + FAA22461E52111D9A4E8B133 /* UITraitCollection+StripeUICore.swift */, + FDBBC76FD31F07EC67C52075 /* UIView+StripeUICore.swift */, + 12973742CA1AA39D3B84F072 /* UIViewController+StripeUICore.swift */, + 59215CEEBA0D56FF41C3E412 /* UIWindow+StripeUICore.swift */, + ); + path = Categories; + sourceTree = ""; + }; + 55059C90F775384FCFE9D116 /* Elements */ = { + isa = PBXGroup; + children = ( + BB7836BA51814D0841B688E1 /* AddressSectionElementSnapshotTest.swift */, + D917F78E89D67691EF760D14 /* CheckboxButtonSnapshotTests.swift */, + 48E2652D09C1A4EBBDE2FB61 /* DateFieldElementSnapshotTest.swift */, + 62575FAAAAEFF31406C9B417 /* DropdownFieldElementSnapshotTest.swift */, + 7796F7211034806DA24D1710 /* PhoneNumberElementSnapshotTests.swift */, + ); + path = Elements; + sourceTree = ""; + }; + 5A4B0EAF2FA9E28C057A2AB2 /* BuildConfigurations */ = { + isa = PBXGroup; + children = ( + 67C454346BE566F9F689543B /* Project-Debug.xcconfig */, + D1FB364DF60C1DEE0DA8EE75 /* Project-Release.xcconfig */, + 9E3254388F5DAB8E8C76AE04 /* StripeiOS Tests-Debug.xcconfig */, + E242FECC90FE2644CF99692D /* StripeiOS Tests-Release.xcconfig */, + E395C593AC970A8C79A8109C /* StripeiOS-Debug.xcconfig */, + 4CBAE2B07C5575F19470464D /* StripeiOS-Release.xcconfig */, + ); + name = BuildConfigurations; + path = ../BuildConfigurations; + sourceTree = ""; + }; + 6053854E6A29F72565EEA479 /* Validators */ = { + isa = PBXGroup; + children = ( + 9CCBFC1830139737F2E947F1 /* BSBNumberTests.swift */, + 263B7E56A83CFA4BA8385638 /* PhoneNumberTests.swift */, + 2055BFB32FE9CF519DC25C8F /* STPBlikCodeValidatorTest.swift */, + 312E77021827300B2504F016 /* STPEmailAddressValidatorTest.swift */, + 53AA9FCEF2640C493D140033 /* STPVPANumberValidatorTest.swift */, + ); + path = Validators; + sourceTree = ""; + }; + 6409D732BE9F9BD02673B79B /* Helpers */ = { + isa = PBXGroup; + children = ( + E657F1ABE46BA5C0692D9D41 /* CompatibleColor.swift */, + A681C4DEAE7B35B4DC0BD0FB /* ImageMaker.swift */, + FB5A05E653BFD01FF65E193C /* InputFormColors.swift */, + EA00332BBDCE27F6F5A615C4 /* RegionCodeProvider.swift */, + 72394C7783071B1C6ED82A48 /* StackViewWithSeparator.swift */, + A4F4CE2A0B16DA57DB249227 /* STPLocalizedString.swift */, + A73D88DFAB03D663217399D0 /* String+CountryEmoji.swift */, + 0D558F65E4C23971C94BAD3E /* String+Localized.swift */, + BA1688A2CE2037846F8E3937 /* String+RegionCodeProvider.swift */, + E68363D1C6BB6468AC1DDA4C /* StripeUICoreBundleLocator.swift */, + ); + path = Helpers; + sourceTree = ""; + }; + 69644936855065629CAD92BE /* Unit */ = { + isa = PBXGroup; + children = ( + 361DA94D6B150221B8694910 /* Categories */, + 14BA0999EF98D5C19F7A0018 /* Elements */, + 6053854E6A29F72565EEA479 /* Validators */, + ); + path = Unit; + sourceTree = ""; + }; + 6A97BE4FB28466364C6C6F2D /* Snapshot */ = { + isa = PBXGroup; + children = ( + B2DFE0CA51E45495789EAE0C /* Controls */, + 55059C90F775384FCFE9D116 /* Elements */, + ); + path = Snapshot; + sourceTree = ""; + }; + 6F2D1EDE6459166E8599C7F6 /* Products */ = { + isa = PBXGroup; + children = ( + B8DC1E33CDF3B3FE549A7210 /* StripeCore.framework */, + 40DF1A7FC5D84F937042D172 /* StripeCoreTestUtils.framework */, + 868B2E8EDC242DB5AFFB3D0C /* StripeUICore.framework */, + E522C47E0874F612E330DA38 /* StripeUICoreTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 778494B821DEC0138FB4D78F /* Address */ = { + isa = PBXGroup; + children = ( + 5B5CCD5D11A49D48D8D21C62 /* AddressSectionElement.swift */, + 4B66D666BB3EA733B92A60AA /* AddressSectionElement+DummyAddressLine.swift */, + 7DC72344B4B313CE07A8AA33 /* AddressSpec.swift */, + 6A4D27EC1A9207F327B1BF20 /* AddressSpec+ElementFactory.swift */, + B844EED0578B990F4772CD01 /* AddressSpecProvider.swift */, + ); + path = Address; + sourceTree = ""; + }; + 7C026500149D281CB84ECA33 /* Validators */ = { + isa = PBXGroup; + children = ( + 312236317D0DA79F2D32CBE2 /* BankRoutingNumber.swift */, + FF05370E4825D3225D9A6910 /* PhoneNumber.swift */, + E99B2D80F3FBAC287CBF86B0 /* STPBlikCodeValidator.swift */, + DE233EED40F9D3FADDFE5951 /* STPEmailAddressValidator.swift */, + D634B04FC897DFB74B81DED6 /* STPVPANumberValidator.swift */, + ); + path = Validators; + sourceTree = ""; + }; + 82B232DB32C55ABC81F03EEA /* TextField */ = { + isa = PBXGroup; + children = ( + 4FD764D16A365FD46700FB25 /* FloatingPlaceholderTextFieldView.swift */, + A004788FA286E5ED22334238 /* TextFieldElement.swift */, + CFB80CA75F00847E4D74F821 /* TextFieldElement+Validation.swift */, + 8367A676E35A663E320E1B37 /* TextFieldElementConfiguration.swift */, + 34178B51599FDCB0D0D7475A /* TextFieldFormatter.swift */, + D5A312A55A629CED2C6404F1 /* TextFieldView.swift */, + ); + path = TextField; + sourceTree = ""; + }; + 89921F2BD8A1893F01033619 = { + isa = PBXGroup; + children = ( + 9F91E3826B63A2743D9EAFD8 /* Project */, + E4A3A33D13A26769A10EA12B /* Frameworks */, + 6F2D1EDE6459166E8599C7F6 /* Products */, + ); + sourceTree = ""; + }; + 89DCFAE23D35B3B8313A52E2 /* StripeUICore */ = { + isa = PBXGroup; + children = ( + 36DAB16F61F10767C1FAA6CB /* Resources */, + 47D21B13FCCDA3A340E0E45B /* Source */, + 40E0822DE6CFA0D3CCD5D513 /* Info.plist */, + CB8A9F7B4B2E8AA5A7E4FE98 /* StripeUICore.h */, + ); + path = StripeUICore; + sourceTree = ""; + }; + 91389CA21E7E02309F469572 /* Controls */ = { + isa = PBXGroup; + children = ( + 8678CFB3968AE5232932C461 /* ActivityIndicator.swift */, + 7EB48FCA3B2447A5F4CCEC69 /* Button.swift */, + 815DB8DE48AE3CE1609C8316 /* OneTimeCodeTextField-TextStorage.swift */, + AC2BFFB32C50AB4EECA90326 /* OneTimeCodeTextField.swift */, + ); + path = Controls; + sourceTree = ""; + }; + 95A37FFB73E533980DEB7AC6 /* StripeUICoreTests */ = { + isa = PBXGroup; + children = ( + 6A97BE4FB28466364C6C6F2D /* Snapshot */, + 69644936855065629CAD92BE /* Unit */, + 2AAE278AD27DB71B88C97B1A /* Info.plist */, + ); + path = StripeUICoreTests; + sourceTree = ""; + }; + 9F91E3826B63A2743D9EAFD8 /* Project */ = { + isa = PBXGroup; + children = ( + 5A4B0EAF2FA9E28C057A2AB2 /* BuildConfigurations */, + 89DCFAE23D35B3B8313A52E2 /* StripeUICore */, + 95A37FFB73E533980DEB7AC6 /* StripeUICoreTests */, + ); + name = Project; + sourceTree = ""; + }; + A33F87F760EA97E2416B5E3F /* JSON */ = { + isa = PBXGroup; + children = ( + 6E66B0CF81D4F8EDBB642C9F /* au_becs_bsb.json */, + 4E25905FCD05DF8B72888AAF /* localized_address_data.json */, + ); + path = JSON; + sourceTree = ""; + }; + B15F8A0D7BC00200896F00FD /* PickerField */ = { + isa = PBXGroup; + children = ( + 68F3046A526D3A4CE402FB83 /* PickerFieldView.swift */, + C0355EA21D04DDC29E620CEA /* PickerTextField.swift */, + ); + path = PickerField; + sourceTree = ""; + }; + B2DFE0CA51E45495789EAE0C /* Controls */ = { + isa = PBXGroup; + children = ( + EB5E2F9A5B1A3E8E7B765E4F /* ButtonSnapshotTest.swift */, + ); + path = Controls; + sourceTree = ""; + }; + B3A1C7E17AE91C2352DE42DB /* Section */ = { + isa = PBXGroup; + children = ( + F1C0A3DE4BCF8DA03AD17144 /* SectionContainerView.swift */, + 7BB17B284B6D063AF0329DAD /* SectionElement.swift */, + B6737026DF19C8D10AE8114A /* SectionElement+MultiElementRow.swift */, + CF3C9C0D2302B8E7F421EFBD /* SectionView.swift */, + ); + path = Section; + sourceTree = ""; + }; + CF2B7051099FA08B7F0C446F /* PhoneNumber */ = { + isa = PBXGroup; + children = ( + 8D0478259F9F7A04A8CF43BB /* PhoneNumberElement.swift */, + ); + path = PhoneNumber; + sourceTree = ""; + }; + E4A3A33D13A26769A10EA12B /* Frameworks */ = { + isa = PBXGroup; + children = ( + D4621DA31A1A5FC51F76E562 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E6F4FB2DDDAEEC6EAA177E77 /* BSB */ = { + isa = PBXGroup; + children = ( + CC3DD58BEF337017E7286459 /* BSBNumberProvider.swift */, + ); + path = BSB; + sourceTree = ""; + }; + F08533861A4AC1BB01315CCE /* Factories */ = { + isa = PBXGroup; + children = ( + 778494B821DEC0138FB4D78F /* Address */, + E6F4FB2DDDAEEC6EAA177E77 /* BSB */, + 374D0BE980D58B75FA04DE66 /* DropdownFieldElement+AddressFactory.swift */, + 92623B785C43F351D8A944D1 /* IDNumberTextFieldConfiguration.swift */, + D69C3FF914611057972ABA41 /* TextFieldElement+AccountFactory.swift */, + EDD7C1AAB9D26E00526AFB26 /* TextFieldElement+AddressFactory.swift */, + 4D4BD46908791D2E4150C4DC /* TextFieldElement+Factory.swift */, + ); + path = Factories; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 7124034061B4EB1E9FEE5082 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4755A24604B396D5A25058CD /* StripeUICore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A4B3F8AEF10396425E1A79D0 /* StripeUICoreTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FBFE0F03077B5B14BAA451B5 /* Build configuration list for PBXNativeTarget "StripeUICoreTests" */; + buildPhases = ( + 1EEBDA54180851BDA264A95A /* Sources */, + 22944EF7CA9505D7EAA24DDF /* Resources */, + 8B58BADB5D787E66A0A50257 /* Embed Frameworks */, + B361F19E111D84FD84927757 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + C47525D30F1B3DA22E5700ED /* PBXTargetDependency */, + ); + name = StripeUICoreTests; + packageProductDependencies = ( + 9B701A244243959A191FF16F /* iOSSnapshotTestCase */, + ); + productName = StripeUICoreTests; + productReference = E522C47E0874F612E330DA38 /* StripeUICoreTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + DE3C3F3D3BB67DD660A44B1E /* StripeUICore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0F1ADFB3411970E0071C1BC7 /* Build configuration list for PBXNativeTarget "StripeUICore" */; + buildPhases = ( + 7124034061B4EB1E9FEE5082 /* Headers */, + 8E796B4724BEBD4752011F86 /* Sources */, + 5744B094678B91577696C76C /* Resources */, + 7823EBE0BD66DC6070DC1530 /* Embed Frameworks */, + 0D674E67240745BB10E9C307 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StripeUICore; + productName = StripeUICore; + productReference = 868B2E8EDC242DB5AFFB3D0C /* StripeUICore.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A39BBBD15F0F6B54725E52AF /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = 2331F8341192F01F80C0469D /* Build configuration list for PBXProject "StripeUICore" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + "bg-BG", + "ca-ES", + "cs-CZ", + da, + de, + "el-GR", + en, + "en-GB", + es, + "es-419", + "et-EE", + fi, + fil, + fr, + "fr-CA", + hr, + hu, + id, + it, + ja, + ko, + "lt-LT", + "lv-LV", + "ms-MY", + mt, + nb, + nl, + "nn-NO", + "pl-PL", + "pt-BR", + "pt-PT", + "ro-RO", + ru, + "sk-SK", + "sl-SI", + sv, + tr, + vi, + "zh-HK", + "zh-Hans", + "zh-Hant", + ); + mainGroup = 89921F2BD8A1893F01033619; + packageReferences = ( + 538E8F52DECFD138BE60A67D /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */, + ); + productRefGroup = 6F2D1EDE6459166E8599C7F6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + DE3C3F3D3BB67DD660A44B1E /* StripeUICore */, + A4B3F8AEF10396425E1A79D0 /* StripeUICoreTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 22944EF7CA9505D7EAA24DDF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5744B094678B91577696C76C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2ADEF1482E62A173A0C7F7AD /* au_becs_bsb.json in Resources */, + D912BF580DBAB7416040B637 /* localized_address_data.json in Resources */, + ECCA9BD118D763DBF658E5FD /* Localizable.strings in Resources */, + 717D176DAF084461C18F2A09 /* StripeUICore.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1EEBDA54180851BDA264A95A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D01976C07EB39B2BED64CCAC /* ButtonSnapshotTest.swift in Sources */, + FC1F8C4DC70C8212B507AE7E /* AddressSectionElementSnapshotTest.swift in Sources */, + 0D8AE1CEFBE7FA004A3DD62D /* CheckboxButtonSnapshotTests.swift in Sources */, + C6B11F4F219F7ED04321A33F /* DateFieldElementSnapshotTest.swift in Sources */, + FF4E844383E5A5FD5C099B41 /* DropdownFieldElementSnapshotTest.swift in Sources */, + 1424B1529E24582122F86149 /* PhoneNumberElementSnapshotTests.swift in Sources */, + 9DBFEB7045692EE931CE014D /* Locale+StripeUICoreTests.swift in Sources */, + 976AFE0D02FE65BFA1757E4E /* NSAttributedString+StripeUICoreTests.swift in Sources */, + 69B082B15479DCC4560E3D92 /* UIColor+StripeUICoreTests.swift in Sources */, + B106FCB3D7C3DE7C40F0AE5C /* AddressSectionElementTest.swift in Sources */, + 073D41F1EC0560423FEF87AA /* AddressSpecProviderTest.swift in Sources */, + 32D69CBB3CE780A29AA553AB /* BSBNumberProviderTest.swift in Sources */, + 2AA023A5031CFF0E56C7A14E /* DateFieldElementTest.swift in Sources */, + F0EB247FEFF4600CD44B8261 /* DropdownFieldElementTest.swift in Sources */, + 88F53AB8F31B1DA2187E5740 /* IDNumberTextFieldConfigurationTest.swift in Sources */, + 4C519A87445AB16A55FE2408 /* PhoneNumberElementTests.swift in Sources */, + A21277FDCDD0C6BFB73A4B51 /* SectionElementTest.swift in Sources */, + 163A77E9E4C40AFFA5226E23 /* TestFieldElement+AccountFactoryTest.swift in Sources */, + 08C773D1E6A5452B7BD7CF81 /* TextFieldElement+AddressFactoryTest.swift in Sources */, + 993E51173490AAE5D7AF4C4E /* TextFieldElementTest.swift in Sources */, + EE28852FFCF42A8C47098051 /* TextFieldFormatterTest.swift in Sources */, + 74F65B6435E46B0FC8386FBB /* BSBNumberTests.swift in Sources */, + 64E61A5E0A705F1C4582381A /* PhoneNumberTests.swift in Sources */, + BEB4F0E3B6218CA3E5DE95F9 /* STPBlikCodeValidatorTest.swift in Sources */, + A5C379C7D1EEF497FD845306 /* STPEmailAddressValidatorTest.swift in Sources */, + 2FD444AC5FC064EFCF6259ED /* STPVPANumberValidatorTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8E796B4724BEBD4752011F86 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 824858D45F9D952BBDF822E2 /* CALayer+StripeUICore.swift in Sources */, + AA8F938C3A8B7BC7F11B5048 /* Enums+CustomStringConvertible.swift in Sources */, + 2F1D03471202E25FD7682148 /* Locale+StripeUICore.swift in Sources */, + BC40443B2A2F7130351589A7 /* NSAttributedString+StripeUICore.swift in Sources */, + DA6550F1FFA1376DB656D6E0 /* NSDirectionalEdgeInsets+StripeUICore.swift in Sources */, + 67FCE4493235656689E915F6 /* UIBarButtonItem+StripeUICore.swift in Sources */, + 7272A43410D4BA1365D71E70 /* UIButton+StripeUICore.swift in Sources */, + 02B503A3227EF1409ABF53F1 /* UIColor+StripeUICore.swift in Sources */, + 900EFF5918D96B9716CFB673 /* UIFont+StripeUICore.swift in Sources */, + D6D8BCCF86C964B40D2FA58E /* UIKeyboardType+StripeUICore.swift in Sources */, + 80B0519BC9CC21D9B650FC88 /* UISpringTimingParameters+StripeUICore.swift in Sources */, + 159D7B9A2960A1CD7E661191 /* UIStackView+StripeUICore.swift in Sources */, + 30972A45F8A32DEEC17DA4F6 /* UITraitCollection+StripeUICore.swift in Sources */, + 62601F856C41CFA1C7A8B18F /* UIView+StripeUICore.swift in Sources */, + D47D77A0B82DC6AE15E0A74E /* UIViewController+StripeUICore.swift in Sources */, + 4CD207111219BF250A400ACC /* UIWindow+StripeUICore.swift in Sources */, + 778BACD1A29BEDEE21ED3FBE /* ActivityIndicator.swift in Sources */, + FDF52A43A01CD72D4B5A2CA9 /* Button.swift in Sources */, + 4B05CF7F485F0AED498DAE49 /* OneTimeCodeTextField-TextStorage.swift in Sources */, + 48D3B7C2983A8A25C6599119 /* OneTimeCodeTextField.swift in Sources */, + C44A57646A325EE26B75E6BF /* CheckboxButton.swift in Sources */, + C3D6D899B671398717F22520 /* CheckboxElement.swift in Sources */, + 8B8D3BD090415EFF9948D2C2 /* ContainerElement.swift in Sources */, + A5E59185A9708613676988C6 /* DateFieldElement.swift in Sources */, + C23C78D87D8E6682F31345CB /* DropdownFieldElement.swift in Sources */, + D7DB5C5724CD47E33245B25A /* Element.swift in Sources */, + FAD790056C7A9E645A6B2C74 /* ElementsUI.swift in Sources */, + 377058C2363FA0348AFBD32E /* AddressSectionElement+DummyAddressLine.swift in Sources */, + BE42104922DCA4DCB3919DD4 /* AddressSectionElement.swift in Sources */, + 0CB675370A06DEC6F23608C4 /* AddressSpec+ElementFactory.swift in Sources */, + E9CA12DAB591AC834CE9539A /* AddressSpec.swift in Sources */, + E94BA0179485AED17D412865 /* AddressSpecProvider.swift in Sources */, + 225B20CEF547BB1F6C6D447E /* BSBNumberProvider.swift in Sources */, + F86769C5CFD9AD3732127951 /* DropdownFieldElement+AddressFactory.swift in Sources */, + 67216EB4E004BDB1D2E49BD4 /* IDNumberTextFieldConfiguration.swift in Sources */, + 65B9A839BD4AAC315231B421 /* TextFieldElement+AccountFactory.swift in Sources */, + 0A3130227F7602524C9824D3 /* TextFieldElement+AddressFactory.swift in Sources */, + FC48FCDC5FD43E5E8AFC32D2 /* TextFieldElement+Factory.swift in Sources */, + 4A5EADAF2F6514299BA4B8D8 /* FormElement.swift in Sources */, + C1CA6209591EDDBBE019FF22 /* FormView.swift in Sources */, + 86427678E119E4AD22410E30 /* PhoneNumberElement.swift in Sources */, + 019C76A03A30A67AE9F1FAEE /* PickerFieldView.swift in Sources */, + 57C288DFD2CC2CFC216E47CC /* PickerTextField.swift in Sources */, + 32971AFF5D0DF9B28E9C464C /* SectionContainerView.swift in Sources */, + 5A9B21FE5A6941713087B94B /* SectionElement+MultiElementRow.swift in Sources */, + 336B882978CA47EE46260774 /* SectionElement.swift in Sources */, + 93CF3CE90B520B4A25208E95 /* SectionView.swift in Sources */, + 4B414F0A0E46D914C89B3741 /* StaticElement.swift in Sources */, + 0FBBB5C7FD2DA9A11E7FE2BC /* FloatingPlaceholderTextFieldView.swift in Sources */, + E40999CDEDEA23451CA89707 /* TextFieldElement+Validation.swift in Sources */, + 9DEAD347B741A53E2F1764B4 /* TextFieldElement.swift in Sources */, + 5936629C4665BC698C3458B1 /* TextFieldElementConfiguration.swift in Sources */, + 39E61B5E6A88E5AF1922EE62 /* TextFieldFormatter.swift in Sources */, + 986924FDA1EEF4146CD81B50 /* TextFieldView.swift in Sources */, + A29B5AC2F03116E2F48970EE /* TextOrDropdownElement.swift in Sources */, + 36376E12AE7ABA21FFC474E6 /* Events.swift in Sources */, + FB33F2F446570394AABB7EC7 /* CompatibleColor.swift in Sources */, + 7B90479C19C30407FC21B228 /* ImageMaker.swift in Sources */, + 343DD093A6095DEAA06D245E /* InputFormColors.swift in Sources */, + 6606DC43D230ADD183AEF5DA /* RegionCodeProvider.swift in Sources */, + 504FA2DE5FE66FDE90842019 /* STPLocalizedString.swift in Sources */, + EDDE1C83333AB1A1F2BD0F3E /* StackViewWithSeparator.swift in Sources */, + 11BD8DFB36FACB6966D0236F /* String+CountryEmoji.swift in Sources */, + 0BA1E7C26903E45912FDE25A /* String+Localized.swift in Sources */, + D083BAAF86707F9865AF2AF4 /* String+RegionCodeProvider.swift in Sources */, + B6107E5E2D6E116417D22DD9 /* StripeUICoreBundleLocator.swift in Sources */, + 68F7D5EEB894A68DDC184ADA /* Image.swift in Sources */, + A3F0D42EB3A3FF2299F2F473 /* BankRoutingNumber.swift in Sources */, + F901303E0B78F2D8E4C8A2F1 /* PhoneNumber.swift in Sources */, + 710D7FB87C39EEB0DB1F3E75 /* STPBlikCodeValidator.swift in Sources */, + 1EB16D8F60923EE9C890CE43 /* STPEmailAddressValidator.swift in Sources */, + 0019E30E7B8189C3DEA719DC /* STPVPANumberValidator.swift in Sources */, + CF9D4CC40A7008DD1E8136A3 /* DoneButtonToolbar.swift in Sources */, + B1B177526E04EE65A9D1C64A /* DynamicHeightContainerView.swift in Sources */, + 11C99D83A1DA88996A45BA47 /* DynamicImageView.swift in Sources */, + 6CB223E48029E6BA6ED48041 /* LinkOpeningTextView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + C47525D30F1B3DA22E5700ED /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StripeUICore; + target = DE3C3F3D3BB67DD660A44B1E /* StripeUICore */; + targetProxy = 980541F3E23EAD7E20DBCA47 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 71C8163AEB97D2FF8BB3A1C8 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 7D4E60C42E09A23ACD60BCD8 /* bg-BG */, + 7B899349F7EF770D8F19509D /* ca-ES */, + A360D5AE016620B560FB8A77 /* cs-CZ */, + A1A02A2319937F5033495984 /* da */, + 848196B6CCE964A735BFCD46 /* de */, + B2161853A89C273B745B8A60 /* el-GR */, + 02DA3E661669718BD61C702D /* en */, + BA366F6105D04C9136240341 /* en-GB */, + 18C5B42A6D5AF223F0CFB23F /* es */, + D59C1B7B69C92DFC9A25DAB2 /* es-419 */, + C85C015B47A8EBF87E5F662E /* et-EE */, + AE4AB8A1DB70CDE06D9887CF /* fi */, + 94FB3B96E7884F5B5C1C3EA8 /* fil */, + 71DCDDEE36E2CD98E4A03752 /* fr */, + 4F37C42EA28BA31EC5CCDC7C /* fr-CA */, + B081CD063903B2FDBE327A15 /* hr */, + 0ED24D748AEB8B1E0BA1FBAD /* hu */, + 3A99D0F01D29E302C52217FE /* id */, + 74EDF4CC65F409F55E14EB25 /* it */, + 53900894BD0E2FF59029D2B6 /* ja */, + 11C37DE7B064DC9E3F7E55CD /* ko */, + AB2C786B198F91AE124C790A /* lt-LT */, + 00455FBF8F3D7C9B0E65DA54 /* lv-LV */, + 8278020A829AE8C5CB8B6A9C /* ms-MY */, + E0C8DFA9A617506071C59815 /* mt */, + 34EC2F4C025AD1B1DD44CF78 /* nb */, + 1A50D68D24D1DE0459ED9529 /* nl */, + B84B32F86F684EA5233DDED5 /* nn-NO */, + B871CED2EB8A1F7EEC0FE087 /* pl-PL */, + 72EB2DE23B3740D8DA3081E5 /* pt-BR */, + B9CAB799E5645867F2400F0F /* pt-PT */, + 699ED5466892D95ADC151B64 /* ro-RO */, + BD52DCDA490D452AAA4B5E44 /* ru */, + 011872F74559CB0D0D61170C /* sk-SK */, + 2BAA056C8170A4B0E8C763AD /* sl-SI */, + EB942257C8B6B357AD0F6FC9 /* sv */, + A032BB17A6FA85EE4E3D00AE /* tr */, + 2D9B5640C88DD94D17666E0E /* vi */, + 81A4AE07CB9049B6D8C8D7D2 /* zh-Hans */, + 9559041BED6315C71F796A5F /* zh-Hant */, + 70B4BE2ACC46DF88949121CD /* zh-HK */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0E538ACBF1E137ADF66DB8F8 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4CBAE2B07C5575F19470464D /* StripeiOS-Release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeUICore/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-ui-core"; + PRODUCT_NAME = StripeUICore; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Release; + }; + 4C7CEEDE1477774F09EA2E3F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 67C454346BE566F9F689543B /* Project-Debug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 6600E0743D5AD7028F6ABC2C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E395C593AC970A8C79A8109C /* StripeiOS-Debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = StripeUICore/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = "com.stripe.stripe-ui-core"; + PRODUCT_NAME = StripeUICore; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,2,7"; + }; + name = Debug; + }; + 8D85CFA6FA82B62D41B6313A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D1FB364DF60C1DEE0DA8EE75 /* Project-Release.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 918E06A524F977D2E40E149B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E242FECC90FE2644CF99692D /* StripeiOS Tests-Release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeUICoreTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeUICoreTests; + PRODUCT_NAME = StripeUICoreTests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Release; + }; + FDF219D174EA73AC86665131 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9E3254388F5DAB8E8C76AE04 /* StripeiOS Tests-Debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + INFOPLIST_FILE = StripeUICoreTests/Info.plist; + PRODUCT_BUNDLE_IDENTIFIER = com.stripe.StripeUICoreTests; + PRODUCT_NAME = StripeUICoreTests; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTS_MACCATALYST = YES; + TARGETED_DEVICE_FAMILY = "1,7"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0F1ADFB3411970E0071C1BC7 /* Build configuration list for PBXNativeTarget "StripeUICore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6600E0743D5AD7028F6ABC2C /* Debug */, + 0E538ACBF1E137ADF66DB8F8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2331F8341192F01F80C0469D /* Build configuration list for PBXProject "StripeUICore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4C7CEEDE1477774F09EA2E3F /* Debug */, + 8D85CFA6FA82B62D41B6313A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FBFE0F03077B5B14BAA451B5 /* Build configuration list for PBXNativeTarget "StripeUICoreTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FDF219D174EA73AC86665131 /* Debug */, + 918E06A524F977D2E40E149B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 538E8F52DECFD138BE60A67D /* XCRemoteSwiftPackageReference "ios-snapshot-test-case" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/uber/ios-snapshot-test-case"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 9B701A244243959A191FF16F /* iOSSnapshotTestCase */ = { + isa = XCSwiftPackageProductDependency; + productName = iOSSnapshotTestCase; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = A39BBBD15F0F6B54725E52AF /* Project object */; +} diff --git a/StripeUICore/StripeUICore.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/StripeUICore/StripeUICore.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/StripeUICore/StripeUICore.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/StripeUICore/StripeUICore.xcodeproj/xcshareddata/xcschemes/StripeUICore.xcscheme b/StripeUICore/StripeUICore.xcodeproj/xcshareddata/xcschemes/StripeUICore.xcscheme new file mode 100644 index 00000000..44095b6c --- /dev/null +++ b/StripeUICore/StripeUICore.xcodeproj/xcshareddata/xcschemes/StripeUICore.xcscheme @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StripeUICore/StripeUICore/Info.plist b/StripeUICore/StripeUICore/Info.plist new file mode 100644 index 00000000..cd4a496b --- /dev/null +++ b/StripeUICore/StripeUICore/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(CURRENT_PROJECT_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/StripeUICore/StripeUICore/Resources/JSON/au_becs_bsb.json b/StripeUICore/StripeUICore/Resources/JSON/au_becs_bsb.json new file mode 100644 index 00000000..74d8dca8 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/JSON/au_becs_bsb.json @@ -0,0 +1,123 @@ +{ + "10": "BankSA (division of Westpac Bank)", + "11": "St George Bank (division of Westpac Bank)", + "12": "Bank of Queensland", + "14": "Rabobank", + "15": "Town & Country Bank", + "18": "Macquarie Bank", + "19": "Bank of Melbourne (division of Westpac Bank)", + "21": "JP Morgan Chase Bank", + "22": "BNP Paribas", + "23": "Bank of America", + "24": "Citibank", + "25": "BNP Paribas Securities", + "26": "Bankers Trust Australia (division of Westpac Bank)", + "29": "Bank of Tokyo-Mitsubishi", + "30": "Bankwest (division of Commonwealth Bank)", + "33": "St George Bank (division of Westpac Bank)", + "34": "HSBC Bank Australia", + "35": "Bank of China", + "40": "Commonwealth Bank of Australia", + "41": "Deutsche Bank", + "42": "Commonwealth Bank of Australia", + "45": "OCBC Bank", + "46": "Advance Bank (division of Westpac Bank)", + "47": "Challenge Bank (division of Westpac Bank)", + "48": "Suncorp-Metway", + "52": "Commonwealth Bank of Australia", + "55": "Bank of Melbourne (division of Westpac Bank)", + "57": "Australian Settlements", + "61": "Adelaide Bank (division of Bendigo and Adelaide Bank)", + "70": "Indue", + "73": "Westpac Banking Corporation", + "76": "Commonwealth Bank of Australia", + "80": "Cuscal", + "90": "Australia Post", + "311": "in1bank", + "313": "Bankmecu", + "323": "KEB Hana Bank", + "325": "Beyond Bank Australia", + "432": "Standard Chartered Bank", + "510": "Citibank N.A.", + "512": "Community First Credit Union", + "514": "QT Mutual Bank", + "517": "Australian Settlements Limited", + "533": "Bananacoast Community Credit Union", + "611": "Select Credit Union", + "630": "ABS Building Society", + "632": "B&E", + "633": "Bendigo Bank", + "634": "Uniting Financial Services", + "636": "Cuscal Limited", + "637": "Greater Building Society", + "638": "Heritage Bank", + "639": "Home Building Society (division of Bank of Queensland)", + "640": "Hume Bank", + "641": "IMB", + "642": "Australian Defence Credit Union", + "645": "Wide Bay Australia", + "646": "Maitland Mutual Building Society", + "647": "IMB", + "650": "Newcastle Permanent Building Society", + "653": "Pioneer Permanent Building Society (division of Bank of Queensland)", + "654": "ECU Australia", + "655": "The Rock Building Society", + "656": "Wide Bay Australia", + "657": "Greater Building Society", + "659": "SGE Credit Union", + "664": "Suncorp-Metway", + "670": "Cuscal Limited", + "676": "Gateway Credit Union", + "680": "Greater Bank Limited", + "721": "Holiday Coast Credit Union", + "722": "Southern Cross Credit", + "723": "Heritage Isle Credit Union", + "724": "Railways Credit Union", + "725": "Judo Bank Pty Ltd", + "728": "Summerland Credit Union", + "775": "Australian Settlements Limited", + "777": "Police & Nurse", + "812": "Teachers Mutual Bank", + "813": "Capricornian", + "814": "Credit Union Australia", + "815": "Police Bank", + "817": "Warwick Credit Union", + "818": "Bank of Communications", + "819": "Industrial & Commercial Bank of China", + "820": "Global Payments Australia 1 Pty Ltd", + "823": "Encompass Credit Union", + "824": "Sutherland Credit Union", + "825": "Big Sky Building Society", + "833": "Defence Bank Limited", + "840": "Split Payments Pty Ltd", + "880": "Heritage Bank", + "882": "Maritime Mining & Power Credit Union", + "888": "China Construction Bank Corporation", + "889": "DBS Bank Ltd.", + "911": "Sumitomo Mitsui Banking Corporation", + "913": "State Street Bank & Trust Company", + "917": "Arab Bank Australia", + "918": "Mizuho Bank", + "922": "United Overseas Bank", + "923": "ING Bank", + "931": "Mega International Commercial Bank", + "932": "Community Mutual", + "936": "ING Bank", + "939": "AMP Bank", + "941": "Delphi Bank (division of Bendigo and Adelaide Bank)", + "942": "Bank of Sydney", + "943": "Taiwan Business Bank", + "944": "Members Equity Bank", + "946": "UBS AG", + "951": "BOQ Specialist Bank", + "952": "Royal Bank of Scotland", + "969": "Tyro Payments", + "980": "Bank of China", + "985": "HSBC Bank Australia", + "01": "Australia and New Zealand Banking Group", + "03": "Westpac Banking Corporation", + "04": "Westpac Banking Corporation", + "06": "Commonwealth Bank of Australia", + "08": "National Australia Bank", + "09": "Reserve Bank of Australia" +} diff --git a/StripeUICore/StripeUICore/Resources/JSON/localized_address_data.json b/StripeUICore/StripeUICore/Resources/JSON/localized_address_data.json new file mode 100644 index 00000000..e5c1d380 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/JSON/localized_address_data.json @@ -0,0 +1,1197 @@ +{ + "AC":{ + "_comment": "This file was adapted from https://git.corp.stripe.com/stripe-internal/stripe-js-v3/blob/bdc2eeed/src/elements/inner/shared/address/addressData.ts", + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"ASCN 1ZZ" + }, + "AD":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"AD[1-7]0\\d" + }, + "AE":{ + "fmt":"%N%n%O%n%A%n%S", + "lfmt":"%N%n%O%n%A%n%S", + "require":"AS", + "state_name_type":"emirate" + }, + "AF":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"\\d{4}" + }, + "AG":{ + "require":"A" + }, + "AI":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"(?:AI-)?2640" + }, + "AL":{ + "fmt":"%N%n%O%n%A%n%Z%n%C", + "zip":"\\d{4}" + }, + "AM":{ + "fmt":"%N%n%O%n%A%n%Z%n%C%n%S", + "lfmt":"%N%n%O%n%A%n%Z%n%C%n%S", + "zip":"(?:37)?\\d{4}" + }, + "AO":{ + "fmt":"%C" + }, + "AR":{ + "fmt":"%N%n%O%n%A%n%Z %C%n%S", + "zip":"((?:[A-HJ-NP-Z])?\\d{4})([A-Z]{3})?" + }, + "AT":{ + "fmt":"%O%n%N%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{4}" + }, + "AU":{ + "fmt":"%O%n%N%n%A%n%C %S %Z", + "locality_name_type":"suburb", + "require":"ACSZ", + "state_name_type":"state", + "zip":"\\d{4}" + }, + "AW":{ + "fmt":"%C" + }, + "AX":{ + "fmt":"%O%n%N%n%A%nAX-%Z %C%nÅLAND", + "postprefix":"AX-", + "require":"ACZ", + "zip":"22\\d{3}" + }, + "AZ":{ + "fmt":"%N%n%O%n%A%nAZ %Z %C", + "postprefix":"AZ ", + "zip":"\\d{4}" + }, + "BA":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "BB":{ + "fmt":"%N%n%O%n%A%n%C, %S %Z", + "state_name_type":"parish", + "zip":"BB\\d{5}" + }, + "BD":{ + "fmt":"%N%n%O%n%A%n%C - %Z", + "zip":"\\d{4}" + }, + "BE":{ + "fmt":"%O%n%N%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{4}" + }, + "BF":{ + "fmt":"%N%n%O%n%A%n%C %X" + }, + "BG":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "BH":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"(?:\\d|1[0-2])\\d{2}" + }, + "BI":{ + "fmt":"%C" + }, + "BJ":{ + "fmt":"%C" + }, + "BL":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78][01]\\d{2}" + }, + "BM":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"[A-Z]{2} ?[A-Z0-9]{2}" + }, + "BN":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"[A-Z]{2} ?\\d{4}" + }, + "BO":{ + "fmt":"%C" + }, + "BQ":{ + "fmt":"%C" + }, + "BR":{ + "fmt":"%O%n%N%n%A%n%D%n%C-%S%n%Z", + "require":"ASCZ", + "state_name_type":"state", + "sublocality_name_type":"neighborhood", + "zip":"\\d{5}-?\\d{3}" + }, + "BS":{ + "fmt":"%N%n%O%n%A%n%C, %S", + "state_name_type":"island" + }, + "BT":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}" + }, + "BV":{ + "fmt":"%C" + }, + "BW":{ + "fmt":"%C" + }, + "BY":{ + "fmt":"%S%n%Z %C%n%A%n%O%n%N", + "zip":"\\d{6}" + }, + "BZ":{ + "fmt":"%C" + }, + "CA":{ + "fmt":"%N%n%O%n%A%n%C %S %Z", + "require":"ACSZ", + "zip":"[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d", + "sub_keys": [ + "AB", + "BC", + "MB", + "NB", + "NL", + "NT", + "NS", + "NU", + "ON", + "PE", + "QC", + "SK", + "YT" + ], + "sub_labels": [ + "Alberta", + "British Columbia", + "Manitoba", + "New Brunswick", + "Newfoundland and Labrador", + "Northwest Territories", + "Nova Scotia", + "Nunavut", + "Ontario", + "Prince Edward Island", + "Quebec", + "Saskatchewan", + "Yukon" + ] + }, + "CD":{ + "fmt":"%C" + }, + "CF":{ + "fmt":"%C" + }, + "CG":{ + "fmt":"%C" + }, + "CH":{ + "fmt":"%O%n%N%n%A%nCH-%Z %C", + "postprefix":"CH-", + "require":"ACZ", + "zip":"\\d{4}" + }, + "CI":{ + "fmt":"%N%n%O%n%X %A %C %X" + }, + "CK":{ + "fmt":"%C" + }, + "CL":{ + "fmt":"%N%n%O%n%A%n%Z %C%n%S", + "zip":"\\d{7}" + }, + "CM":{ + "fmt":"%C" + }, + "CN":{ + "fmt":"%Z%n%S%C%D%n%A%n%O%n%N", + "lfmt":"%N%n%O%n%A%n%D%n%C%n%S, %Z", + "require":"ACSZ", + "sublocality_name_type":"district", + "zip":"\\d{6}" + }, + "CO":{ + "fmt":"%N%n%O%n%A%n%C, %S, %Z", + "require":"AS", + "state_name_type":"department", + "zip":"\\d{6}" + }, + "CR":{ + "fmt":"%N%n%O%n%A%n%S, %C%n%Z", + "require":"ACS", + "zip":"\\d{4,5}|\\d{3}-\\d{4}" + }, + "CV":{ + "fmt":"%N%n%O%n%A%n%Z %C%n%S", + "state_name_type":"island", + "zip":"\\d{4}" + }, + "CW":{ + "fmt":"%C" + }, + "CY":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "CZ":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{3} ?\\d{2}" + }, + "DE":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{5}" + }, + "DJ":{ + "fmt":"%C" + }, + "DK":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{4}" + }, + "DM":{ + "fmt":"%C" + }, + "DO":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "DZ":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "EC":{ + "fmt":"%N%n%O%n%A%n%Z%n%C", + "zip":"\\d{6}" + }, + "EE":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{5}" + }, + "EG":{ + "fmt":"%N%n%O%n%A%n%C%n%S%n%Z", + "lfmt":"%N%n%O%n%A%n%C%n%S%n%Z", + "zip":"\\d{5}" + }, + "EH":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "ER":{ + "fmt":"%C" + }, + "ES":{ + "fmt":"%N%n%O%n%A%n%Z %C %S", + "require":"ACSZ", + "zip":"\\d{5}" + }, + "ET":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "FI":{ + "fmt":"%O%n%N%n%A%nFI-%Z %C", + "postprefix":"FI-", + "require":"ACZ", + "zip":"\\d{5}" + }, + "FJ":{ + "fmt":"%C" + }, + "FK":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"ACZ", + "zip":"FIQQ 1ZZ" + }, + "FO":{ + "fmt":"%N%n%O%n%A%nFO%Z %C", + "postprefix":"FO", + "zip":"\\d{3}" + }, + "FR":{ + "fmt":"%O%n%N%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{2} ?\\d{3}" + }, + "GA":{ + "fmt":"%C" + }, + "GB":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "locality_name_type":"post_town", + "require":"ACZ", + "zip":"GIR ?0AA|(?:(?:AB|AL|B|BA|BB|BD|BF|BH|BL|BN|BR|BS|BT|BX|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(?:\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}))|BFPO ?\\d{1,4}", + "zipex":"EC1Y 8SY,GIR 0AA,M2 5BQ,M34 4AB,CR0 2YR,DN16 9AA,W1A 4ZZ,EC1A 1HQ,OX14 4PG,BS18 8HF,NR25 7HG,RH6 0NP,BH23 6AA,B6 5BA,SO23 9AP,PO1 3AX,BFPO 61" + }, + "GD":{ + "fmt":"%C" + }, + "GE":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "GF":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78]3\\d{2}" + }, + "GG":{ + "fmt":"%N%n%O%n%A%n%C%nGUERNSEY%n%Z", + "require":"ACZ", + "zip":"GY\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}" + }, + "GH":{ + "fmt":"%C" + }, + "GI":{ + "fmt":"%N%n%O%n%A%nGIBRALTAR%n%Z", + "require":"A", + "zip":"GX11 1AA" + }, + "GL":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"39\\d{2}" + }, + "GM":{ + "fmt":"%C" + }, + "GN":{ + "fmt":"%N%n%O%n%Z %A %C", + "zip":"\\d{3}" + }, + "GP":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78][01]\\d{2}" + }, + "GQ":{ + "fmt":"%C" + }, + "GR":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{3} ?\\d{2}" + }, + "GS":{ + "fmt":"%N%n%O%n%A%n%n%C%n%Z", + "require":"ACZ", + "zip":"SIQQ 1ZZ" + }, + "GT":{ + "fmt":"%N%n%O%n%A%n%Z- %C", + "zip":"\\d{5}" + }, + "GU":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "require":"ACZ", + "zip":"(969(?:[12]\\d|3[12]))(?:[ \\-](\\d{4}))?", + "zip_name_type":"zip" + }, + "GW":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "GY":{ + "fmt":"%C" + }, + "HK":{ + "fmt":"%S%n%C%n%A%n%O%n%N", + "lfmt":"%N%n%O%n%A%n%C%n%S", + "locality_name_type":"district", + "require":"AS", + "state_name_type":"area" + }, + "HN":{ + "fmt":"%N%n%O%n%A%n%C, %S%n%Z", + "require":"ACS", + "state_name_type":"department", + "zip":"\\d{5}" + }, + "HR":{ + "fmt":"%N%n%O%n%A%nHR-%Z %C", + "postprefix":"HR-", + "zip":"\\d{5}" + }, + "HT":{ + "fmt":"%N%n%O%n%A%nHT%Z %C", + "postprefix":"HT", + "zip":"\\d{4}" + }, + "HU":{ + "fmt":"%N%n%O%n%C%n%A%n%Z", + "require":"ACZ", + "zip":"\\d{4}" + }, + "ID":{ + "fmt":"%N%n%O%n%A%n%C%n%S %Z", + "require":"AS", + "zip":"\\d{5}" + }, + "IE":{ + "fmt":"%N%n%O%n%A%n%D%n%C%n%S %Z", + "state_name_type":"county", + "sublocality_name_type":"townland", + "zip":"[\\dA-Z]{3} ?[\\dA-Z]{4}", + "zip_name_type":"eircode" + }, + "IL":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}(?:\\d{2})?" + }, + "IM":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"ACZ", + "zip":"IM\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}" + }, + "IN":{ + "fmt":"%N%n%O%n%A%n%C %Z%n%S", + "require":"ACSZ", + "state_name_type":"state", + "zip":"\\d{6}", + "zip_name_type":"pin" + }, + "IO":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"ACZ", + "zip":"BBND 1ZZ" + }, + "IQ":{ + "fmt":"%O%n%N%n%A%n%C, %S%n%Z", + "require":"ACS", + "zip":"\\d{5}" + }, + "IS":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{3}" + }, + "IT":{ + "fmt":"%N%n%O%n%A%n%Z %C %S", + "require":"ACSZ", + "zip":"\\d{5}" + }, + "JE":{ + "fmt":"%N%n%O%n%A%n%C%nJERSEY%n%Z", + "require":"ACZ", + "zip":"JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}" + }, + "JM":{ + "fmt":"%N%n%O%n%A%n%C%n%S %X", + "require":"ACS", + "state_name_type":"parish" + }, + "JO":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}" + }, + "JP":{ + "fmt":"〒%Z%n%S%n%A%n%O%n%N", + "lfmt":"%N%n%O%n%A, %S%n%Z", + "require":"ASZ", + "state_name_type":"prefecture", + "zip":"\\d{3}-?\\d{4}" + }, + "KE":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"\\d{5}" + }, + "KG":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{6}" + }, + "KH":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}" + }, + "KI":{ + "fmt":"%N%n%O%n%A%n%S%n%C", + "state_name_type":"island" + }, + "KM":{ + "fmt":"%C" + }, + "KN":{ + "fmt":"%N%n%O%n%A%n%C, %S", + "require":"ACS", + "state_name_type":"island" + }, + "KR":{ + "fmt":"%S %C%D%n%A%n%O%n%N%n%Z", + "lfmt":"%N%n%O%n%A%n%D%n%C%n%S%n%Z", + "require":"ACSZ", + "state_name_type":"do_si", + "sublocality_name_type":"district", + "zip":"\\d{5}" + }, + "KW":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "KY":{ + "fmt":"%N%n%O%n%A%n%S %Z", + "require":"AS", + "state_name_type":"island", + "zip":"KY\\d-\\d{4}" + }, + "KZ":{ + "fmt":"%Z%n%S%n%C%n%A%n%O%n%N", + "zip":"\\d{6}" + }, + "LA":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "LB":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"(?:\\d{4})(?: ?(?:\\d{4}))?" + }, + "LC":{ + "fmt":"%C" + }, + "LI":{ + "fmt":"%O%n%N%n%A%nFL-%Z %C", + "postprefix":"FL-", + "require":"ACZ", + "zip":"948[5-9]|949[0-8]" + }, + "LK":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"\\d{5}" + }, + "LR":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "LS":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{3}" + }, + "LT":{ + "fmt":"%O%n%N%n%A%nLT-%Z %C", + "postprefix":"LT-", + "require":"ACZ", + "zip":"\\d{5}" + }, + "LU":{ + "fmt":"%O%n%N%n%A%nL-%Z %C", + "postprefix":"L-", + "require":"ACZ", + "zip":"\\d{4}" + }, + "LV":{ + "fmt":"%N%n%O%n%A%n%C, %Z", + "require":"ACZ", + "zip":"LV-\\d{4}" + }, + "LY":{ + "fmt":"%C" + }, + "MA":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "MC":{ + "fmt":"%N%n%O%n%A%nMC-%Z %C %X", + "postprefix":"MC-", + "zip":"980\\d{2}" + }, + "MD":{ + "fmt":"%N%n%O%n%A%nMD-%Z %C", + "postprefix":"MD-", + "zip":"\\d{4}" + }, + "ME":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"8\\d{4}" + }, + "MF":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78][01]\\d{2}" + }, + "MG":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{3}" + }, + "MK":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "ML":{ + "fmt":"%C" + }, + "MM":{ + "fmt":"%N%n%O%n%A%n%C, %Z", + "zip":"\\d{5}" + }, + "MN":{ + "fmt":"%N%n%O%n%A%n%C%n%S %Z", + "zip":"\\d{5}" + }, + "MO":{ + "fmt":"%A%n%O%n%N", + "lfmt":"%N%n%O%n%A", + "require":"A" + }, + "MQ":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78]2\\d{2}" + }, + "MR":{ + "fmt":"%C" + }, + "MS":{ + "fmt":"%C" + }, + "MT":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"[A-Z]{3} ?\\d{2,4}" + }, + "MU":{ + "fmt":"%N%n%O%n%A%n%Z%n%C", + "zip":"\\d{3}(?:\\d{2}|[A-Z]{2}\\d{3})" + }, + "MV":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}" + }, + "MW":{ + "fmt":"%N%n%O%n%A%n%C %X" + }, + "MX":{ + "fmt":"%N%n%O%n%A%n%D%n%Z %C, %S", + "require":"ACSZ", + "state_name_type":"state", + "sublocality_name_type":"neighborhood", + "zip":"\\d{5}" + }, + "MY":{ + "fmt":"%N%n%O%n%A%n%D%n%Z %C%n%S", + "require":"ACZ", + "state_name_type":"state", + "sublocality_name_type":"village_township", + "zip":"\\d{5}" + }, + "MZ":{ + "fmt":"%N%n%O%n%A%n%Z %C%S", + "zip":"\\d{4}" + }, + "NA":{ + "fmt":"%N%n%O%n%A%n%Cn%Z", + "zip":"\\d{5}" + }, + "NC":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"988\\d{2}" + }, + "NE":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "NG":{ + "fmt":"%N%n%O%n%A%n%D%n%C %Z%n%S", + "state_name_type":"state", + "zip":"\\d{6}" + }, + "NI":{ + "fmt":"%N%n%O%n%A%n%Z%n%C, %S", + "state_name_type":"department", + "zip":"\\d{5}" + }, + "NL":{ + "fmt":"%O%n%N%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{4} ?[A-Z]{2}" + }, + "NO":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "locality_name_type":"post_town", + "require":"ACZ", + "zip":"\\d{4}" + }, + "NP":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}" + }, + "NR":{ + "fmt":"%N%n%O%n%A%n%S", + "require":"AS", + "state_name_type":"district" + }, + "NU":{ + "fmt":"%C" + }, + "NZ":{ + "fmt":"%N%n%O%n%A%n%D%n%C %Z", + "require":"ACZ", + "zip":"\\d{4}" + }, + "OM":{ + "fmt":"%N%n%O%n%A%n%Z%n%C", + "zip":"(?:PC )?\\d{3}" + }, + "PA":{ + "fmt":"%N%n%O%n%A%n%C%n%S" + }, + "PE":{ + "fmt":"%N%n%O%n%A%n%C %Z%n%S", + "locality_name_type":"district", + "zip":"(?:LIMA \\d{1,2}|CALLAO 0?\\d)|[0-2]\\d{4}" + }, + "PF":{ + "fmt":"%N%n%O%n%A%n%Z %C %S", + "require":"ACSZ", + "state_name_type":"island", + "zip":"987\\d{2}" + }, + "PG":{ + "fmt":"%N%n%O%n%A%n%C %Z %S", + "require":"ACS", + "zip":"\\d{3}" + }, + "PH":{ + "fmt":"%N%n%O%n%A%n%D, %C%n%Z %S", + "zip":"\\d{4}" + }, + "PK":{ + "fmt":"%N%n%O%n%A%n%C-%Z", + "zip":"\\d{5}" + }, + "PL":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{2}-\\d{3}" + }, + "PM":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78]5\\d{2}" + }, + "PN":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"ACZ", + "zip":"PCRN 1ZZ" + }, + "PR":{ + "fmt":"%N%n%O%n%A%n%C PR %Z", + "postprefix":"PR ", + "require":"ACZ", + "zip":"(00[679]\\d{2})(?:[ \\-](\\d{4}))?", + "zip_name_type":"zip" + }, + "PS":{ + "fmt":"%C" + }, + "PT":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{4}-\\d{3}" + }, + "PY":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "QA":{ + "fmt":"%C" + }, + "RE":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"9[78]4\\d{2}" + }, + "RO":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{6}" + }, + "RS":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5,6}" + }, + "RU":{ + "fmt":"%N%n%O%n%A%n%C%n%S%n%Z", + "lfmt":"%N%n%O%n%A%n%C%n%S%n%Z", + "require":"ACSZ", + "state_name_type":"oblast", + "zip":"\\d{6}" + }, + "RW":{ + "fmt":"%C" + }, + "SA":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"\\d{5}" + }, + "SB":{ + "fmt":"%C" + }, + "SC":{ + "fmt":"%N%n%O%n%A%n%C%n%S", + "state_name_type":"island" + }, + "SE":{ + "fmt":"%O%n%N%n%A%nSE-%Z %C", + "locality_name_type":"post_town", + "postprefix":"SE-", + "require":"ACZ", + "zip":"\\d{3} ?\\d{2}" + }, + "SG":{ + "fmt":"%N%n%O%n%A%nSINGAPORE %Z", + "require":"AZ", + "zip":"\\d{6}" + }, + "SH":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"ACZ", + "zip":"(?:ASCN|STHL) 1ZZ" + }, + "SI":{ + "fmt":"%N%n%O%n%A%nSI-%Z %C", + "postprefix":"SI-", + "zip":"\\d{4}" + }, + "SJ":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "locality_name_type":"post_town", + "require":"ACZ", + "zip":"\\d{4}" + }, + "SK":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"ACZ", + "zip":"\\d{3} ?\\d{2}" + }, + "SL":{ + "fmt":"%C" + }, + "SM":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "require":"AZ", + "zip":"4789\\d" + }, + "SN":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "SO":{ + "fmt":"%N%n%O%n%A%n%C, %S %Z", + "require":"ACS", + "zip":"[A-Z]{2} ?\\d{5}" + }, + "SR":{ + "fmt":"%N%n%O%n%A%n%C%n%S" + }, + "SS":{ + "fmt":"%C" + }, + "ST":{ + "fmt":"%C" + }, + "SV":{ + "fmt":"%N%n%O%n%A%n%Z-%C%n%S", + "require":"ACS", + "zip":"CP [1-3][1-7][0-2]\\d" + }, + "SX":{ + "fmt":"%C" + }, + "SZ":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"[HLMS]\\d{3}" + }, + "TA":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "zip":"TDCU 1ZZ" + }, + "TC":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"ACZ", + "zip":"TKCA 1ZZ" + }, + "TD":{ + "fmt":"%C" + }, + "TF":{ + "fmt":"%C" + }, + "TG":{ + "fmt":"%C" + }, + "TH":{ + "fmt":"%N%n%O%n%A%n%D %C%n%S %Z", + "lfmt":"%N%n%O%n%A%n%D, %C%n%S %Z", + "zip":"\\d{5}" + }, + "TJ":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{6}" + }, + "TK":{ + "fmt":"%C" + }, + "TL":{ + "fmt":"%C" + }, + "TM":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{6}" + }, + "TN":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4}" + }, + "TO":{ + "fmt":"%C" + }, + "TR":{ + "fmt":"%N%n%O%n%A%n%Z %C/%S", + "locality_name_type":"district", + "require":"ACZ", + "zip":"\\d{5}" + }, + "TT":{ + "fmt":"%C" + }, + "TV":{ + "fmt":"%N%n%O%n%A%n%C%n%S", + "state_name_type":"island" + }, + "TW":{ + "fmt":"%Z%n%S%C%n%A%n%O%n%N", + "lfmt":"%N%n%O%n%A%n%C, %S %Z", + "require":"ACSZ", + "state_name_type":"county", + "zip":"\\d{3}(?:\\d{2,3})?" + }, + "TZ":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{4,5}" + }, + "UA":{ + "fmt":"%N%n%O%n%A%n%C%n%S%n%Z", + "lfmt":"%N%n%O%n%A%n%C%n%S%n%Z", + "require":"ACZ", + "state_name_type":"oblast", + "zip":"\\d{5}" + }, + "UG":{ + "fmt":"%C" + }, + "US":{ + "fmt":"%N%n%O%n%A%n%C, %S %Z", + "require":"ACSZ", + "state_name_type":"state", + "zip":"\\d{5}", + "zip_name_type":"zip", + "sub_keys": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "AA", + "AE", + "AP", + "CA", + "CO", + "CT", + "DE", + "DC", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "FM", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "sub_labels": [ + "Alabama", + "Alaska", + "American Samoa", + "Arizona", + "Arkansas", + "Armed Forces (AA)", + "Armed Forces (AE)", + "Armed Forces (AP)", + "California", + "Colorado", + "Connecticut", + "Delaware", + "District of Columbia", + "Florida", + "Georgia", + "Guam", + "Hawaii", + "Idaho", + "Illinois", + "Indiana", + "Iowa", + "Kansas", + "Kentucky", + "Louisiana", + "Maine", + "Marshall Islands", + "Maryland", + "Massachusetts", + "Michigan", + "Micronesia", + "Minnesota", + "Mississippi", + "Missouri", + "Montana", + "Nebraska", + "Nevada", + "New Hampshire", + "New Jersey", + "New Mexico", + "New York", + "North Carolina", + "North Dakota", + "Northern Mariana Islands", + "Ohio", + "Oklahoma", + "Oregon", + "Palau", + "Pennsylvania", + "Puerto Rico", + "Rhode Island", + "South Carolina", + "South Dakota", + "Tennessee", + "Texas", + "Utah", + "Vermont", + "Virgin Islands", + "Virginia", + "Washington", + "West Virginia", + "Wisconsin", + "Wyoming" + ] + }, + "UY":{ + "fmt":"%N%n%O%n%A%n%Z %C %S", + "zip":"\\d{5}" + }, + "UZ":{ + "fmt":"%N%n%O%n%A%n%Z %C%n%S", + "zip":"\\d{6}" + }, + "VA":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"00120" + }, + "VC":{ + "fmt":"%N%n%O%n%A%n%C %Z", + "zip":"VC\\d{4}" + }, + "VE":{ + "fmt":"%N%n%O%n%A%n%C %Z, %S", + "require":"ACS", + "state_name_type":"state", + "zip":"\\d{4}" + }, + "VG":{ + "fmt":"%N%n%O%n%A%n%C%n%Z", + "require":"A", + "zip":"VG\\d{4}" + }, + "VN":{ + "fmt":"%N%n%O%n%A%n%C%n%S %Z", + "lfmt":"%N%n%O%n%A%n%C%n%S %Z", + "zip":"\\d{5}\\d?" + }, + "VU":{ + "fmt":"%C" + }, + "WF":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"986\\d{2}" + }, + "WS":{ + "fmt":"%C" + }, + "XK":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"[1-7]\\d{4}" + }, + "YE":{ + "fmt":"%C" + }, + "YT":{ + "fmt":"%O%n%N%n%A%n%Z %C %X", + "require":"ACZ", + "zip":"976\\d{2}" + }, + "ZA":{ + "fmt":"%N%n%O%n%A%n%D%n%C%n%Z", + "require":"ACZ", + "zip":"\\d{4}" + }, + "ZM":{ + "fmt":"%N%n%O%n%A%n%Z %C", + "zip":"\\d{5}" + }, + "ZW":{ + "fmt":"%C" + } +} diff --git a/StripeUICore/StripeUICore/Resources/Localizations/bg-BG.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/bg-BG.lproj/Localizable.strings new file mode 100644 index 00000000..3ec5f7f3 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/bg-BG.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (по желание)"; + +"Account number" = "Номер на сметката"; + +"Address" = "Адрес"; + +"Address line 1" = "Ред 1 на адреса"; + +"Address line 2" = "Ред 2 на адреса"; + +"Area" = "Район"; + +"BLIK code" = "BLIK код"; + +"BSB number" = "BSB номер"; + +"Bank account •••• %@" = "Банкова сметка •••• %@"; + +"Billing address is same as shipping" = "Адресът за фактуриране е същият като за доставка"; + +"Cancel" = "Отмяна"; + +"Card brand" = "Марка на картата"; + +"City" = "Град"; + +"Code field" = "Поле за код"; + +"Company" = "Компания"; + +"Continue" = "Продължаване"; + +"Country" = "Държава"; + +"Country or region" = "Държава или регион"; + +"County" = "Окръг"; + +"Date is empty." = "Датата е празна."; + +"Department" = "Отдел"; + +"District" = "Окръг"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Искате ли да затворите този формуляр?"; + +"Done" = "Готово"; + +"Double tap to edit" = "Докоснете два пъти, за да редактирате"; + +"Edit" = "Редактиране"; + +"Eircode" = "Eircode"; + +"Email" = "Имейл"; + +"Emirate" = "Емирство"; + +"Error" = "Грешка"; + +"First" = "Собствено"; + +"Full name" = "Трите имена"; + +"Incomplete phone number" = "Непълен телефонен номер"; + +"Invalid UPI ID" = "Невалиден идентификационен номер на UPI"; + +"Island" = "Остров"; + +"Last" = "Фамилия"; + +"Name" = "Име"; + +"Name on account" = "Име на сметката"; + +"OK" = "OK"; + +"Oblast" = "Област"; + +"Other" = "Друга"; + +"Parish" = "Енория"; + +"Phone number" = "Телефонен номер"; + +"Postal code" = "Пощенски код"; + +"Prefecture" = "Префектура"; + +"Province" = "Област"; + +"Remove" = "Премахване"; + +"Remove bank account?" = "Премахване на банкова сметка?"; + +"Remove card" = "Премахване на банкова карта"; + +"Search" = "Търсене"; + +"Select card brand (optional)" = "Изберете марка на картата (по избор)"; + +"Sort code" = "Идентификационен банков код"; + +"State" = "Щат"; + +"Suburb" = "Предградие"; + +"Suburb or city" = "Предградие или град"; + +"The BSB you entered is incomplete." = "BSB, който сте въвели, е непълен."; + +"The ID number you entered is incomplete." = "Идентификационният номер, който сте въвели, е непълен."; + +"The account number you entered is incomplete." = "Въведеният от Вас номер на сметката е невалиден."; + +"The sort code you entered is invalid." = "Идентификационният банков код, който въведохте, е невалиден."; + +"Town or city" = "Град"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Неуспешно анализиране на телефонния номер"; + +"Use rotor to access links" = "Използване на ротор за достъп до връзки"; + +"Your BLIK code is incomplete." = "Вашият BLIK код е непълен."; + +"Your BLIK code is invalid." = "Вашият BLIK код е невалиден."; + +"Your ZIP is incomplete." = "Вашият ZIP код е непълен."; + +"Your email is invalid." = "Вашият имейл е невалиден."; + +"Your payment information will not be saved." = "Вашата информация за плащане няма да бъде запазена."; + +"Your postal code is incomplete." = "Вашият пощенски код е непълен."; + +"ZIP" = "п.к."; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/ca-ES.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/ca-ES.lproj/Localizable.strings new file mode 100644 index 00000000..57eedb2b --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/ca-ES.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opcional)"; + +"Account number" = "Número de Compte"; + +"Address" = "Adreça"; + +"Address line 1" = "Línia de l'adreça 1"; + +"Address line 2" = "Línia de l'adreça 2"; + +"Area" = "Àrea"; + +"BLIK code" = "Codi BLIK"; + +"BSB number" = "Número BSB"; + +"Bank account •••• %@" = "Compte bancari •••• %@"; + +"Billing address is same as shipping" = "L'adreça de facturació és la mateixa que la d'enviament"; + +"Cancel" = "Cancel·la"; + +"Card brand" = "Marca de la targeta"; + +"City" = "Ciutat"; + +"Code field" = "Camp pel codi"; + +"Company" = "Empresa"; + +"Continue" = "Continua"; + +"Country" = "País"; + +"Country or region" = "País o regió"; + +"County" = "Comtat"; + +"Date is empty." = "La data és buida."; + +"Department" = "Departament"; + +"District" = "Districte"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Voleu tancar el formulari?"; + +"Done" = "Fet"; + +"Double tap to edit" = "Toca dos cops per editar"; + +"Edit" = "Editar"; + +"Eircode" = "Eircode"; + +"Email" = "Correu electrònic"; + +"Emirate" = "Emirat"; + +"Error" = "Error"; + +"First" = "Nom"; + +"Full name" = "Nom complet"; + +"Incomplete phone number" = "Número de telèfon incomplet"; + +"Invalid UPI ID" = "ID d'UPI no vàlid"; + +"Island" = "Illa"; + +"Last" = "Cognom"; + +"Name" = "Nom"; + +"Name on account" = "Nom al compte"; + +"OK" = "D'acord"; + +"Oblast" = "Óblast"; + +"Other" = "Un altre"; + +"Parish" = "Parròquia"; + +"Phone number" = "Número de telèfon"; + +"Postal code" = "Codi postal"; + +"Prefecture" = "Prefectura"; + +"Province" = "Província"; + +"Remove" = "Eliminar"; + +"Remove bank account?" = "Voleu eliminar el compte bancari?"; + +"Remove card" = "Elimina la targeta"; + +"Search" = "Cerca"; + +"Select card brand (optional)" = "Seleccioneu la marca de la targeta (opcional)"; + +"Sort code" = "Codi de classificació"; + +"State" = "País"; + +"Suburb" = "Suburbi"; + +"Suburb or city" = "Suburbi o ciutat"; + +"The BSB you entered is incomplete." = "El BSB que has introduït és incomplet."; + +"The ID number you entered is incomplete." = "El número d'identificació que has introduït és incomplet."; + +"The account number you entered is incomplete." = "El número de compte que heu introduït és incomplet."; + +"The sort code you entered is invalid." = "El codi d'ordenació que heu introduït no és vàlid."; + +"Town or city" = "Municipi o ciutat"; + +"UPI ID" = "ID d'UPI"; + +"Unable to parse phone number" = "No s'ha pogut analitzar el número de telèfon"; + +"Use rotor to access links" = "Empreu el rotor per accedir als enllaços"; + +"Your BLIK code is incomplete." = "El codi BLIK no està complet."; + +"Your BLIK code is invalid." = "El codi BLIK no és vàlid."; + +"Your ZIP is incomplete." = "El teu ZIP no està complet."; + +"Your email is invalid." = "L'adreça electrònica no és vàlida."; + +"Your payment information will not be saved." = "La informació de pagament no es desarà."; + +"Your postal code is incomplete." = "El teu codi postal no està complet."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/cs-CZ.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/cs-CZ.lproj/Localizable.strings new file mode 100644 index 00000000..10cfe5b9 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/cs-CZ.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (volitelné)"; + +"Account number" = "Číslo účtu"; + +"Address" = "Adresa"; + +"Address line 1" = "1. řádek adresy"; + +"Address line 2" = "2. řádek adresy"; + +"Area" = "Oblast"; + +"BLIK code" = "Kód BLIK"; + +"BSB number" = "Číslo BSB"; + +"Bank account •••• %@" = "Bankovní účet •••• %@"; + +"Billing address is same as shipping" = "Fakturační adresa je stejná jako dodací"; + +"Cancel" = "Zrušit"; + +"Card brand" = "Značka karty"; + +"City" = "Město"; + +"Code field" = "Pole pro kód"; + +"Company" = "Společnost"; + +"Continue" = "Pokračovat"; + +"Country" = "Země"; + +"Country or region" = "Země nebo region"; + +"County" = "Okres"; + +"Date is empty." = "Datum je prázdné."; + +"Department" = "Oddělení"; + +"District" = "Okrsek"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Chcete tento formulář zavřít?"; + +"Done" = "Hotovo"; + +"Double tap to edit" = "Dvakrát klepněte pro úpravu"; + +"Edit" = "Upravit"; + +"Eircode" = "Eircode"; + +"Email" = "E-mail"; + +"Emirate" = "Emirát"; + +"Error" = "Chyba"; + +"First" = "Jméno"; + +"Full name" = "Celé jméno"; + +"Incomplete phone number" = "Neúplné telefonní číslo"; + +"Invalid UPI ID" = "Neplatné UPI ID"; + +"Island" = "Ostrov"; + +"Last" = "Příjmení"; + +"Name" = "Jméno"; + +"Name on account" = "Jméno na účtu"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Jiné"; + +"Parish" = "Obec"; + +"Phone number" = "Telefonní číslo"; + +"Postal code" = "Poštovní směrovací číslo"; + +"Prefecture" = "Prefektura"; + +"Province" = "Provincie"; + +"Remove" = "Odebrat"; + +"Remove bank account?" = "Odebrat bankovní účet?"; + +"Remove card" = "Odebrat kartu"; + +"Search" = "Hledat"; + +"Select card brand (optional)" = "Vyberte značku karty (volitelné)"; + +"Sort code" = "Kód řazení"; + +"State" = "Stát"; + +"Suburb" = "Předměstí"; + +"Suburb or city" = "Předměstí nebo město"; + +"The BSB you entered is incomplete." = "Zadaný BSB je neúplný."; + +"The ID number you entered is incomplete." = "Identifikační číslo, které jste zadali, je neúplné."; + +"The account number you entered is incomplete." = "Zadané číslo účtu je neúplné."; + +"The sort code you entered is invalid." = "Zadaný lokální bankovní kód je neplatný."; + +"Town or city" = "Město nebo velkoměsto"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Nelze analyzovat telefonní číslo"; + +"Use rotor to access links" = "Přístup k odkazům pomocí rotoru"; + +"Your BLIK code is incomplete." = "Váš kód BLIK je neúplný."; + +"Your BLIK code is invalid." = "Váš kód BLIK je neplatný."; + +"Your ZIP is incomplete." = "PSČ je neúplné."; + +"Your email is invalid." = "Váš e-mail je neplatný."; + +"Your payment information will not be saved." = "Vaše platební údaje nebudou uloženy."; + +"Your postal code is incomplete." = "Vaše poštovní směrovací číslo je neúplné."; + +"ZIP" = "PSČ"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/da.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/da.lproj/Localizable.strings new file mode 100644 index 00000000..af258a76 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/da.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (valgfrit)"; + +"Account number" = "Kontonummer"; + +"Address" = "Adresse"; + +"Address line 1" = "Adresselinje 1"; + +"Address line 2" = "Adresselinje 2"; + +"Area" = "Område"; + +"BLIK code" = "BLIK-kode"; + +"BSB number" = "BSB-nummer"; + +"Bank account •••• %@" = "Bankkonto •••• %@"; + +"Billing address is same as shipping" = "Faktureringsadresse er den samme som forsendelse"; + +"Cancel" = "Annuller"; + +"Card brand" = "Kortbrand"; + +"City" = "By"; + +"Code field" = "Kodefelt"; + +"Company" = "Virksomhed"; + +"Continue" = "Fortsæt"; + +"Country" = "Land"; + +"Country or region" = "Land eller region"; + +"County" = "Amt"; + +"Date is empty." = "Dato er tom."; + +"Department" = "Afdeling"; + +"District" = "Distrikt"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Vil du lukke denne formular?"; + +"Done" = "Færdig"; + +"Double tap to edit" = "Tryk to gange for at redigere"; + +"Edit" = "Rediger"; + +"Eircode" = "Eircode"; + +"Email" = "E-mailadresse"; + +"Emirate" = "Emirat"; + +"Error" = "Fejl"; + +"First" = "Første"; + +"Full name" = "Fulde navn"; + +"Incomplete phone number" = "Ufuldstændigt telefonnummer"; + +"Invalid UPI ID" = "Ugyldigt UPI ID"; + +"Island" = "Ø"; + +"Last" = "Sidste"; + +"Name" = "Navn"; + +"Name on account" = "Navn på konto"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Andet"; + +"Parish" = "Sogn"; + +"Phone number" = "Telefonnummer"; + +"Postal code" = "Postnummer"; + +"Prefecture" = "Præfektur"; + +"Province" = "Provins"; + +"Remove" = "Fjern"; + +"Remove bank account?" = "Vil du fjerne bankkontoen?"; + +"Remove card" = "Fjern kort"; + +"Search" = "Søg"; + +"Select card brand (optional)" = "Vælg kortbrand (valgfrit)"; + +"Sort code" = "Sort code"; + +"State" = "Stat"; + +"Suburb" = "Forstad"; + +"Suburb or city" = "Forstad eller storby"; + +"The BSB you entered is incomplete." = "Den BSB, du angav, er ufuldstændig."; + +"The ID number you entered is incomplete." = "Det indtastede ID-nummer er ikke komplet."; + +"The account number you entered is incomplete." = "Det indtastede kontonummer er ikke komplet."; + +"The sort code you entered is invalid." = "Den sorteringskode, du angav, er ugyldig."; + +"Town or city" = "Landsby eller by"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Kunne ikke analysere telefonnummer"; + +"Use rotor to access links" = "Brug hjulet til at tilgå links"; + +"Your BLIK code is incomplete." = "Din BLIK-kode er ufuldstændig."; + +"Your BLIK code is invalid." = "Din BLIK-kode er ugyldig."; + +"Your ZIP is incomplete." = "Dit postnummer er ikke fuldendt."; + +"Your email is invalid." = "Din e-mail er ugyldig."; + +"Your payment information will not be saved." = "Dine betalingsoplysninger gemmes ikke."; + +"Your postal code is incomplete." = "Dit postnummer er ikke fuldendt."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/de.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/de.lproj/Localizable.strings new file mode 100644 index 00000000..f739542d --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/de.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (optional)"; + +"Account number" = "Kontonummer"; + +"Address" = "Adresse"; + +"Address line 1" = "Adresszeile 1"; + +"Address line 2" = "Adresszeile 2"; + +"Area" = "Region"; + +"BLIK code" = "BLIK-Code"; + +"BSB number" = "BSB-Nummer"; + +"Bank account •••• %@" = "Bankkonto •••• %@"; + +"Billing address is same as shipping" = "Rechnungsadresse und Versandadresse sind identisch."; + +"Cancel" = "Abbrechen"; + +"Card brand" = "Kartenmarke"; + +"City" = "Ort"; + +"Code field" = "Code-Feld"; + +"Company" = "Unternehmen"; + +"Continue" = "Weiter"; + +"Country" = "Land"; + +"Country or region" = "Land oder Region"; + +"County" = "Kreis/Bezirk"; + +"Date is empty." = "Datum ist leer."; + +"Department" = "Departement"; + +"District" = "Distrikt"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Möchten Sie dieses Formular schließen?"; + +"Done" = "Fertig"; + +"Double tap to edit" = "Doppelklick zum Bearbeiten"; + +"Edit" = "Bearbeiten"; + +"Eircode" = "Eircode"; + +"Email" = "E-Mail"; + +"Emirate" = "Emirat"; + +"Error" = "Fehler"; + +"First" = "Vorname"; + +"Full name" = "Vollständiger Name"; + +"Incomplete phone number" = "Unvollständige Telefonnummer"; + +"Invalid UPI ID" = "Ungültige UPI-ID"; + +"Island" = "Insel"; + +"Last" = "Name"; + +"Name" = "Name"; + +"Name on account" = "Name des/der Kontoinhaber/in"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Sonstiges"; + +"Parish" = "Parish"; + +"Phone number" = "Telefonnummer"; + +"Postal code" = "Postleitzahl"; + +"Prefecture" = "Präfektur"; + +"Province" = "Provinz / Kanton"; + +"Remove" = "Entfernen"; + +"Remove bank account?" = "Bankkonto entfernen?"; + +"Remove card" = "Karte entfernen"; + +"Search" = "Suchen"; + +"Select card brand (optional)" = "Kartenmarke auswählen (optional)"; + +"Sort code" = "Bankleitzahl"; + +"State" = "Bundesland"; + +"Suburb" = "Vorort"; + +"Suburb or city" = "Vorort oder Stadt"; + +"The BSB you entered is incomplete." = "Die eingegebene BSB-Nummer ist unvollständig."; + +"The ID number you entered is incomplete." = "Die eingegebene Ausweisnummer ist unvollständig."; + +"The account number you entered is incomplete." = "Die von Ihnen eingegebene Kontonummer ist unvollständig."; + +"The sort code you entered is invalid." = "Die eingegebene Bankleitzahl ist ungültig."; + +"Town or city" = "Stadt oder Ort"; + +"UPI ID" = "UPI-ID"; + +"Unable to parse phone number" = "Telefonnummer kann nicht geparst werden"; + +"Use rotor to access links" = "Rotor zum Zugriff auf Links nutzen"; + +"Your BLIK code is incomplete." = "Ihr BLIK-Code ist unvollständig."; + +"Your BLIK code is invalid." = "Ihr BLIK code ist ungültig."; + +"Your ZIP is incomplete." = "PLZ ist unvollständig."; + +"Your email is invalid." = "Ihre E-Mail-Adresse ist ungültig."; + +"Your payment information will not be saved." = "Ihre Zahlungsinformationen werden nicht gespeichert."; + +"Your postal code is incomplete." = "Postleitzahl ist unvollständig."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/el-GR.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/el-GR.lproj/Localizable.strings new file mode 100644 index 00000000..6a1b8a38 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/el-GR.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (προαιρετικό)"; + +"Account number" = "Αριθμός λογαριασμού"; + +"Address" = "Διεύθυνση"; + +"Address line 1" = "Γραμμή διεύθυνσης 1"; + +"Address line 2" = "Γραμμή διεύθυνσης 2"; + +"Area" = "Περιοχή"; + +"BLIK code" = "Κωδικός BLIK"; + +"BSB number" = "Αριθμός BSB"; + +"Bank account •••• %@" = "Τραπεζικός λογαριασμός •••• %@"; + +"Billing address is same as shipping" = "Η διεύθυνση τιμολόγησης είναι ίδια με τη διεύθυνση αποστολής"; + +"Cancel" = "Ακύρωση"; + +"Card brand" = "Επωνυμία κάρτας"; + +"City" = "Πόλη"; + +"Code field" = "Πεδίο κωδικού"; + +"Company" = "Εταιρεία"; + +"Continue" = "Συνέχεια"; + +"Country" = "Χώρα"; + +"Country or region" = "Χώρα ή περιοχή"; + +"County" = "Κομητεία"; + +"Date is empty." = "Η ημερομηνία είναι κενή."; + +"Department" = "Τμήμα"; + +"District" = "Περιφέρεια"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Θέλετε να κλείσετε αυτή τη φόρμα;"; + +"Done" = "Τέλος"; + +"Double tap to edit" = "Πατήστε δύο φορές για επεξεργασία"; + +"Edit" = "Επεξεργασία"; + +"Eircode" = "Ταχυδρομικός κώδικας"; + +"Email" = "Διεύθυνση ηλεκτρονικού ταχυδρομείου"; + +"Emirate" = "Εμιράτο"; + +"Error" = "Σφάλμα"; + +"First" = "Όνομα"; + +"Full name" = "Πλήρες όνομα"; + +"Incomplete phone number" = "Ελλιπής αριθμός τηλεφώνου"; + +"Invalid UPI ID" = "Μη έγκυρο αναγνωριστικό UPI"; + +"Island" = "Νησί"; + +"Last" = "Επώνυμο"; + +"Name" = "Όνομα"; + +"Name on account" = "Όνομα στον λογαριασμό"; + +"OK" = "OK"; + +"Oblast" = "Περιφέρεια"; + +"Other" = "Άλλο"; + +"Parish" = "Δήμος"; + +"Phone number" = "Αριθμός τηλεφώνου"; + +"Postal code" = "Ταχυδρομικός κώδικας"; + +"Prefecture" = "Νομός"; + +"Province" = "Επαρχία"; + +"Remove" = "Αφαίρεση"; + +"Remove bank account?" = "Αφαίρεση τραπεζικού λογαριασμού;"; + +"Remove card" = "Αφαίρεση κάρτας"; + +"Search" = "Αναζήτηση"; + +"Select card brand (optional)" = "Επιλέξτε επωνυμία κάρτας (προαιρετικό)"; + +"Sort code" = "Κωδικός sort"; + +"State" = "Πολιτεία"; + +"Suburb" = "Προάστιο"; + +"Suburb or city" = "Προάστιο ή πόλη"; + +"The BSB you entered is incomplete." = "Ο κωδικός BSB που εισάγατε είναι ατελής."; + +"The ID number you entered is incomplete." = "Ο αριθμός ταυτότητας που εισαγάγατε είναι ελλιπής."; + +"The account number you entered is incomplete." = "Ο αριθμός λογαριασμού που εισαγάγατε είναι ελλιπής."; + +"The sort code you entered is invalid." = "Ο κωδικός sort που εισαγάγατε δεν είναι έγκυρος."; + +"Town or city" = "Κωμόπολη ή πόλη"; + +"UPI ID" = "Αναγνωριστικό UPI"; + +"Unable to parse phone number" = "Δεν ήταν δυνατή η ανάλυση του αριθμού τηλεφώνου"; + +"Use rotor to access links" = "Χρησιμοποιήστε τον ρότορα για να προσπελάσετε τους συνδέσμους"; + +"Your BLIK code is incomplete." = "Ο κωδικός σας BLIK είναι ελλιπής."; + +"Your BLIK code is invalid." = "Ο κωδικός σας BLIK δεν είναι έγκυρος."; + +"Your ZIP is incomplete." = "Ο ταχυδρομικός σας κώδικας είναι ελλιπής."; + +"Your email is invalid." = "Η διεύθυνση email σας δεν είναι έγκυρη."; + +"Your payment information will not be saved." = "Τα στοιχεία πληρωμής δε θα αποθηκευτούν."; + +"Your postal code is incomplete." = "Ο ταχυδρομικός κώδικάς σας είναι ελλιπής."; + +"ZIP" = "Τ.Κ."; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/en-GB.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/en-GB.lproj/Localizable.strings new file mode 100644 index 00000000..1a59524f --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/en-GB.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (optional)"; + +"Account number" = "Account number"; + +"Address" = "Address"; + +"Address line 1" = "Address line 1"; + +"Address line 2" = "Address line 2"; + +"Area" = "Area"; + +"BLIK code" = "BLIK code"; + +"BSB number" = "BSB number"; + +"Bank account •••• %@" = "Bank account •••• %@"; + +"Billing address is same as shipping" = "Billing address is same as shipping"; + +"Cancel" = "Cancel"; + +"Card brand" = "Card brand"; + +"City" = "City"; + +"Code field" = "Code field"; + +"Company" = "Company"; + +"Continue" = "Continue"; + +"Country" = "Country"; + +"Country or region" = "Country or region"; + +"County" = "County"; + +"Date is empty." = "Date is empty."; + +"Department" = "Department"; + +"District" = "District"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Do you want to close this form?"; + +"Done" = "Done"; + +"Double tap to edit" = "Double tap to edit"; + +"Edit" = "Edit"; + +"Eircode" = "Eircode"; + +"Email" = "Email"; + +"Emirate" = "Emirate"; + +"Error" = "Error"; + +"First" = "First"; + +"Full name" = "Full name"; + +"Incomplete phone number" = "Incomplete phone number"; + +"Invalid UPI ID" = "Invalid UPI ID"; + +"Island" = "Island"; + +"Last" = "Last"; + +"Name" = "Name"; + +"Name on account" = "Name on account"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Other"; + +"Parish" = "Parish"; + +"Phone number" = "Phone number"; + +"Postal code" = "Postcode"; + +"Prefecture" = "Prefecture"; + +"Province" = "Province"; + +"Remove" = "Remove"; + +"Remove bank account?" = "Remove bank account?"; + +"Remove card" = "Remove card"; + +"Search" = "Search"; + +"Select card brand (optional)" = "Select card brand (optional)"; + +"Sort code" = "Sort code"; + +"State" = "State"; + +"Suburb" = "Suburb"; + +"Suburb or city" = "Suburb or city"; + +"The BSB you entered is incomplete." = "The BSB you entered is incomplete."; + +"The ID number you entered is incomplete." = "The ID number you entered is incomplete."; + +"The account number you entered is incomplete." = "The account number you entered is incomplete."; + +"The sort code you entered is invalid." = "The sort code you entered is invalid."; + +"Town or city" = "Town or city"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Unable to parse phone number"; + +"Use rotor to access links" = "Use rotor to access links"; + +"Your BLIK code is incomplete." = "Your BLIK code is incomplete."; + +"Your BLIK code is invalid." = "Your BLIK code is invalid."; + +"Your ZIP is incomplete." = "Your ZIP is incomplete."; + +"Your email is invalid." = "Your email is invalid."; + +"Your payment information will not be saved." = "Your payment information will not be saved."; + +"Your postal code is incomplete." = "Your postcode is incomplete."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings new file mode 100644 index 00000000..375de00f --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings @@ -0,0 +1,220 @@ +/* The label of a text field that is optional. For example, 'Email (optional)' or 'Name (optional) */ +"%@ (optional)" = "%@ (optional)"; + +/* Caption for account number */ +"Account number" = "Account number"; + +/* Caption for Address field on address form +Section header for address fields */ +"Address" = "Address"; + +/* Address line 1 placeholder for billing address form.\nLabel for address line 1 field */ +"Address line 1" = "Address line 1"; + +/* Label for address line 2 field */ +"Address line 2" = "Address line 2"; + +/* Label of an address field */ +"Area" = "Area"; + +/* Content for alert popup prompting to confirm removing a saved bank account. e.g. 'Bank account •••• 4242' */ +"Bank account •••• %@" = "Bank account •••• %@"; + +/* Label for a checkbox that makes customers billing address same as their shipping address */ +"Billing address is same as shipping" = "Billing address is same as shipping"; + +/* Label for BLIK code number field on form */ +"BLIK code" = "BLIK code"; + +/* Placeholder for AU BECS BSB number */ +"BSB number" = "BSB number"; + +/* Button title to cancel action in an alert */ +"Cancel" = "Cancel"; + +/* Label an input field to update card brand */ +"Card brand" = "Card brand"; + +/* Caption for City field on address form */ +"City" = "City"; + +/* Accessibility label describing a field for entering a login code */ +"Code field" = "Code field"; + +/* Label for Company field on form */ +"Company" = "Company"; + +/* Text for continue button */ +"Continue" = "Continue"; + +/* Caption for Country field on address form */ +"Country" = "Country"; + +/* Country selector and postal code entry form header title\nLabel of an address field */ +"Country or region" = "Country or region"; + +/* Caption for County field on address form (only countries that use county, like United Kingdom) +Label of an address field */ +"County" = "County"; + +/* Error message for empty date. */ +"Date is empty." = "Date is empty."; + +/* Label of an address field */ +"Department" = "Department"; + +/* Label for the district field on an address form */ +"District" = "District"; + +/* Label of an address field */ +"Do Si" = "Do Si"; + +/* Used as the title for prompting the user if they want to close the sheet */ +"Do you want to close this form?" = "Do you want to close this form?"; + +/* Done button title */ +"Done" = "Done"; + +/* Accessibility hint for a text field */ +"Double tap to edit" = "Double tap to edit"; + +/* Button title to enter editing mode */ +"Edit" = "Edit"; + +/* Label of an address field */ +"Eircode" = "Eircode"; + +/* Label for Email field on form */ +"Email" = "Email"; + +/* Label of an address field */ +"Emirate" = "Emirate"; + +/* Text for error labels */ +"Error" = "Error"; + +/* Label for first (given) name field */ +"First" = "First"; + +/* Label for Full name field on form */ +"Full name" = "Full name"; + +/* Error description for incomplete phone number */ +"Incomplete phone number" = "Incomplete phone number"; + +/* Error message when UPI ID is invalid */ +"Invalid UPI ID" = "Invalid UPI ID"; + +/* Label of an address field */ +"Island" = "Island"; + +/* Label for last (family) name field */ +"Last" = "Last"; + +/* Label for Name field on form */ +"Name" = "Name"; + +/* Label for Name on account field on form */ +"Name on account" = "Name on account"; + +/* Label of an address field */ +"Oblast" = "Oblast"; + +/* ok button */ +"OK" = "OK"; + +/* An option in a dropdown selector indicating the customer's desired selection is not in the list. e.g., 'Choose your bank: Bank1, Bank2, Other' */ +"Other" = "Other"; + +/* Label of an address field */ +"Parish" = "Parish"; + +/* Caption for Phone number field on address form */ +"Phone number" = "Phone number"; + +/* Label of an address field +Short string for postal code (text used in non-US countries) */ +"Postal code" = "Postal code"; + +/* Label of an address field */ +"Prefecture" = "Prefecture"; + +/* Caption for Province field on address form (only countries that use province, like Canada) +Label of an address field */ +"Province" = "Province"; + +/* Button title for confirmation alert to remove a saved payment method */ +"Remove" = "Remove"; + +/* Title for confirmation alert to remove a saved bank account payment method */ +"Remove bank account?" = "Remove bank account?"; + +/* Label on a button for removing a card */ +"Remove card" = "Remove card"; + +/* Title of a button with a 🔍 (magnifying glass) icon that starts a search when tapped */ +"Search" = "Search"; + +/* Message when a user is selecting a card brand in a dropdown */ +"Select card brand (optional)" = "Select card brand (optional)"; + +/* Placeholder for Bacs sort code (a bank routing number used in the UK and Ireland) */ +"Sort code" = "Sort code"; + +/* Caption for State field on address form (only countries that use state , like United States) +Label of an address field */ +"State" = "State"; + +/* Label of an address field */ +"Suburb" = "Suburb"; + +/* Label of an address field */ +"Suburb or city" = "Suburb or city"; + +/* Error description for incomplete account number */ +"The account number you entered is incomplete." = "The account number you entered is incomplete."; + +/* Error string displayed to user when they have entered an incomplete BSB number. */ +"The BSB you entered is incomplete." = "The BSB you entered is incomplete."; + +/* An error message. */ +"The ID number you entered is incomplete." = "The ID number you entered is incomplete."; + +/* Error string displayed to user when they have entered an invalid 'sort code' (a bank routing number used in the UK and Ireland) */ +"The sort code you entered is invalid." = "The sort code you entered is invalid."; + +/* Label of an address field */ +"Town or city" = "Town or city"; + +/* Error string when we can't parse a phone number */ +"Unable to parse phone number" = "Unable to parse phone number"; + +/* Label for UPI ID number field on form */ +"UPI ID" = "UPI ID"; + +/* Accessibility hint indicating to use the accessibility rotor to open links. The word 'rotor' should be localized to match Apple's language here: https://support.apple.com/HT204783 */ +"Use rotor to access links" = "Use rotor to access links"; + +/* Error message when BLIK code is incomplete */ +"Your BLIK code is incomplete." = "Your BLIK code is incomplete."; + +/* Error message when BLIK code is invalid */ +"Your BLIK code is invalid." = "Your BLIK code is invalid."; + +/* Error message when email is invalid */ +"Your email is invalid." = "Your email is invalid."; + +/* Used as the title for prompting the user if they want to close the sheet */ +"Your payment information will not be saved." = "Your payment information will not be saved."; + +/* Error message for when postal code in form is incomplete */ +"Your postal code is incomplete." = "Your postal code is incomplete."; + +/* Error message for when ZIP code in form is incomplete (US only) */ +"Your ZIP is incomplete." = "Your ZIP is incomplete."; + +/* Label of an address field +Short string for zip code (United States only) +Zip code placeholder US only */ +"ZIP" = "ZIP"; + diff --git a/StripeUICore/StripeUICore/Resources/Localizations/es-419.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/es-419.lproj/Localizable.strings new file mode 100644 index 00000000..7af638ac --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/es-419.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opcional)"; + +"Account number" = "Número de cuenta"; + +"Address" = "Dirección"; + +"Address line 1" = "Primera línea de la dirección"; + +"Address line 2" = "Segunda línea de la dirección"; + +"Area" = "Área"; + +"BLIK code" = "Código BLIK"; + +"BSB number" = "Número de BSB"; + +"Bank account •••• %@" = "Cuenta bancaria •••• %@"; + +"Billing address is same as shipping" = "La dirección de facturación es la misma que la de envío."; + +"Cancel" = "Cancelar"; + +"Card brand" = "Marca de la tarjeta"; + +"City" = "Ciudad"; + +"Code field" = "Campo del código"; + +"Company" = "Empresa"; + +"Continue" = "Continuar"; + +"Country" = "País"; + +"Country or region" = "País o región"; + +"County" = "Condado"; + +"Date is empty." = "La fecha está vacía"; + +"Department" = "Departamento"; + +"District" = "Distrito"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "¿Deseas cerrar este formulario?"; + +"Done" = "¡Listo!"; + +"Double tap to edit" = "Pulsa dos veces para editar"; + +"Edit" = "Editar"; + +"Eircode" = "Eircode"; + +"Email" = "Correo electrónico"; + +"Emirate" = "Emirato"; + +"Error" = "Error"; + +"First" = "Nombre"; + +"Full name" = "Nombre completo"; + +"Incomplete phone number" = "Número de teléfono incompleto"; + +"Invalid UPI ID" = "ID de UPI no válida"; + +"Island" = "Isla"; + +"Last" = "Apellido"; + +"Name" = "Nombre"; + +"Name on account" = "Nombre en la cuenta"; + +"OK" = "Aceptar"; + +"Oblast" = "Óblast"; + +"Other" = "Otro"; + +"Parish" = "Municipio"; + +"Phone number" = "Número de teléfono"; + +"Postal code" = "Código postal"; + +"Prefecture" = "Prefectura"; + +"Province" = "Provincia"; + +"Remove" = "Eliminar"; + +"Remove bank account?" = "¿Deseas eliminar la cuenta bancaria?"; + +"Remove card" = "Quitar tarjeta"; + +"Search" = "Buscar"; + +"Select card brand (optional)" = "Selecciona una marca de tarjeta (opcional)"; + +"Sort code" = "Código Sort"; + +"State" = "Estado"; + +"Suburb" = "Suburbio"; + +"Suburb or city" = "Suburbio o ciudad"; + +"The BSB you entered is incomplete." = "El BSB que ingresaste está incompleto."; + +"The ID number you entered is incomplete." = "El número de documento que ingresaste está incompleto."; + +"The account number you entered is incomplete." = "El número de cuenta que ingresaste está incompleto."; + +"The sort code you entered is invalid." = "El código Sort que ingresaste no es válido."; + +"Town or city" = "Pueblo o ciudad"; + +"UPI ID" = "ID de UPI"; + +"Unable to parse phone number" = "No se pudo analizar el número de teléfono."; + +"Use rotor to access links" = "Usa el rotor para acceder a los enlaces"; + +"Your BLIK code is incomplete." = "Tu código BLIK está incompleto."; + +"Your BLIK code is invalid." = "El código BLIK no es válido."; + +"Your ZIP is incomplete." = "El código postal está incompleto."; + +"Your email is invalid." = "El correo electrónico no es válido."; + +"Your payment information will not be saved." = "Tus datos de pago no se guardarán."; + +"Your postal code is incomplete." = "El código postal está incompleto."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/es.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/es.lproj/Localizable.strings new file mode 100644 index 00000000..44db4fcc --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/es.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opcional)"; + +"Account number" = "Número de cuenta"; + +"Address" = "Dirección"; + +"Address line 1" = "Primera línea de la dirección"; + +"Address line 2" = "Segunda línea de la dirección"; + +"Area" = "Área"; + +"BLIK code" = "Código BLIK"; + +"BSB number" = "Número de BSB"; + +"Bank account •••• %@" = "Cuenta bancaria •••• %@"; + +"Billing address is same as shipping" = "La dirección de facturación es la misma que la de envío."; + +"Cancel" = "Cancelar"; + +"Card brand" = "Marca de la tarjeta"; + +"City" = "Ciudad"; + +"Code field" = "Campo del código"; + +"Company" = "Empresa"; + +"Continue" = "Continuar"; + +"Country" = "País"; + +"Country or region" = "País o región"; + +"County" = "Condado"; + +"Date is empty." = "La fecha está vacía."; + +"Department" = "Departamento"; + +"District" = "Distrito"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "¿Deseas cerrar este formulario?"; + +"Done" = "Listo"; + +"Double tap to edit" = "Pulsa dos veces para editar"; + +"Edit" = "Modificar"; + +"Eircode" = "Eircode"; + +"Email" = "Correo electrónico"; + +"Emirate" = "Emirato"; + +"Error" = "Error"; + +"First" = "Nombre"; + +"Full name" = "Nombre completo"; + +"Incomplete phone number" = "Número de teléfono incompleto"; + +"Invalid UPI ID" = "ID de UPI no válida"; + +"Island" = "Isla"; + +"Last" = "Apellidos"; + +"Name" = "Nombre"; + +"Name on account" = "Nombre de la cuenta"; + +"OK" = "Aceptar"; + +"Oblast" = "Óblast"; + +"Other" = "Otro"; + +"Parish" = "Parroquia"; + +"Phone number" = "Número de teléfono"; + +"Postal code" = "Código postal"; + +"Prefecture" = "Prefectura"; + +"Province" = "Provincia"; + +"Remove" = "Eliminar"; + +"Remove bank account?" = "¿Deseas eliminar la cuenta bancaria?"; + +"Remove card" = "Eliminar tarjeta"; + +"Search" = "Buscar"; + +"Select card brand (optional)" = "Seleccionar marca de tarjeta (opcional)"; + +"Sort code" = "Código de clasificación"; + +"State" = "Estado"; + +"Suburb" = "Zona residencial"; + +"Suburb or city" = "Zona residencial o ciudad"; + +"The BSB you entered is incomplete." = "El BSB introducido está incompleto."; + +"The ID number you entered is incomplete." = "El número de ID que has introducido está incompleto."; + +"The account number you entered is incomplete." = "El número de cuenta que has introducido está incompleto."; + +"The sort code you entered is invalid." = "El código Sort introducido no es válido."; + +"Town or city" = "Pueblo o ciudad"; + +"UPI ID" = "ID de UPI"; + +"Unable to parse phone number" = "No se ha podido analizar el número de teléfono"; + +"Use rotor to access links" = "Utiliza el rotor para acceder a los enlaces"; + +"Your BLIK code is incomplete." = "Tu código BLIK no está completo."; + +"Your BLIK code is invalid." = "El código BLIK no es válido."; + +"Your ZIP is incomplete." = "El código ZIP está incompleto."; + +"Your email is invalid." = "El correo electrónico no es válido."; + +"Your payment information will not be saved." = "Tus datos de pago no se guardarán."; + +"Your postal code is incomplete." = "El código postal está incompleto."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/et-EE.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/et-EE.lproj/Localizable.strings new file mode 100644 index 00000000..dc1e91c1 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/et-EE.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (valikuline)"; + +"Account number" = "Kontonumber"; + +"Address" = "Aadress"; + +"Address line 1" = "Aadressirida 1"; + +"Address line 2" = "Aadressirida 2"; + +"Area" = "Piirkond"; + +"BLIK code" = "BLIK-kood"; + +"BSB number" = "BSB-number"; + +"Bank account •••• %@" = "Pangakonto •••• %@"; + +"Billing address is same as shipping" = "Arveldusaadress on sama mis tarneaadress"; + +"Cancel" = "Tühista"; + +"Card brand" = "Kaardi bränd"; + +"City" = "Linn"; + +"Code field" = "Koodi väli"; + +"Company" = "Ettevõte"; + +"Continue" = "Jätka"; + +"Country" = "Riik"; + +"Country or region" = "Riik või regioon"; + +"County" = "Maakond"; + +"Date is empty." = "Kuupäev on tühi."; + +"Department" = "Departemang"; + +"District" = "Regioon"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Kas soovite selle vormi sulgeda?"; + +"Done" = "Valmis"; + +"Double tap to edit" = "Redigeerimiseks topeltklõpsake"; + +"Edit" = "Muutmine"; + +"Eircode" = "Eircode"; + +"Email" = "Meiliaadress"; + +"Emirate" = "Emiraat"; + +"Error" = "Viga"; + +"First" = "Esimene"; + +"Full name" = "Täisnimi"; + +"Incomplete phone number" = "Telefoninumber puudulik"; + +"Invalid UPI ID" = "Kehtetu UPI ID"; + +"Island" = "Saar"; + +"Last" = "Viimane"; + +"Name" = "Nimi"; + +"Name on account" = "Nimi kontol"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Muu"; + +"Parish" = "Vald"; + +"Phone number" = "Telefoninumber"; + +"Postal code" = "Sihtnumber"; + +"Prefecture" = "Prefektuur"; + +"Province" = "Maakond"; + +"Remove" = "Eemalda"; + +"Remove bank account?" = "Kas soovite pangakonto eemaldada?"; + +"Remove card" = "Eemalda kaart"; + +"Search" = "Otsing"; + +"Select card brand (optional)" = "Valige kaardi kaubamärk (valikuline)"; + +"Sort code" = "Sorteerimiskood"; + +"State" = "Osariik"; + +"Suburb" = "Eeslinn"; + +"Suburb or city" = "Eeslinn või linn"; + +"The BSB you entered is incomplete." = "Sisestatud BSB-number on puudulik."; + +"The ID number you entered is incomplete." = "Sisestatud isikukood on puudulik."; + +"The account number you entered is incomplete." = "Sisestatud kontonumber on puudulik."; + +"The sort code you entered is invalid." = "Sisestatud sorteerimiskood ei ole kehtiv."; + +"Town or city" = "Linn"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Telefoninumbri parsimine ebaõnnestus"; + +"Use rotor to access links" = "Kasutage linkidele juurdepääsuks rootorit"; + +"Your BLIK code is incomplete." = "Teie BLIK-kood on puudulik."; + +"Your BLIK code is invalid." = "Teie BLIK-kood on kehtetu."; + +"Your ZIP is incomplete." = "Teie sihtnumber on puudulik."; + +"Your email is invalid." = "Teie meiliaadress on kehtetu."; + +"Your payment information will not be saved." = "Teie makseteavet ei salvestata."; + +"Your postal code is incomplete." = "Sihtnumber on puudulik."; + +"ZIP" = "Sihtnr"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/fi.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/fi.lproj/Localizable.strings new file mode 100644 index 00000000..e96a140f --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/fi.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (valinnainen)"; + +"Account number" = "Tilinumero"; + +"Address" = "Osoite"; + +"Address line 1" = "Osoiterivi 1"; + +"Address line 2" = "Osoiterivi 2"; + +"Area" = "Alue"; + +"BLIK code" = "BLIK-koodi"; + +"BSB number" = "BSB-numero"; + +"Bank account •••• %@" = "Pankkitili •••• %@"; + +"Billing address is same as shipping" = "Laskutusosoite on sama kuin toimitusosoite"; + +"Cancel" = "Peruuta"; + +"Card brand" = "Kortin merkki"; + +"City" = "Paikkakunta"; + +"Code field" = "Koodikenttä"; + +"Company" = "Yritys"; + +"Continue" = "Jatka"; + +"Country" = "Maa"; + +"Country or region" = "Maa tai alue"; + +"County" = "Lääni"; + +"Date is empty." = "Päivämäärä on tyhjä."; + +"Department" = "Osasto"; + +"District" = "Piirikunta"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Haluatko sulkea tämän lomakkeen?"; + +"Done" = "Valmis"; + +"Double tap to edit" = "Napauta kahdesti ja muokkaa"; + +"Edit" = "Muokkaa"; + +"Eircode" = "EIR-koodi"; + +"Email" = "Sähköposti"; + +"Emirate" = "Emiraatti"; + +"Error" = "Virhe"; + +"First" = "Etunimi"; + +"Full name" = "Koko nimi"; + +"Incomplete phone number" = "Puutteellinen puhelinnumero"; + +"Invalid UPI ID" = "Virheellinen UPI-tunnus"; + +"Island" = "Saari"; + +"Last" = "Sukunimi"; + +"Name" = "Nimi"; + +"Name on account" = "Tilin nimi"; + +"OK" = "OK"; + +"Oblast" = "Oblasti"; + +"Other" = "Muu"; + +"Parish" = "Kunta"; + +"Phone number" = "Puhelinnumero"; + +"Postal code" = "Postinumero"; + +"Prefecture" = "Prefektuuri"; + +"Province" = "Provinssi"; + +"Remove" = "Poista"; + +"Remove bank account?" = "Poistetaanko pankkitili?"; + +"Remove card" = "Poista kortti"; + +"Search" = "Etsi"; + +"Select card brand (optional)" = "Valitse korttimerkki (valinnainen)"; + +"Sort code" = "Pankin tunnusnumero"; + +"State" = "Osavaltio"; + +"Suburb" = "Esikaupunki"; + +"Suburb or city" = "Esikaupunki tai kaupunki"; + +"The BSB you entered is incomplete." = "Antamasi BSB-numero on puutteellinen."; + +"The ID number you entered is incomplete." = "Antamasi tunnusnumero on puutteellinen."; + +"The account number you entered is incomplete." = "Antamasi tilinumero on puutteellinen."; + +"The sort code you entered is invalid." = "Antamasi pankin tunnusnumero on virheellinen."; + +"Town or city" = "Kaupunki tai kunta"; + +"UPI ID" = "UPI-tunnus"; + +"Unable to parse phone number" = "Puhelinnumeron jäsennys epäonnistui"; + +"Use rotor to access links" = "Käytä roottoria linkeille"; + +"Your BLIK code is incomplete." = "BLIK-koodisi on puutteellinen."; + +"Your BLIK code is invalid." = "BLIK-koodi ei kelpaa."; + +"Your ZIP is incomplete." = "Postinumero on puutteellinen."; + +"Your email is invalid." = "Sähköposti on virheellinen."; + +"Your payment information will not be saved." = "Maksutietojasi ei tallenneta."; + +"Your postal code is incomplete." = "Postinumero on puutteellinen."; + +"ZIP" = "Postinumero"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/fil.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/fil.lproj/Localizable.strings new file mode 100644 index 00000000..7f2476a0 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/fil.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@(opsyonal)"; + +"Account number" = "Numero ng account"; + +"Address" = "Adres"; + +"Address line 1" = "Unang linya ng adres"; + +"Address line 2" = "Linya 2 ng address"; + +"Area" = "Lugar"; + +"BLIK code" = "BLIK code"; + +"BSB number" = "Numero ng BSB"; + +"Bank account •••• %@" = "Account sa bangko •••• %@"; + +"Billing address is same as shipping" = "Ang address sa billing ay kapareho ng sa pagpapadala"; + +"Cancel" = "Kanselahin"; + +"Card brand" = "Brand ng card"; + +"City" = "Siyudad"; + +"Code field" = "Patlang ng code"; + +"Company" = "Kompanya"; + +"Continue" = "Magpatuloy"; + +"Country" = "Bansa"; + +"Country or region" = "Bansa o rehiyon"; + +"County" = "County"; + +"Date is empty." = "Walang laman ang petsa."; + +"Department" = "Departamento"; + +"District" = "Distrito"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Gusto mo bang isara ang form na ito?"; + +"Done" = "Tapos Na"; + +"Double tap to edit" = "I-double tap para baguhin"; + +"Edit" = "I-edit"; + +"Eircode" = "Eircode"; + +"Email" = "Email"; + +"Emirate" = "Emirate"; + +"Error" = "Error"; + +"First" = "Pangalan"; + +"Full name" = "Buong pangalan"; + +"Incomplete phone number" = "Kulang na numero ng telepono"; + +"Invalid UPI ID" = "Di valid na UPI ID"; + +"Island" = "Isla"; + +"Last" = "Apelyido"; + +"Name" = "Pangalan"; + +"Name on account" = "Pangalan sa account"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Iba Pa"; + +"Parish" = "Parokya"; + +"Phone number" = "Numero ng telepono"; + +"Postal code" = "Postal code"; + +"Prefecture" = "Prefecture"; + +"Province" = "Probinsiya"; + +"Remove" = "Alisin"; + +"Remove bank account?" = "Alisin ang account sa bangko?"; + +"Remove card" = "Alisin ang kard"; + +"Search" = "Maghanap"; + +"Select card brand (optional)" = "Pumili ng brand ng kard (opsyonal)"; + +"Sort code" = "Sort code"; + +"State" = "State"; + +"Suburb" = "Suburb"; + +"Suburb or city" = "Suburb o lungsod"; + +"The BSB you entered is incomplete." = "Ang BSB na ipinasok mo ay di kumpleto."; + +"The ID number you entered is incomplete." = "Ang numero ng ID na inilagay mo ay kulang."; + +"The account number you entered is incomplete." = "Ang numero ng account na inilagay mo ay kulang."; + +"The sort code you entered is invalid." = "Ang sort code na inilagay mo ay hindi valid."; + +"Town or city" = "Bayan o siyudad"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Hindi ma=parse ang numero ng telepono"; + +"Use rotor to access links" = "Gumamit ng rotor para i-access ang mga link"; + +"Your BLIK code is incomplete." = "Ang iyong BLIK code ay kulang."; + +"Your BLIK code is invalid." = "Ang iyong BLIK code ay hindi valid."; + +"Your ZIP is incomplete." = "Ang iyong ZIP ay di kumpleto."; + +"Your email is invalid." = "Ang iyong email ay imbalido."; + +"Your payment information will not be saved." = "Hindi mase-save ang iyong impormasyon ng pagbabayad."; + +"Your postal code is incomplete." = "Ang iyong postal code ay di kumpleto."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/fr-CA.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/fr-CA.lproj/Localizable.strings new file mode 100644 index 00000000..1d3a2d53 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/fr-CA.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (facultatif)"; + +"Account number" = "Numéro de compte"; + +"Address" = "Adresse"; + +"Address line 1" = "Ligne d'adresse 1"; + +"Address line 2" = "Ligne d'adresse 2"; + +"Area" = "Région"; + +"BLIK code" = "Code BLIK"; + +"BSB number" = "Numéro BSB"; + +"Bank account •••• %@" = "Compte bancaire •••• %@"; + +"Billing address is same as shipping" = "L'adresse de facturation et de livraison sont identiques"; + +"Cancel" = "Annuler"; + +"Card brand" = "Marque de la carte"; + +"City" = "Ville"; + +"Code field" = "Champ de code"; + +"Company" = "Entreprise"; + +"Continue" = "Continuer"; + +"Country" = "Pays"; + +"Country or region" = "Pays ou région"; + +"County" = "Comté"; + +"Date is empty." = "Le champ date est vide."; + +"Department" = "Département"; + +"District" = "Arrondissement"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Voulez-vous vraiment fermer ce formulaire?"; + +"Done" = "Terminé"; + +"Double tap to edit" = "Touchez deux fois pour saisir votre texte"; + +"Edit" = "Modifier"; + +"Eircode" = "Eircode"; + +"Email" = "Courriel"; + +"Emirate" = "Émirat"; + +"Error" = "Erreur"; + +"First" = "Prénom"; + +"Full name" = "Nom complet"; + +"Incomplete phone number" = "Numéro de téléphone incomplet"; + +"Invalid UPI ID" = "ID d'UPI non valide"; + +"Island" = "Île"; + +"Last" = "Nom"; + +"Name" = "Nom"; + +"Name on account" = "Nom du titulaire du compte"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Autre"; + +"Parish" = "Paroisse"; + +"Phone number" = "Numéro de téléphone"; + +"Postal code" = "Code postal"; + +"Prefecture" = "Préfecture"; + +"Province" = "Province"; + +"Remove" = "Supprimer"; + +"Remove bank account?" = "Supprimer le compte bancaire?"; + +"Remove card" = "Retirer la carte"; + +"Search" = "Rechercher"; + +"Select card brand (optional)" = "Sélectionner la marque de la carte (facultatif)"; + +"Sort code" = "Code guichet"; + +"State" = "État"; + +"Suburb" = "Banlieue"; + +"Suburb or city" = "Banlieue ou ville"; + +"The BSB you entered is incomplete." = "Le numéro BSB que vous avez saisi est incomplet."; + +"The ID number you entered is incomplete." = "Le numéro d'identification que vous avez saisi est incomplet."; + +"The account number you entered is incomplete." = "Le numéro de compte saisi est incomplet."; + +"The sort code you entered is invalid." = "Le code guichet que vous avez saisi n'est pas valide."; + +"Town or city" = "Ville"; + +"UPI ID" = "ID d'UPI"; + +"Unable to parse phone number" = "Impossible d'analyser le numéro de téléphone"; + +"Use rotor to access links" = "Utilisez le rotor pour accéder aux liens"; + +"Your BLIK code is incomplete." = "Votre code BLIK est incomplet."; + +"Your BLIK code is invalid." = "Votre code BLIK n'est pas valide."; + +"Your ZIP is incomplete." = "Votre code postal est incomplet."; + +"Your email is invalid." = "Votre adresse de courriel n'est pas valide."; + +"Your payment information will not be saved." = "Vos informations de paiement ne seront pas enregistrées."; + +"Your postal code is incomplete." = "Votre code postal est incomplet."; + +"ZIP" = "Code postal"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/fr.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/fr.lproj/Localizable.strings new file mode 100644 index 00000000..2c15dc87 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/fr.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (facultatif)"; + +"Account number" = "Numéro de compte"; + +"Address" = "Adresse"; + +"Address line 1" = "Adresse - Ligne 1"; + +"Address line 2" = "Adresse - Ligne 2"; + +"Area" = "Zone"; + +"BLIK code" = "Code BLIK"; + +"BSB number" = "Numéro BSB"; + +"Bank account •••• %@" = "Compte bancaire •••• %@"; + +"Billing address is same as shipping" = "L'adresse de facturation et l'adresse de livraison sont identiques"; + +"Cancel" = "Annuler"; + +"Card brand" = "Marque de la carte bancaire"; + +"City" = "Ville"; + +"Code field" = "Champ de code"; + +"Company" = "Entreprise"; + +"Continue" = "Continuer"; + +"Country" = "Pays"; + +"Country or region" = "Pays ou région"; + +"County" = "Département"; + +"Date is empty." = "Le champ date est vide."; + +"Department" = "Département"; + +"District" = "Arrondissement"; + +"Do Si" = "Province et ville"; + +"Do you want to close this form?" = "Voulez-vous vraiment fermer ce formulaire ?"; + +"Done" = "Terminé"; + +"Double tap to edit" = "Appuyez deux fois pour saisir votre texte"; + +"Edit" = "Modifier"; + +"Eircode" = "Code postal"; + +"Email" = "E-mail"; + +"Emirate" = "Émirat"; + +"Error" = "Erreur"; + +"First" = "Prénom"; + +"Full name" = "Nom complet"; + +"Incomplete phone number" = "Numéro de téléphone incomplet"; + +"Invalid UPI ID" = "ID d'UPI non valide"; + +"Island" = "Île"; + +"Last" = "Nom"; + +"Name" = "Nom"; + +"Name on account" = "Nom du titulaire du compte"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Autre"; + +"Parish" = "Paroisse"; + +"Phone number" = "Numéro de téléphone"; + +"Postal code" = "Code postal"; + +"Prefecture" = "Préfecture"; + +"Province" = "Province"; + +"Remove" = "Supprimer"; + +"Remove bank account?" = "Supprimer le compte bancaire ?"; + +"Remove card" = "Supprimer la carte bancaire"; + +"Search" = "Rechercher"; + +"Select card brand (optional)" = "Sélectionnez la marque de la carte (facultatif)"; + +"Sort code" = "Code guichet"; + +"State" = "État"; + +"Suburb" = "Banlieue"; + +"Suburb or city" = "Banlieue ou ville"; + +"The BSB you entered is incomplete." = "Le numéro BSB que vous avez saisi est incomplet."; + +"The ID number you entered is incomplete." = "Le numéro d'identification que vous avez saisi est incomplet."; + +"The account number you entered is incomplete." = "Le numéro de compte saisi est incomplet."; + +"The sort code you entered is invalid." = "Le code guichet que vous avez saisi n'est pas valide."; + +"Town or city" = "Ville"; + +"UPI ID" = "ID d'UPI"; + +"Unable to parse phone number" = "Impossible d'analyser le numéro de téléphone"; + +"Use rotor to access links" = "Utilisez le rotor pour accéder aux liens"; + +"Your BLIK code is incomplete." = "Votre code BLIK est incomplet."; + +"Your BLIK code is invalid." = "Votre code BLIK n'est pas valide."; + +"Your ZIP is incomplete." = "Votre code postal est incomplet."; + +"Your email is invalid." = "Votre adresse e-mail n'est pas valide."; + +"Your payment information will not be saved." = "Vos informations de paiement ne seront pas enregistrées."; + +"Your postal code is incomplete." = "Votre code postal est incomplet."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/hr.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/hr.lproj/Localizable.strings new file mode 100644 index 00000000..b8d35bbf --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/hr.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (neobavezno)"; + +"Account number" = "Broj računa"; + +"Address" = "Adresa"; + +"Address line 1" = "Ulica i kućni broj 1"; + +"Address line 2" = "Broj stana, kat ili poštanski pretinac"; + +"Area" = "Područje"; + +"BLIK code" = "BLIK kod"; + +"BSB number" = "BSB broj"; + +"Bank account •••• %@" = "Bankovni račun •••• %@"; + +"Billing address is same as shipping" = "Adresa računa jednaka je adresi dostave"; + +"Cancel" = "Otkaži"; + +"Card brand" = "Robna marka kartice"; + +"City" = "Grad"; + +"Code field" = "Polje za kod"; + +"Company" = "Poduzeće"; + +"Continue" = "Nastavi"; + +"Country" = "Zemlja"; + +"Country or region" = "Zemlja ili regija"; + +"County" = "Grofovija"; + +"Date is empty." = "Datum je prazan."; + +"Department" = "Administrativna jedinica"; + +"District" = "Okrug"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Želite li zatvoriti ovaj obrazac?"; + +"Done" = "Gotovo"; + +"Double tap to edit" = "Dodirnite dva puta za uređivanje"; + +"Edit" = "Uredi"; + +"Eircode" = "Eircode"; + +"Email" = "E-mail"; + +"Emirate" = "Emirat"; + +"Error" = "Greška"; + +"First" = "Ime"; + +"Full name" = "Puno ime"; + +"Incomplete phone number" = "Nepotpuni telefonski broj"; + +"Invalid UPI ID" = "Nevažeći UPI ID"; + +"Island" = "Otok"; + +"Last" = "Prezime"; + +"Name" = "Ime"; + +"Name on account" = "Ime na računu"; + +"OK" = "U redu"; + +"Oblast" = "Oblast"; + +"Other" = "Ostalo"; + +"Parish" = "Župa"; + +"Phone number" = "Telefonski broj"; + +"Postal code" = "Poštanski broj"; + +"Prefecture" = "Prefektura"; + +"Province" = "Provincija"; + +"Remove" = "Ukloni"; + +"Remove bank account?" = "Ukloniti bankovni račun?"; + +"Remove card" = "Ukloni karticu"; + +"Search" = "Pretraži"; + +"Select card brand (optional)" = "Odaberite marku kartice (nije obavezno)"; + +"Sort code" = "Bankovni kod za razvrstavanje (UK)"; + +"State" = "Država"; + +"Suburb" = "Predgrađe"; + +"Suburb or city" = "Predgrađe ili grad"; + +"The BSB you entered is incomplete." = "Uneseni BSB nije kompletan."; + +"The ID number you entered is incomplete." = "Uneseni identifikacijski broj nije potpun."; + +"The account number you entered is incomplete." = "Uneseni broj računa je nepotpun."; + +"The sort code you entered is invalid." = "Bankovni kod za razvrstavanje koji ste unijeli nije važeći."; + +"Town or city" = "Gradić ili grad"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Broj telefona nije moguće pročitati"; + +"Use rotor to access links" = "Upotrijebite rotor za pristup poveznicama"; + +"Your BLIK code is incomplete." = "Vaš BLIK kod je nepotpun."; + +"Your BLIK code is invalid." = "Vaš BLIK kod je nevažeći."; + +"Your ZIP is incomplete." = "Vaš ZIP broj nije kompletan."; + +"Your email is invalid." = "Vaš e-mail je neispravan."; + +"Your payment information will not be saved." = "Vaši podaci o plaćanju neće biti spremljeni."; + +"Your postal code is incomplete." = "Vaš poštanski broj nije kompletan."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/hu.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/hu.lproj/Localizable.strings new file mode 100644 index 00000000..f64ff72c --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/hu.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (nem kötelező)"; + +"Account number" = "Számlaszám"; + +"Address" = "Cím"; + +"Address line 1" = "Cím 1. sora"; + +"Address line 2" = "2. címsor"; + +"Area" = "Terület"; + +"BLIK code" = "BLIK kód"; + +"BSB number" = "BSB-szám"; + +"Bank account •••• %@" = "Bankszámla •••• %@"; + +"Billing address is same as shipping" = "A számlázási cím megegyezik a szállítási címmel"; + +"Cancel" = "Mégse"; + +"Card brand" = "Kártyatípus"; + +"City" = "Város"; + +"Code field" = "Kódmező"; + +"Company" = "Vállalat"; + +"Continue" = "Folytatás"; + +"Country" = "Ország"; + +"Country or region" = "Ország vagy régió"; + +"County" = "Ország"; + +"Date is empty." = "A dátum nincs kitöltve."; + +"Department" = "Megye"; + +"District" = "Kerület"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Bezárja ezt az űrlapot?"; + +"Done" = "Kész"; + +"Double tap to edit" = "A szerkesztéshez koppintson kétszer"; + +"Edit" = "Szerkesztés"; + +"Eircode" = "Irányítószám"; + +"Email" = "E-mail"; + +"Emirate" = "Emirátus"; + +"Error" = "Hiba"; + +"First" = "Utónév"; + +"Full name" = "Teljes név"; + +"Incomplete phone number" = "Hiányos telefonszám"; + +"Invalid UPI ID" = "Érvénytelen UPI-azonosító"; + +"Island" = "Sziget"; + +"Last" = "Vezetéknév"; + +"Name" = "Név"; + +"Name on account" = "A számlán szereplő név"; + +"OK" = "OK"; + +"Oblast" = "Terület"; + +"Other" = "Egyéb"; + +"Parish" = "Egyházközség"; + +"Phone number" = "Telefonszám"; + +"Postal code" = "Irányítószám"; + +"Prefecture" = "Prefektúra"; + +"Province" = "Tartomány"; + +"Remove" = "Eltávolítás"; + +"Remove bank account?" = "Eltávolítja a bankszámlát?"; + +"Remove card" = "Kártya törlése"; + +"Search" = "Keresés"; + +"Select card brand (optional)" = "Válassza ki a kártya márkáját (nem kötelező)"; + +"Sort code" = "Bankazonosító"; + +"State" = "Állam"; + +"Suburb" = "Külváros"; + +"Suburb or city" = "Külváros vagy város"; + +"The BSB you entered is incomplete." = "A beírt BSB-szám hiányos."; + +"The ID number you entered is incomplete." = "A beírt azonosítószám hiányos."; + +"The account number you entered is incomplete." = "A számlaszám hiányos."; + +"The sort code you entered is invalid." = "A beírt bankazonosító érvénytelen."; + +"Town or city" = "Kisváros vagy város"; + +"UPI ID" = "UPI-azonosító"; + +"Unable to parse phone number" = "A telefonszám nem értelmezhető"; + +"Use rotor to access links" = "Rotor használata a hivatkozások kezeléséhez"; + +"Your BLIK code is incomplete." = "BLIK kódja hiányos."; + +"Your BLIK code is invalid." = "BLIK kódja érvénytelen."; + +"Your ZIP is incomplete." = "A ZIP hiányos."; + +"Your email is invalid." = "Érvénytelen e-mail-cím."; + +"Your payment information will not be saved." = "Fizetési adatai nem lesznek mentve."; + +"Your postal code is incomplete." = "Az irányítószám hiányos."; + +"ZIP" = "Irányítószám"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/id.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/id.lproj/Localizable.strings new file mode 100644 index 00000000..dc262581 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/id.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opsional)"; + +"Account number" = "Nomor rekening"; + +"Address" = "Alamat"; + +"Address line 1" = "Baris alamat ke-1"; + +"Address line 2" = "Baris alamat ke-2"; + +"Area" = "Area"; + +"BLIK code" = "Kode BLIK"; + +"BSB number" = "Nomor BSB"; + +"Bank account •••• %@" = "Rekening bank •••• %@"; + +"Billing address is same as shipping" = "Alamat tagihan sama dengan pengiriman"; + +"Cancel" = "Batalkan"; + +"Card brand" = "Brand kartu"; + +"City" = "Kota"; + +"Code field" = "Bidang kode"; + +"Company" = "Perusahaan"; + +"Continue" = "Lanjutkan"; + +"Country" = "Negara"; + +"Country or region" = "Negara atau wilayah"; + +"County" = "County"; + +"Date is empty." = "Data kosong."; + +"Department" = "Departemen"; + +"District" = "Distrik"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Anda ingin menutup formulir ini?"; + +"Done" = "Selesai"; + +"Double tap to edit" = "Sentuh dua kali untuk mengedit"; + +"Edit" = "Edit"; + +"Eircode" = "Eircode"; + +"Email" = "Email"; + +"Emirate" = "Emirat"; + +"Error" = "Kesalahan"; + +"First" = "Depan"; + +"Full name" = "Nama lengkap"; + +"Incomplete phone number" = "Nomor telepon tidak lengkap"; + +"Invalid UPI ID" = "Identifikasi UPI tidak valid"; + +"Island" = "Pulau"; + +"Last" = "Belakang"; + +"Name" = "Nama"; + +"Name on account" = "Nama pada rekening"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Lainnya"; + +"Parish" = "Parish"; + +"Phone number" = "Nomor telepon"; + +"Postal code" = "Kode pos"; + +"Prefecture" = "Prefektur"; + +"Province" = "Provinsi"; + +"Remove" = "Hapus"; + +"Remove bank account?" = "Hapus rekening bank?"; + +"Remove card" = "Hapus kartu"; + +"Search" = "Cari"; + +"Select card brand (optional)" = "Pilih brand kartu (opsional)"; + +"Sort code" = "Kode sort"; + +"State" = "Negara Bagian"; + +"Suburb" = "Suburb"; + +"Suburb or city" = "Suburb atau kota"; + +"The BSB you entered is incomplete." = "BSB yang Anda masukkan tidak lengkap."; + +"The ID number you entered is incomplete." = "Nomor identifikasi yang Anda masukkan tidak lengkap."; + +"The account number you entered is incomplete." = "Nomor rekening yang Anda masukkan tidak lengkap."; + +"The sort code you entered is invalid." = "Kode sort yang Anda masukkan tidak valid."; + +"Town or city" = "Kabupaten atau kota"; + +"UPI ID" = "Identifikasi UPI"; + +"Unable to parse phone number" = "Tidak dapat menguraikan nomor telepon"; + +"Use rotor to access links" = "Gunakan rotor untuk mengakses tautan"; + +"Your BLIK code is incomplete." = "Kode BLIK Anda tidak lengkap."; + +"Your BLIK code is invalid." = "Kode BLIK Anda tidak valid."; + +"Your ZIP is incomplete." = "ZIP Anda tidak lengkap."; + +"Your email is invalid." = "Email Anda tidak valid."; + +"Your payment information will not be saved." = "Informasi pembayaran Anda tidak akan disimpan."; + +"Your postal code is incomplete." = "Kode pos Anda tidak lengkap."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/it.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/it.lproj/Localizable.strings new file mode 100644 index 00000000..f95f6971 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/it.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (campo facoltativo)"; + +"Account number" = "Numero conto"; + +"Address" = "Indirizzo"; + +"Address line 1" = "Indirizzo riga 1"; + +"Address line 2" = "Indirizzo riga 2"; + +"Area" = "Area"; + +"BLIK code" = "Codice BLIK"; + +"BSB number" = "Numero BSB"; + +"Bank account •••• %@" = "Conto bancario •••• %@"; + +"Billing address is same as shipping" = "L'indirizzo di fatturazione è uguale all'indirizzo di spedizione"; + +"Cancel" = "Annulla"; + +"Card brand" = "Circuito carta"; + +"City" = "Città"; + +"Code field" = "Campo codice"; + +"Company" = "Azienda"; + +"Continue" = "Continua"; + +"Country" = "Paese"; + +"Country or region" = "Paese o area geografica"; + +"County" = "Contea"; + +"Date is empty." = "Il campo della data è vuoto."; + +"Department" = "Dipartimento"; + +"District" = "Distretto"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Chiudere il modulo?"; + +"Done" = "Fine"; + +"Double tap to edit" = "Tocca due volte per modificare"; + +"Edit" = "Modifica"; + +"Eircode" = "Eircode"; + +"Email" = "Email"; + +"Emirate" = "Emirato"; + +"Error" = "Errore"; + +"First" = "Nome"; + +"Full name" = "Nome completo"; + +"Incomplete phone number" = "Numero di telefono incompleto"; + +"Invalid UPI ID" = "ID UPI non valido"; + +"Island" = "Isola"; + +"Last" = "Cognome"; + +"Name" = "Nome"; + +"Name on account" = "Nome sul conto"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Altro"; + +"Parish" = "Contea"; + +"Phone number" = "Numero di telefono"; + +"Postal code" = "Codice postale"; + +"Prefecture" = "Prefettura"; + +"Province" = "Provincia"; + +"Remove" = "Rimuovi"; + +"Remove bank account?" = "Desideri rimuovere il conto bancario?"; + +"Remove card" = "Rimuovi la carta"; + +"Search" = "Cerca"; + +"Select card brand (optional)" = "Seleziona il circuito della carta (facoltativo)"; + +"Sort code" = "Sort code"; + +"State" = "Stato"; + +"Suburb" = "Località"; + +"Suburb or city" = "Località o città"; + +"The BSB you entered is incomplete." = "Il numero BSB inserito è incompleto"; + +"The ID number you entered is incomplete." = "Il numero del documento di identità inserito non è completo."; + +"The account number you entered is incomplete." = "Il numero del conto inserito è incompleto."; + +"The sort code you entered is invalid." = "Il sort code inserito non è valido."; + +"Town or city" = "Città"; + +"UPI ID" = "ID UPI"; + +"Unable to parse phone number" = "Impossibile analizzare il numero di telefono"; + +"Use rotor to access links" = "Utilizza l'ingranaggio per accedere ai link"; + +"Your BLIK code is incomplete." = "Il codice BLIK non è completo."; + +"Your BLIK code is invalid." = "Il codice BLIK non è valido."; + +"Your ZIP is incomplete." = "Il CAP è incompleto."; + +"Your email is invalid." = "Indirizzo email non valido."; + +"Your payment information will not be saved." = "I dati di pagamento non verranno salvati."; + +"Your postal code is incomplete." = "Il codice postale è incompleto."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/ja.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/ja.lproj/Localizable.strings new file mode 100644 index 00000000..47ac40e7 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/ja.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (オプション)"; + +"Account number" = "アカウント番号"; + +"Address" = "住所"; + +"Address line 1" = "住所 (1 行目)"; + +"Address line 2" = "住所 (2 行目)"; + +"Area" = "地域"; + +"BLIK code" = "BLIK コード"; + +"BSB number" = "BSB 番号"; + +"Bank account •••• %@" = "銀行口座 •••• %@"; + +"Billing address is same as shipping" = "請求先住所は配送先住所と同じ"; + +"Cancel" = "キャンセル"; + +"Card brand" = "カードブランド"; + +"City" = "市"; + +"Code field" = "コードフィールド"; + +"Company" = "会社"; + +"Continue" = "続ける"; + +"Country" = "国"; + +"Country or region" = "国または地域"; + +"County" = "群"; + +"Date is empty." = "カートは空です。"; + +"Department" = "県"; + +"District" = "地区"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "このフォームを閉じてもよいですか?"; + +"Done" = "完了"; + +"Double tap to edit" = "ダブルタップして編集します"; + +"Edit" = "編集"; + +"Eircode" = "郵便番号 (アイルランド)"; + +"Email" = "メールアドレス"; + +"Emirate" = "首長国"; + +"Error" = "エラー"; + +"First" = "名"; + +"Full name" = "名前"; + +"Incomplete phone number" = "電話番号の不備"; + +"Invalid UPI ID" = "UPI ID が無効です"; + +"Island" = "島しょ"; + +"Last" = "姓"; + +"Name" = "名前"; + +"Name on account" = "口座名義人"; + +"OK" = "OK"; + +"Oblast" = "州 (ロシア)"; + +"Other" = "その他"; + +"Parish" = "郡"; + +"Phone number" = "電話番号"; + +"Postal code" = "郵便番号"; + +"Prefecture" = "都道府県"; + +"Province" = "都道府県"; + +"Remove" = "削除"; + +"Remove bank account?" = "銀行口座を削除しますか?"; + +"Remove card" = "カードを削除"; + +"Search" = "検索"; + +"Select card brand (optional)" = "カードブランドを選択 (任意)"; + +"Sort code" = "銀行コード"; + +"State" = "都道府県"; + +"Suburb" = "近郊"; + +"Suburb or city" = "市外または市内"; + +"The BSB you entered is incomplete." = "入力した BSB コードに不備があります。"; + +"The ID number you entered is incomplete." = "入力した ID 番号に不備があります。"; + +"The account number you entered is incomplete." = "入力した口座番号に不備があります。"; + +"The sort code you entered is invalid." = "入力したソートコードが無効です。"; + +"Town or city" = "市区町村"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "電話番号を解析できません"; + +"Use rotor to access links" = "ローターを使用してリンクにアクセスします"; + +"Your BLIK code is incomplete." = "BLIK コードの入力に不備があります。"; + +"Your BLIK code is invalid." = "BLIK コードが無効です。"; + +"Your ZIP is incomplete." = "郵便番号の入力に不備があります。"; + +"Your email is invalid." = "メールアドレスが無効です。"; + +"Your payment information will not be saved." = "支払い情報は保存されません。"; + +"Your postal code is incomplete." = "郵便番号の入力に不備があります。"; + +"ZIP" = "郵便番号"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/ko.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/ko.lproj/Localizable.strings new file mode 100644 index 00000000..a8a244ef --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/ko.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@(선택 사항)"; + +"Account number" = "계좌 번호"; + +"Address" = "주소"; + +"Address line 1" = "주소란 1"; + +"Address line 2" = "주소란 2"; + +"Area" = "지역"; + +"BLIK code" = "BLIK 코드"; + +"BSB number" = "BSB 번호"; + +"Bank account •••• %@" = "은행 계좌 •••• %@"; + +"Billing address is same as shipping" = "청구 주소가 배송 주소와 동일함"; + +"Cancel" = "취소"; + +"Card brand" = "카드 브랜드"; + +"City" = "시"; + +"Code field" = "코드 필드"; + +"Company" = "회사"; + +"Continue" = "계속"; + +"Country" = "국가"; + +"Country or region" = "국가 또는 지역"; + +"County" = "구/군"; + +"Date is empty." = "날짜가 비어 있습니다."; + +"Department" = "행정구/현"; + +"District" = "지구"; + +"Do Si" = "도시"; + +"Do you want to close this form?" = "이 양식을 닫으시겠습니까?"; + +"Done" = "완료"; + +"Double tap to edit" = "두 번 탭하여 편집"; + +"Edit" = "편집"; + +"Eircode" = "우편번호(Eircode)"; + +"Email" = "이메일"; + +"Emirate" = "에미리트"; + +"Error" = "오류"; + +"First" = "이름"; + +"Full name" = "성명"; + +"Incomplete phone number" = "불완전한 전화번호"; + +"Invalid UPI ID" = "잘못된 UPI ID"; + +"Island" = "섬"; + +"Last" = "성"; + +"Name" = "이름"; + +"Name on account" = "계좌 명의"; + +"OK" = "확인"; + +"Oblast" = "주"; + +"Other" = "기타"; + +"Parish" = "교구"; + +"Phone number" = "전화번호"; + +"Postal code" = "우편번호"; + +"Prefecture" = "도/현"; + +"Province" = "도"; + +"Remove" = "제거"; + +"Remove bank account?" = "은행 계좌를 삭제하시겠습니까?"; + +"Remove card" = "카드 제거"; + +"Search" = "검색"; + +"Select card brand (optional)" = "카드 브랜드 선택(선택 사항)"; + +"Sort code" = "코드 정렬"; + +"State" = "주"; + +"Suburb" = "교외"; + +"Suburb or city" = "교외 또는 도시"; + +"The BSB you entered is incomplete." = "입력하신 BSB가 완료되지 않았습니다."; + +"The ID number you entered is incomplete." = "입력한 ID 번호가 불완전합니다."; + +"The account number you entered is incomplete." = "입력하신 계좌 번호가 불완전합니다."; + +"The sort code you entered is invalid." = "입력하신 분류 코드가 유효하지 않습니다."; + +"Town or city" = "마을 또는 도시"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "전화번호 구문 분석 불가능"; + +"Use rotor to access links" = "링크 액세스에 로터 사용"; + +"Your BLIK code is incomplete." = "BLIK 코드가 불완전합니다."; + +"Your BLIK code is invalid." = "BLIK 코드가 유효하지 않습니다."; + +"Your ZIP is incomplete." = "우편번호가 불완전합니다."; + +"Your email is invalid." = "이메일이 잘못되었습니다."; + +"Your payment information will not be saved." = "귀하의 결제 정보가 저장되지 않습니다."; + +"Your postal code is incomplete." = "우편번호가 불완전합니다."; + +"ZIP" = "우편번호"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/lt-LT.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/lt-LT.lproj/Localizable.strings new file mode 100644 index 00000000..ab458ec6 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/lt-LT.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (pasirenkamas)"; + +"Account number" = "Sąskaitos numeris"; + +"Address" = "Adresas"; + +"Address line 1" = "1 adreso eilutė"; + +"Address line 2" = "2 adreso eilutė"; + +"Area" = "Sritis"; + +"BLIK code" = "BLIK kodas"; + +"BSB number" = "BSB numeris"; + +"Bank account •••• %@" = "Banko sąskaita •••• %@"; + +"Billing address is same as shipping" = "Atsiskaitymo ir siuntimo adresai vienodi"; + +"Cancel" = "Atšaukti"; + +"Card brand" = "Kortelės prekės ženklas"; + +"City" = "Miestas"; + +"Code field" = "Kodo laukas"; + +"Company" = "Bendrovė"; + +"Continue" = "Tęsti"; + +"Country" = "Šalis"; + +"Country or region" = "Šalis arba regionas"; + +"County" = "Apygarda"; + +"Date is empty." = "Data neužpildyta."; + +"Department" = "Skyrius"; + +"District" = "Rajonas"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Ar norite uždaryti šią formą?"; + +"Done" = "Atlikta"; + +"Double tap to edit" = "Norėdami redaguoti, dukart bakstelėkite"; + +"Edit" = "Redaguoti"; + +"Eircode" = "Pašto kodas (EIR)"; + +"Email" = "El. paštas"; + +"Emirate" = "Emyratas"; + +"Error" = "Klaida"; + +"First" = "Vardas"; + +"Full name" = "Vardas, pavardė"; + +"Incomplete phone number" = "Ne visas telefono numeris"; + +"Invalid UPI ID" = "Netinkamas UPI ID"; + +"Island" = "Sala"; + +"Last" = "Pavardė"; + +"Name" = "Vardas, pavardė"; + +"Name on account" = "Sąskaitos turėtojo vardas ir pavardė"; + +"OK" = "GERAI"; + +"Oblast" = "Sritis"; + +"Other" = "Kita"; + +"Parish" = "Parapija"; + +"Phone number" = "Telefono numeris"; + +"Postal code" = "Pašto kodas"; + +"Prefecture" = "Prefektūra"; + +"Province" = "Provincija"; + +"Remove" = "Pašalinti"; + +"Remove bank account?" = "Pašalinti banko sąskaitą?"; + +"Remove card" = "Pašalinti kortelę"; + +"Search" = "Paieška"; + +"Select card brand (optional)" = "Pasirinkite kortelės prekės ženklą (neprivaloma)"; + +"Sort code" = "Identifikacinis kodas"; + +"State" = "Valstija"; + +"Suburb" = "Priemiestis"; + +"Suburb or city" = "Priemiestis arba miestas"; + +"The BSB you entered is incomplete." = "Įvestas neišsamus BSB numeris."; + +"The ID number you entered is incomplete." = "Įvedėte ne visą identifikacinį numerį."; + +"The account number you entered is incomplete." = "Įvedėte ne visą sąskaitos numerį."; + +"The sort code you entered is invalid." = "Jūsų įvestas identifikacinis kodas negalioja."; + +"Town or city" = "Miestelis arba miestas"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Nepavyko išanalizuoti telefono numerio"; + +"Use rotor to access links" = "Naudoti pasukimo ratuką prieigai prie saitų"; + +"Your BLIK code is incomplete." = "Jūsų BLIK kodas neišsamus."; + +"Your BLIK code is invalid." = "Jūsų BLIK kodas netinkamas."; + +"Your ZIP is incomplete." = "Neišsamus jūsų pašto kodas."; + +"Your email is invalid." = "Jūsų el. paštas yra neteisingas."; + +"Your payment information will not be saved." = "Jūsų mokėjimo informacija nebus įrašyta."; + +"Your postal code is incomplete." = "Pašto kodas neišsamus."; + +"ZIP" = "Pašto kodas"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/lv-LV.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/lv-LV.lproj/Localizable.strings new file mode 100644 index 00000000..9f296b1f --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/lv-LV.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (nav obligāti)"; + +"Account number" = "Konta numurs"; + +"Address" = "Adrese"; + +"Address line 1" = "1. adreses rindiņa"; + +"Address line 2" = "2. adreses rindiņa"; + +"Area" = "Apgabals"; + +"BLIK code" = "BLIK kods"; + +"BSB number" = "BSB numurs"; + +"Bank account •••• %@" = "Bankas konts •••• %@"; + +"Billing address is same as shipping" = "Norēķinu adrese un piegādes adrese sakrīt"; + +"Cancel" = "Atcelt"; + +"Card brand" = "Kartes zīmols"; + +"City" = "Pilsēta"; + +"Code field" = "Koda lauks"; + +"Company" = "Uzņēmums"; + +"Continue" = "Turpināt"; + +"Country" = "Valsts"; + +"Country or region" = "Valsts vai reģions"; + +"County" = "Apriņķis"; + +"Date is empty." = "Datums ir tukšs."; + +"Department" = "Departaments"; + +"District" = "Rajons"; + +"Do Si" = "Province un pilsēta (Do Si)"; + +"Do you want to close this form?" = "Vai vēlaties aizvērt šo formu?"; + +"Done" = "Gatavs"; + +"Double tap to edit" = "Pieskarieties divreiz, lai rediģētu"; + +"Edit" = "Rediģēt"; + +"Eircode" = "Pasta indekss (Eirkods)"; + +"Email" = "E-pasts"; + +"Emirate" = "Emirāts"; + +"Error" = "Kļūda"; + +"First" = "Vārds"; + +"Full name" = "Vārds, uzvārds"; + +"Incomplete phone number" = "Nepilnīgs tālruņa numurs"; + +"Invalid UPI ID" = "Nederīgs UPI ID"; + +"Island" = "Sala"; + +"Last" = "Uzvārds"; + +"Name" = "Vārds, uzvārds"; + +"Name on account" = "Konta īpašnieks"; + +"OK" = "Labi"; + +"Oblast" = "Apgabals"; + +"Other" = "Cits"; + +"Parish" = "Pagasts"; + +"Phone number" = "Tālruņa numurs"; + +"Postal code" = "Pasta indekss"; + +"Prefecture" = "Prefektūra"; + +"Province" = "Province"; + +"Remove" = "Noņemt"; + +"Remove bank account?" = "Noņemt bankas kontu?"; + +"Remove card" = "Noņemt karti"; + +"Search" = "Meklēšana"; + +"Select card brand (optional)" = "Atlasīt kartes zīmolu (pēc izvēles)"; + +"Sort code" = "Bankas filiāles kods"; + +"State" = "Novads/štats"; + +"Suburb" = "Priekšpilsēta"; + +"Suburb or city" = "Priekšpilsēta vai pilsēta"; + +"The BSB you entered is incomplete." = "Ievadītais BSB ir nepilnīgs."; + +"The ID number you entered is incomplete." = "Ievadītais ID numurs nav pilnīgs."; + +"The account number you entered is incomplete." = "Jūsu ievadītais konta numurs nav pilnīgs."; + +"The sort code you entered is invalid." = "Jūsu ievadītais bankas filiāles kods nav derīgs."; + +"Town or city" = "Ciems vai pilsēta"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Neizdevās parsēt tālruņa numuru"; + +"Use rotor to access links" = "Izmantot rotoru, lai piekļūtu saitēm"; + +"Your BLIK code is incomplete." = "Jūsu BLIK kods nav pilnīgs."; + +"Your BLIK code is invalid." = "Jūsu BLIK kods nav derīgs."; + +"Your ZIP is incomplete." = "Pasta indekss nav pilnīgs."; + +"Your email is invalid." = "E-pasta adrese nav derīga."; + +"Your payment information will not be saved." = "Jūsu maksājuma informācija nav saglabāta."; + +"Your postal code is incomplete." = "Pasta indekss nav pilnīgs."; + +"ZIP" = "Pasta ind."; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/ms-MY.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/ms-MY.lproj/Localizable.strings new file mode 100644 index 00000000..0be3a98a --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/ms-MY.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opsyenal)"; + +"Account number" = "Nombor akaun"; + +"Address" = "Alamat"; + +"Address line 1" = "Alamat baris 1"; + +"Address line 2" = "Alamat baris 2"; + +"Area" = "Kawasan"; + +"BLIK code" = "Kod BLIK"; + +"BSB number" = "Nombor BSB"; + +"Bank account •••• %@" = "Akaun bank •••• %@"; + +"Billing address is same as shipping" = "Alamat pengebilan sama dengan alamat penghantaran"; + +"Cancel" = "Batalkan"; + +"Card brand" = "Jenama kad"; + +"City" = "Bandar"; + +"Code field" = "Medan kod"; + +"Company" = "Syarikat"; + +"Continue" = "Teruskan"; + +"Country" = "Negara"; + +"Country or region" = "Negara atau rantau"; + +"County" = "Kaunti"; + +"Date is empty." = "Tarikh kosong."; + +"Department" = "Jabatan"; + +"District" = "Daerah"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Adakah anda ingin menutup borang ini?"; + +"Done" = "Selesai"; + +"Double tap to edit" = "Ketik dua kali untuk edit"; + +"Edit" = "Edit"; + +"Eircode" = "Eircode"; + +"Email" = "E-mel"; + +"Emirate" = "Amiriah"; + +"Error" = "Ralat"; + +"First" = "Pertama"; + +"Full name" = "Nama penuh"; + +"Incomplete phone number" = "Nombor telefon tidak lengkap"; + +"Invalid UPI ID" = "ID UPI tidak sah"; + +"Island" = "Pulau"; + +"Last" = "Akhir"; + +"Name" = "Nama"; + +"Name on account" = "Nama pada akaun"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Lain-lain"; + +"Parish" = "Kariah"; + +"Phone number" = "Nombor telefon"; + +"Postal code" = "Poskod"; + +"Prefecture" = "Wilayah"; + +"Province" = "Wilayah"; + +"Remove" = "Alih Keluar"; + +"Remove bank account?" = "Alih keluar akaun bank?"; + +"Remove card" = "Alih keluar kad"; + +"Search" = "Cari"; + +"Select card brand (optional)" = "Pilih jenama kad (opsyenal)"; + +"Sort code" = "Kod isihan"; + +"State" = "Negeri"; + +"Suburb" = "Pinggir Bandar"; + +"Suburb or city" = "Pinggir bandar atau bandar"; + +"The BSB you entered is incomplete." = "BSB yang anda masukkan tidak lengkap."; + +"The ID number you entered is incomplete." = "Nombor ID yang anda masukkan tidak lengkap."; + +"The account number you entered is incomplete." = "Nombor akaun yang anda masukkan tidak lengkap."; + +"The sort code you entered is invalid." = "Kod isihan yang anda masukkan tidak sah."; + +"Town or city" = "Pekan atau bandar"; + +"UPI ID" = "ID UPI"; + +"Unable to parse phone number" = "Nombor telefon tidak dapat dihuraikan"; + +"Use rotor to access links" = "Gunakan rotor untuk mengakses pautan"; + +"Your BLIK code is incomplete." = "Kod BLIK anda tidak lengkap."; + +"Your BLIK code is invalid." = "Kod BLIK anda tidak sah."; + +"Your ZIP is incomplete." = "Kod ZIP anda tidak lengkap."; + +"Your email is invalid." = "E-mel anda tidak sah."; + +"Your payment information will not be saved." = "Maklumat pembayaran anda tidak akan disimpan."; + +"Your postal code is incomplete." = "Poskod anda tidak lengkap."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/mt.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/mt.lproj/Localizable.strings new file mode 100644 index 00000000..5c035ed6 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/mt.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (mhux obbligatorja)"; + +"Account number" = "In-numru tal-kont"; + +"Address" = "Indirizz"; + +"Address line 1" = "Indirizz linja 1"; + +"Address line 2" = "It-tieni linja tal-indirizz"; + +"Area" = "Żona"; + +"BLIK code" = "Kodiċi BLIK"; + +"BSB number" = "Numru BSB"; + +"Bank account •••• %@" = "Kont tal-bank •••• %@"; + +"Billing address is same as shipping" = "L-indirizz tal-fatturazzjoni huwa l-istess bħall-indirizz tal-posta"; + +"Cancel" = "Ikkanċella"; + +"Card brand" = "Id-ditta tal-karta"; + +"City" = "Belt"; + +"Code field" = "Taqsima tal-kodiċi"; + +"Company" = "Kumpanija"; + +"Continue" = "Kompli"; + +"Country" = "Pajjiż"; + +"Country or region" = "Pajjiż jew reġjun"; + +"County" = "Kontea"; + +"Date is empty." = "Id-data hija battala."; + +"Department" = "Dipartiment"; + +"District" = "Distrett"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Trid tagħlaq din il-formola?"; + +"Done" = "Bil-lest"; + +"Double tap to edit" = "Taptap biex tibdel"; + +"Edit" = "Editja"; + +"Eircode" = "Eircode"; + +"Email" = "Email"; + +"Emirate" = "Emirat"; + +"Error" = "Żball"; + +"First" = "Ismek"; + +"Full name" = "Isem sħiħ"; + +"Incomplete phone number" = "In-numru tat-telefon mhuwiex komplut"; + +"Invalid UPI ID" = "L-ID UPI mhix tajba"; + +"Island" = "Gżira"; + +"Last" = "L-aħħar"; + +"Name" = "Isem"; + +"Name on account" = "Isem sid il-kont"; + +"OK" = "Tajjeb"; + +"Oblast" = "Oblast"; + +"Other" = "Oħrajn"; + +"Parish" = "Parroċċa"; + +"Phone number" = "In-numru tal-mowbajl"; + +"Postal code" = "Kodiċi postali"; + +"Prefecture" = "Prefettura"; + +"Province" = "Provinċja"; + +"Remove" = "Neħħi"; + +"Remove bank account?" = "Tixtieq tneħħi l-kont tal-bank?"; + +"Remove card" = "Neħħi l-karta"; + +"Search" = "Fittex"; + +"Select card brand (optional)" = "Agħżel id-ditta tal-karta (mhux obbligatorja)"; + +"Sort code" = "Il-kodiċi Sort"; + +"State" = "Stat"; + +"Suburb" = "Subborg"; + +"Suburb or city" = "Subborg jew belt"; + +"The BSB you entered is incomplete." = "Il-BSB li daħħalt mhux komplut."; + +"The ID number you entered is incomplete." = "In-numru tal-ID li daħħalt mhuwiex komplut."; + +"The account number you entered is incomplete." = "In-numru tal-kont li daħħalt mhux komplut."; + +"The sort code you entered is invalid." = "Il-kodiċi sort li daħħalt mhux tajjeb."; + +"Town or city" = "Belt"; + +"UPI ID" = "ID UPI"; + +"Unable to parse phone number" = "Ma jistax jaqra n-numru tat-telefown"; + +"Use rotor to access links" = "Uża r-rotor biex tiftaħ il-ħoloq"; + +"Your BLIK code is incomplete." = "Il-kodiċi BLIK li ktibt mhux komplut."; + +"Your BLIK code is invalid." = "Il-kodiċi BLIK li ktibt mhux tajjeb."; + +"Your ZIP is incomplete." = "Il-kodiċi postali tiegħek mhux komplut."; + +"Your email is invalid." = "L-indirizz tal-email li daħħalt mhux tajjeb."; + +"Your payment information will not be saved." = "L-informazzjoni tal-pagament tiegħek mhux se tiġi merfugħa."; + +"Your postal code is incomplete." = "Il-kodiċi postali tiegħek mhux komplut."; + +"ZIP" = "Kodiċi Postali"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/nb.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/nb.lproj/Localizable.strings new file mode 100644 index 00000000..313c6730 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/nb.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (valgfritt)"; + +"Account number" = "Kontonummer"; + +"Address" = "Adresse"; + +"Address line 1" = "Adresselinje 1"; + +"Address line 2" = "Adresselinje 2"; + +"Area" = "Område"; + +"BLIK code" = "BLIK-kode"; + +"BSB number" = "BSB-nummer"; + +"Bank account •••• %@" = "Bankkonto •••• %@"; + +"Billing address is same as shipping" = "Fakturaaddresse er samme som levering"; + +"Cancel" = "Avbryt"; + +"Card brand" = "Kortmerke"; + +"City" = "By"; + +"Code field" = "Kodefelt"; + +"Company" = "Selskap"; + +"Continue" = "Fortsett"; + +"Country" = "Land"; + +"Country or region" = "Land eller region"; + +"County" = "Fylke"; + +"Date is empty." = "Dato er tom."; + +"Department" = "Avdeling"; + +"District" = "Distrikt"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Vil du lukke dette skjemaet?"; + +"Done" = "Ferdig"; + +"Double tap to edit" = "Dobbelttrykk for å redigere"; + +"Edit" = "Rediger"; + +"Eircode" = "Eircode"; + +"Email" = "E-post"; + +"Emirate" = "Emirat"; + +"Error" = "Feil"; + +"First" = "Fornavn"; + +"Full name" = "Fullt navn"; + +"Incomplete phone number" = "Ufullstendig telefonnummer"; + +"Invalid UPI ID" = "Ugyldig UPI-ID"; + +"Island" = "Øy"; + +"Last" = "Etternavn"; + +"Name" = "Navn"; + +"Name on account" = "Navn på kontoen"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Andre"; + +"Parish" = "Sogn"; + +"Phone number" = "Telefonnummer"; + +"Postal code" = "Postnummer"; + +"Prefecture" = "Prefektur"; + +"Province" = "Provins"; + +"Remove" = "Fjern"; + +"Remove bank account?" = "Vil du fjerne bankkonto?"; + +"Remove card" = "Fjern kort"; + +"Search" = "Søk"; + +"Select card brand (optional)" = "Velg merkevare for kort (valgfritt)"; + +"Sort code" = "Sorteringskode"; + +"State" = "Stat"; + +"Suburb" = "Forstad"; + +"Suburb or city" = "Forstad eller by"; + +"The BSB you entered is incomplete." = "Du skrev inn ufullstendig BSB."; + +"The ID number you entered is incomplete." = "ID-nummeret du la inn, er ufullstendig."; + +"The account number you entered is incomplete." = "Kontonummeret du la inn, er ufullstendig."; + +"The sort code you entered is invalid." = "Sorteringskoden du skrev inn, er ugyldig."; + +"Town or city" = "Stad eller by"; + +"UPI ID" = "UPI-ID"; + +"Unable to parse phone number" = "Kan ikke analyse mobilnummeret"; + +"Use rotor to access links" = "Bruk rotor for tilgang til lenker"; + +"Your BLIK code is incomplete." = "BLIK-koden er ufullstendig."; + +"Your BLIK code is invalid." = "BLIK-koden er ugyldig."; + +"Your ZIP is incomplete." = "ZIP er ufullstendig."; + +"Your email is invalid." = "E-posten din er ugyldig."; + +"Your payment information will not be saved." = "Betalingsinformasjonen din lagres ikke."; + +"Your postal code is incomplete." = "Postnummeret er ufullstendig."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/nl.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/nl.lproj/Localizable.strings new file mode 100644 index 00000000..15de22c3 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/nl.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (optioneel)"; + +"Account number" = "Rekeningnummer"; + +"Address" = "Adres"; + +"Address line 1" = "Adresregel 1"; + +"Address line 2" = "Adresregel 2"; + +"Area" = "Regio"; + +"BLIK code" = "BLIK-code"; + +"BSB number" = "BSB-nummer"; + +"Bank account •••• %@" = "Bankrekening •••• %@"; + +"Billing address is same as shipping" = "Factuuradres is hetzelfde als het verzendadres"; + +"Cancel" = "Annuleren"; + +"Card brand" = "Merk betaalkaart"; + +"City" = "Plaats"; + +"Code field" = "Codeveld"; + +"Company" = "Bedrijf"; + +"Continue" = "Doorgaan"; + +"Country" = "Land"; + +"Country or region" = "Land of regio"; + +"County" = "Graafschap"; + +"Date is empty." = "Datum is leeg."; + +"Department" = "Departement"; + +"District" = "District"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Wil je dit formulier sluiten?"; + +"Done" = "Gereed"; + +"Double tap to edit" = "Dubbeltikken om te bewerken"; + +"Edit" = "Bewerken"; + +"Eircode" = "Eircode"; + +"Email" = "E-mailadres"; + +"Emirate" = "Emiraat"; + +"Error" = "Fout"; + +"First" = "Voornaam"; + +"Full name" = "Volledige naam"; + +"Incomplete phone number" = "Onvolledig telefoonnummer"; + +"Invalid UPI ID" = "Ongeldige UPI-ID"; + +"Island" = "Eiland"; + +"Last" = "Achternaam"; + +"Name" = "Naam"; + +"Name on account" = "Naam op account"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Overige"; + +"Parish" = "Parochie"; + +"Phone number" = "Telefoonnummer"; + +"Postal code" = "Postcode"; + +"Prefecture" = "Prefectuur"; + +"Province" = "Provincie"; + +"Remove" = "Verwijderen"; + +"Remove bank account?" = "Bankrekening verwijderen?"; + +"Remove card" = "Betaalkaart verwijderen"; + +"Search" = "Zoeken"; + +"Select card brand (optional)" = "Merk van betaalkaart selecteren (optioneel)"; + +"Sort code" = "Bankcode"; + +"State" = "Staat"; + +"Suburb" = "Voorstad"; + +"Suburb or city" = "Stad of voorstad"; + +"The BSB you entered is incomplete." = "Het ingevoerde BSB-nummer is onvolledig."; + +"The ID number you entered is incomplete." = "Het opgegeven identificatienummer is onvolledig."; + +"The account number you entered is incomplete." = "Het opgegeven rekeningnummer is onvolledig."; + +"The sort code you entered is invalid." = "De opgegeven bankcode is ongeldig."; + +"Town or city" = "Stad of plaats"; + +"UPI ID" = "UPI-ID"; + +"Unable to parse phone number" = "Kan telefoonnummer niet parseren"; + +"Use rotor to access links" = "Gebruik de rotor om een link te openen"; + +"Your BLIK code is incomplete." = "Je BLIK-code is onvolledig."; + +"Your BLIK code is invalid." = "Je BLIK-code is ongeldig."; + +"Your ZIP is incomplete." = "Je ZIP is onvolledig."; + +"Your email is invalid." = "Je e-mailadres is ongeldig."; + +"Your payment information will not be saved." = "Je betaalgegevens worden niet opgeslagen."; + +"Your postal code is incomplete." = "Je postcode is onvolledig."; + +"ZIP" = "Postcode"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/nn-NO.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/nn-NO.lproj/Localizable.strings new file mode 100644 index 00000000..3680bed1 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/nn-NO.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (valfritt)"; + +"Account number" = "Kontonummer"; + +"Address" = "Adresse"; + +"Address line 1" = "Adresselinje 1"; + +"Address line 2" = "Adresselinje 2"; + +"Area" = "Område"; + +"BLIK code" = "BLIK-kode"; + +"BSB number" = "BSB-nummer"; + +"Bank account •••• %@" = "Bankkonto •••• %@"; + +"Billing address is same as shipping" = "Fakturaadressa er den same som leveringsadressa"; + +"Cancel" = "Avbryt"; + +"Card brand" = "Kortmerke"; + +"City" = "Stad"; + +"Code field" = "Kodefelt"; + +"Company" = "Selskap"; + +"Continue" = "Fortsett"; + +"Country" = "Land"; + +"Country or region" = "Land eller region"; + +"County" = "Fylke"; + +"Date is empty." = "Dato er tom."; + +"Department" = "Område"; + +"District" = "Distrikt"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Vil du lukke dette skjemaet?"; + +"Done" = "Ferdig"; + +"Double tap to edit" = "Dobbeltæpp for å redigere"; + +"Edit" = "Rediger"; + +"Eircode" = "Eircode"; + +"Email" = "E-post"; + +"Emirate" = "Emirat"; + +"Error" = "Feil"; + +"First" = "Fornamn"; + +"Full name" = "Fullt namn"; + +"Incomplete phone number" = "Ufullstendig telefonnummer"; + +"Invalid UPI ID" = "Ugyldig UPI ID"; + +"Island" = "Øy"; + +"Last" = "Etternamn"; + +"Name" = "Namn"; + +"Name on account" = "Namn på konto"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Annan"; + +"Parish" = "Sogn"; + +"Phone number" = "Telefonnummer"; + +"Postal code" = "Postnummer"; + +"Prefecture" = "Prefektur"; + +"Province" = "Område"; + +"Remove" = "Fjern"; + +"Remove bank account?" = "Fjerne bankkonto?"; + +"Remove card" = "Fjern kort"; + +"Search" = "Søk"; + +"Select card brand (optional)" = "Vel kortmerkje (valfritt)"; + +"Sort code" = "Sorteringskode"; + +"State" = "Stat"; + +"Suburb" = "Forstad"; + +"Suburb or city" = "Forstad eller stad"; + +"The BSB you entered is incomplete." = "BSB-koden du skreiv inn er ufullstendig."; + +"The ID number you entered is incomplete." = "ID-nummeret du skrev inn, er ufullstendig."; + +"The account number you entered is incomplete." = "Kontonummeret du skrev inn, er ufullstendig."; + +"The sort code you entered is invalid." = "Sorteringskoden du angjev, er ugyldig."; + +"Town or city" = "Stad"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Kan ikkje analysere telefonnummer"; + +"Use rotor to access links" = "Bruk rotor for å opne koplingar"; + +"Your BLIK code is incomplete." = "BLIK-koden er ufullstendig."; + +"Your BLIK code is invalid." = "BLIK-koden er ugyldig."; + +"Your ZIP is incomplete." = "ZIP-koden er ufullstendig."; + +"Your email is invalid." = "E-postadressa di er ugyldig."; + +"Your payment information will not be saved." = "Betalingsinformasjonen din blir ikkje lagra."; + +"Your postal code is incomplete." = "Postnummeret er ufullstendig."; + +"ZIP" = "POSTNR."; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/pl-PL.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/pl-PL.lproj/Localizable.strings new file mode 100644 index 00000000..85d34b6b --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/pl-PL.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opcjonalnie)"; + +"Account number" = "Numer konta"; + +"Address" = "Adres"; + +"Address line 1" = "Linia adresu 1"; + +"Address line 2" = "Linia adresu 2"; + +"Area" = "Obszar"; + +"BLIK code" = "Kod BLIK"; + +"BSB number" = "Numer BSB"; + +"Bank account •••• %@" = "Konto bankowe •••• %@"; + +"Billing address is same as shipping" = "Adres rozliczenia ten sam, co adres wysyłki"; + +"Cancel" = "Anuluj"; + +"Card brand" = "Marka karty"; + +"City" = "Miejscowość"; + +"Code field" = "Pole kodu"; + +"Company" = "Firma"; + +"Continue" = "Kontynuuj"; + +"Country" = "Kraj"; + +"Country or region" = "Kraj lub region"; + +"County" = "Hrabstwo"; + +"Date is empty." = "Data jest pusta."; + +"Department" = "Departament"; + +"District" = "Dystrykt"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Czy chcesz zamknąć ten formularz?"; + +"Done" = "Gotowe"; + +"Double tap to edit" = "Dotknij dwa razy, aby edytować"; + +"Edit" = "Edytuj"; + +"Eircode" = "Eircode"; + +"Email" = "Adres e-mail"; + +"Emirate" = "Emiraty"; + +"Error" = "Błąd"; + +"First" = "Imię"; + +"Full name" = "Imię i nazwisko"; + +"Incomplete phone number" = "Niekompletny numer telefonu"; + +"Invalid UPI ID" = "Nieprawidłowy UPI ID"; + +"Island" = "Wyspa"; + +"Last" = "Nazwisko"; + +"Name" = "Imię i nazwisko"; + +"Name on account" = "Imię i nazwisko na rachunku"; + +"OK" = "OK"; + +"Oblast" = "Obwód"; + +"Other" = "Inne"; + +"Parish" = "Parafia"; + +"Phone number" = "Numer telefonu"; + +"Postal code" = "Kod pocztowy"; + +"Prefecture" = "Prefektura"; + +"Province" = "Prowincja"; + +"Remove" = "Usuń"; + +"Remove bank account?" = "Usunąc konto bankowe?"; + +"Remove card" = "Usuń kartę"; + +"Search" = "Wyszukaj"; + +"Select card brand (optional)" = "Wybierz markę karty (opcjonalne)"; + +"Sort code" = "Kod sortowania"; + +"State" = "Stan"; + +"Suburb" = "Przedmieścia"; + +"Suburb or city" = "Przedmieścia lub miasto"; + +"The BSB you entered is incomplete." = "Wprowadzony numer BSB jest niepełny."; + +"The ID number you entered is incomplete." = "Podany identyfikator jest niekompletny."; + +"The account number you entered is incomplete." = "Podany numer konta jest niekompletny."; + +"The sort code you entered is invalid." = "Wprowadzony kod sortowania jest nieprawidłowy."; + +"Town or city" = "Miejscowość"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Nie można dokonać parsowania numeru telefonu"; + +"Use rotor to access links" = "Użyj pokrętła, aby uzyskać dostęp do łączy"; + +"Your BLIK code is incomplete." = "Kod BLIK jest niekompletny."; + +"Your BLIK code is invalid." = "Nieprawidłowy kod BLIK."; + +"Your ZIP is incomplete." = "Kod pocztowy (ZIP) jest niekompletny."; + +"Your email is invalid." = "Adres e-mail jest nieprawidłowy."; + +"Your payment information will not be saved." = "Informacje dotyczące płatności nie zostaną zapisane."; + +"Your postal code is incomplete." = "Kod pocztowy jest niekompletny."; + +"ZIP" = "Kod p"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/pt-BR.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/pt-BR.lproj/Localizable.strings new file mode 100644 index 00000000..84530ed3 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/pt-BR.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opcional)"; + +"Account number" = "Número da conta"; + +"Address" = "Endereço"; + +"Address line 1" = "Endereço – Linha 1"; + +"Address line 2" = "Endereço – Linha 2"; + +"Area" = "Área"; + +"BLIK code" = "Código BLIK"; + +"BSB number" = "Número BSB"; + +"Bank account •••• %@" = "Conta bancária •••• %@"; + +"Billing address is same as shipping" = "O endereço de faturamento é igual ao de envio"; + +"Cancel" = "Cancelar"; + +"Card brand" = "Bandeira do cartão"; + +"City" = "Cidade"; + +"Code field" = "Campo do código"; + +"Company" = "Empresa"; + +"Continue" = "Continuar"; + +"Country" = "País"; + +"Country or region" = "País ou região"; + +"County" = "Condado"; + +"Date is empty." = "A data está vazia."; + +"Department" = "Departamento"; + +"District" = "Distrito"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Deseja fechar este formulário?"; + +"Done" = "Concluído"; + +"Double tap to edit" = "Toque duas vezes para editar"; + +"Edit" = "Editar"; + +"Eircode" = "Eircode"; + +"Email" = "E-mail"; + +"Emirate" = "Emirado"; + +"Error" = "Erro"; + +"First" = "Nome"; + +"Full name" = "Nome completo"; + +"Incomplete phone number" = "Número de telefone incompleto"; + +"Invalid UPI ID" = "ID UPI inválido"; + +"Island" = "Ilha"; + +"Last" = "Sobrenome"; + +"Name" = "Nome"; + +"Name on account" = "Nome na conta"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Outro"; + +"Parish" = "Paróquia"; + +"Phone number" = "Telefone"; + +"Postal code" = "Código postal"; + +"Prefecture" = "Prefeitura"; + +"Province" = "Província"; + +"Remove" = "Remover"; + +"Remove bank account?" = "Remover conta bancária?"; + +"Remove card" = "Remover cartão"; + +"Search" = "Pesquisar"; + +"Select card brand (optional)" = "Selecionar bandeira de cartão (opcional)"; + +"Sort code" = "Sort code"; + +"State" = "Estado"; + +"Suburb" = "Subúrbio"; + +"Suburb or city" = "Subúrbio ou cidade"; + +"The BSB you entered is incomplete." = "O BSB inserido está incompleto."; + +"The ID number you entered is incomplete." = "O número da identificação inserido está incompleto."; + +"The account number you entered is incomplete." = "O número de conta inserido está incompleto."; + +"The sort code you entered is invalid." = "O sorte code que você inseriu é inválido."; + +"Town or city" = "Cidade ou município"; + +"UPI ID" = "ID UPI"; + +"Unable to parse phone number" = "Não foi possível analisar o número de telefone"; + +"Use rotor to access links" = "Use o rotor para acessar os links"; + +"Your BLIK code is incomplete." = "Seu código BLIK está incompleto."; + +"Your BLIK code is invalid." = "Seu código BLIK é inválido."; + +"Your ZIP is incomplete." = "Código postal incompleto."; + +"Your email is invalid." = "Seu e-mail é inválido."; + +"Your payment information will not be saved." = "Seus dados de pagamento não serão salvos."; + +"Your postal code is incomplete." = "Código postal incompleto."; + +"ZIP" = "CEP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/pt-PT.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/pt-PT.lproj/Localizable.strings new file mode 100644 index 00000000..6eb9db6e --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/pt-PT.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opcional)"; + +"Account number" = "Número de conta"; + +"Address" = "Endereço"; + +"Address line 1" = "Linha de endereço 1"; + +"Address line 2" = "Linha de endereço 2"; + +"Area" = "Área"; + +"BLIK code" = "Código BLIK"; + +"BSB number" = "Número de BSB"; + +"Bank account •••• %@" = "Conta bancária •••• %@"; + +"Billing address is same as shipping" = "O endereço de faturação é igual ao endereço de envio"; + +"Cancel" = "Cancelar"; + +"Card brand" = "Marca do cartão"; + +"City" = "Cidade"; + +"Code field" = "Campo do código"; + +"Company" = "Empresa"; + +"Continue" = "Continuar"; + +"Country" = "País"; + +"Country or region" = "País ou região"; + +"County" = "Concelho"; + +"Date is empty." = "A data está vazia."; + +"Department" = "Departamento"; + +"District" = "Distrito"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Pretende fechar este formulário?"; + +"Done" = "Concluído"; + +"Double tap to edit" = "Toque duas vezes para editar"; + +"Edit" = "Editar"; + +"Eircode" = "Eircode"; + +"Email" = "E-mail"; + +"Emirate" = "Emirado"; + +"Error" = "Erro"; + +"First" = "Nome próprio"; + +"Full name" = "Nome completo"; + +"Incomplete phone number" = "Número de telefone incompleto"; + +"Invalid UPI ID" = "ID UPI inválida"; + +"Island" = "Ilha"; + +"Last" = "Apelido"; + +"Name" = "Nome"; + +"Name on account" = "Nome na conta"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Outro"; + +"Parish" = "Paróquia"; + +"Phone number" = "Número de telefone"; + +"Postal code" = "Código postal"; + +"Prefecture" = "Câmara municipal"; + +"Province" = "Província"; + +"Remove" = "Remover"; + +"Remove bank account?" = "Remover conta bancária?"; + +"Remove card" = "Eliminar cartão"; + +"Search" = "Pesquisar"; + +"Select card brand (optional)" = "Selecione a marca do cartão (opcional)"; + +"Sort code" = "Código da agência bancária"; + +"State" = "Estado"; + +"Suburb" = "Subúrbio"; + +"Suburb or city" = "Subúrbio ou cidade"; + +"The BSB you entered is incomplete." = "O BSB que introduziu está incompleto."; + +"The ID number you entered is incomplete." = "O número de identificação que introduziu está incompleto."; + +"The account number you entered is incomplete." = "O número da conta que introduziu está incompleto."; + +"The sort code you entered is invalid." = "O código da agência bancária que introduziu é inválido."; + +"Town or city" = "Localidade ou cidade"; + +"UPI ID" = "ID UPI"; + +"Unable to parse phone number" = "Impossível analisar número de telefone"; + +"Use rotor to access links" = "Utilizar o rotor para aceder a ligações"; + +"Your BLIK code is incomplete." = "O seu código BLIK está incompleto."; + +"Your BLIK code is invalid." = "O seu código BLIK é inválido."; + +"Your ZIP is incomplete." = "O seu código ZIP está incompleto."; + +"Your email is invalid." = "O seu email é inválido."; + +"Your payment information will not be saved." = "As suas informações de pagamento não serão guardadas."; + +"Your postal code is incomplete." = "O seu código postal está incompleto."; + +"ZIP" = "C.P."; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/ro-RO.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/ro-RO.lproj/Localizable.strings new file mode 100644 index 00000000..e9847f70 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/ro-RO.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (opțional)"; + +"Account number" = "Număr cont"; + +"Address" = "Adresa"; + +"Address line 1" = "Rândul 1 pentru adresă"; + +"Address line 2" = "Rândul 2 pentru adresă"; + +"Area" = "Zonă"; + +"BLIK code" = "Cod BLIK"; + +"BSB number" = "Număr BSB"; + +"Bank account •••• %@" = "Cont bancar •••• %@"; + +"Billing address is same as shipping" = "Adresa de facturare este la fel cu cea de expediere"; + +"Cancel" = "Anulare"; + +"Card brand" = "Marcă card"; + +"City" = "Oraș"; + +"Code field" = "Câmp pentru introducerea codului"; + +"Company" = "Companie"; + +"Continue" = "Continuare"; + +"Country" = "Țară"; + +"Country or region" = "Țară sau regiune"; + +"County" = "Județ"; + +"Date is empty." = "Data nu este completată."; + +"Department" = "Departament"; + +"District" = "District"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Doriți să închideți acest formular?"; + +"Done" = "Efectuat"; + +"Double tap to edit" = "Atingeți de două ori pentru a edita"; + +"Edit" = "Editare"; + +"Eircode" = "Eircode"; + +"Email" = "E-mail"; + +"Emirate" = "Emirat"; + +"Error" = "Eroare"; + +"First" = "Prenume"; + +"Full name" = "Nume complet"; + +"Incomplete phone number" = "Număr de telefon incomplet"; + +"Invalid UPI ID" = "ID UPI nevalid"; + +"Island" = "Insulă"; + +"Last" = "Nume de familie"; + +"Name" = "Nume"; + +"Name on account" = "Numele contului"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Altele"; + +"Parish" = "Parohie"; + +"Phone number" = "Număr de telefon"; + +"Postal code" = "Cod poștal"; + +"Prefecture" = "Prefectură"; + +"Province" = "Provincie"; + +"Remove" = "Eliminare"; + +"Remove bank account?" = "Eliminați contul bancar?"; + +"Remove card" = "Eliminare card"; + +"Search" = "Căutare"; + +"Select card brand (optional)" = "Selectare marcă card (opțional)"; + +"Sort code" = "Cod de sortare"; + +"State" = "Stat"; + +"Suburb" = "Suburbie"; + +"Suburb or city" = "Suburbie sau oraș"; + +"The BSB you entered is incomplete." = "Numărul BSB pe care l-ați introdus nu este complet."; + +"The ID number you entered is incomplete." = "Numărul de identificare pe care l-ați introdus nu este complet."; + +"The account number you entered is incomplete." = "Numărul de cont pe care l-ați introdus nu este complet."; + +"The sort code you entered is invalid." = "Codul de identificare a băncii pe care l-ați introdus nu este valid."; + +"Town or city" = "Municipalitate sau oraș"; + +"UPI ID" = "ID UPI"; + +"Unable to parse phone number" = "Nu se poate analiza numărul de telefon"; + +"Use rotor to access links" = "Utilizați rotorul pentru a accesa linkurile"; + +"Your BLIK code is incomplete." = "Codul dvs. BLIK nu este complet."; + +"Your BLIK code is invalid." = "Codul dvs. BLIK nu este valid."; + +"Your ZIP is incomplete." = "Codul dvs. poștal nu este complet."; + +"Your email is invalid." = "E-mailul dvs. nu este valid."; + +"Your payment information will not be saved." = "Informațiile dvs. de plată nu vor fi salvate."; + +"Your postal code is incomplete." = "Codul dvs. poștal nu este complet."; + +"ZIP" = "Cod poștal"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/ru.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/ru.lproj/Localizable.strings new file mode 100644 index 00000000..64678e90 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/ru.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (необязательно)"; + +"Account number" = "Номер счета"; + +"Address" = "Адрес"; + +"Address line 1" = "Адрес (строка 1)"; + +"Address line 2" = "Адрес (строка 2)"; + +"Area" = "Область"; + +"BLIK code" = "Код BLIK"; + +"BSB number" = "Номер BSB"; + +"Bank account •••• %@" = "Банковский счет ••••%@"; + +"Billing address is same as shipping" = "Адрес выставления счетов такой же, как адрес доставки"; + +"Cancel" = "Отмена"; + +"Card brand" = "Бренд карты"; + +"City" = "Город"; + +"Code field" = "Поле кода"; + +"Company" = "Компания"; + +"Continue" = "Продолжить"; + +"Country" = "Страна"; + +"Country or region" = "Страна или регион"; + +"County" = "Графство"; + +"Date is empty." = "Дата не указана."; + +"Department" = "Департамент"; + +"District" = "Район"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Закрыть форму?"; + +"Done" = "Готово"; + +"Double tap to edit" = "Коснитесь дважды, чтобы отредактировать"; + +"Edit" = "Редактировать"; + +"Eircode" = "Почтовый индекс (Eircode)"; + +"Email" = "Эл. почта"; + +"Emirate" = "Эмират"; + +"Error" = "Ошибка"; + +"First" = "Имя"; + +"Full name" = "Имя, фамилия"; + +"Incomplete phone number" = "Номер телефона введен не полностью"; + +"Invalid UPI ID" = "Недействительный идентификатор UPI"; + +"Island" = "Остров"; + +"Last" = "Фамилия"; + +"Name" = "Имя"; + +"Name on account" = "Владелец счета"; + +"OK" = "ОК"; + +"Oblast" = "Область"; + +"Other" = "другой"; + +"Parish" = "Приход"; + +"Phone number" = "Номер телефона"; + +"Postal code" = "Почтовый индекс"; + +"Prefecture" = "Префектура"; + +"Province" = "Провинция"; + +"Remove" = "Удалить"; + +"Remove bank account?" = "Удалить банковский счет?"; + +"Remove card" = "Удалить карту"; + +"Search" = "Поиск"; + +"Select card brand (optional)" = "Выберите бренд карты (необязательно)"; + +"Sort code" = "Код банка"; + +"State" = "Штат"; + +"Suburb" = "Пригород"; + +"Suburb or city" = "Населенный пункт"; + +"The BSB you entered is incomplete." = "Введенный код BSB неполон."; + +"The ID number you entered is incomplete." = "Номер документа, удостоверяющего личность, введен не полностью."; + +"The account number you entered is incomplete." = "Номер счета введен не полностью."; + +"The sort code you entered is invalid." = "Введенный код сортировки неверен."; + +"Town or city" = "Город"; + +"UPI ID" = "Идентификатор UPI"; + +"Unable to parse phone number" = "Не удается разобрать номер телефона"; + +"Use rotor to access links" = "Используйте ротор для доступа к ссылкам"; + +"Your BLIK code is incomplete." = "Ваш код BLIK неполный."; + +"Your BLIK code is invalid." = "Ваш код BLIK недействителен."; + +"Your ZIP is incomplete." = "Почтовый индекс введен не полностью."; + +"Your email is invalid." = "Недействительный адрес эл. почты."; + +"Your payment information will not be saved." = "Ваши платежные реквизиты не будут сохранены."; + +"Your postal code is incomplete." = "Введенный почтовый индекс неполон."; + +"ZIP" = "Почтовый индекс"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/sk-SK.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/sk-SK.lproj/Localizable.strings new file mode 100644 index 00000000..db58ef35 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/sk-SK.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (voliteľné)"; + +"Account number" = "Číslo účtu"; + +"Address" = "Adresa"; + +"Address line 1" = "Riadok adresy 1"; + +"Address line 2" = "Riadok adresy 2"; + +"Area" = "Okres"; + +"BLIK code" = "Kód BLIK"; + +"BSB number" = "BSB číslo"; + +"Bank account •••• %@" = "Bankový účet •••• %@"; + +"Billing address is same as shipping" = "Fakturačná adresa je totožná s dodacou"; + +"Cancel" = "Zrušiť"; + +"Card brand" = "Značka karty"; + +"City" = "Mesto"; + +"Code field" = "Pole kódu"; + +"Company" = "Spoločnosť"; + +"Continue" = "Pokračovať"; + +"Country" = "Krajina"; + +"Country or region" = "Krajina alebo región"; + +"County" = "Kraj"; + +"Date is empty." = "Dátum je prázdny."; + +"Department" = "Oddelenie"; + +"District" = "Obvod"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Chcete zatvoriť tento formulár?"; + +"Done" = "Hotovo"; + +"Double tap to edit" = "Dvojitým ťuknutím upravte"; + +"Edit" = "Upraviť"; + +"Eircode" = "Kód Eir"; + +"Email" = "E-mail"; + +"Emirate" = "Emirát"; + +"Error" = "Chyba"; + +"First" = "Meno"; + +"Full name" = "Celé meno"; + +"Incomplete phone number" = "Neúplné telefónne číslo"; + +"Invalid UPI ID" = "Neplatné identifikačné číslo UPI"; + +"Island" = "Island"; + +"Last" = "Priezvisko"; + +"Name" = "Meno"; + +"Name on account" = "Meno uvedené v účte"; + +"OK" = "OK"; + +"Oblast" = "Oblasť"; + +"Other" = "Iné"; + +"Parish" = "Parish"; + +"Phone number" = "Telefónne číslo"; + +"Postal code" = "PSČ"; + +"Prefecture" = "Prefektúra"; + +"Province" = "Provincia"; + +"Remove" = "Odstrániť"; + +"Remove bank account?" = "Odstrániť bankový účet?"; + +"Remove card" = "Odstrániť kartu"; + +"Search" = "Vyhľadať"; + +"Select card brand (optional)" = "Vybrať značku karty (voliteľne)"; + +"Sort code" = "Triediaci kód"; + +"State" = "Štát"; + +"Suburb" = "Predmestie"; + +"Suburb or city" = "Predmestie alebo mesto"; + +"The BSB you entered is incomplete." = "Zadané BSB je neúplné."; + +"The ID number you entered is incomplete." = "Zadané identifikačné číslo je neúplné."; + +"The account number you entered is incomplete." = "Zadané číslo účtu je neúplné."; + +"The sort code you entered is invalid." = "Zadaný triediaci kód je neplatný."; + +"Town or city" = "Obec alebo mesto"; + +"UPI ID" = "Identifikačné číslo UPI"; + +"Unable to parse phone number" = "Nepodarilo sa analyzovať telefónne číslo"; + +"Use rotor to access links" = "Použite funkciu rotora na prístup k odkazom"; + +"Your BLIK code is incomplete." = "Váš kód BLIK je neúplný."; + +"Your BLIK code is invalid." = "Váš kód BLIK je neplatný."; + +"Your ZIP is incomplete." = "Vaše PSČ je neúplné."; + +"Your email is invalid." = "Váš e-mail je neplatný."; + +"Your payment information will not be saved." = "Vaše informácie o platbe nebudú uložené."; + +"Your postal code is incomplete." = "Vaše poštové smerovacie číslo je neúplné."; + +"ZIP" = "PSČ"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/sl-SI.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/sl-SI.lproj/Localizable.strings new file mode 100644 index 00000000..d0176180 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/sl-SI.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (izbirno)"; + +"Account number" = "Številka računa"; + +"Address" = "Naslov"; + +"Address line 1" = "Naslov 1"; + +"Address line 2" = "Vrstica naslova 2"; + +"Area" = "Območje"; + +"BLIK code" = "Koda BLIK"; + +"BSB number" = "Številka BSB"; + +"Bank account •••• %@" = "Bančni račun •••• %@"; + +"Billing address is same as shipping" = "Naslov za obračunavanje je isti kot dostavni naslov"; + +"Cancel" = "Prekliči"; + +"Card brand" = "Blagovna znamka kartice"; + +"City" = "Mesto"; + +"Code field" = "Polje za kodo"; + +"Company" = "Podjetje"; + +"Continue" = "Nadaljuj"; + +"Country" = "Država"; + +"Country or region" = "Država ali regija"; + +"County" = "Okraj"; + +"Date is empty." = "Polje z datumom je prazno."; + +"Department" = "Oddelek"; + +"District" = "Okrožje"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Ali želite zapreti ta obrazec?"; + +"Done" = "Dokončano"; + +"Double tap to edit" = "Dvotapnite, da uredite"; + +"Edit" = "Uredi"; + +"Eircode" = "Eircode"; + +"Email" = "E-poštni naslov"; + +"Emirate" = "Emirat"; + +"Error" = "Napaka"; + +"First" = "Ime"; + +"Full name" = "Polno ime"; + +"Incomplete phone number" = "Nepopolna telefonska številka mobilnega telefona."; + +"Invalid UPI ID" = "Neveljaven UPI ID"; + +"Island" = "Otok"; + +"Last" = "Priimek"; + +"Name" = "Ime"; + +"Name on account" = "Ime na računu"; + +"OK" = "V redu"; + +"Oblast" = "Oblast"; + +"Other" = "Drugo"; + +"Parish" = "Župnija"; + +"Phone number" = "Telefonska številka"; + +"Postal code" = "Poštna številka"; + +"Prefecture" = "Prefektura"; + +"Province" = "Provinca"; + +"Remove" = "Odstrani"; + +"Remove bank account?" = "Ali želite odstraniti bančni račun?"; + +"Remove card" = "Odstrani kartico"; + +"Search" = "Išči"; + +"Select card brand (optional)" = "Izberite blagovno znamko kartice (neobvezno)"; + +"Sort code" = "Številka banke"; + +"State" = "Zvezna država"; + +"Suburb" = "Predmestje"; + +"Suburb or city" = "Predmestje ali mesto"; + +"The BSB you entered is incomplete." = "Vneseni BSB ni popoln."; + +"The ID number you entered is incomplete." = "Vnesena številka osebnega dokumenta ni popolna."; + +"The account number you entered is incomplete." = "Vnesena številka računa ni popolna."; + +"The sort code you entered is invalid." = "Ta številka banke ni veljavna."; + +"Town or city" = "Kraj ali mesto"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Telefonske številke ni mogoče razčleniti"; + +"Use rotor to access links" = "Uporabite rotor za dostop do povezav"; + +"Your BLIK code is incomplete." = "Vaša koda BLIK ni popolna."; + +"Your BLIK code is invalid." = "Vaša koda BLIK ni veljavna."; + +"Your ZIP is incomplete." = "Vaša poštna številka ni popolna."; + +"Your email is invalid." = "Vaš e-poštni naslov ni veljaven."; + +"Your payment information will not be saved." = "Vaši podatki o plačilo ne bodo shranjeni."; + +"Your postal code is incomplete." = "Vaša poštna številka ni popolna."; + +"ZIP" = "Poštna številka"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/sv.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/sv.lproj/Localizable.strings new file mode 100644 index 00000000..78a55140 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/sv.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (valfritt)"; + +"Account number" = "Kontonummer"; + +"Address" = "Adress"; + +"Address line 1" = "Adressrad 1"; + +"Address line 2" = "Adressrad 2"; + +"Area" = "Område"; + +"BLIK code" = "BLIK-kod"; + +"BSB number" = "BSB-nummer"; + +"Bank account •••• %@" = "Bankkonto •••• %@"; + +"Billing address is same as shipping" = "Faktureringsadress är samma som leveransadress"; + +"Cancel" = "Avbryt"; + +"Card brand" = "Kortmärke"; + +"City" = "Ort"; + +"Code field" = "Fält för kod"; + +"Company" = "Företag"; + +"Continue" = "Fortsätt"; + +"Country" = "Land"; + +"Country or region" = "Land eller region"; + +"County" = "Landskap"; + +"Date is empty." = "Datumfältet är tomt."; + +"Department" = "Departement"; + +"District" = "Distrikt"; + +"Do Si" = "Provins och stad"; + +"Do you want to close this form?" = "Vill du stänga det här formuläret?"; + +"Done" = "Klar"; + +"Double tap to edit" = "Dubbelklicka för att redigera"; + +"Edit" = "Redigera"; + +"Eircode" = "Eircode"; + +"Email" = "E-post"; + +"Emirate" = "Emirat"; + +"Error" = "Fel"; + +"First" = "Förnamn"; + +"Full name" = "Fullständigt namn"; + +"Incomplete phone number" = "Ofullständigt telefonnummer"; + +"Invalid UPI ID" = "Ogiltigt UPI ID"; + +"Island" = "Ö"; + +"Last" = "Efternamn"; + +"Name" = "Namn"; + +"Name on account" = "Namn på kontot"; + +"OK" = "OK"; + +"Oblast" = "Oblast"; + +"Other" = "Annan"; + +"Parish" = "Parish"; + +"Phone number" = "Telefonnummer"; + +"Postal code" = "Postnummer"; + +"Prefecture" = "Prefektur"; + +"Province" = "Provins"; + +"Remove" = "Ta bort"; + +"Remove bank account?" = "Ta bort bankkonto?"; + +"Remove card" = "Ta bort kort"; + +"Search" = "Sök"; + +"Select card brand (optional)" = "Välj korttyp (valfritt)"; + +"Sort code" = "Clearingnummer"; + +"State" = "Delstat"; + +"Suburb" = "Förort"; + +"Suburb or city" = "Förort eller stad"; + +"The BSB you entered is incomplete." = "Det BSB du angav är ofullständigt."; + +"The ID number you entered is incomplete." = "ID-numret du angav är ofullständigt."; + +"The account number you entered is incomplete." = "Kontonumret du angav är ofullständigt."; + +"The sort code you entered is invalid." = "Det angivna clearingnumret (sort code) är ogiltigt."; + +"Town or city" = "Stad"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "Det gick inte att analysera telefonnumret"; + +"Use rotor to access links" = "Använd rotorn för att komma åt länkar"; + +"Your BLIK code is incomplete." = "Din BLIK-kod är ofullständig."; + +"Your BLIK code is invalid." = "Din BLIK-kod är ogiltig."; + +"Your ZIP is incomplete." = "Ditt postnummer är ofullständigt."; + +"Your email is invalid." = "Din e-postadress är ogiltig."; + +"Your payment information will not be saved." = "Din betalningsinformation kommer inte att sparas."; + +"Your postal code is incomplete." = "Ditt postnummer är ofullständigt."; + +"ZIP" = "Postkod"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/tr.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/tr.lproj/Localizable.strings new file mode 100644 index 00000000..ac42c0db --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/tr.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (isteğe bağlı)"; + +"Account number" = "Hesap numarası"; + +"Address" = "Adres"; + +"Address line 1" = "Adres satırı 1"; + +"Address line 2" = "Adres satırı 2"; + +"Area" = "Bölge"; + +"BLIK code" = "BLIK kodu"; + +"BSB number" = "BSB numarası"; + +"Bank account •••• %@" = "Banka hesabı •••• %@"; + +"Billing address is same as shipping" = "Faturalama adresi, gönderim adresiyle aynı"; + +"Cancel" = "İptal"; + +"Card brand" = "Kartın markası"; + +"City" = "Şehir"; + +"Code field" = "Kod alanı"; + +"Company" = "Şirket"; + +"Continue" = "Devam et"; + +"Country" = "Ülke"; + +"Country or region" = "Ülke veya bölge"; + +"County" = "Ülke"; + +"Date is empty." = "Tarih boş."; + +"Department" = "İdari Bölüm"; + +"District" = "İlçe"; + +"Do Si" = "Do Si"; + +"Do you want to close this form?" = "Bu formu kapatmak istiyor musunuz?"; + +"Done" = "Tamam"; + +"Double tap to edit" = "Düzenlemek için iki kez dokunun"; + +"Edit" = "Düzenle"; + +"Eircode" = "Eircode"; + +"Email" = "E-posta"; + +"Emirate" = "Emirlik"; + +"Error" = "Hata"; + +"First" = "Ad"; + +"Full name" = "Ad ve soyadınız"; + +"Incomplete phone number" = "Eksik telefon numarası"; + +"Invalid UPI ID" = "Geçersiz UPI Numarası"; + +"Island" = "Ada"; + +"Last" = "Soyadı"; + +"Name" = "Ad"; + +"Name on account" = "Hesaptaki ad"; + +"OK" = "TAMAM"; + +"Oblast" = "Oblast"; + +"Other" = "Diğer"; + +"Parish" = "Dini bölge"; + +"Phone number" = "Telefon numarası"; + +"Postal code" = "Posta kodu"; + +"Prefecture" = "İdari bölge"; + +"Province" = "İlçe"; + +"Remove" = "Kaldır"; + +"Remove bank account?" = "Banka hesabı kaldırılsın mı?"; + +"Remove card" = "Kartı kaldır"; + +"Search" = "Ara"; + +"Select card brand (optional)" = "Kart markasını seçin (isteğe bağlı)"; + +"Sort code" = "Şube kodu"; + +"State" = "Eyalet"; + +"Suburb" = "Banliyö"; + +"Suburb or city" = "Banliyö veya şehir"; + +"The BSB you entered is incomplete." = "Girdiğiniz BSB eksik."; + +"The ID number you entered is incomplete." = "Girdiğiniz kimlik numarası eksik."; + +"The account number you entered is incomplete." = "Girdiğiniz hesap numarası eksik."; + +"The sort code you entered is invalid." = "Girdiğiniz banka/şube kodu geçersiz."; + +"Town or city" = "Kasaba veya şehir"; + +"UPI ID" = "UPI Numarası"; + +"Unable to parse phone number" = "Telefon numaranızı ayrıştırılamadı"; + +"Use rotor to access links" = "Linklere erişmek için rotoru kullanın."; + +"Your BLIK code is incomplete." = "BLIK kodunuz eksik."; + +"Your BLIK code is invalid." = "BLIK kodunuz geçersiz."; + +"Your ZIP is incomplete." = "Posta kodunuz eksik."; + +"Your email is invalid." = "E-postanız geçersiz."; + +"Your payment information will not be saved." = "Ödeme bilgileriniz kaydedilmeyecek."; + +"Your postal code is incomplete." = "Posta kodunuz eksik."; + +"ZIP" = "Posta Kodu"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/vi.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/vi.lproj/Localizable.strings new file mode 100644 index 00000000..a108d95e --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/vi.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@ (tùy chọn)"; + +"Account number" = "Số tài khoản"; + +"Address" = "Địa chỉ"; + +"Address line 1" = "Dòng địa chỉ 1"; + +"Address line 2" = "Dòng địa chỉ 2"; + +"Area" = "Khu vực"; + +"BLIK code" = "Mã BLIK"; + +"BSB number" = "Số BSB"; + +"Bank account •••• %@" = "Tài khoản ngân hàng •••• %@"; + +"Billing address is same as shipping" = "Địa chỉ xuất hóa đơn giống địa chỉ chuyển hàng"; + +"Cancel" = "Hủy"; + +"Card brand" = "Thương hiệu thẻ"; + +"City" = "Thành phố"; + +"Code field" = "Trường mã"; + +"Company" = "Công ty"; + +"Continue" = "Tiếp tục"; + +"Country" = "Quốc gia"; + +"Country or region" = "Quốc gia hoặc khu vực"; + +"County" = "Quận"; + +"Date is empty." = "Ngày trống."; + +"Department" = "Sở ngành"; + +"District" = "Quận/Huyện"; + +"Do Si" = "Tỉnh/Thành phố"; + +"Do you want to close this form?" = "Bạn có muốn đóng biểu mẫu này không?"; + +"Done" = "Xong"; + +"Double tap to edit" = "Nhấp chạm đúp để hiệu đính"; + +"Edit" = "Sửa"; + +"Eircode" = "Mã Eircode"; + +"Email" = "Email"; + +"Emirate" = "Tiểu vương quốc"; + +"Error" = "Lỗi"; + +"First" = "Tên"; + +"Full name" = "Họ tên"; + +"Incomplete phone number" = "Số điện thoại chưa đầy đủ"; + +"Invalid UPI ID" = "Số ID UPI không hợp lệ"; + +"Island" = "Đảo"; + +"Last" = "Họ"; + +"Name" = "Tên"; + +"Name on account" = "Tên trên tài khoản"; + +"OK" = "OK"; + +"Oblast" = "Vùng"; + +"Other" = "Khác"; + +"Parish" = "Giáo xứ"; + +"Phone number" = "Số điện thoại"; + +"Postal code" = "Mã bưu điện"; + +"Prefecture" = "Tỉnh"; + +"Province" = "Tỉnh"; + +"Remove" = "Xóa"; + +"Remove bank account?" = "Bỏ tài khoản ngân hàng?"; + +"Remove card" = "Xóa thẻ"; + +"Search" = "Tìm kiếm"; + +"Select card brand (optional)" = "Chọn thương hiệu thẻ (tùy chọn)"; + +"Sort code" = "Mã sắp xếp"; + +"State" = "Tiểu bang"; + +"Suburb" = "Ngoại ô"; + +"Suburb or city" = "Ngoại ô hoặc thành phố"; + +"The BSB you entered is incomplete." = "BSB mà bạn nhập chưa đầy đủ."; + +"The ID number you entered is incomplete." = "Số ID bạn đã nhập chưa đầy đủ."; + +"The account number you entered is incomplete." = "Số tài khoản quý vị đã nhập không đầy đủ."; + +"The sort code you entered is invalid." = "Mã sắp xếp bạn đã nhập không hợp lệ."; + +"Town or city" = "Thị trấn hoặc thành phố"; + +"UPI ID" = "Số ID UPI"; + +"Unable to parse phone number" = "Không thể phân tích cú pháp số điện thoại"; + +"Use rotor to access links" = "Sử dụng rô-to để truy cập liên kết"; + +"Your BLIK code is incomplete." = "Mã BLIK của quý vị chưa đầy đủ."; + +"Your BLIK code is invalid." = "Mã BLIK của quý vị không hợp lệ."; + +"Your ZIP is incomplete." = "Mã ZIP chưa đầy đủ."; + +"Your email is invalid." = "Email của quý vị không hợp lệ."; + +"Your payment information will not be saved." = "Thông tin thanh toán của bạn sẽ không lưu lại."; + +"Your postal code is incomplete." = "Mã bưu điện chưa đầy đủ."; + +"ZIP" = "ZIP"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/zh-HK.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/zh-HK.lproj/Localizable.strings new file mode 100644 index 00000000..4fda7aec --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/zh-HK.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@(可選)"; + +"Account number" = "賬號"; + +"Address" = "地址"; + +"Address line 1" = "地址第 1 行"; + +"Address line 2" = "地址第 2 行"; + +"Area" = "地區"; + +"BLIK code" = "BLIK 代碼"; + +"BSB number" = "BSB 號碼"; + +"Bank account •••• %@" = "銀行賬戶 •••• %@"; + +"Billing address is same as shipping" = "賬單地址與收貨地址相同"; + +"Cancel" = "取消"; + +"Card brand" = "銀行卡品牌"; + +"City" = "城市"; + +"Code field" = "驗證碼欄位"; + +"Company" = "公司"; + +"Continue" = "繼續"; + +"Country" = "國家"; + +"Country or region" = "國家或地區"; + +"County" = "縣"; + +"Date is empty." = "日期為空。"; + +"Department" = "部門"; + +"District" = "地區"; + +"Do Si" = "道/縣"; + +"Do you want to close this form?" = "您確定要保存此表單嗎?"; + +"Done" = "完成"; + +"Double tap to edit" = "按兩下可編輯"; + +"Edit" = "編輯"; + +"Eircode" = "愛爾蘭郵區編號"; + +"Email" = "電郵地址"; + +"Emirate" = "酋長國"; + +"Error" = "錯誤"; + +"First" = "名字"; + +"Full name" = "全名"; + +"Incomplete phone number" = "電話號碼不完整"; + +"Invalid UPI ID" = "UPI ID 無效"; + +"Island" = "島"; + +"Last" = "姓氏"; + +"Name" = "姓名"; + +"Name on account" = "賬戶上的姓名"; + +"OK" = "確定"; + +"Oblast" = "州"; + +"Other" = "其他"; + +"Parish" = "堂區"; + +"Phone number" = "手機號碼"; + +"Postal code" = "郵區編號"; + +"Prefecture" = "轄區"; + +"Province" = "省"; + +"Remove" = "移除"; + +"Remove bank account?" = "移除銀行賬戶?"; + +"Remove card" = "解除綁定"; + +"Search" = "搜尋"; + +"Select card brand (optional)" = "選擇銀行卡品牌(可選)"; + +"Sort code" = "分類代碼"; + +"State" = "州"; + +"Suburb" = "郊區"; + +"Suburb or city" = "市郊或城市"; + +"The BSB you entered is incomplete." = "您輸入的 BSB 不完整。"; + +"The ID number you entered is incomplete." = "您輸入的證件號碼不完整。"; + +"The account number you entered is incomplete." = "您輸入的賬號不完整。"; + +"The sort code you entered is invalid." = "您輸入的分類代碼無效。"; + +"Town or city" = "城鎮或城市"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "無法粘貼手機號碼"; + +"Use rotor to access links" = "用轉子訪問連結"; + +"Your BLIK code is incomplete." = "您的 BLIK 代碼不完整。"; + +"Your BLIK code is invalid." = "您的 BLIK 代碼無效。"; + +"Your ZIP is incomplete." = "您的郵區編號不完整。"; + +"Your email is invalid." = "您的電郵地址無效。"; + +"Your payment information will not be saved." = "不會保存您的付款資訊。"; + +"Your postal code is incomplete." = "您的郵區編號不完整。"; + +"ZIP" = "郵區編號"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/zh-Hans.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/zh-Hans.lproj/Localizable.strings new file mode 100644 index 00000000..45a3f89d --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@(可选)"; + +"Account number" = "账号"; + +"Address" = "地址"; + +"Address line 1" = "地址第 1 行"; + +"Address line 2" = "地址第 2 行"; + +"Area" = "地区"; + +"BLIK code" = "BLIK 代码"; + +"BSB number" = "BSB 号码"; + +"Bank account •••• %@" = "银行账户 •••• %@"; + +"Billing address is same as shipping" = "账单地址与收货地址相同"; + +"Cancel" = "取消"; + +"Card brand" = "银行卡品牌"; + +"City" = "城市"; + +"Code field" = "代码字段"; + +"Company" = "公司"; + +"Continue" = "继续"; + +"Country" = "国家"; + +"Country or region" = "国家或地区"; + +"County" = "县"; + +"Date is empty." = "日期为空。"; + +"Department" = "部门"; + +"District" = "区"; + +"Do Si" = "道/县"; + +"Do you want to close this form?" = "您想关闭此表单吗?"; + +"Done" = "完成"; + +"Double tap to edit" = "双击可编辑"; + +"Edit" = "编辑"; + +"Eircode" = "爱尔兰邮编"; + +"Email" = "电子邮件"; + +"Emirate" = "酋长国"; + +"Error" = "错误"; + +"First" = "名字"; + +"Full name" = "全名"; + +"Incomplete phone number" = "电话号码不完整"; + +"Invalid UPI ID" = "UPI ID 无效"; + +"Island" = "岛"; + +"Last" = "姓氏"; + +"Name" = "姓名"; + +"Name on account" = "账户名"; + +"OK" = "确定"; + +"Oblast" = "州"; + +"Other" = "其他"; + +"Parish" = "堂区"; + +"Phone number" = "手机号码"; + +"Postal code" = "邮政编码"; + +"Prefecture" = "辖区"; + +"Province" = "省"; + +"Remove" = "移除"; + +"Remove bank account?" = "移除银行账户?"; + +"Remove card" = "移除卡"; + +"Search" = "搜索"; + +"Select card brand (optional)" = "选择银行卡品牌(可选)"; + +"Sort code" = "分类代码"; + +"State" = "州"; + +"Suburb" = "市郊"; + +"Suburb or city" = "市郊或城市"; + +"The BSB you entered is incomplete." = "您输入的 BSB 不完整。"; + +"The ID number you entered is incomplete." = "您输入的证件号码不完整。"; + +"The account number you entered is incomplete." = "您输入的账号不完整。"; + +"The sort code you entered is invalid." = "您输入的分类代码无效。"; + +"Town or city" = "城镇或城市"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "无法粘贴手机号码"; + +"Use rotor to access links" = "用转子访问链接"; + +"Your BLIK code is incomplete." = "您的 BLIK 代码不完整。"; + +"Your BLIK code is invalid." = "您的 BLIK 代码无效。"; + +"Your ZIP is incomplete." = "您的邮编不完整。"; + +"Your email is invalid." = "您的邮件地址无效。"; + +"Your payment information will not be saved." = "不会保存您的付款信息。"; + +"Your postal code is incomplete." = "您的邮编不完整。"; + +"ZIP" = "邮编"; diff --git a/StripeUICore/StripeUICore/Resources/Localizations/zh-Hant.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/zh-Hant.lproj/Localizable.strings new file mode 100644 index 00000000..0411b0ec --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/Localizations/zh-Hant.lproj/Localizable.strings @@ -0,0 +1,141 @@ +"%@ (optional)" = "%@(可選)"; + +"Account number" = "帳號"; + +"Address" = "地址"; + +"Address line 1" = "地址第 1 行"; + +"Address line 2" = "地址第 2 行"; + +"Area" = "地區"; + +"BLIK code" = "BLIK 驗證碼"; + +"BSB number" = "BSB 號碼"; + +"Bank account •••• %@" = "銀行帳戶 •••• %@"; + +"Billing address is same as shipping" = "帳單地址與送貨地址相同"; + +"Cancel" = "取消"; + +"Card brand" = "金融卡品牌"; + +"City" = "城市"; + +"Code field" = "驗證碼欄位"; + +"Company" = "公司"; + +"Continue" = "繼續"; + +"Country" = "國家"; + +"Country or region" = "國家或地區"; + +"County" = "縣"; + +"Date is empty." = "日期為空值。"; + +"Department" = "部門"; + +"District" = "地區"; + +"Do Si" = "道/縣"; + +"Do you want to close this form?" = "您確定要保存此表單嗎?"; + +"Done" = "完成"; + +"Double tap to edit" = "按兩下可編輯"; + +"Edit" = "編輯"; + +"Eircode" = "愛爾蘭郵遞區號"; + +"Email" = "電郵地址"; + +"Emirate" = "大公國"; + +"Error" = "錯誤"; + +"First" = "名字"; + +"Full name" = "全名"; + +"Incomplete phone number" = "電話號碼不完整"; + +"Invalid UPI ID" = "UPI ID 無效"; + +"Island" = "島"; + +"Last" = "姓氏"; + +"Name" = "姓名"; + +"Name on account" = "帳戶上的姓名"; + +"OK" = "確認"; + +"Oblast" = "州"; + +"Other" = "其他"; + +"Parish" = "堂區"; + +"Phone number" = "手機號碼"; + +"Postal code" = "郵遞區號"; + +"Prefecture" = "轄區"; + +"Province" = "省"; + +"Remove" = "移除"; + +"Remove bank account?" = "移除銀行帳戶?"; + +"Remove card" = "解除綁定"; + +"Search" = "搜尋"; + +"Select card brand (optional)" = "選擇金融卡品牌(可選)"; + +"Sort code" = "分類代碼"; + +"State" = "州"; + +"Suburb" = "郊區"; + +"Suburb or city" = "市郊或城市"; + +"The BSB you entered is incomplete." = "您輸入的 BSB 不完整。"; + +"The ID number you entered is incomplete." = "您輸入的證件號碼不完整。"; + +"The account number you entered is incomplete." = "您輸入的帳號不完整。"; + +"The sort code you entered is invalid." = "您輸入的分類代碼無效。"; + +"Town or city" = "城鎮或城市"; + +"UPI ID" = "UPI ID"; + +"Unable to parse phone number" = "無法粘貼手機號碼"; + +"Use rotor to access links" = "用轉子訪問連結"; + +"Your BLIK code is incomplete." = "您的 BLIK 驗證碼不完整。"; + +"Your BLIK code is invalid." = "您的 BLIK 驗證碼無效。"; + +"Your ZIP is incomplete." = "您的郵遞區號不完整。"; + +"Your email is invalid." = "您的電郵地址無效。"; + +"Your payment information will not be saved." = "不會保存您的付款資訊。"; + +"Your postal code is incomplete." = "您的郵遞區號不完整。"; + +"ZIP" = "郵遞區號"; diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/Contents.json b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/brand_stripe.imageset/Contents.json b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/brand_stripe.imageset/Contents.json new file mode 100644 index 00000000..1660c10e --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/brand_stripe.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "brand_stripe.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/brand_stripe.imageset/brand_stripe.svg b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/brand_stripe.imageset/brand_stripe.svg new file mode 100644 index 00000000..5a646c32 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/brand_stripe.imageset/brand_stripe.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/Contents.json b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/Contents.json new file mode 100644 index 00000000..41b08963 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "form_error_icon.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "form_error_icon_dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/form_error_icon.pdf b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/form_error_icon.pdf new file mode 100644 index 00000000..99430af6 Binary files /dev/null and b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/form_error_icon.pdf differ diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/form_error_icon_dark.pdf b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/form_error_icon_dark.pdf new file mode 100644 index 00000000..954d0410 Binary files /dev/null and b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/form_error_icon.imageset/form_error_icon_dark.pdf differ diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_chevron_down.imageset/Contents.json b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_chevron_down.imageset/Contents.json new file mode 100644 index 00000000..82cc3c22 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_chevron_down.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "chevronDown.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_chevron_down.imageset/chevronDown.pdf b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_chevron_down.imageset/chevronDown.pdf new file mode 100644 index 00000000..a7807cbd Binary files /dev/null and b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_chevron_down.imageset/chevronDown.pdf differ diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_clear.imageset/Contents.json b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_clear.imageset/Contents.json new file mode 100644 index 00000000..f1c57b0f --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_clear.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon_clear.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_clear.imageset/icon_clear.svg b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_clear.imageset/icon_clear.svg new file mode 100644 index 00000000..7f0fb023 --- /dev/null +++ b/StripeUICore/StripeUICore/Resources/StripeUICore.xcassets/icon_clear.imageset/icon_clear.svg @@ -0,0 +1,3 @@ + + + diff --git a/StripeUICore/StripeUICore/Source/Categories/CALayer+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/CALayer+StripeUICore.swift new file mode 100644 index 00000000..0b576e5d --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/CALayer+StripeUICore.swift @@ -0,0 +1,26 @@ +// +// CALayer+StripeUICore.swift +// StripeUICore +// +// Created by Nick Porter on 3/16/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// +import Foundation +import QuartzCore +import UIKit + +@_spi(STP) public extension CALayer { + + func applyShadow(shadow: ElementsAppearance.Shadow?) { + guard let shadow = shadow else { + shadowOpacity = 0 + return + } + + shadowColor = shadow.color.cgColor + shadowOpacity = Float(shadow.opacity) + shadowOffset = shadow.offset + shadowRadius = CGFloat(shadow.radius) + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/Enums+CustomStringConvertible.swift b/StripeUICore/StripeUICore/Source/Categories/Enums+CustomStringConvertible.swift new file mode 100644 index 00000000..16a78a03 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/Enums+CustomStringConvertible.swift @@ -0,0 +1,7 @@ +// +// Enums+CustomStringConvertible.swift +// Stripe +// +// Autogenerated by generate_objc_enum_string_values.rb +// Copyright © 2021 Stripe, Inc. All rights reserved. +// diff --git a/StripeUICore/StripeUICore/Source/Categories/Locale+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/Locale+StripeUICore.swift new file mode 100644 index 00000000..e42640c6 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/Locale+StripeUICore.swift @@ -0,0 +1,41 @@ +// +// Locale+StripeUICore.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/29/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public extension Locale { + /// Returns the given array of country/region codes sorted alphabetically by their localized display names + func sortedByTheirLocalizedNames( + _ regionCollection: [T], + thisRegionFirst: Bool = false + ) -> [T] { + var mutableRegionCollection = regionCollection + + // Pull out the current country if needed + var prepend: [T] = [] + if thisRegionFirst, + let regionCode = self.stp_regionCode, + let index = regionCollection.firstIndex(where: { $0.regionCode == regionCode }) { + prepend = [mutableRegionCollection.remove(at: index)] + } + + // Convert to display strings, sort, then map back to value + mutableRegionCollection = mutableRegionCollection.map { ( + value: $0, + display: localizedString(forRegionCode: $0.regionCode) ?? $0.regionCode + ) }.sorted { + $0.display.compare($1.display, options: [.diacriticInsensitive, .caseInsensitive], locale: self) == .orderedAscending + }.map({ + $0.value + }) + + // Prepend current country if needed + return prepend + mutableRegionCollection + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/NSAttributedString+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/NSAttributedString+StripeUICore.swift new file mode 100644 index 00000000..b7ecb69c --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/NSAttributedString+StripeUICore.swift @@ -0,0 +1,31 @@ +// +// NSAttributedString+StripeUICore.swift +// StripeUICore +// +// Created by Nick Porter on 8/31/23. +// + +import Foundation +import UIKit + +extension NSAttributedString { + + /// Returns true if this attributed string has a text attachment + var hasTextAttachment: Bool { + return attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: length)).contains(where: { (key, value) -> Bool in + return key == NSAttributedString.Key.attachment && value is NSTextAttachment + }) + } + + func switchAttachments(for traitCollection: UITraitCollection) -> NSAttributedString { + let mutable = NSMutableAttributedString(attributedString: self) + mutable.enumerateAttribute(.attachment, in: NSRange(location: 0, length: mutable.length), options: []) { attachment, range, _ in + guard let attachment = attachment as? NSTextAttachment else { return } + guard let asset = attachment.image?.imageAsset else { return } + attachment.image = asset.image(with: traitCollection) + mutable.replaceCharacters(in: range, with: NSAttributedString(attachment: attachment)) + } + return mutable + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/NSDirectionalEdgeInsets+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/NSDirectionalEdgeInsets+StripeUICore.swift new file mode 100644 index 00000000..6f022546 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/NSDirectionalEdgeInsets+StripeUICore.swift @@ -0,0 +1,19 @@ +// +// NSDirectionalEdgeInsets+StripeUICore.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension NSDirectionalEdgeInsets { + static func insets(top: CGFloat = 0, leading: CGFloat = 0, bottom: CGFloat = 0, trailing: CGFloat = 0) -> Self { + return .init(top: top, leading: leading, bottom: bottom, trailing: trailing) + } + + static func insets(amount: CGFloat) -> Self { + return .init(top: amount, leading: amount, bottom: amount, trailing: amount) + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIBarButtonItem+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIBarButtonItem+StripeUICore.swift new file mode 100644 index 00000000..ae58c527 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIBarButtonItem+StripeUICore.swift @@ -0,0 +1,21 @@ +// +// UIBarButtonItem+StripeUICore.swift +// StripeUICore +// +// Created by Ramon Torres on 10/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UIBarButtonItem { + + /// Creates a new flexible width space item. + /// + /// Backport for iOS < 14.0 + /// - Returns: A flexible-width space UIBarButtonItem. + class func flexibleSpace() -> Self { + return .init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIButton+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIButton+StripeUICore.swift new file mode 100644 index 00000000..49b1b02b --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIButton+StripeUICore.swift @@ -0,0 +1,51 @@ +// +// UIButton+StripeUICore.swift +// StripeUICore +// +// Created by Cameron Sabol on 2/17/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public extension UIButton { + static let minimumTapSize: CGSize = CGSize(width: 44, height: 44) + + class var doneButtonTitle: String { + return STPLocalizedString("Done", "Done button title") + } + + class var editButtonTitle: String { + return .Localized.edit + } + + /// A helper method that returns a UIButton that: + /// 1. Retains the provided `didTap` closure and calls it when the button is tapped. + /// 2. Expands the tap target area to be 44x44 + class func make(type buttonType: UIButton.ButtonType, didTap: @escaping () -> Void) -> UIButton { + class ClosureButton: UIButton { + var didTap: () -> Void = {} + public override init(frame: CGRect) { + super.init(frame: frame) + addTarget(self, action: #selector(didTapSelector), for: .touchUpInside) + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let newArea = bounds.insetBy( + dx: -(Self.minimumTapSize.width - bounds.width) / 2, + dy: -(Self.minimumTapSize.height - bounds.height) / 2) + return newArea.contains(point) + } + @objc func didTapSelector() { + didTap() + } + } + + let button = ClosureButton(type: buttonType) + button.didTap = didTap + return button + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIColor+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIColor+StripeUICore.swift new file mode 100644 index 00000000..970b8412 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIColor+StripeUICore.swift @@ -0,0 +1,218 @@ +// +// UIColor+StripeUICore.swift +// StripeUICore +// +// Created by Ramon Torres on 11/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UIColor { + + /// Increases the brightness of the color by the given `amount`. + /// + /// The brightness of the resulting color will be clamped to a max value of`1.0`. + /// - Parameter amount: Adjustment amount (range: 0.0 - 1.0.) + /// - Returns: Adjusted color. + func lighten(by amount: CGFloat) -> UIColor { + return byModifyingBrightness { min($0 + amount, 1) } + } + + /// Decreases the brightness of the color by the given `amount`. + /// + /// The brightness of the resulting color will be clamped to a min value of`0.0`. + /// - Parameter amount: Adjustment amount (range: 0.0 - 1.0.) + /// - Returns: Adjusted color. + func darken(by amount: CGFloat) -> UIColor { + return byModifyingBrightness { max($0 - amount, 0) } + } + + static func dynamic(light: UIColor, dark: UIColor) -> UIColor { + return UIColor(dynamicProvider: { (traitCollection) in + switch traitCollection.userInterfaceStyle { + case .light, .unspecified: + return light + case .dark: + return dark + @unknown default: + return light + } + }) + } + + /// The relative luminance of the color. + /// + /// # Reference + /// + /// * [Relative Luminance](https://en.wikipedia.org/wiki/Relative_luminance) + /// * [WCAG 2.2 specification](https://www.w3.org/TR/WCAG21/#dfn-relative-luminance) + /// + var luminance: CGFloat { + var sr: CGFloat = 0 + var sg: CGFloat = 0 + var sb: CGFloat = 0 + + // get the (extended) sRGB components + getRed(&sr, green: &sg, blue: &sb, alpha: nil) + + // Convert from sRGB to linear RGB + let r = sr < 0.04045 ? sr / 12.92 : pow((sr + 0.055) / 1.055, 2.4) + let g = sg < 0.04045 ? sg / 12.92 : pow((sg + 0.055) / 1.055, 2.4) + let b = sb < 0.04045 ? sb / 12.92 : pow((sb + 0.055) / 1.055, 2.4) + + // Calculate luminance (Y) + let y = r * 0.2126 + g * 0.7152 + b * 0.0722 + + return min(max(y, 0), 1) + } + + /// Calculates the contrast ratio to another color as defined by WCAG 2.1. + /// + /// The resulting ratios can range from 1 to 21. + /// + /// # Reference + /// + /// [WCAG 2.1 Contrast Ratio spec](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-contrast-ratio) + /// + /// - Parameter other: Color to calculate the contrast against. + /// - Returns: Contrast ratio. + func contrastRatio(to other: UIColor) -> CGFloat { + let luminanceA = self.luminance + let luminanceB = other.luminance + return (max(luminanceA, luminanceB) + 0.05) / (min(luminanceA, luminanceB) + 0.05) + } + + /// Returns a contrasting color to this color + /// - Returns: Either white or black color depending on which will contrast best with this color + var contrastingColor: UIColor { + let contrastRatioToWhite = contrastRatio(to: .white) + let contrastRatioToBlack = contrastRatio(to: .black) + + let isDarkMode = UITraitCollection.current.isDarkMode + + // Prefer using a white foreground as long as a minimum contrast threshold is met. + // Factor the container color to compensate for "local adaptation". + // https://github.com/w3c/wcag/issues/695 + let threshold: CGFloat = isDarkMode ? 3.6 : 2.2 + if contrastRatioToWhite > threshold { + return .white + } + + // Pick the foreground color that offers the best contrast ratio + return contrastRatioToWhite > contrastRatioToBlack ? .white : .black + } + + /// Adjust color for minimum contrast with a given background color + /// + /// # Reference + /// + /// [WCAG 2.1 Contrast Minimum](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html#dfn-contrast-ratio) + /// - Parameters: + /// - backgroundColor: The background color with which to get minimum contrast + /// - minimumRatio: The minimum contrast ratio (defaults to WGAG minimum ratio of 4.5) + func adjustedForContrast(with backgroundColor: UIColor, minimumRatio: CGFloat = 4.5) -> UIColor { + var adjustedColor = self + + let shouldLighten = backgroundColor.luminance < 0.5 + + // Adjust the brightness until we reach the minimum contrast ratio + // or as much contrast ratio as possible (black or white) + while adjustedColor.contrastRatio(to: backgroundColor) < minimumRatio + && ((shouldLighten && adjustedColor.brightness < 1) + || (!shouldLighten && adjustedColor.brightness > 0)) { + if shouldLighten { + // If the background color dark, go lighter + adjustedColor = adjustedColor.lighten(by: 0.1) + } else { + // If the background color light, go darker + adjustedColor = adjustedColor.darken(by: 0.1) + } + } + + return adjustedColor + } + + /// Returns this color in a "disabled" state by reducing the alpha by 60% + var disabledColor: UIColor { + let (_, _, _, alpha) = rgba + return self.withAlphaComponent(alpha * 0.4) + } + + /// Returns this color in a "disabled" state by reducing the alpha by 40% if `isDisabled` is `true`, + /// or the original color if `false`. + func disabled(_ isDisabled: Bool = true) -> UIColor { + guard isDisabled else { return self } + + return disabledColor + } + + /// The rgba space of the color + var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return (red, green, blue, alpha) + } + + var brightness: CGFloat { + var brightness: CGFloat = 0 + getHue(nil, saturation: nil, brightness: &brightness, alpha: nil) + return brightness + } + + var perceivedBrightness: CGFloat { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + if getRed(&red, green: &green, blue: &blue, alpha: nil) { + // We're using the luma value from YIQ + // https://en.wikipedia.org/wiki/YIQ#From_RGB_to_YIQ + // recommended by https://www.w3.org/WAI/ER/WD-AERT/#color-contrast + return red * CGFloat(0.299) + green * CGFloat(0.587) + blue * CGFloat(0.114) + } else { + // Couldn't get RGB for this color, device couldn't convert it from whatever + // colorspace it's in. + // Make it "bright", since most of the color space is (based on our current + // formula), but not very bright. + return CGFloat(0.4) + } + } + + var isBright: Bool { perceivedBrightness > 0.3 } + + var isDark: Bool { !isBright } +} + +// MARK: - Helpers + +private extension UIColor { + + /// Transforms the brightness and returns the resulting color. + /// + /// - Parameter transform: A block for transforming the brightness. + /// - Returns: Updated color. + func byModifyingBrightness(_ transform: @escaping (CGFloat) -> CGFloat) -> UIColor { + // Similar to `UIColor.withAlphaComponent()`, the returned color must be dynamic. This ensures + // that the color automatically adapts between light and dark mode. + return UIColor(dynamicProvider: { _ in + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + + self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + return UIColor( + hue: hue, + saturation: saturation, + brightness: transform(brightness), + alpha: alpha + ) + }) + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIFont+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIFont+StripeUICore.swift new file mode 100644 index 00000000..cd842dd5 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIFont+StripeUICore.swift @@ -0,0 +1,31 @@ +// +// UIFont+StripeUICore.swift +// StripeUICore +// +// Created by Ramon Torres on 11/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UIFont { + + func scaled( + withTextStyle textStyle: UIFont.TextStyle, + maximumPointSize: CGFloat? = nil, + compatibleWith traitCollection: UITraitCollection? = nil + ) -> UIFont { + let fontMetrics = UIFontMetrics(forTextStyle: textStyle) + + if let maximumFontSize = maximumPointSize { + return fontMetrics.scaledFont( + for: self, + maximumPointSize: maximumFontSize, + compatibleWith: traitCollection + ) + } + + return fontMetrics.scaledFont(for: self, compatibleWith: traitCollection) + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIKeyboardType+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIKeyboardType+StripeUICore.swift new file mode 100644 index 00000000..4d8de8e3 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIKeyboardType+StripeUICore.swift @@ -0,0 +1,33 @@ +// +// UIKeyboardType+StripeUICore.swift +// StripeUICore +// +// Created by Mel Ludowise on 10/11/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +extension UIKeyboardType { + /// Whether this keyboard has a return key + var hasReturnKey: Bool { + switch self { + case .default, + .asciiCapable, + .numbersAndPunctuation, + .URL, + .namePhonePad, + .emailAddress, + .webSearch: + return true + case .numberPad, + .phonePad, + .decimalPad, + .twitter, + .asciiCapableNumberPad: + return false + @unknown default: + return true + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UISpringTimingParameters+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UISpringTimingParameters+StripeUICore.swift new file mode 100644 index 00000000..41c0a594 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UISpringTimingParameters+StripeUICore.swift @@ -0,0 +1,22 @@ +// +// UISpringTimingParameters+StripeUICore.swift +// StripeUICore +// +// Created by David Estes on 1/19/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UISpringTimingParameters { + convenience init(mass: CGFloat, dampingRatio: CGFloat, frequencyResponse: CGFloat) { + // h/t https://medium.com/ios-os-x-development/demystifying-uikit-spring-animations-2bb868446773 + let stiffness: CGFloat = pow(2 * .pi / frequencyResponse, 2) * mass + let damping: CGFloat = 4 * .pi * dampingRatio * mass / frequencyResponse + self.init( + mass: mass, + stiffness: stiffness, + damping: damping, + initialVelocity: .zero) + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIStackView+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIStackView+StripeUICore.swift new file mode 100644 index 00000000..4ad31dcd --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIStackView+StripeUICore.swift @@ -0,0 +1,122 @@ +// +// UIStackView+StripeUICore.swift +// StripeUICore +// +// Created by Ramon Torres on 10/22/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +// MARK: - Animation utilities + +@_spi(STP) public extension UIStackView { + + /// Hides an arranged subview with optional animation. + /// + /// - Parameters: + /// - index: The index of the arranged subview to hide. + /// - animated: Whether or not to animate the transition. + func showArrangedSubview(at index: Int, animated: Bool) { + let view = arrangedSubviews[index] + toggleArrangedSubview(view, shouldShow: true, animated: animated) + } + + /// Hides an arranged subview with optional animation. + /// + /// - Parameters: + /// - index: The index of the arranged subview to hide. + /// - animated: Whether or not to animate the transition. + func hideArrangedSubview(at index: Int, animated: Bool) { + let view = arrangedSubviews[index] + toggleArrangedSubview(view, shouldShow: false, animated: animated) + } + + /// Toggles the visibility of an arranged subview with optional animation. + /// + /// - Parameters: + /// - view: Arranged subview to update. + /// - shouldShow: Whether or not to show the view. + /// - animated: Whether or not to animate the transition. + func toggleArrangedSubview(_ view: UIView, shouldShow: Bool, animated: Bool) { + toggleArrangedSubviews([view], shouldShow: shouldShow, animated: animated) + } + + /// Removes an arranged subview at a given index. + /// + /// - Parameters: + /// - index: The index of the arranged subview to be removed. + /// - animated: Whether or not to animate the removal. + /// - completion: A block to be called after removing the view. + func removeArrangedSubview(at index: Int, animated: Bool, completion: (() -> Void)? = nil) { + removeArrangedSubview(arrangedSubviews[index], animated: animated, completion: completion) + } + + /// Removes the provided view from the arranged subviews with an animation. + /// + /// - Parameters: + /// - view: The view to be removed from the array of views arranged by the stack. + /// - animated: Whether or not to animate the removal. + /// - completion: A block to be called after removing the view. + func removeArrangedSubview(_ view: UIView, animated: Bool, completion: (() -> Void)? = nil) { + toggleArrangedSubviews([view], shouldShow: false, animated: animated) { _ in + view.removeFromSuperview() + view.isHidden = false + view.alpha = 1 + completion?() + } + } + + // MARK: - Helpers + + /// Toggles the visibility of arranged subviews with animation. + /// + /// This method enhances the default constraint based animation by adding fade-in/out as + /// secondary action. Making the animation more correct. + /// + /// - Parameters: + /// - views: The arranged subviews to be toggled. + /// - shouldShow: Whether or not it should show the views. + /// - animated: Whether or not to animate the transition. + /// - completion: A block to be called when the animation finishes. + func toggleArrangedSubviews( + _ views: [UIView], + shouldShow: Bool, + animated: Bool, + completion: ((Bool) -> Void)? = nil + ) { + let viewsToUpdate = views.filter { $0.isHidden == shouldShow } + + if animated { + let outTransform = CGAffineTransform(translationX: 0, y: -10) + + viewsToUpdate.forEach { view in + view.isHidden = shouldShow + view.alpha = shouldShow ? 0 : 1 + view.transform = shouldShow ? outTransform : .identity + } + + UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) { + viewsToUpdate.forEach { view in + view.isHidden = !shouldShow + view.alpha = shouldShow ? 1 : 0 + view.transform = shouldShow ? .identity : outTransform + } + + self.setNeedsLayout() + self.layoutIfNeeded() + } completion: { done in + viewsToUpdate.forEach { view in + view.transform = .identity + } + + completion?(done) + } + } else { + viewsToUpdate.forEach { view in + view.isHidden = !shouldShow + } + } + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UITraitCollection+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UITraitCollection+StripeUICore.swift new file mode 100644 index 00000000..44f196e9 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UITraitCollection+StripeUICore.swift @@ -0,0 +1,17 @@ +// +// UITraitCollection+StripeUICore.swift +// StripeUICore +// +// Created by Ramon Torres on 10/3/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UITraitCollection { + + var isDarkMode: Bool { + return userInterfaceStyle == .dark + } + +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIView+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIView+StripeUICore.swift new file mode 100644 index 00000000..5666667f --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIView+StripeUICore.swift @@ -0,0 +1,115 @@ +// +// UIView+StripeUICore.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +@_spi(STP) public extension UIView { + /// - Note: This variant of `addAndPinSubview` respects the view's `directionalLayoutMargins` property. + /// This is useful if your margins can change dynamically. + func addAndPinSubview(_ view: UIView, directionalLayoutMargins: NSDirectionalEdgeInsets) { + self.directionalLayoutMargins = directionalLayoutMargins + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), + view.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), + view.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + view.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + ]) + } + + func addAndPinSubview(_ view: UIView, insets: NSDirectionalEdgeInsets = .zero) { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: topAnchor, constant: insets.top), + view.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -insets.bottom), + view.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.leading), + view.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.trailing), + ]) + } + + func addAndPinSubviewToSafeArea(_ view: UIView, insets: NSDirectionalEdgeInsets = .zero) { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + NSLayoutConstraint.activate([ + view.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: insets.top), + view.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -insets.bottom), + view.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: insets.leading), + view.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -insets.trailing), + ]) + } + + /// Animates changes to one or more views alongside the keyboard. + /// + /// - Parameters: + /// - notification: Keyboard change notification. + /// - animations: A block containing the changes to commit to the views. + static func animateAlongsideKeyboard( + _ notification: Notification, + animations: @escaping () -> Void + ) { + + guard let userInfo = notification.userInfo, + let animationCurveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int, + let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double, + let animationCurve = UIView.AnimationCurve(rawValue: animationCurveValue) else { + // Just run the animation block as a fallback + animations() + return + } + + // Animate the container above the keyboard + // Note: We prefer UIViewPropertyAnimator over UIView.animate because it handles consecutive animation calls better. Sometimes this happens when one text field resigns and another immediately becomes first responder. + let animator = UIViewPropertyAnimator(duration: animationDuration, curve: animationCurve) { + animations() + } + animator.startAnimation() + } + + // Don't set isHidden redundantly or you might hit a bug: http://www.openradar.me/25087688 + func setHiddenIfNecessary(_ shouldHide: Bool) { + if isHidden != shouldHide { + isHidden = shouldHide + } + } + + func firstResponder() -> UIView? { + for subview in subviews { + if let firstResponder = subview.firstResponder() { + return firstResponder + } + } + return isFirstResponder ? self : nil + } + + func updateTrailingAnchor(constant: CGFloat) { + if let superview = superview { + for constraint in superview.constraints where constraint.firstItem === self || constraint.secondItem === self { + if constraint.firstAttribute == .trailing || constraint.secondAttribute == .trailing { + constraint.constant = constant + break + } + } + } + } + + static func makeSpacerView(width: CGFloat? = nil, height: CGFloat? = nil) -> UIView { + let spacerView = UIView(frame: .zero) + spacerView.translatesAutoresizingMaskIntoConstraints = false + if let width { + spacerView.widthAnchor.constraint(equalToConstant: width).isActive = true + } + if let height { + spacerView.heightAnchor.constraint(equalToConstant: height).isActive = true + } + return spacerView + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIViewController+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIViewController+StripeUICore.swift new file mode 100644 index 00000000..0b83259d --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIViewController+StripeUICore.swift @@ -0,0 +1,54 @@ +// +// UIViewController+StripeUICore.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UIViewController { + /// Use this to animate changes that affect the height of the sheet + func animateHeightChange(forceAnimation: Bool = false, duration: CGFloat = 0.5, _ animations: (() -> Void)? = nil, postLayoutAnimations: (() -> Void)? = nil, completion: ((Bool) -> Void)? = nil) + { + guard forceAnimation || !isBeingPresented else { + animations?() + return + } + // Note: For unknown reasons, using `UIViewPropertyAnimator` here caused an infinite layout loop + UIView.animate( + withDuration: duration, + delay: 0, + usingSpringWithDamping: 1, + initialSpringVelocity: 0, + options: [], + animations: { + animations?() + self.rootParent.presentationController?.containerView?.layoutIfNeeded() + postLayoutAnimations?() + }, completion: { f in + completion?(f) + } + ) + } + + var rootParent: UIViewController { + if let parent = parent { + return parent.rootParent + } + return self + } + + /// Walks the presented view controller hierarchy and return the top most presented controller. + /// - Returns: Returns the top most presented view controller, or `nil` if this view controller is not presenting another controller. + func findTopMostPresentedViewController() -> UIViewController? { + var topMostController = self.presentedViewController + + while let presented = topMostController?.presentedViewController { + topMostController = presented + } + + return topMostController + } +} diff --git a/StripeUICore/StripeUICore/Source/Categories/UIWindow+StripeUICore.swift b/StripeUICore/StripeUICore/Source/Categories/UIWindow+StripeUICore.swift new file mode 100644 index 00000000..2b4743c5 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Categories/UIWindow+StripeUICore.swift @@ -0,0 +1,20 @@ +// +// UIWindow+Stripe.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 2/3/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public extension UIWindow { + + /// Returns the top most presented view controller including the root view controller. + /// - Returns: The top most view controller, or `nil` if the window has no root view controller. + func findTopMostPresentedViewController() -> UIViewController? { + return self.rootViewController?.findTopMostPresentedViewController() + ?? self.rootViewController + } + +} diff --git a/StripeUICore/StripeUICore/Source/Controls/ActivityIndicator.swift b/StripeUICore/StripeUICore/Source/Controls/ActivityIndicator.swift new file mode 100644 index 00000000..22bc1e60 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Controls/ActivityIndicator.swift @@ -0,0 +1,267 @@ +// +// ActivityIndicator.swift +// StripeUICore +// +// Created by Ramon Torres on 12/3/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +/// A custom replacement for `UIActivityIndicatorView`. +/// For internal SDK use only +@objc(STP_Internal_ActivityIndicator) +@_spi(STP) public final class ActivityIndicator: UIView { + + #if DEBUG + /// Disables animation. This should be only be modified for snapshot tests. + public static var isAnimationEnabled = true + #endif + + struct Constants { + /// Animation speed in revolutions per second + static let animationSpeed: Double = 1.8 + static let animationKey: String = "spin" + } + + /// Size of the activity indicator. + public enum Size { + /// The default size of an activity indicator (20x20). + case medium + /// A large activity indicator (37x37). + case large + } + + /// If `true`, the activity indicator will hide itself when not animating. + public var hidesWhenStopped: Bool = true { + didSet { + if hidesWhenStopped { + updateVisibility() + } else { + isHidden = false + } + } + } + + /// The color of the activity indicator. + public var color: UIColor { + get { + return tintColor + } + set { + tintColor = newValue + } + } + + public private(set) var isAnimating: Bool = false + + private let size: Size + + private var radius: CGFloat { + switch size { + case .medium: + return 8 + case .large: + return 14.5 + } + } + + private var thickness: CGFloat { + switch size { + case .medium: + return 2 + case .large: + return 4 + } + } + + private lazy var cometLayer: CAGradientLayer = { + let shape = CAShapeLayer() + shape.path = makeArcPath(radius: radius, startAngle: 0.05, endAngle: 0.95) + shape.lineWidth = thickness + shape.lineCap = .round + shape.strokeColor = UIColor.black.cgColor + shape.fillColor = UIColor.clear.cgColor + + let gradientLayer = CAGradientLayer() + gradientLayer.type = .conic + gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5) + gradientLayer.endPoint = CGPoint(x: 1, y: 0.5) + gradientLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5) + gradientLayer.contentsGravity = .center + gradientLayer.mask = shape + return gradientLayer + }() + + private var contentLayer: CALayer { + return cometLayer + } + + public override var intrinsicContentSize: CGSize { + let size: CGFloat = (radius + thickness) * 2 + return CGSize(width: size, height: size) + } + + public convenience init() { + self.init(size: .medium) + } + + /// Creates a new activity indicator. + /// - Parameter size: Size of the activity indicator. + public init(size: Size) { + self.size = size + super.init(frame: .zero) + layer.addSublayer(contentLayer) + + setContentHuggingPriority(.defaultHigh, for: .horizontal) + setContentHuggingPriority(.defaultHigh, for: .vertical) + + updateVisibility() + updateColor() + + NotificationCenter.default.addObserver( + self, + selector: #selector(applicationWillEnterForeground(_:)), + name: UIApplication.willEnterForegroundNotification, + object: nil + ) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func tintColorDidChange() { + super.tintColorDidChange() + updateColor() + } + +#if !canImport(CompositorServices) + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateColor() + } +#endif + + public override func layoutSubviews() { + super.layoutSubviews() + + CATransaction.begin() + // `bounds` and `position` are both animatable. Disable actions to turn off + // implicit animations when updating them. + CATransaction.setDisableActions(true) + + contentLayer.bounds = CGRect(origin: .zero, size: intrinsicContentSize) + contentLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + + CATransaction.commit() + } + + public override func willMove(toWindow newWindow: UIWindow?) { + super.willMove(toWindow: newWindow) + + if let window = newWindow { + contentLayer.shouldRasterize = true +#if !canImport(CompositorServices) + contentLayer.rasterizationScale = window.screen.scale +#endif + } + + if isAnimating { + startAnimating() + } else { + stopAnimating() + } + } +} + +// MARK: - Methods + +public extension ActivityIndicator { + + func startAnimating() { + defer { + isAnimating = true + updateVisibility() + } + + #if DEBUG + guard ActivityIndicator.isAnimationEnabled else { return } + #endif + + let rotatingAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + rotatingAnimation.byValue = 2 * Float.pi + rotatingAnimation.duration = 1 / Constants.animationSpeed + rotatingAnimation.isAdditive = true + rotatingAnimation.repeatCount = .infinity + contentLayer.add(rotatingAnimation, forKey: Constants.animationKey) + } + + func stopAnimating() { + contentLayer.removeAnimation(forKey: Constants.animationKey) + + isAnimating = false + updateVisibility() + } +} + +// MARK: - Private methods + +private extension ActivityIndicator { + + func updateColor() { + // Tint color gradient from 0% to 100% alpha + cometLayer.colors = [ + tintColor.withAlphaComponent(0).cgColor, + tintColor.cgColor, + ] + } + + @objc + func applicationWillEnterForeground(_ notification: Notification) { + if isAnimating { + // Resume animations + startAnimating() + } + } + + func updateVisibility() { + if hidesWhenStopped { + isHidden = !isAnimating + } + } + + /// Creates an path containing an arc shape of a given radius and angles. + /// + /// Angles must be expressed in turns. + /// + /// + /// + /// - Parameters: + /// - radius: Arc radius. + /// - startAngle: Start angle. + /// - endAngle: End angle. + /// - Returns: Arc path. + func makeArcPath(radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) -> CGPath { + let path = CGMutablePath() + + let center = CGPoint( + x: intrinsicContentSize.width / 2, + y: intrinsicContentSize.height / 2 + ) + + path.addArc( + center: center, + radius: radius, + startAngle: CGFloat.pi * 2 * startAngle, + endAngle: CGFloat.pi * 2 * endAngle, + clockwise: false + ) + + return path + } +} diff --git a/StripeUICore/StripeUICore/Source/Controls/Button.swift b/StripeUICore/StripeUICore/Source/Controls/Button.swift new file mode 100644 index 00000000..3ac6925d --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Controls/Button.swift @@ -0,0 +1,559 @@ +// +// Button.swift +// StripeUICore +// +// Created by Ramon Torres on 11/7/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +/// The custom button used throughout the Stripe SDK. +/// For internal SDK use only +@objc(STP_Internal_Button) +@_spi(STP) public class Button: UIControl { + struct Constants { + // TODO(ramont): move to `Configuration` + static let minTitleLabelHeight: CGFloat = 24 + static let minItemSpacing: CGFloat = 8 + } + + /// Configuration for the button appearance. + /// + /// Most of the time you should use one of the built-in configurations such as `.primary()` or `.secondary()`. For + /// one-off customizations, you can modify the button's configuration once it has been instantiated, as follows: + /// + /// ``` + /// let myButton = Button(configuration: .secondary(), title: "Cancel") + /// myButton.configuration.cornerRadius = 4 + /// ``` + /// + /// If you find yourself applying the same customizations multiple times, you should consider creating a reusable configuration. To create + /// one, simply add an extension and implement a static method that returns the custom configuration: + /// + /// ``` + /// extension Button.Configuration { + /// static func panicButton() -> Self { + /// let configuration: Button.Configuration = .primary() + /// configuration.font = .boldSytemFont(ofSize: 32) + /// configuration.insets = .init(top: 16, leading: 16, bottom: 16, trailing: 16) + /// configuration.cornerRadius = 4 + /// configuration.backgroundColor = .systemRed + /// return configuration + /// } + /// } + /// ``` + /// + /// Then use it the same way you would use any of the built-in configurations: + /// + /// ``` + /// let button = Button(configuration: .panicButton(), title: "Cancel") + /// ``` + public struct Configuration { + /// A special color value that resolves to the button's tint color at runtime. + /// + /// This is equivalent to `UIColor.tintColor` in iOS 15, except that it only works + /// within the `Button` component and relies on identity comparison (`===`). Ideally we will + /// backport `UIColor.tintColor`, but this is not currently possible due to its reliance on + /// private APIs. + public static let tintColor: UIColor = .init(red: 0, green: 0.5, blue: 1, alpha: 1) + + public var font: UIFont = .preferredFont(forTextStyle: .body, weight: .medium) + public var cornerRadius: CGFloat = 10 + + public var borderWidth: CGFloat = 0 + + // Normal state + public var foregroundColor: UIColor? + public var backgroundColor: UIColor? + public var borderColor: UIColor? + + // Disabled state + public var disabledForegroundColor: UIColor? + public var disabledBackgroundColor: UIColor? + public var disabledBorderColor: UIColor? + + // Color transforms + public var colorTransforms: ColorTransformConfiguration = .init() + + /// Attributes to automatically apply to the title. + public var titleAttributes: [NSAttributedString.Key: Any]? + + public var insets: NSDirectionalEdgeInsets = .init(top: 10, leading: 10, bottom: 10, trailing: 10) + } + + public struct ColorTransformConfiguration { + public var disabledForeground: ColorTransform? + public var disabledBackground: ColorTransform? + public var disabledBorder: ColorTransform? + public var highlightedForeground: ColorTransform? + public var highlightedBackground: ColorTransform? + public var highlightedBorder: ColorTransform? + } + + public enum ColorTransform { + case darken(amount: CGFloat) + case lighten(amount: CGFloat) + case setAlpha(amount: CGFloat) + } + + /// Position of the icon. + public enum IconPosition { + /// Leading edge of the button. + case leading + /// Trailing edge of the button. + case trailing + } + + struct CustomStates { + static let loading = State(rawValue: 1 << 16) + } + + public override var state: UIControl.State { + var state = super.state + + if isLoading { + state.insert(CustomStates.loading) + } + + return state + } + + public override var intrinsicContentSize: CGSize { + var contentHeight: CGFloat { + return max( + iconView.intrinsicContentSize.height, + titleLabel.intrinsicContentSize.height, + Constants.minTitleLabelHeight + ) + } + + let height = ( + directionalLayoutMargins.top + + contentHeight + + directionalLayoutMargins.bottom + ) + + return CGSize( + width: UIView.noIntrinsicMetric, + height: height + ) + } + + public override var isEnabled: Bool { + didSet { + updateColors() + updateAccessibilityContent() + } + } + + public override var isHighlighted: Bool { + didSet { + updateColors() + } + } + + public var configuration: Configuration { + didSet { + configurationDidChange(oldValue) + } + } + + public var icon: UIImage? { + didSet { + iconView.image = icon + + let shouldHideIconView = icon == nil + if iconView.isHidden != shouldHideIconView { + iconView.isHidden = shouldHideIconView + setNeedsUpdateConstraints() + } + } + } + + public var iconPosition: IconPosition = .leading { + didSet { + if iconPosition != oldValue { + setNeedsUpdateConstraints() + } + } + } + + public var title: String? { + didSet { + updateTitle() + updateAccessibilityContent() + } + } + + public var isLoading: Bool = false { + didSet { + if isLoading { + iconView.alpha = 0 + titleLabel.alpha = 0 + isUserInteractionEnabled = false + activityIndicator.startAnimating() + } else { + iconView.alpha = 1 + titleLabel.alpha = 1 + isUserInteractionEnabled = true + activityIndicator.stopAnimating() + } + } + } + + /// Whether or not the button should automatically update its font when the device's content size category changes. + public var adjustsFontForContentSizeCategory: Bool { + get { + return titleLabel.adjustsFontForContentSizeCategory + } + set { + titleLabel.adjustsFontForContentSizeCategory = newValue + } + } + + private let titleLabel: UILabel = { + let label = UILabel() + label.adjustsFontForContentSizeCategory = true + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let iconView: UIImageView = { + let iconView = UIImageView() + iconView.translatesAutoresizingMaskIntoConstraints = false + iconView.isHidden = true + return iconView + }() + + private lazy var activityIndicator: ActivityIndicator = { + let activityIndicator = ActivityIndicator() + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + return activityIndicator + }() + + private var dynamicConstraints: [NSLayoutConstraint] = [] + + /// Creates a button with the default configuration. + public convenience init() { + self.init(configuration: .primary()) + } + + /// Creates a button with the default configuration and the given title. + /// - Parameter title: Button title. + public convenience init(title: String) { + self.init(configuration: .primary(), title: title) + } + + /// Creates a button with the specified configuration. + /// - Parameters: + /// - configuration: Button configuration. + public convenience init(configuration: Configuration) { + self.init(configuration: configuration, title: nil) + } + + /// Creates a button with the specified configuration and title. + /// - Parameters + /// - configuration: Button configuration. + /// - title: Button title. + public init(configuration: Configuration, title: String?) { + self.configuration = configuration + self.title = title + super.init(frame: .zero) + + isAccessibilityElement = true + accessibilityTraits = .button + + setup() + configurationDidChange(nil) + updateAccessibilityContent() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + addSubview(activityIndicator) + addSubview(iconView) + addSubview(titleLabel) + + NSLayoutConstraint.activate([ + // Center label + titleLabel.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor), + titleLabel.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor), + + // Center activity indicator + activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + } + + public override func tintColorDidChange() { + super.tintColorDidChange() + updateColors() + } + +#if !canImport(CompositorServices) + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + invalidateIntrinsicContentSize() + updateColors() + } +#endif + + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return bounds.contains(point) ? self : nil + } +} + +public extension Button { + + override func updateConstraints() { + if !dynamicConstraints.isEmpty { + NSLayoutConstraint.deactivate(dynamicConstraints) + dynamicConstraints.removeAll() + } + + let shouldShowIconView = icon != nil + + if shouldShowIconView { + // Center icon vertically + dynamicConstraints.append( + iconView.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor) + ) + + switch iconPosition { + case .leading: + dynamicConstraints.append(contentsOf: [ + iconView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + iconView.trailingAnchor.constraint( + lessThanOrEqualTo: titleLabel.leadingAnchor, + constant: Constants.minItemSpacing + ), + titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.trailingAnchor), + ]) + case .trailing: + dynamicConstraints.append(contentsOf: [ + titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.leadingAnchor), + titleLabel.trailingAnchor.constraint( + lessThanOrEqualTo: iconView.leadingAnchor, + constant: Constants.minItemSpacing + ), + iconView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + ]) + } + } else { + // Pin the leading and trailing edges of the label to the edges of the button. + dynamicConstraints.append(contentsOf: [ + titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.leadingAnchor), + titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.trailingAnchor), + ]) + } + + NSLayoutConstraint.activate(dynamicConstraints) + + // `super.updateConstraints()` must be called as the final step, as + // suggested by the documentation. + super.updateConstraints() + } + +} + +private extension Button { + + func configurationDidChange(_ previousConfiguration: Configuration?) { + titleLabel.font = configuration.font + layer.cornerRadius = configuration.cornerRadius + layer.borderWidth = configuration.borderWidth + directionalLayoutMargins = configuration.insets + + updateColors() + updateTitle() + + if configuration.shouldInvalidateIntrinsicContentSize(previousConfiguration) { + invalidateIntrinsicContentSize() + } + } + + func updateColors() { + let color = foregroundColor(for: state) + + titleLabel.textColor = color + iconView.tintColor = color + activityIndicator.tintColor = color + + backgroundColor = backgroundColor(for: state) + layer.borderColor = borderColor(for: state)?.cgColor + } + + func updateTitle() { + if let title = title, + let attributes = configuration.titleAttributes { + titleLabel.attributedText = NSAttributedString(string: title, attributes: attributes) + } else { + titleLabel.text = title + } + } + + func foregroundColor(for state: State) -> UIColor? { + switch state { + case .disabled: + return resolveColor( + baseColor: configuration.foregroundColor, + preferredColor: configuration.disabledForegroundColor, + transform: configuration.colorTransforms.disabledForeground + ) + case .highlighted: + return resolveColor( + baseColor: configuration.foregroundColor, + transform: configuration.colorTransforms.highlightedForeground + ) + default: + return resolveColor(baseColor: configuration.foregroundColor) + } + } + + func backgroundColor(for state: State) -> UIColor? { + switch state { + case .disabled: + return resolveColor( + baseColor: configuration.backgroundColor, + preferredColor: configuration.disabledBackgroundColor, + transform: configuration.colorTransforms.disabledBackground + ) + case .highlighted: + return resolveColor( + baseColor: configuration.backgroundColor, + transform: configuration.colorTransforms.highlightedBackground + ) + default: + return resolveColor(baseColor: configuration.backgroundColor) + } + } + + func borderColor(for state: State) -> UIColor? { + switch state { + case .disabled: + return resolveColor( + baseColor: configuration.borderColor, + preferredColor: configuration.disabledBorderColor, + transform: configuration.colorTransforms.disabledBorder + ) + case .highlighted: + return resolveColor( + baseColor: configuration.borderColor, + transform: configuration.colorTransforms.highlightedBorder + ) + default: + return resolveColor(baseColor: configuration.borderColor) + } + } + + /// Determines the best color to use given a base color, a preferred color, and a color transform. + /// + /// If a preferred colors is provided, this method will always return the preferred color. Otherwise, the + /// method will return the base color with an optional color transformation applied to it. + /// + /// - Parameters: + /// - baseColor: Base (untransformed) color. + /// - preferredColor: Preferred color. + /// - transform: Optional color transform to apply to `baseColor`. + /// - Returns: Color to use. + func resolveColor( + baseColor: UIColor?, + preferredColor: UIColor? = nil, + transform: ColorTransform? = nil + ) -> UIColor? { + let resolveToTintColor: (UIColor?) -> UIColor? = { color in + return color === Configuration.tintColor ? self.tintColor : color + } + + if let preferredColor = preferredColor { + return resolveToTintColor(preferredColor) + } + + let color = resolveToTintColor(baseColor) + + switch transform { + case .setAlpha(let amount): + return color?.withAlphaComponent(amount) + case .darken(let amount): + return color?.darken(by: amount) + case .lighten(let amount): + return color?.lighten(by: amount) + case .none: + return color + } + } + + func updateAccessibilityContent() { + accessibilityLabel = title + + if isEnabled { + accessibilityTraits.remove(.notEnabled) + } else { + accessibilityTraits.insert(.notEnabled) + } + } +} + +public extension Button.Configuration { + /// The default button configuration. + static func primary() -> Self { + return .init( + foregroundColor: .white, + backgroundColor: Self.tintColor, + disabledBackgroundColor: .systemGray4, + colorTransforms: .init( + highlightedBackground: .darken(amount: 0.2) + ) + ) + } + + /// A less prominent button. + static func secondary() -> Self { + return .init( + foregroundColor: Self.tintColor, + backgroundColor: .secondarySystemFill, + disabledForegroundColor: .systemGray, + colorTransforms: .init( + highlightedBackground: .darken(amount: 0.2) + ) + ) + } + + /// A plain button. + static func plain() -> Self { + return .init( + font: .preferredFont(forTextStyle: .body, weight: .regular), + foregroundColor: Self.tintColor, + backgroundColor: .clear, + // Match the custom color of UIButton(style: .system) + disabledForegroundColor: .dynamic( + light: UIColor(white: 0.484669, alpha: 0.35), + dark: UIColor(white: 0.484669, alpha: 0.45) + ), + colorTransforms: .init( + highlightedForeground: .setAlpha(amount: 0.5) + ), + // Match the insets of UIButton(style: .system) + insets: .insets(top: 3, leading: 0, bottom: 3, trailing: 0) + ) + } + +} + +// MARK: - Configuration diffing + +extension Button.Configuration { + + func shouldInvalidateIntrinsicContentSize(_ previousConfiguration: Self?) -> Bool { + return ( + self.font != previousConfiguration?.font || + self.insets != previousConfiguration?.insets + ) + } + +} diff --git a/StripeUICore/StripeUICore/Source/Controls/OneTimeCodeTextField-TextStorage.swift b/StripeUICore/StripeUICore/Source/Controls/OneTimeCodeTextField-TextStorage.swift new file mode 100644 index 00000000..3bd0a6ab --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Controls/OneTimeCodeTextField-TextStorage.swift @@ -0,0 +1,217 @@ +// +// OneTimeCodeTextField-TextStorage.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 3/29/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +extension OneTimeCodeTextField { + final class TextStorage { + var value: String = "" + + let capacity: Int + + var start: TextPosition { + return TextPosition(0) + } + + var end: TextPosition { + return TextPosition(value.count) + } + + /// Returns a range for placing the caret at the end of the content. + /// + /// A zero-length range is `UITextInput`'s way of representing the caret position. This property will + /// always return a zero-length range at the end of the content. + var endCaretRange: TextRange { + return TextRange(start: end, end: end) + } + + /// A range that covers from the beginning to the end of the content. + var extent: TextRange { + return TextRange(start: start, end: end) + } + + var isFull: Bool { + return value.count >= capacity + } + + private let allowedCharacters: CharacterSet = .init(charactersIn: "0123456789") + + init(capacity: Int) { + assert(capacity >= 0, "Cannot have a negative capacity") + self.capacity = max(capacity, 0) + } + + func insert(_ text: String, at range: TextRange) -> TextRange { + let sanitizedText = text.filter({ + $0.unicodeScalars.allSatisfy(allowedCharacters.contains(_:)) + }) + + value.replaceSubrange(range.stringRange(for: value), with: sanitizedText) + + if value.count > capacity { + // Truncate to capacity + value = String(value.prefix(capacity)) + } + + let newInsertionPoint = TextPosition(range._start.index + sanitizedText.count) + return TextRange(start: newInsertionPoint, end: newInsertionPoint) + } + + func delete(range: TextRange) -> TextRange { + value.removeSubrange(range.stringRange(for: value)) + return TextRange(start: range._start, end: range._start) + } + + func text(in range: TextRange) -> String? { + guard !range.isEmpty else { + return nil + } + + let stringRange = range.stringRange(for: value) + return String(value[stringRange]) + } + + /// Utility method for creating a text range. + /// + /// Returns `nil` if any of the given positions is out of bounds. + /// + /// - Parameters: + /// - start: Start position of the range. + /// - end: End position of the range. + /// - Returns: Text position. + func makeRange(from start: TextPosition, to end: TextPosition) -> TextRange? { + guard + extent.contains(start.index), + extent.contains(end.index) + else { + return nil + } + + return TextRange(start: start, end: end) + } + } +} + +// MARK: - UITextPosition + +extension OneTimeCodeTextField { + /// Represents a position within our text storage. + /// + /// For internal SDK use only + @objc(STP_Internal_OneTimeCodeTextField_TextPosition) + final class TextPosition: UITextPosition { + let index: Int + + init(_ index: Int) { + self.index = index + } + + override var description: String { + let props: [String] = [ + String(format: "%@: %p", NSStringFromClass(type(of: self)), self), + "index = \(String(describing: index))", + ] + return "<\(props.joined(separator: "; "))>" + } + + override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? TextPosition else { + return false + } + + return self.index == other.index + } + + func compare(_ otherPosition: TextPosition) -> ComparisonResult { + if index < otherPosition.index { + return .orderedAscending + } + + if index > otherPosition.index { + return .orderedDescending + } + + return .orderedSame + } + } + +} + +// MARK: - TextRange + +extension OneTimeCodeTextField { + /// A range within our text storage. + /// + /// For internal SDK use only. + @objc(STP_Internal_OneTimeCodeTextField_TextRange) + final class TextRange: UITextRange { + let _start: TextPosition + let _end: TextPosition + + override var isEmpty: Bool { + return _start.index == _end.index + } + + override var start: UITextPosition { + return _start + } + + override var end: UITextPosition { + return _end + } + + convenience init?(start: UITextPosition, end: UITextPosition) { + guard + let start = start as? TextPosition, + let end = end as? TextPosition + else { + return nil + } + + self.init(start: start, end: end) + } + + init(start: TextPosition, end: TextPosition) { + self._start = start + self._end = end + } + + override var description: String { + let props: [String] = [ + String(format: "%@: %p", NSStringFromClass(type(of: self)), self), + "start = \(String(describing: start))", + "end = \(String(describing: end))", + ] + return "<\(props.joined(separator: "; "))>" + } + + override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? TextRange else { + return false + } + + return self.start == other.start && self.end == other.end + } + + func contains(_ index: Int) -> Bool { + let lowerBound = min(_start.index, _end.index) + let upperBound = max(_start.index, _end.index) + return index >= lowerBound && index <= upperBound + } + + func stringRange(for string: String) -> Range { + let lowerBound = min(_start.index, _end.index) + let upperBound = max(_start.index, _end.index) + + let beginIndex = string.index(string.startIndex, offsetBy: min(lowerBound, string.count)) + let endIndex = string.index(string.startIndex, offsetBy: min(upperBound, string.count)) + + return beginIndex.. 4 && configuration.numberOfDigits.isMultiple(of: 2) + } + + private lazy var digitViews: [DigitView] = (0.. Bool { + let result = super.becomeFirstResponder() + + if result { + selectedTextRange = textStorage.endCaretRange + } + + return result + } + + @discardableResult + public override func resignFirstResponder() -> Bool { + let result = super.resignFirstResponder() + + if result { + #if !canImport(CompositorServices) + hideMenu() + #endif + update() + } + + return result + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + + guard let point = touches.first?.location(in: self), + bounds.contains(point) else { + return + } + + if isFirstResponder { + #if !canImport(CompositorServices) + toggleMenu() + #endif + } else { + becomeFirstResponder() + } + } + +} + +// MARK: - Private methods + +private extension OneTimeCodeTextField { + + func setupUI() { + let stackView = UIStackView(arrangedSubviews: arrangedDigitViews()) + stackView.spacing = shouldGroupDigits ? configuration.groupSpacing : configuration.itemSpacing + stackView.alignment = .center + stackView.distribution = .fillEqually + stackView.semanticContentAttribute = .forceLeftToRight + addAndPinSubview(stackView) + } + + func arrangedDigitViews() -> [UIView] { + guard shouldGroupDigits else { + // No grouping, simply return all the digit views. + return digitViews + } + + // Split the digit views into two groups. + let groupSize = configuration.numberOfDigits / 2 + + let groups = stride(from: 0, to: digitViews.count, by: groupSize).map { + Array(digitViews[$0.. Bool { + return action == #selector(paste(_:)) && UIPasteboard.general.hasStrings + } + + override func paste(_ sender: Any?) { + if let string = UIPasteboard.general.string { + insertText(string) + update() + } + } + +} + +// MARK: - Animation + +public extension OneTimeCodeTextField { + + /// Performs a shake animation, useful for indicating a bad code. + /// - Parameter shouldClearValue: Whether or not the field's value should be cleared at the end of the animation. + func performInvalidCodeAnimation(shouldClearValue: Bool = true) { + // Temporarily disables user interaction while the animation plays. + isUserInteractionEnabled = false + + let duration: CFTimeInterval = 0.4 + let beginTime = CACurrentMediaTime() + let staggerDelay: CFTimeInterval = 0.025 + let timingFunction = CAMediaTimingFunction(controlPoints: 0.3, 0.3, 0.3, 1) + + for (index, digitView) in digitViews.enumerated() { + // TODO(ramont): Move this to DigitView + let jumpAnimation = CAKeyframeAnimation(keyPath: "transform.translation.y") + jumpAnimation.beginTime = beginTime + (CFTimeInterval(index) * staggerDelay) + jumpAnimation.duration = duration + jumpAnimation.values = [0, -8, 2, 0] + jumpAnimation.keyTimes = [0.0, 0.33, 0.66, 1.0] + jumpAnimation.timingFunctions = [timingFunction, timingFunction, timingFunction, timingFunction] + + let borderColorAnimation = CABasicAnimation(keyPath: "borderColor") + borderColorAnimation.beginTime = beginTime + (CFTimeInterval(index) * staggerDelay) + borderColorAnimation.duration = duration / 3 + borderColorAnimation.fromValue = theme.colors.border.cgColor + borderColorAnimation.toValue = theme.colors.danger.cgColor + borderColorAnimation.fillMode = .forwards + borderColorAnimation.isRemovedOnCompletion = false + + digitView.layer.add(jumpAnimation, forKey: "jump") + digitView.borderLayer.add(borderColorAnimation, forKey: "borderColor") + } + +#if !canImport(CompositorServices) + feedbackGenerator.notificationOccurred(.error) +#endif + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in + self?.digitViews.forEach { digitView in + digitView.layer.removeAllAnimations() + digitView.borderLayer.removeAllAnimations() + } + + if shouldClearValue { + self?.value = "" + } + + self?.isUserInteractionEnabled = true + self?.becomeFirstResponder() + } + } + +} + +// MARK: - UIKeyInput + +extension OneTimeCodeTextField: UIKeyInput { + + public var hasText: Bool { + return value.count > 0 + } + + public func insertText(_ text: String) { + guard let range = selectedTextRange as? TextRange else { + return + } + + inputDelegate?.textWillChange(self) + selectedTextRange = textStorage.insert(text, at: range) + inputDelegate?.textDidChange(self) + + sendActions(for: [.editingChanged, .valueChanged]) + #if !canImport(CompositorServices) + hideMenu() + #endif + update() + } + + public func deleteBackward() { + guard let range = selectedTextRange as? TextRange else { + return + } + + inputDelegate?.textWillChange(self) + selectedTextRange = textStorage.delete(range: range) + inputDelegate?.textDidChange(self) + + sendActions(for: [.editingChanged, .valueChanged]) + #if !canImport(CompositorServices) + hideMenu() + #endif + update() + } + +} + +// MARK: - Utils + +extension OneTimeCodeTextField { + + private func clampIndex(_ index: Int) -> Int { + return max(min(index, configuration.numberOfDigits - 1), 0) + } + +} + +// MARK: - UITextInput + +extension OneTimeCodeTextField: UITextInput { + + public var markedTextRange: UITextRange? { + // We don't support marked text + return nil + } + + public var markedTextStyle: [NSAttributedString.Key: Any]? { + get { + return nil + } + set(markedTextStyle) { + // We don't support marked text + } + } + + public var beginningOfDocument: UITextPosition { + return textStorage.start + } + + public var endOfDocument: UITextPosition { + return textStorage.end + } + + public func text(in range: UITextRange) -> String? { + guard let range = range as? TextRange else { + return nil + } + + return textStorage.text(in: range) + } + + public func replace(_ range: UITextRange, withText text: String) { + // No-op + } + + public func setMarkedText(_ markedText: String?, selectedRange: NSRange) { + // We don't support marked text + } + + public func unmarkText() { + // We don't support marked text + } + + public func textRange(from fromPosition: UITextPosition, to toPosition: UITextPosition) -> UITextRange? { + guard + let fromPosition = fromPosition as? TextPosition, + let toPosition = toPosition as? TextPosition + else { + return nil + } + + return textStorage.makeRange(from: fromPosition, to: toPosition) + } + + public func position(from position: UITextPosition, offset: Int) -> UITextPosition? { + guard let position = position as? TextPosition else { + return nil + } + + let newIndex = position.index + offset + + guard textStorage.extent.contains(newIndex) else { + // Out of bounds + return nil + } + + return TextPosition(newIndex) + } + + public func position( + from position: UITextPosition, + in direction: UITextLayoutDirection, + offset: Int + ) -> UITextPosition? { + switch direction { + case .right: + return self.position(from: position, offset: offset) + case .left: + return self.position(from: position, offset: -offset) + case .up: + return offset > 0 ? beginningOfDocument : endOfDocument + case .down: + return offset > 0 ? endOfDocument : beginningOfDocument + @unknown default: + return nil + } + } + + public func compare(_ position: UITextPosition, to other: UITextPosition) -> ComparisonResult { + guard + let position = position as? TextPosition, + let other = other as? TextPosition + else { + return .orderedSame + } + + return position.compare(other) + } + + public func offset(from: UITextPosition, to toPosition: UITextPosition) -> Int { + guard + let from = from as? TextPosition, + let toPosition = toPosition as? TextPosition + else { + return 0 + } + + return toPosition.index - from.index + } + + public func position(within range: UITextRange, farthestIn direction: UITextLayoutDirection) -> UITextPosition? { + guard let range = range as? TextRange else { + return nil + } + + switch direction { + case .left, .up: + return range.start + case .right, .down: + return range.end + @unknown default: + return nil + } + } + + public func characterRange( + byExtending position: UITextPosition, + in direction: UITextLayoutDirection + ) -> UITextRange? { + switch direction { + case .right: + return self.textRange(from: position, to: endOfDocument) + case .left: + return self.textRange(from: beginningOfDocument, to: position) + case .up, .down: + return nil + @unknown default: + return nil + } + } + + public func baseWritingDirection( + for position: UITextPosition, + in direction: UITextStorageDirection + ) -> NSWritingDirection { + // Numeric input should be left-to-right always. + return .leftToRight + } + + public func setBaseWritingDirection(_ writingDirection: NSWritingDirection, for range: UITextRange) { + // No-op + } + + public func firstRect(for range: UITextRange) -> CGRect { + guard let range = range as? TextRange, !range.isEmpty else { + return .zero + } + + // This method should return a rectangle that contains the digit views that + // fall inside the given TextRange. For example, a [0,2] TextRange should + // return a rectangle that contains digit views 0 and 1: + // + // 0 1 2 3 4 5 6 <- TextPosition + // [*] [*] [*] [*] [*] [*] <- UI + // 0 1 2 3 4 5 <- DigitView index + // ^ ^ + // |_______| <- [0,2] TextRange + + let firstDigitView = digitViews[clampIndex(range._start.index)] + let secondDigitView = digitViews[clampIndex(range._end.index - 1)] + + let firstRect = firstDigitView.convert(firstDigitView.bounds, to: self) + let secondRect = secondDigitView.convert(secondDigitView.bounds, to: self) + + return firstRect.union(secondRect) + } + + public func caretRect(for position: UITextPosition) -> CGRect { + guard let position = position as? TextPosition else { + return .zero + } + + let digitView = digitViews[clampIndex(position.index)] + return digitView.convert(digitView.caretRect, to: self) + } + + public func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { + // No text-selection + return [] + } + + public func closestPosition(to point: CGPoint) -> UITextPosition? { + return closestPosition(to: point, within: textStorage.extent) + } + + public func closestPosition(to point: CGPoint, within range: UITextRange) -> UITextPosition? { + guard + let range = range as? TextRange, + let digitView = hitTest(point, with: nil) as? DigitView, + let index = digitViews.firstIndex(of: digitView) + else { + return nil + } + + return range.contains(index) ? TextPosition(index) : nil + } + + public func characterRange(at point: CGPoint) -> UITextRange? { + guard + let startPosition = closestPosition(to: point) as? TextPosition, + let endPosition = position(from: startPosition, offset: 1) + else { + return nil + } + + return self.textRange(from: startPosition, to: endPosition) + } + +} + +// MARK: - Digit View + +private extension OneTimeCodeTextField { + + final class DigitView: UIView { + + struct Configuration { + let borderWidth: CGFloat = 1 + let cornerRadius: CGFloat + let focusRingThickness: CGFloat = 2 + let font: UIFont + let itemHeight: CGFloat + + init( + cornerRadius: CGFloat, + font: UIFont, + itemHeight: CGFloat + ) { + self.cornerRadius = cornerRadius + self.font = font + self.itemHeight = itemHeight + } + } + + var isActive: Bool = false { + didSet { + updateLayers() + } + } + + var character: Character? { + didSet { + label.text = character.map { String($0) } + updateLayers() + } + } + + var isEnabled: Bool = true { + didSet { + updateColors() + } + } + + private let configuration: Configuration + + private let theme: ElementsAppearance + + private(set) lazy var borderLayer: CALayer = { + let borderLayer = CALayer() + borderLayer.borderWidth = configuration.borderWidth + borderLayer.cornerRadius = configuration.cornerRadius + return borderLayer + }() + + private lazy var focusRing: CALayer = { + let focusRing = CALayer() + focusRing.borderWidth = configuration.focusRingThickness + focusRing.cornerRadius = configuration.cornerRadius + (configuration.focusRingThickness / 2) + return focusRing + }() + + private lazy var caret: CALayer = .init() + + private lazy var label: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.isAccessibilityElement = false + label.textColor = theme.colors.textFieldText + label.font = configuration.font + return label + }() + + var caretRect: CGRect { + let caretSize = CGSize(width: 2, height: configuration.font.ascender) + + return CGRect( + x: (bounds.width - caretSize.width) / 2, + y: (bounds.height - caretSize.height) / 2, + width: caretSize.width, + height: caretSize.height + ) + } + + override var intrinsicContentSize: CGSize { + return CGSize(width: UIView.noIntrinsicMetric, height: configuration.itemHeight) + } + + init(configuration: Configuration, theme: ElementsAppearance) { + self.configuration = configuration + self.theme = theme + super.init(frame: .zero) + + layer.addSublayer(borderLayer) + layer.addSublayer(caret) + layer.addSublayer(focusRing) + + addSubview(label) + + updateColors() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + label.frame = bounds + borderLayer.frame = bounds + + // Update caret + caret.frame = caretRect + caret.cornerRadius = caret.frame.width / 2 + + focusRing.frame = bounds.insetBy( + dx: configuration.focusRingThickness / 2 * -1, + dy: configuration.focusRingThickness / 2 * -1 + ) + } + + private func updateLayers() { + CATransaction.begin() + CATransaction.setDisableActions(true) + + focusRing.isHidden = !isActive + + let isEmpty = character == nil + let shouldShowCaret = isActive && isEmpty + + if shouldShowCaret { + showCaret() + } else { + hideCaret() + } + + CATransaction.commit() + } + + private func updateColors() { + borderLayer.backgroundColor = isEnabled ? theme.colors.componentBackground.cgColor : theme.colors.disabledBackground.cgColor + borderLayer.borderColor = theme.colors.border.cgColor + caret.backgroundColor = label.textColor.cgColor + focusRing.borderColor = tintColor.cgColor + } + + private func showCaret() { + caret.isHidden = false + + let blinkingAnimation = CAKeyframeAnimation(keyPath: "opacity") + // Matches caret animation of iOS >= 13 + blinkingAnimation.keyTimes = [0, 0.5, 0.5375, 0.575, 0.6125, 0.65, 0.85, 0.8875, 0.925, 0.9625, 1] + blinkingAnimation.values = [1, 1, 0.75, 0.5, 0.25, 0, 0, 0.25, 0.5, 0.75, 1] + blinkingAnimation.duration = 1 + blinkingAnimation.repeatCount = .infinity + blinkingAnimation.calculationMode = .discrete + + caret.add(blinkingAnimation, forKey: "blink") + } + + private func hideCaret() { + caret.isHidden = true + caret.removeAllAnimations() + } + + override func tintColorDidChange() { + super.tintColorDidChange() + updateColors() + } + +#if !canImport(CompositorServices) + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateColors() + } +#endif + } + +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Checkbox/CheckboxButton.swift b/StripeUICore/StripeUICore/Source/Elements/Checkbox/CheckboxButton.swift new file mode 100644 index 00000000..77f2292e --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Checkbox/CheckboxButton.swift @@ -0,0 +1,342 @@ +// +// CheckboxButton.swift +// StripeUICore +// +// Created by Cameron Sabol on 12/11/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public protocol CheckboxButtonDelegate: AnyObject { + /// Return `true` to open the URL in the device's default browser. + /// Return `false` to custom handle the URL. + func checkboxButton(_ checkboxButton: CheckboxButton, shouldOpen url: URL) -> Bool +} + +/// For internal SDK use only +@objc(STP_Internal_CheckboxButton) +@_spi(STP) public class CheckboxButton: UIControl { + // MARK: - Properties + + public weak var delegate: CheckboxButtonDelegate? + + private var font: UIFont { + return theme.fonts.footnote + } + + private var emphasisFont: UIFont { + return theme.fonts.footnoteEmphasis + } + + private lazy var textView: UITextView = { + let textView = LinkOpeningTextView() + textView.isEditable = false + textView.isSelectable = false + textView.isScrollEnabled = false + textView.backgroundColor = nil + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + textView.adjustsFontForContentSizeCategory = true + textView.delegate = self + textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + return textView + }() + + private lazy var descriptionLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.isAccessibilityElement = false + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + return label + }() + + private lazy var checkbox: CheckBox = { + let checkbox = CheckBox(theme: theme) + checkbox.isSelected = true + checkbox.translatesAutoresizingMaskIntoConstraints = false + return checkbox + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [textView, descriptionLabel]) + stackView.spacing = 4 + stackView.axis = .vertical + stackView.distribution = .equalSpacing + stackView.alignment = .leading + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + /// Aligns the checkbox vertically to the first baseline of `label`. + private lazy var checkboxAlignmentConstraint: NSLayoutConstraint = { + return checkbox.centerYAnchor.constraint( + equalTo: textView.firstBaselineAnchor, + constant: 0 + ) + }() + + public override var isSelected: Bool { + didSet { + if isSelected { + accessibilityTraits.update(with: .selected) + } else { + accessibilityTraits.remove(.selected) + } + checkbox.isSelected = isSelected + } + } + + public override var isEnabled: Bool { + didSet { + checkbox.isUserInteractionEnabled = isEnabled + textView.isUserInteractionEnabled = isEnabled + } + } + + public private(set) var hasReceivedTap: Bool = false + + public override var isHidden: Bool { + didSet { + checkbox.setNeedsDisplay() + setNeedsDisplay() + } + } + + public var theme: ElementsAppearance { + didSet { + checkbox.theme = theme + updateLabels() + } + } + + // MARK: - Initializers + + public init(description: String? = nil, theme: ElementsAppearance = .default) { + self.theme = theme + super.init(frame: .zero) + + isAccessibilityElement = true + accessibilityHint = description + accessibilityTraits = UISwitch().accessibilityTraits + + descriptionLabel.text = description + + setupUI() + + let didTapGestureRecognizer = UITapGestureRecognizer( + target: self, action: #selector(didTap)) + addGestureRecognizer(didTapGestureRecognizer) + } + + public convenience init(text: String, description: String? = nil, theme: ElementsAppearance = .default) { + self.init(description: description, theme: theme) + setText(text) + } + + public convenience init(attributedText: NSAttributedString, description: String? = nil, theme: ElementsAppearance = .default) { + self.init(description: description, theme: theme) + setAttributedText(attributedText) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func layoutSubviews() { + super.layoutSubviews() + + // Preferred max width sometimes is off when changing font size + descriptionLabel.preferredMaxLayoutWidth = stackView.bounds.width + textView.invalidateIntrinsicContentSize() + } + +#if !canImport(CompositorServices) + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateLabels() + } +#endif + + public func setText(_ text: String) { + textView.text = text + updateLabels() + updateAccessibility() + } + + public func setAttributedText(_ attributedText: NSAttributedString) { + textView.attributedText = attributedText + updateLabels() + updateAccessibility() + } + + private func setupUI() { + addSubview(checkbox) + addSubview(stackView) + + let minimizeHeight = stackView.heightAnchor.constraint(equalTo: heightAnchor) + minimizeHeight.priority = .defaultLow + NSLayoutConstraint.activate([ + // Checkbox + checkboxAlignmentConstraint, + checkbox.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + checkbox.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor), + checkbox.leadingAnchor.constraint(equalTo: leadingAnchor), + + // Stack view + stackView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + stackView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 6), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + minimizeHeight, + ]) + } + + @objc + private func didTap() { + hasReceivedTap = true + isSelected.toggle() + sendActions(for: .touchUpInside) + } + + private func updateLabels() { + let hasDescription = descriptionLabel.text != nil + + let textFont = hasDescription ? emphasisFont : font + textView.font = textFont + textView.textColor = hasDescription ? theme.colors.bodyText : theme.colors.secondaryText + + descriptionLabel.font = font + descriptionLabel.isHidden = !hasDescription + descriptionLabel.textColor = theme.colors.secondaryText + + // Align checkbox to center of first line of text. The center of the checkbox is already + // pinned to the first baseline via a constraint, so we just need to calculate + // the offset from baseline to line center, and apply the offset to the constraint. + let baselineToLineCenterOffset = (textFont.ascender + textFont.descender) / 2 + checkboxAlignmentConstraint.constant = -baselineToLineCenterOffset + } + + private func updateAccessibility() { + // Copy the text view's accessibilityValue which will describe any links + // contained in the text to the user + accessibilityLabel = textView.accessibilityValue ?? textView.text + + // If the text contains a link, allow links to be opened with the text + // view's link rotor + let linkRotors = textView.accessibilityCustomRotors?.filter({ $0.systemRotorType == .link }) ?? [] + accessibilityCustomRotors = linkRotors + + // iOS 13 automatically includes a hint if there is a link rotor, but + // iOS 14+ do not so we must add one ourselves. + if #available(iOS 14, *) { + var hints = [descriptionLabel.text] + if !linkRotors.isEmpty { + hints.append(.Localized.useRotorToAccessLinks) + } + accessibilityHint = hints.compactMap { $0 }.joined(separator: ", ") + } + } +} + +// MARK: - UITextViewDelegate +extension CheckboxButton: UITextViewDelegate { + #if !canImport(CompositorServices) + // This is only used by StripeIdentity, which does not support visionOS. + public func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange) -> Bool { + return delegate?.checkboxButton(self, shouldOpen: url) ?? true + } + #endif +} + +// MARK: - CheckBox +/// For internal SDK use only +@objc(STP_Internal_CheckBox) +class CheckBox: UIView { + var isSelected: Bool = false { + didSet { + setNeedsDisplay() + } + } + + private var fillColor: UIColor { + if isSelected { + return theme.colors.primary + } + + return theme.colors.parentBackground + } + + var theme: ElementsAppearance { + didSet { + layer.applyShadow(shadow: theme.shadow) + setNeedsDisplay() + } + } + + override var intrinsicContentSize: CGSize { + return CGSize(width: 20, height: 20) + } + + init(theme: ElementsAppearance = .default) { + self.theme = theme + super.init(frame: .zero) + + backgroundColor = .clear + layer.applyShadow(shadow: theme.shadow) + + setContentHuggingPriority(.defaultHigh, for: .horizontal) + setContentHuggingPriority(.defaultHigh, for: .vertical) + setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ rect: CGRect) { + let rect = rect.inset(by: superview!.alignmentRectInsets) + let borderRectWidth = min(16, rect.width - 2) + let borderRectHeight = min(16, rect.height - 2) + let borderRect = CGRect( + x: max(0, rect.midX - 0.5 * borderRectWidth), + y: max(0, rect.midY - 0.5 * borderRectHeight), width: borderRectWidth, + height: borderRectHeight) + + let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: 3) + borderPath.lineWidth = 1 + if isUserInteractionEnabled { + fillColor.setFill() + } else { + fillColor.setFill() + } + borderPath.fill() + if theme.colors.border.rgba.alpha != 0 { + theme.colors.border.setStroke() + } else { + // If the border is clear, fall back to secondaryText + theme.colors.secondaryText.setStroke() + } + borderPath.stroke() + + if isSelected { + let checkmarkPath = UIBezierPath() + checkmarkPath.move(to: CGPoint(x: borderRect.minX + 3.5, y: borderRect.minY + 9)) + checkmarkPath.addLine( + to: CGPoint(x: borderRect.minX + 3.5 + 4, y: borderRect.minY + 8 + 4)) + checkmarkPath.addLine(to: CGPoint(x: borderRect.maxX - 3, y: borderRect.minY + 4)) + + checkmarkPath.lineCapStyle = .square + checkmarkPath.lineJoinStyle = .bevel + checkmarkPath.lineWidth = 2 + if isUserInteractionEnabled { + fillColor.contrastingColor.setStroke() + } else { + fillColor.contrastingColor.disabledColor.setStroke() + } + checkmarkPath.stroke() + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Checkbox/CheckboxElement.swift b/StripeUICore/StripeUICore/Source/Elements/Checkbox/CheckboxElement.swift new file mode 100644 index 00000000..ddcd2dfd --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Checkbox/CheckboxElement.swift @@ -0,0 +1,70 @@ +// +// CheckboxElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/15/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +@_spi(STP) public final class CheckboxElement { + public weak var delegate: ElementDelegate? + public private(set) lazy var checkboxButton: CheckboxButton = { + let checkbox = CheckboxButton( + text: label, + theme: theme + ) + checkbox.addTarget(self, action: #selector(didToggleCheckbox), for: .touchUpInside) + checkbox.isSelected = isSelectedByDefault + return checkbox + }() + let label: String + let isSelectedByDefault: Bool + let theme: ElementsAppearance + var didToggle: (Bool) -> Void + @_spi(STP) public var isSelected: Bool { + get { + return checkboxButton.isSelected + } + set { + checkboxButton.isSelected = newValue + } + } + + @objc func didToggleCheckbox() { + didToggle(checkboxButton.isSelected) + delegate?.didUpdate(element: self) + } + + public init( + theme: ElementsAppearance, + label: String, + isSelectedByDefault: Bool, + didToggle: ((Bool) -> Void)? = nil + ) { + self.label = label + self.isSelectedByDefault = isSelectedByDefault + self.theme = theme + self.didToggle = didToggle ?? { _ in } + } +} + +/// :nodoc: +extension CheckboxElement: Element { + public var collectsUserInput: Bool { + true + } + + public var view: UIView { + return checkboxButton + } +} + +// MARK: - DebugDescription +extension CheckboxElement { + public var debugDescription: String { + return "; label = \(label); isSelected = \(isSelected); validationState = \(validationState)" + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/ContainerElement.swift b/StripeUICore/StripeUICore/Source/Elements/ContainerElement.swift new file mode 100644 index 00000000..6dad532d --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/ContainerElement.swift @@ -0,0 +1,84 @@ +// +// ContainerElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 3/25/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/** + A protocol for Elements that contain other Elements. + It offers default implementations for the methods required to participate in the Element hierarchy. + + - Note:You still need to set your sub-element's delegates = self! + */ +@_spi(STP) public protocol ContainerElement: Element, ElementDelegate { + var elements: [Element] { get } +} + +extension ContainerElement { + // MARK: - Element + public var collectsUserInput: Bool { + // Returns true if any of the child elements collect user input + return elements.reduce(false) { partialResult, element in + element.collectsUserInput || partialResult + } + } + + public func beginEditing() -> Bool { + guard !view.isHidden else { + // Prevent focusing on a child element if the container is hidden. + return false + } + + return elements.first?.beginEditing() ?? false + } + + public var validationState: ElementValidationState { + elements.first { + if case .valid = $0.validationState { + return false + } + return true + }?.validationState ?? .valid + } + + // MARK: - ElementDelegate + + public func didUpdate(element: Element) { + // Glue: Update the view and our delegate + delegate?.didUpdate(element: self) + } + + public func continueToNextField(element: Element) { + let remainingElements = elements + .drop { $0 !== element } // Drop elements (starting from the first) until we find `element` + .dropFirst() // Drop `element` too + for next in remainingElements { + // Don't auto select hidden elements + if !(next is SectionElement.HiddenElement), + !next.view.isHidden, + next.beginEditing() { + UIAccessibility.post(notification: .screenChanged, argument: next.view) + return + } + } + // Failed to become first responder + delegate?.continueToNextField(element: self) + } +} + +extension ContainerElement { + public var debugDescription: String { + return "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())>" + subElementDebugDescription + } + + public var subElementDebugDescription: String { + elements.reduce("") { partialResult, element in + partialResult + "\n└─ \(String(describing: element).replacingOccurrences(of: "└─", with: " └─"))" + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/DateFieldElement.swift b/StripeUICore/StripeUICore/Source/Elements/DateFieldElement.swift new file mode 100644 index 00000000..e04233ab --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/DateFieldElement.swift @@ -0,0 +1,196 @@ +// +// DateFieldElement.swift +// StripeUICore +// +// Created by Mel Ludowise on 10/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/** + A textfield whose input view is a `UIDatePicker` + */ +@_spi(STP) public class DateFieldElement { + public typealias DidUpdateSelectedDate = (Date) -> Void + struct DateEmptyError: ElementValidationError { + var localizedDescription: String = STPLocalizedString( + "Date is empty.", + "Error message for empty date." + ) + } + + weak public var delegate: ElementDelegate? + private(set) lazy var datePickerView: UIDatePicker = { + let picker = UIDatePicker() + if #available(iOS 13.4, *) { + picker.preferredDatePickerStyle = .wheels + } + picker.datePickerMode = .date + picker.addTarget(self, action: #selector(didSelectDate), for: .valueChanged) + return picker + }() + private(set) lazy var pickerFieldView: PickerFieldView = { + let pickerFieldView = PickerFieldView( + label: label, + shouldShowChevron: false, + pickerView: datePickerView, + delegate: self, + theme: theme + ) + return pickerFieldView + }() + + private var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + public private(set) var selectedDate: Date? { + didSet { + updateDisplayText() + } + } + private var previouslySelectedDate: Date? + public var validationState: ElementValidationState { + if selectedDate != nil { + return .valid + } else { + return .invalid(error: DateEmptyError(), shouldDisplay: false) + } + } + public var didUpdate: DidUpdateSelectedDate? + + private let label: String? + private let theme: ElementsAppearance + + /** + - Parameters: + - label: The label of this picker + - defaultDate: If this field should be prefilled before the user interacts with it, then provide a default date to display initially. + - minimumDate: The minimum date that can be selected + - maximumDate: The maximum date that can be selected + - locale: The locale to use to format the date into display text and configure the date picker + - timeZone: The timeZone to use to format the date into display text and configure the date picker + - didUpdate: Called when the user has selected a new date. + - theme: Theme for the element + + - Note: + - If a minimum or maximum date is provided and `defaultDate` is outside of of that range, then the given default is ignored. + - `didUpdate` is not called if the user does not change their input before hitting "Done" + */ + public init( + label: String? = nil, + defaultDate: Date? = nil, + minimumDate: Date? = nil, + maximumDate: Date? = nil, + locale: Locale = .current, + timeZone: TimeZone = .current, + theme: ElementsAppearance = .default, + customDateFormatter: DateFormatter? = nil, + didUpdate: DidUpdateSelectedDate? = nil + ) { + self.label = label + self.theme = theme + if let customDateFormatter = customDateFormatter { + self.dateFormatter = customDateFormatter + } + dateFormatter.locale = locale + dateFormatter.timeZone = timeZone + + datePickerView.locale = locale + datePickerView.timeZone = timeZone + datePickerView.minimumDate = minimumDate + datePickerView.maximumDate = maximumDate + if let defaultDate = DateFieldElement.dateWithinBounds(defaultDate, min: minimumDate, max: maximumDate) { + datePickerView.date = defaultDate + selectedDate = defaultDate + updateDisplayText() + } + + self.previouslySelectedDate = defaultDate + self.didUpdate = didUpdate + } + + // MARK: - Internal Methods + + @objc func didSelectDate() { + selectedDate = datePickerView.date + } + + private func updateDisplayText() { + let selectedDate = selectedDate.map { dateFormatter.string(from: $0) } + pickerFieldView.displayText = NSAttributedString(string: selectedDate ?? "") + } +} + +// MARK: Element + +extension DateFieldElement: Element { + public var collectsUserInput: Bool { true } + + public var view: UIView { + return pickerFieldView + } + + public func beginEditing() -> Bool { + return pickerFieldView.becomeFirstResponder() + } +} + +// MARK: - PickerFieldViewDelegate + +extension DateFieldElement: PickerFieldViewDelegate { + func didBeginEditing(_ pickerFieldView: PickerFieldView) { + selectedDate = datePickerView.date + } + + func didFinish(_ pickerFieldView: PickerFieldView, shouldAutoAdvance: Bool) { + if previouslySelectedDate != selectedDate, + let selectedDate = selectedDate + { + didUpdate?(selectedDate) + previouslySelectedDate = selectedDate + delegate?.didUpdate(element: self) + } + if shouldAutoAdvance { + delegate?.continueToNextField(element: self) + } + } + + func didCancel(_ pickerFieldView: PickerFieldView) { + // no-op + } +} + +// MARK: - Private Helpers + +private extension DateFieldElement { + /// Returns the date if it is within the min & max bounds, when applicable. Otherwise returns nil + static func dateWithinBounds( + _ date: Date?, + min: Date?, + max: Date? + ) -> Date? { + guard let date = date else { + return nil + } + + if let min = min, + date < min + { + return nil + } + + if let max = max, + date > max + { + return nil + } + + return date + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/DropdownFieldElement.swift b/StripeUICore/StripeUICore/Source/Elements/DropdownFieldElement.swift new file mode 100644 index 00000000..7245df2f --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/DropdownFieldElement.swift @@ -0,0 +1,306 @@ +// +// DropdownFieldElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/17/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/** + A textfield whose input view is a `UIPickerView` (on iOS) or a `UIMenu` (on Catalyst) with a list of the strings. + + For internal SDK use only + */ +@_spi(STP) public final class DropdownFieldElement { + public typealias DidPresent = () -> Void + public typealias DidUpdateSelectedIndex = (Int) -> Void + public typealias DidTapClose = () -> Void + + public struct DropdownItem { + public init(pickerDisplayName: NSAttributedString, labelDisplayName: NSAttributedString, accessibilityValue: String, rawData: String, isPlaceholder: Bool = false) { + self.pickerDisplayName = pickerDisplayName + self.labelDisplayName = labelDisplayName + self.accessibilityValue = accessibilityValue + self.isPlaceholder = isPlaceholder + self.rawData = rawData + } + + public init(pickerDisplayName: String, labelDisplayName: String, accessibilityValue: String, rawData: String, isPlaceholder: Bool = false) { + self = .init(pickerDisplayName: NSAttributedString(string: pickerDisplayName), + labelDisplayName: NSAttributedString(string: labelDisplayName), + accessibilityValue: accessibilityValue, + rawData: rawData, + isPlaceholder: isPlaceholder) + } + + /// Item label displayed in the picker + public let pickerDisplayName: NSAttributedString + + /// Item label displayed in inline label when item has been selected + public let labelDisplayName: NSAttributedString + + /// Accessibility value to use when this is in the inline label + public let accessibilityValue: String + + /// The underlying data for this dropdown item. + /// e.g., A country dropdown item might display "United States" but its `rawData` is "US". + /// This is ignored by `DropdownFieldElement`, and is intended as a convenience to be used in conjunction with `selectedItem` + public let rawData: String + + /// If true, this item will be styled with greyed out secondary text + public let isPlaceholder: Bool + } + + // MARK: - Public properties + weak public var delegate: ElementDelegate? + public private(set) var items: [DropdownItem] + public var nonPlacerholderItems: [DropdownItem] { + return items.filter({ !$0.isPlaceholder }) + } + public var selectedItem: DropdownItem { + return items[selectedIndex] + } + public var selectedIndex: Int { + didSet { + updatePickerField() + } + } + public var didPresent: DidPresent? + public var didUpdate: DidUpdateSelectedIndex? + public var didTapClose: DidTapClose? + public let theme: ElementsAppearance + public let hasPadding: Bool + + /// A label displayed in the dropdown field UI e.g. "Country or region" for a country dropdown + public let label: String? +#if targetEnvironment(macCatalyst) || canImport(CompositorServices) + private(set) lazy var pickerView: UIButton = { + let button = UIButton() + let action = { (action: UIAction) in + self.selectedIndex = Int(action.identifier.rawValue) ?? 0 + } + + if #available(macCatalyst 14.0, *) { + let menu = UIMenu(children: + items.enumerated().map { (index, item) in + UIAction(title: item.pickerDisplayName.string, identifier: .init(rawValue: String(index)), handler: action) + } + ) + button.menu = menu + button.showsMenuAsPrimaryAction = true + } + + // We don't need to show this button, we're just using it to accept hits and present the menu. + button.isHidden = true + return button + }() +#else + private(set) lazy var pickerView: UIPickerView = { + let picker = UIPickerView() + picker.delegate = pickerViewDelegate + picker.dataSource = pickerViewDelegate + return picker + }() +#endif + + private(set) lazy var pickerFieldView: PickerFieldView = { + let pickerFieldView = PickerFieldView( + label: label, + shouldShowChevron: disableDropdownWithSingleElement ? items.count != 1 : true, + pickerView: pickerView, + delegate: self, + theme: theme, + hasPadding: hasPadding, + isOptional: isOptional + ) + if disableDropdownWithSingleElement && items.count == 1 { + pickerFieldView.isUserInteractionEnabled = false + } + return pickerFieldView + }() + + // MARK: - Private properties + private var previouslySelectedIndex: Int + private let disableDropdownWithSingleElement: Bool + private let isOptional: Bool + lazy var pickerViewDelegate: PickerViewDelegate = { PickerViewDelegate(self) }() + + /** + - Parameters: + - items: Items to populate this dropdown with. + - defaultIndex: Defaults the dropdown to the item with the corresponding index. + - label: Label for the dropdown + - didUpdate: Called when the user has finished selecting a new item. + + - Note: + - Items must contain at least one item. + - If `defaultIndex` is outside of the bounds of the `items` array, then a default of `0` is used. + - `didUpdate` is not called if the user does not change their input before hitting "Done" + */ + public init( + items: [DropdownItem], + defaultIndex: Int = 0, + label: String?, + theme: ElementsAppearance = .default, + hasPadding: Bool = true, + disableDropdownWithSingleElement: Bool = false, + isOptional: Bool = false, + didPresent: DidPresent? = nil, + didUpdate: DidUpdateSelectedIndex? = nil, + didTapClose: DidTapClose? = nil + ) { + assert(!items.isEmpty, "`items` must contain at least one item") + + self.label = label + self.theme = theme + self.items = items + self.disableDropdownWithSingleElement = disableDropdownWithSingleElement + self.isOptional = isOptional + self.didPresent = didPresent + self.didUpdate = didUpdate + self.didTapClose = didTapClose + self.hasPadding = hasPadding + + // Default to defaultIndex, if in bounds + if defaultIndex < 0 || defaultIndex >= items.count { + self.selectedIndex = 0 + } else { + self.selectedIndex = defaultIndex + } + self.previouslySelectedIndex = selectedIndex + + if !items.isEmpty { + updatePickerField() + } + } + + public func select(index: Int, shouldAutoAdvance: Bool = true) { + selectedIndex = index + didFinish(pickerFieldView, shouldAutoAdvance: shouldAutoAdvance) + } + + public func update(items: [DropdownItem]) { + assert(!items.isEmpty, "`items` must contain at least one item") + // Try to re-select the same item afer updating, if not possible default to the first item in the list + let newSelectedIndex = items.firstIndex(where: { $0.rawData == self.items[selectedIndex].rawData }) ?? 0 + + self.items = items + self.select(index: newSelectedIndex, shouldAutoAdvance: false) + } +} + +private extension DropdownFieldElement { + + func updatePickerField() { + #if targetEnvironment(macCatalyst) || canImport(CompositorServices) + if #available(macCatalyst 14.0, *) { + // Mark the enabled menu item as selected + pickerView.menu?.children.forEach { ($0 as? UIAction)?.state = .off } + (pickerView.menu?.children[selectedIndex] as? UIAction)?.state = .on + } + #else + if pickerView.selectedRow(inComponent: 0) != selectedIndex { + pickerView.reloadComponent(0) + pickerView.selectRow(selectedIndex, inComponent: 0, animated: false) + } + #endif + + pickerFieldView.displayText = items[selectedIndex].labelDisplayName + pickerFieldView.displayTextAccessibilityValue = items[selectedIndex].accessibilityValue + } + +} + +// MARK: Element + +extension DropdownFieldElement: Element { + public var collectsUserInput: Bool { true } + + public var view: UIView { + return pickerFieldView + } + + public func beginEditing() -> Bool { + return pickerFieldView.becomeFirstResponder() + } +} + +// MARK: UIPickerViewDelegate & UIPickerViewDataSource + +extension DropdownFieldElement { + // A silly bridge class to work around the fact that UIPickerViewDelegate must be an NSObject + class PickerViewDelegate: NSObject, UIPickerViewDelegate, UIPickerViewDataSource { + weak var dropdownFieldElement: DropdownFieldElement? + init(_ dropdownFieldElement: DropdownFieldElement?) { + self.dropdownFieldElement = dropdownFieldElement + } + + public func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { + guard let dropdownFieldElement else { return nil } + let item = dropdownFieldElement.items[row] + + guard item.isPlaceholder else { return item.pickerDisplayName } + + // If this item is marked as a placeholder, apply placeholder text color + let attributes: [NSAttributedString.Key: Any] = [.foregroundColor: dropdownFieldElement.theme.colors.placeholderText] + let placeholderString = NSAttributedString(string: item.pickerDisplayName.string, attributes: attributes) + return placeholderString + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + dropdownFieldElement?.pickerView(pickerView, didSelectRow: row, inComponent: component) + } + + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return dropdownFieldElement?.items.count ?? 0 + } + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + selectedIndex = row + } +} + +// MARK: - PickerFieldViewDelegate + +extension DropdownFieldElement: PickerFieldViewDelegate { + func didBeginEditing(_ pickerFieldView: PickerFieldView) { + didPresent?() + } + + func didFinish(_ pickerFieldView: PickerFieldView, shouldAutoAdvance: Bool) { + if previouslySelectedIndex != selectedIndex { + didUpdate?(selectedIndex) + } + previouslySelectedIndex = selectedIndex + + if shouldAutoAdvance { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.didUpdate(element: self) + self.delegate?.continueToNextField(element: self) + } + } + } + + func didCancel(_ pickerFieldView: PickerFieldView) { + // Reset to previously selected index when canceling + selectedIndex = previouslySelectedIndex + didTapClose?() + } +} + +// MARK: - DebugDescription +extension DropdownFieldElement { + public var debugDescription: String { + return "; label = \(label ?? "nil"); validationState = \(validationState); rawData = \(rawData)" + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Element.swift b/StripeUICore/StripeUICore/Source/Elements/Element.swift new file mode 100644 index 00000000..01d5d075 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Element.swift @@ -0,0 +1,103 @@ +// +// Element.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +// MARK: - Element + +/** + Conform to this protocol to participate in the collection of details for a Payment/SetupIntent. + + Think of this as a light-weight, specialized view controller. + */ +@_spi(STP) public protocol Element: AnyObject, CustomDebugStringConvertible { + /// - Note: This is set by your parent. + var delegate: ElementDelegate? { get set } + + /// Return your UIView instance. + var view: UIView { get } + + /// - Returns: Whether or not this Element began editing. + func beginEditing() -> Bool + + /// Whether this element contains valid user input or not. + var validationState: ElementValidationState { get } + + /// Text to display to the user under the item, if any. + var subLabelText: String? { get } + + /// Whether or not this Element collects user input (e.g. a text field, dropdown, picker, checkbox). + var collectsUserInput: Bool { get } +} + +public extension Element { + func beginEditing() -> Bool { + return false + } + + var validationState: ElementValidationState { + return .valid + } + + var subLabelText: String? { + return nil + } +} + +// MARK: - ElementDelegate + +/** + An Element uses this delegate to communicate events to its owner, which is typically also an Element. + */ +@_spi(STP) public protocol ElementDelegate: AnyObject { + /** + This method is called whenever your public/internally visable state changes. + Note for implementors: Be sure to chain this call upwards to your own ElementDelegate. + */ + func didUpdate(element: Element) + + /** + This method is called when the user finishes editing the caller e.g., by pressing the 'return' key. + Note for implementors: Be sure to chain this call upwards to your own ElementDelegate. + */ + func continueToNextField(element: Element) +} + +/** + An Element uses this delegate to present a view controller + */ +@_spi(STP) public protocol PresentingViewControllerDelegate: ElementDelegate { + /** + Elements will call this function to delegate presentation of a view controller + */ + func presentViewController(viewController: UIViewController, completion: (() -> Void)?) +} + +@_spi(STP) @frozen public enum ElementValidationState { + case valid + case invalid(error: ElementValidationError, shouldDisplay: Bool) + + /// A convenience property to check if the state is valid because it's hard to make this type Equatable + public var isValid: Bool { + if case .valid = self { + return true + } + return false + } +} + +@_spi(STP) public protocol ElementValidationError: Error { + var localizedDescription: String { get } +} + +extension Element { + public var debugDescription: String { + return "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())>" + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/ElementsUI.swift b/StripeUICore/StripeUICore/Source/Elements/ElementsUI.swift new file mode 100644 index 00000000..78e9908f --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/ElementsUI.swift @@ -0,0 +1,139 @@ +// +// ElementsUI.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public enum ElementsUI { + + /// The distances between an Element's content and its containing view + public static let contentViewInsets: NSDirectionalEdgeInsets = .insets(top: 4, leading: 11, bottom: 4, trailing: 11) + public static let fieldBorderColor: UIColor = .systemGray3 + public static let fieldBorderWidth: CGFloat = 1 + public static let textFieldFont: UIFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14)) + public static let sectionTitleFont: UIFont = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 13, weight: .semibold)) + /// The spacing between elements of a SectionElement + public static let sectionSpacing: CGFloat = 4 + /// The spacing between elements of a FormElement + public static let formSpacing: CGFloat = 12 + public static let defaultCornerRadius: CGFloat = 6 + public static let backgroundColor: UIColor = { + // systemBackground has a 'base' and 'elevated' state; we don't want this behavior. + return .dynamic(light: .systemBackground, dark: .secondarySystemBackground) + }() + + public static let disabledBackgroundColor: UIColor = { + return .dynamic( + light: UIColor(red: 248.0 / 255.0, green: 248.0 / 255.0, blue: 248.0 / 255.0, alpha: 1), + dark: UIColor(red: 116.0 / 255.0, green: 116.0 / 255.0, blue: 128.0 / 255.0, alpha: 0.18) + ) + }() + + public static func makeErrorLabel(theme: ElementsAppearance) -> UILabel { + let label = UILabel() + label.font = theme.fonts.footnote + label.textColor = theme.colors.danger + label.numberOfLines = 0 + label.setContentHuggingPriority(.required, for: .vertical) + return label + } + + public static func makeSmallFootnote(theme: ElementsAppearance) -> UITextView { + let textView = UITextView() + textView.isScrollEnabled = false + textView.isEditable = false + textView.font = theme.fonts.smallFootnote + textView.backgroundColor = .clear + textView.textColor = theme.colors.secondaryText + textView.linkTextAttributes = [.foregroundColor: theme.colors.primary] + textView.isUserInteractionEnabled = false + return textView + } + + public static func makeNoticeTextField(theme: ElementsAppearance) -> UITextView { + let textView = UITextView() + textView.isScrollEnabled = false + textView.isEditable = false + textView.font = theme.fonts.footnote + textView.backgroundColor = .clear + textView.textColor = theme.colors.secondaryText + textView.linkTextAttributes = [.foregroundColor: theme.colors.primary] + return textView + } + + public static func makeSectionTitleLabel(theme: ElementsAppearance) -> UILabel { + let label = UILabel() + label.font = theme.fonts.sectionHeader + label.textColor = theme.colors.secondaryText + label.accessibilityTraits = [.header] + return label + } +} + +/// Describes the appearance of an Element +/// A superset of `StripePaymentSheet.PaymentSheetAppearance`. This exists b/c we can't see that type from `StripeUICore`, and we don't want to the public StripePaymentSheet API to be a typealias of this. +@_spi(STP) public struct ElementsAppearance { + + /// The default appearance used for Elements + public static let `default` = ElementsAppearance() + + public var fonts = Font() + public var colors = Color() + + public var borderWidth = ElementsUI.fieldBorderWidth + public var cornerRadius = ElementsUI.defaultCornerRadius + public var shadow: Shadow? = Shadow() + + public struct Font { + public init() {} + + public var subheadline = ElementsUI.textFieldFont + public var subheadlineBold = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 14, weight: .bold)) + public var sectionHeader = ElementsUI.sectionTitleFont + public var caption = UIFont.systemFont(ofSize: 12, weight: .regular).scaled( + withTextStyle: .caption1, + maximumPointSize: 20) + public var footnote = UIFont.preferredFont(forTextStyle: .footnote, weight: .regular, maximumPointSize: 20) + public var smallFootnote = UIFont.preferredFont(forTextStyle: .footnote, weight: .medium, maximumPointSize: 10) + public var footnoteEmphasis = UIFont.preferredFont(forTextStyle: .footnote, weight: .medium, maximumPointSize: 20) + } + + public struct Color { + public init() {} + + public var primary = UIColor.systemBlue + public var parentBackground = UIColor.systemBackground + public var componentBackground = ElementsUI.backgroundColor + public var disabledBackground = ElementsUI.disabledBackgroundColor + public var border = ElementsUI.fieldBorderColor + public var divider = ElementsUI.fieldBorderColor + public var textFieldText = UIColor.label + public var bodyText = UIColor.label + public var secondaryText = UIColor.secondaryLabel + public var placeholderText = UIColor.secondaryLabel + public var danger = UIColor.systemRed + } + + public struct Shadow { + + public var color = UIColor.black + public var opacity = CGFloat(0.05) + public var offset = CGSize(width: 0, height: 2) + public var radius = CGFloat(4) + + init () {} + + public init(color: UIColor, opacity: CGFloat, offset: CGSize, radius: CGFloat) { + self.color = color + self.opacity = opacity + self.offset = offset + self.radius = radius + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSectionElement+DummyAddressLine.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSectionElement+DummyAddressLine.swift new file mode 100644 index 00000000..40c5dff4 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSectionElement+DummyAddressLine.swift @@ -0,0 +1,72 @@ +// +// AddressSectionElement+DummyAddressLine.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 7/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +extension AddressSectionElement { + /// Looks like a "Address" text field but with the text field disabled + @_spi(STP) public class DummyAddressLine: NSObject, Element, TextFieldViewDelegate, UIGestureRecognizerDelegate { + public let collectsUserInput: Bool = false + + public var delegate: ElementDelegate? + public lazy var view: UIView = { + let configuration = TextFieldElement.Address.LineConfiguration(lineType: .autoComplete, defaultValue: nil) + let text = "" + let viewModel = TextFieldElement.ViewModel( + placeholder: configuration.label, + accessibilityLabel: configuration.accessibilityLabel, + attributedText: configuration.makeDisplayText(for: text), + keyboardProperties: configuration.keyboardProperties(for: text), + validationState: configuration.validate(text: text, isOptional: configuration.isOptional), + accessoryView: configuration.accessoryView(for: text, theme: theme), + shouldShowClearButton: configuration.shouldShowClearButton, + isEditable: configuration.isEditable, + theme: theme + ) + let textFieldView = TextFieldView(viewModel: viewModel, delegate: self) + textFieldView.isUserInteractionEnabled = false + let view = UIView() + view.addAndPinSubview(textFieldView) + view.addGestureRecognizer(autocompleteLineTapRecognizer) + return view + }() + public var validationState: ElementValidationState { + return .invalid(error: TextFieldElement.Error.empty, shouldDisplay: false) + } + let didTap: () -> Void + public let theme: ElementsAppearance + private lazy var autocompleteLineTapRecognizer: UITapGestureRecognizer = { + let tap = UITapGestureRecognizer(target: self, action: #selector(_didTap)) + tap.delegate = self + return tap + }() + + @objc func _didTap() { + didTap() + } + + func textFieldViewDidUpdate(view: TextFieldView) { + // no-op + } + + func textFieldViewContinueToNextField(view: TextFieldView) { + // no-op + } + + public func beginEditing() -> Bool { + // no-op but pretend we did begin editing + return true + } + + public init(theme: ElementsAppearance, didTap: @escaping () -> Void = {}) { + self.theme = theme + self.didTap = didTap + super.init() + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSectionElement.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSectionElement.swift new file mode 100644 index 00000000..b8dfebcc --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSectionElement.swift @@ -0,0 +1,460 @@ +// +// AddressSectionElement.swift +// StripeUICore +// +// Created by Mel Ludowise on 10/5/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/** + A section that contains a country dropdown and the country-specific address fields. It updates the address fields whenever the country changes to reflect the address format of that country. + + In addition to the physical address, it can collect other related fields like name. + */ +@_spi(STP) public class AddressSectionElement: ContainerElement { + public typealias DidUpdateAddress = (AddressDetails) -> Void + + /// Describes an address to use as a default for AddressSectionElement + public struct AddressDetails: Equatable { + @_spi(STP) public static let empty = AddressDetails() + public var name: String? + public var phone: String? + public var address: Address + + /// Initializes an Address + public init(name: String? = nil, phone: String? = nil, address: Address = .init()) { + self.name = name + self.phone = phone + self.address = address + } + + public struct Address: Equatable { + /// City, district, suburb, town, or village. + public var city: String? + + /// Two-letter country code (ISO 3166-1 alpha-2). + public var country: String? + + /// Address line 1 (e.g., street, PO Box, or company name). + public var line1: String? + + /// Address line 2 (e.g., apartment, suite, unit, or building). + public var line2: String? + + /// ZIP or postal code. + public var postalCode: String? + + /// State, county, province, or region. + public var state: String? + + /// Initializes an Address + public init(city: String? = nil, country: String? = nil, line1: String? = nil, line2: String? = nil, postalCode: String? = nil, state: String? = nil) { + self.city = city + self.country = country + self.line1 = line1 + self.line2 = line2 + self.postalCode = postalCode + self.state = state + } + } + } + + /// Describes which address fields to collect + public enum CollectionMode: Equatable { + /// The default collection mode. + /// - Parameter autocompletableCountries: If non-empty, the line1 field displays an autocomplete accessory button if the current country is in this list. Set the `didTapAutocompleteButton` property to be notified when the button is tapped. + case all(autocompletableCountries: [String] = []) + /// Collects country and postal code if the country is one of `countriesRequiringPostalCollection` + /// - Note: Really only useful for cards, where we only collect postal for a handful of countries + case countryAndPostal(countriesRequiringPostalCollection: [String] = ["US", "GB", "CA"]) + /// Replaces the address line 1 field with `self.autoCompleteLine` + case autoCompletable + /// Special case used by some Payment Methods that collect country separately. + case noCountry + } + /// Fields that this section can collect in addition to the address + public struct AdditionalFields { + public init( + name: FieldConfiguration = .disabled, + phone: FieldConfiguration = .disabled, + billingSameAsShippingCheckbox: FieldConfiguration = .disabled + ) { + self.name = name + self.phone = phone + self.billingSameAsShippingCheckbox = billingSameAsShippingCheckbox + } + + public enum FieldConfiguration { + case disabled + case enabled(isOptional: Bool = false) + } + + public let name: FieldConfiguration + public let phone: FieldConfiguration + public let billingSameAsShippingCheckbox: FieldConfiguration + } + + // MARK: Element protocol + public let elements: [Element] + public weak var delegate: ElementDelegate? + public lazy var view: UIView = { + let vStack = UIStackView(arrangedSubviews: [addressSection.view, sameAsCheckbox.view].compactMap { $0 }) + vStack.axis = .vertical + vStack.spacing = 16 + return vStack + }() + + // MARK: Elements + let addressSection: SectionElement + public let name: TextFieldElement? + public let phone: PhoneNumberElement? + public let country: DropdownFieldElement + public private(set) var autoCompleteLine: DummyAddressLine? + public private(set) var line1: TextFieldElement? + public private(set) var line2: TextFieldElement? + public private(set) var city: TextFieldElement? + public private(set) var state: TextOrDropdownElement? + public private(set) var postalCode: TextFieldElement? + public let sameAsCheckbox: CheckboxElement + + // MARK: Other properties + public var collectionMode: CollectionMode { + didSet { + if oldValue != collectionMode { + updateAddressFields(for: countryCodes[country.selectedIndex], address: nil) + } + } + } + public var selectedCountryCode: String { + get { + return countryCodes[country.selectedIndex] + } + set { + guard let index = countryCodes.firstIndex(of: newValue) else { return } + country.selectedIndex = index + updateAddressFields( + for: countryCodes[index] + ) + } + } + var addressDetails: AddressDetails { + let address = AddressDetails.Address(city: city?.text, country: selectedCountryCode, line1: line1?.text, line2: line2?.text, postalCode: postalCode?.text, state: state?.rawData) + return .init(name: name?.text, phone: phone?.phoneNumber?.string(as: .e164), address: address) + } + + public let countryCodes: [String] + let addressSpecProvider: AddressSpecProvider + let theme: ElementsAppearance + private(set) var defaults: AddressDetails + let didTapAutocompleteButton: () -> Void + public var didUpdate: DidUpdateAddress? + + // MARK: - Implementation + /** + Creates an address section with a country dropdown populated from the given list of countryCodes. + + - Parameters: + - title: The title for this section + - countries: List of region codes to display in the country picker dropdown. If nil, the list of countries from `addressSpecProvider` is used instead. + - locale: Locale used to generate the display names for each country + - addressSpecProvider: Determines the list of address fields to display for a selected country + - defaults: Default address to prepopulate address fields with + */ + public init( + title: String? = nil, + countries: [String]? = nil, + locale: Locale = .current, + addressSpecProvider: AddressSpecProvider = .shared, + defaults: AddressDetails = .empty, + collectionMode: CollectionMode = .all(), + additionalFields: AdditionalFields = .init(), + theme: ElementsAppearance = .default, + presentAutoComplete: @escaping () -> Void = { } + ) { + let dropdownCountries = countries?.map { $0.uppercased() } ?? addressSpecProvider.countries + let countryCodes = locale.sortedByTheirLocalizedNames(dropdownCountries) + self.collectionMode = collectionMode + self.countryCodes = countryCodes + self.country = DropdownFieldElement.Address.makeCountry( + label: String.Localized.country_or_region, + countryCodes: countryCodes, + theme: theme, + defaultCountry: defaults.address.country, + locale: locale + ) + self.defaults = defaults + self.addressSpecProvider = addressSpecProvider + self.theme = theme + self.didTapAutocompleteButton = presentAutoComplete + + let initialCountry = countryCodes[country.selectedIndex] + + // Initialize additional fields + self.name = { + if case .enabled(let isOptional) = additionalFields.name { + return TextFieldElement.NameConfiguration(defaultValue: defaults.name, + isOptional: isOptional).makeElement(theme: theme) + } else { + return nil + } + }() + self.phone = { + if case .enabled(let isOptional) = additionalFields.phone { + return PhoneNumberElement( + allowedCountryCodes: countryCodes, + defaultCountryCode: initialCountry, + defaultPhoneNumber: defaults.phone, + isOptional: isOptional, + locale: locale, + theme: theme + ) + } else { + return nil + } + }() + self.sameAsCheckbox = CheckboxElement(theme: theme, label: String.Localized.billing_same_as_shipping, isSelectedByDefault: true) + if case .enabled = additionalFields.billingSameAsShippingCheckbox, let defaultCountry = defaults.address.country, countryCodes.contains(defaultCountry) { + // Country must exist in the dropdown, otherwise this address can't be same as shipping + sameAsCheckbox.view.isHidden = false + } else { + sameAsCheckbox.view.isHidden = true + } + addressSection = SectionElement(title: title, elements: [], theme: theme) + elements = ([addressSection, sameAsCheckbox] as [Element?]).compactMap { $0 } + elements.forEach { $0.delegate = self } + + self.updateAddressFields( + for: initialCountry, + address: defaults.address + ) + country.didUpdate = { [weak self] index in + guard let self = self else { return } + self.updateAddressFields( + for: self.countryCodes[index] + ) + } + sameAsCheckbox.didToggle = { [weak self] isToggled in + guard let self = self else { return } + if isToggled { + // Set the country to the default country + self.country.selectedIndex = self.country.items.firstIndex { + $0.rawData == self.defaults.address.country ?? "" + } ?? self.country.selectedIndex + // Populate our fields with the provided defaults + self.updateAddressFields(for: self.defaults.address.country ?? self.country.selectedItem.rawData, address: self.defaults.address) + } else { + // Clear the fields + self.updateAddressFields(for: self.country.selectedItem.rawData, address: .init()) + } + } + } + + /// Updates the "Billing same as shipping" checkbox and the default address used. + /// - Note: This is a very specific method to handle the case where the merchant-provided default shipping address is updated after the AddressSectionElement is rendered + public func updateBillingSameAsShippingDefaultAddress(_ defaultAddress: AddressDetails.Address) { + // First, update the default address we use + self.defaults.address = defaultAddress + + // Next, show/hide the checkbox if address is valid/invalid + sameAsCheckbox.view.isHidden = defaultAddress == .init() || !countryCodes.contains(defaultAddress.country ?? "country doesnt exist") + guard !sameAsCheckbox.view.isHidden else { + // We're done if the checkbox is hidden + return + } + + // Finally... + if sameAsCheckbox.isSelected { + // ...update the fields with the default values if billing checkbox is shown and checked + self.country.selectedIndex = self.country.items.firstIndex { + $0.rawData == defaults.address.country ?? "" + } ?? self.country.selectedIndex + updateAddressFields(for: defaults.address.country ?? self.country.selectedItem.rawData, address: defaults.address) + } else { + // ...or select the checkbox if the address matches + sameAsCheckbox.isSelected = displayedAddressEqualTo(address: defaultAddress) + } + } + + /// - Parameter address: Populates the new fields with the provided defaults, or the current fields' text if `nil`. + private func updateAddressFields( + for countryCode: String, + address: AddressDetails.Address? = nil + ) { + // Create the new address fields' default text + let address = address ?? AddressDetails.Address( + city: city?.text, + country: nil, + line1: line1?.text, + line2: line2?.text, + postalCode: postalCode?.text, + state: state?.rawData + ) + + // Get the address spec for the country and filter out unused fields + let spec = addressSpecProvider.addressSpec(for: countryCode) + let fieldOrdering = spec.fieldOrdering.filter { + switch collectionMode { + case .all, .noCountry: + return true + case .countryAndPostal(let countriesRequiringPostalCollection): + if case .postal = $0 { + return countriesRequiringPostalCollection.contains(countryCode) + } else { + return false + } + case .autoCompletable: + return false + } + } + + if collectionMode == .autoCompletable { + autoCompleteLine = autoCompleteLine ?? DummyAddressLine(theme: theme, didTap: didTapAutocompleteButton) + } else { + autoCompleteLine = nil + } + // Re-create the address fields + if fieldOrdering.contains(.line) { + if case .all(let autocompletableCountries) = collectionMode, autocompletableCountries.caseInsensitiveContains(countryCode) { + line1 = TextFieldElement.Address.LineConfiguration( + lineType: .line1Autocompletable(didTapAutocomplete: didTapAutocompleteButton), + defaultValue: address.line1 + ).makeElement(theme: theme) + } else { + line1 = TextFieldElement.Address.makeLine1(defaultValue: address.line1, theme: theme) + } + } + line2 = fieldOrdering.contains(.line) ? + TextFieldElement.Address.makeLine2(defaultValue: address.line2, theme: theme) : nil + city = fieldOrdering.contains(.city) ? + spec.makeCityElement(defaultValue: address.city, theme: theme) : nil + state = fieldOrdering.contains(.state) ? + spec.makeStateElement(defaultValue: address.state, + stateDict: Dictionary(uniqueKeysWithValues: zip(spec.subKeys ?? [], spec.subLabels ?? [])), + theme: theme) : nil + postalCode = fieldOrdering.contains(.postal) ? + spec.makePostalElement(countryCode: countryCode, defaultValue: address.postalCode, theme: theme) : nil + + // Order the address fields according to `fieldOrdering` + let addressFields: [Element?] = fieldOrdering.reduce([]) { partialResult, fieldType in + // This should be a flatMap but I'm having trouble satisfying the compiler + switch fieldType { + case .line: + return partialResult + [line1, line2] + case .city: + return partialResult + [city] + case .state: + return partialResult + [state] + case .postal: + return partialResult + [postalCode] + } + } + + var initialElements: [Element?] = [name] + if collectionMode != .noCountry { + initialElements.append(country) + } + initialElements.append(autoCompleteLine) + let phoneElement: [Element?] = [phone] + addressSection.elements = (initialElements + addressFields + phoneElement).compactMap { $0 } + } + + /// Returns `true` iff all **displayed** address fields match the given `address`, treating `nil` and "" as equal. + func displayedAddressEqualTo(address: AddressDetails.Address) -> Bool { + var allDisplayedFieldsEqual = true + if let city = city, city.text.nonEmpty != address.city?.nonEmpty { + allDisplayedFieldsEqual = false + } + if country.selectedItem.rawData != address.country?.nonEmpty { + allDisplayedFieldsEqual = false + } + if let line1 = line1, line1.text.nonEmpty != address.line1?.nonEmpty { + allDisplayedFieldsEqual = false + } + if let line2 = line2, line2.text.nonEmpty != address.line2?.nonEmpty { + allDisplayedFieldsEqual = false + } + if let postalCode = postalCode, postalCode.text.nonEmpty != address.postalCode?.nonEmpty { + allDisplayedFieldsEqual = false + } + if let state = state, state.rawData.nonEmpty != address.state?.nonEmpty { + allDisplayedFieldsEqual = false + } + return allDisplayedFieldsEqual + } +} + +// MARK: - Element +extension AddressSectionElement: Element { + @discardableResult + public func beginEditing() -> Bool { + let firstInvalidNonDropDownElement = firstInvalidNonDropdownElement(elements: elements) + + // If first non-dropdown element is auto complete, don't do anything + if firstInvalidNonDropDownElement === autoCompleteLine { + return false + } + + return firstInvalidNonDropDownElement?.beginEditing() ?? false + } + + private func firstInvalidNonDropdownElement(elements: [Element]) -> Element? { + for element in elements { + if let sectionElement = element as? SectionElement, + let firstInvalid = firstInvalidNonDropdownElement(elements: sectionElement.elements) { + return firstInvalid + } + switch element.validationState { + case .valid: + continue + case .invalid: + if !(element is DropdownFieldElement) { + return element + } + } + } + return nil + } +} + +// MARK: - ElementDelegate +extension AddressSectionElement: ElementDelegate { + public func didUpdate(element: Element) { + if !sameAsCheckbox.view.isHidden, sameAsCheckbox.isSelected, !displayedAddressEqualTo(address: defaults.address) { + // Deselect checkbox if the address != the shipping address (our `defaults`) + sameAsCheckbox.isSelected = false + } + delegate?.didUpdate(element: self) + didUpdate?(addressDetails) + + // Update the selected country in the phone element if the no defaults have been provided + // and the phone number element hasn't been modified + // to match the country picker if they don't match + if let phone = phone, + defaults.phone == nil, + !phone.hasBeenModified + && phone.countryDropdownElement.selectedIndex != country.selectedIndex { + phone.selectCountry(index: country.selectedIndex, shouldUpdateDefaultNumber: true) + } + } +} + +@_spi(STP) public extension AddressSectionElement.AddressDetails { + init(billingAddress: BillingAddress, phone: String?) { + self.init( + name: billingAddress.name, + phone: phone, + address: Address( + city: billingAddress.city, + country: billingAddress.countryCode, + line1: billingAddress.line1, + line2: billingAddress.line2, + postalCode: billingAddress.postalCode, + state: billingAddress.state + ) + ) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec+ElementFactory.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec+ElementFactory.swift new file mode 100644 index 00000000..6a726979 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec+ElementFactory.swift @@ -0,0 +1,55 @@ +// +// AddressSpec+ElementFactory.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/9/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Convenience methods to create address fields that are localized according to the AddressSpec +extension AddressSpec { + func makeCityElement(defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement.Address.CityConfiguration( + label: cityNameType.localizedLabel, + defaultValue: defaultValue, + isOptional: !requiredFields.contains(.city) + ).makeElement(theme: theme) + } + + func makeStateElement(defaultValue: String?, stateDict: [String: String], theme: ElementsAppearance = .default) -> TextOrDropdownElement { + // If no state dict just use a textfield for state + if stateDict.isEmpty { + return TextFieldElement.Address.StateConfiguration( + label: stateNameType.localizedLabel, + defaultValue: defaultValue, + isOptional: !requiredFields.contains(.state) + ).makeElement(theme: theme) + } + + // Otherwise create a dropdown with the provided states + let items = stateDict.map({DropdownFieldElement.DropdownItem(pickerDisplayName: $0.value, + labelDisplayName: $0.value, + accessibilityValue: $0.value, + rawData: $0.key)}).sorted { $0.pickerDisplayName.string < $1.pickerDisplayName.string } + + let defaultIndex = items.firstIndex(where: {$0.rawData.lowercased() == defaultValue?.lowercased() + || $0.pickerDisplayName.string.lowercased() == defaultValue?.lowercased()}) ?? 0 + + return DropdownFieldElement(items: items, + defaultIndex: defaultIndex, + label: stateNameType.localizedLabel, + theme: theme, + didUpdate: nil) + } + + func makePostalElement(countryCode: String, defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement.Address.PostalCodeConfiguration( + countryCode: countryCode, + label: zipNameType.localizedLabel, + defaultValue: defaultValue, + isOptional: !requiredFields.contains(.postal) + ).makeElement(theme: theme) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec.swift new file mode 100644 index 00000000..b1a68aca --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec.swift @@ -0,0 +1,147 @@ +// +// AddressSpec.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 7/19/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +/** + This represents the format of each country's dictionary in `localized_address_data.json` + */ +struct AddressSpec: Decodable { + enum StateNameType: String, Codable { + case area, county, department, do_si, emirate, island, oblast, parish, prefecture, state, province + var localizedLabel: String { + switch self { + case .area: return String.Localized.area + case .county: return String.Localized.county + case .department: return String.Localized.department + case .do_si: return String.Localized.do_si + case .emirate: return String.Localized.emirate + case .island: return String.Localized.island + case .oblast: return String.Localized.oblast + case .parish: return String.Localized.parish + case .prefecture: return String.Localized.prefecture + case .state: return String.Localized.state + case .province: return String.Localized.province + } + } + + init(from decoder: Decoder) throws { + let state_name_type = try decoder.singleValueContainer().decode(String.self) + self = StateNameType(rawValue: state_name_type) ?? .prefecture + } + } + enum ZipNameType: String, Codable { + case eircode, pin, zip, postal_code + var localizedLabel: String { + switch self { + case .eircode: return String.Localized.eircode + case .pin: return String.Localized.postal_pin + case .zip: return String.Localized.zip + case .postal_code: return String.Localized.postal_code + } + } + + init(from decoder: Decoder) throws { + let zip_name_type = try decoder.singleValueContainer().decode(String.self) + self = ZipNameType(rawValue: zip_name_type) ?? .postal_code + } + } + enum LocalityNameType: String, Codable { + case district, suburb, post_town, suburb_or_city, city + var localizedLabel: String { + switch self { + case .district: return String.Localized.district + case .suburb: return String.Localized.suburb + case .post_town: return String.Localized.post_town + case .suburb_or_city: return String.Localized.suburb_or_city + case .city: return String.Localized.city + } + } + init(from decoder: Decoder) throws { + let locality_name_type = try decoder.singleValueContainer().decode(String.self) + self = LocalityNameType(rawValue: locality_name_type) ?? .suburb_or_city + } + } + /// An enum of the fields that `AddressSpec` describes. + enum FieldType: String { + /// Address lines 1 and 2 + case line = "A" + case city = "C" + case state = "S" + case postal = "Z" + } + + /// The order to display the fields. + let fieldOrdering: [FieldType] + let requiredFields: [FieldType] + let cityNameType: LocalityNameType + let stateNameType: StateNameType + let zip: String? + let zipNameType: ZipNameType + let subKeys: [String]? // e.g. state abbreviations - "CA" + let subLabels: [String]? // e.g. state display names - "California" + + enum CodingKeys: String, CodingKey { + case format = "fmt" + case require = "require" + case localityNameType = "locality_name_type" // e.g. City + case stateNameType = "state_name_type" + case zip = "zip" + case zipNameType = "zip_name_type" + case subKeys = "sub_keys" + case subLabels = "sub_labels" + } + + static var `default`: AddressSpec { + return AddressSpec() + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.init( + format: try? container.decode(String.self, forKey: .format), + require: try? container.decode(String.self, forKey: .require), + cityNameType: try? container.decode(LocalityNameType.self, forKey: .localityNameType), + stateNameType: try? container.decode(StateNameType.self, forKey: .stateNameType), + zip: try? container.decode(String.self, forKey: .zip), + zipNameType: try? container.decode(ZipNameType.self, forKey: .zipNameType), + subKeys: try? container.decode([String].self, forKey: .subKeys), + subLabels: try? container.decode([String].self, forKey: .subLabels) + ) + } + + init( + format: String? = nil, + require: String? = nil, + cityNameType: LocalityNameType? = nil, + stateNameType: StateNameType? = nil, + zip: String? = nil, + zipNameType: ZipNameType? = nil, + subKeys: [String]? = nil, + subLabels: [String]? = nil + ) { + var fieldOrdering: [FieldType] = (format ?? "NACSZ").compactMap { + FieldType(rawValue: String($0)) + } + // We always collect line1 and line2 ("A"), so prepend if it's missing + if !fieldOrdering.contains(FieldType.line) { + fieldOrdering = [.line] + fieldOrdering + } + self.fieldOrdering = fieldOrdering + self.requiredFields = (require ?? "ACSZ").compactMap { + FieldType(rawValue: String($0)) + } + self.cityNameType = cityNameType ?? .city + self.stateNameType = stateNameType ?? .province + self.zip = zip + self.zipNameType = zipNameType ?? .postal_code + self.subKeys = subKeys + self.subLabels = subLabels + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpecProvider.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpecProvider.swift new file mode 100644 index 00000000..f0f3db3b --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpecProvider.swift @@ -0,0 +1,65 @@ +// +// AddressSpecProvider.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 7/19/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +// This file was adapted from https://git.corp.stripe.com/stripe-internal/stripe-js-v3/blob/bdc2eeed/src/elements/inner/shared/address/addressData.ts +let addressDataFilename = "localized_address_data" + +@_spi(STP) public class AddressSpecProvider { + enum Error: Swift.Error { + case loadSpecsFailure + } + + @_spi(STP) public static var shared: AddressSpecProvider = AddressSpecProvider() + var addressSpecs: [String: AddressSpec] = [:] + public var countries: [String] { + return addressSpecs.map { $0.key } + } + private let addressSpecsUpdateQueue: DispatchQueue = DispatchQueue(label: addressDataFilename, qos: .userInitiated) + + /// Loads address specs with a completion block + public func loadAddressSpecs(completion: (() -> Void)? = nil) { + addressSpecsUpdateQueue.async { + let bundle = StripeUICoreBundleLocator.resourcesBundle + // Early exit if we have already loaded the specs + guard self.addressSpecs.isEmpty else { + completion?() + return + } + + guard let url = bundle.url(forResource: addressDataFilename, withExtension: ".json") else { + let errorAnalytic = ErrorAnalytic(event: .unexpectedStripeUICoreAddressSpecProvider, + error: Error.loadSpecsFailure) + STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) + completion?() + return + } + + do { + let data = try Data(contentsOf: url) + let addressSpecs = try JSONDecoder().decode([String: AddressSpec].self, from: data) + self.addressSpecs = addressSpecs + completion?() + } catch { + let errorAnalytic = ErrorAnalytic(event: .unexpectedStripeUICoreAddressSpecProvider, + error: error) + STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) + completion?() + } + } + } + + func addressSpec(for country: String) -> AddressSpec { + guard let spec = addressSpecs[country] else { + return AddressSpec.default + } + return spec + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/BSB/BSBNumberProvider.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/BSB/BSBNumberProvider.swift new file mode 100644 index 00000000..bbd9edd5 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/BSB/BSBNumberProvider.swift @@ -0,0 +1,70 @@ +// +// BSBNumberProvider.swift +// StripeUICore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public class BSBNumberProvider { + enum Error: Swift.Error { + case bsbLoadFailure + } + + private let bsbDataFilename = "au_becs_bsb" + + @_spi(STP) public static var shared: BSBNumberProvider = BSBNumberProvider() + var bsbNumberToNameMapping: [String: String] = [:] + private let bsbNumberUpdateQueue = DispatchQueue(label: "com.stripe.BSB.BSBNumberProvider", qos: .userInitiated) + + public func loadBSBData(completion: (() -> Void)? = nil) { + bsbNumberUpdateQueue.async { + // Early exit if we have already loaded the BSBNumber mapping + guard self.bsbNumberToNameMapping.isEmpty else { + completion?() + return + } + + let bundle = StripeUICoreBundleLocator.resourcesBundle + guard let url = bundle.url(forResource: self.bsbDataFilename, withExtension: ".json") else { + let errorAnalytic = ErrorAnalytic(event: .unexpectedStripeUICoreBSBNumberProvider, + error: Error.bsbLoadFailure) + STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) + completion?() + return + } + + do { + let data = try Data(contentsOf: url) + let decodedBSBs = try JSONDecoder().decode([String: String].self, from: data) + #if DEBUG + var accumulator: [String: String] = ["00": "Stripe Test Bank"] + decodedBSBs.forEach { (key, value) in + accumulator[key] = value + } + self.bsbNumberToNameMapping = accumulator + #else + self.bsbNumberToNameMapping = decodedBSBs + #endif + completion?() + } catch { + let errorAnalytic = ErrorAnalytic(event: .unexpectedStripeUICoreBSBNumberProvider, + error: error) + STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) + completion?() + } + } + } + + func bsbName(for bsbNumber: String) -> String { + for i in (2...3).reversed() { + let bsbPrefix = String(bsbNumber.prefix(i)) + if let resolvedBSBName = bsbNumberToNameMapping[bsbPrefix] { + return resolvedBSBName + } + } + return "" + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/DropdownFieldElement+AddressFactory.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/DropdownFieldElement+AddressFactory.swift new file mode 100644 index 00000000..ddc94d7c --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/DropdownFieldElement+AddressFactory.swift @@ -0,0 +1,54 @@ +// +// DropdownFieldElement+AddressFactory.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/28/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public extension DropdownFieldElement { + // MARK: - Address + + enum Address { + + // MARK: - Country + + public static func makeCountry( + label: String, + countryCodes: [String], + theme: ElementsAppearance = .default, + defaultCountry: String? = nil, + locale: Locale = Locale.current, + disableDropdownWithSingleCountry: Bool = false + ) -> DropdownFieldElement { + let dropdownItems: [DropdownItem] = countryCodes.map { + let flagEmoji = String.countryFlagEmoji(for: $0) ?? "" // 🇺🇸 + let countryName = locale.localizedString(forRegionCode: $0) ?? $0 // United States + #if targetEnvironment(macCatalyst) || canImport(CompositorServices) + // When using UIMenu with a keyboard, type-ahead search is based on the string name. + // This doesn't work if we prepend an emoji, so leave that out on macOS. + let pickerDisplayName = countryName + #else + let pickerDisplayName = "\(flagEmoji) \(countryName)" + #endif + return DropdownItem(pickerDisplayName: pickerDisplayName, + labelDisplayName: countryName, + accessibilityValue: countryName, + rawData: $0) + } + let defaultCountry = defaultCountry ?? locale.stp_regionCode ?? "" + let defaultCountryIndex = countryCodes.firstIndex(of: defaultCountry) ?? 0 + + return DropdownFieldElement( + items: dropdownItems, + defaultIndex: defaultCountryIndex, + label: String.Localized.country_or_region, + theme: theme, + disableDropdownWithSingleElement: disableDropdownWithSingleCountry + ) + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/IDNumberTextFieldConfiguration.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/IDNumberTextFieldConfiguration.swift new file mode 100644 index 00000000..7cb6b2a2 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/IDNumberTextFieldConfiguration.swift @@ -0,0 +1,144 @@ +// +// IDNumberTextFieldConfiguration.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/27/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public struct IDNumberTextFieldConfiguration: TextFieldElementConfiguration { + + // TODO(mludowise|IDPROD-2596): Check if these are the formats needed to support IDV. + // When finalized, change enum to String type so we can configure allowed formats from our backend response. + public enum IDNumberType { + case BR_CPF + case BR_CPF_CNPJ + case SG_NRIC_OR_FIN + case US_SSN_LAST4 + } + + let type: IDNumberType? + public let label: String + public let defaultValue: String? + + /** + - Parameters: + - type: The type of ID number that should be validated in this input field. If the ID type is unknown, passing `nil` will produce a configuration with no restrictions on the input. + - label: The label of the field + */ + public init(type: IDNumberType?, label: String, defaultValue: String?) { + self.type = type + self.label = label + self.defaultValue = defaultValue + } + + public var disallowedCharacters: CharacterSet { + switch type { + case .BR_CPF, + .BR_CPF_CNPJ, + .US_SSN_LAST4: + return CharacterSet.stp_asciiDigit.inverted + case .SG_NRIC_OR_FIN, + .none: + return .newlines + } + } + + public func maxLength(for text: String) -> Int { + switch type { + case .BR_CPF: + return 11 + case .BR_CPF_CNPJ: + return 14 + case .US_SSN_LAST4: + return 4 + case .SG_NRIC_OR_FIN, + .none: + return .max + } + } + + /** + - Parameters: + - text: The text that will be formatted with this formatter + + - Returns: The format consisting of a string using pound signs `#` for numeric placeholders, and `*` for letters. + */ + func format(text: String) -> String? { + switch type { + case .BR_CPF, + .BR_CPF_CNPJ where text.count <= 11: + return "###.###.###-##" + case .BR_CPF_CNPJ: + return "##.###.###/####-##" + case .US_SSN_LAST4: + return "••• - •• - ####" + case .none: + return nil + case .some(.SG_NRIC_OR_FIN): + return nil + } + } + + public func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + guard !text.isEmpty else { + return isOptional ? .valid : .invalid(TextFieldElement.Error.empty) + } + + switch type { + // CPF is 11 digits but CNPJ is 14 (maxLength), so we will allow 11 here + case .BR_CPF_CNPJ where text.count == 11, + .none: + return .valid + case .SG_NRIC_OR_FIN: + return .valid + default: + return maxLength(for: text) == text.count + ? .valid + : .invalid( + TextFieldElement.Error.incomplete( + localizedDescription: STPLocalizedString( + "The ID number you entered is incomplete.", + "An error message." + ) + ) + ) + } + } + + public func makeDisplayText(for text: String) -> NSAttributedString { + guard let format = format(text: text), + let formatter = TextFieldFormatter(format: format) + else { + return NSAttributedString(string: text) + } + + let formattedString = formatter.applyFormat( + to: text + ) + + return NSAttributedString(string: formattedString) + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + switch type { + case .BR_CPF, + .BR_CPF_CNPJ, + .US_SSN_LAST4: + return .init( + type: .asciiCapableNumberPad, + textContentType: nil, + autocapitalization: .none + ) + default: + return .init( + type: .default, + textContentType: nil, + autocapitalization: .allCharacters + ) + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+AccountFactory.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+AccountFactory.swift new file mode 100644 index 00000000..258d2bf3 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+AccountFactory.swift @@ -0,0 +1,146 @@ +// +// TextFieldElement+AccountFactory.swift +// StripeUICore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public extension TextFieldElement { + + enum Account { + // MARK: - BSB Number + struct BSBConfiguration: TextFieldElementConfiguration { + static let incompleteError = Error.incomplete(localizedDescription: String.Localized.incompleteBSBEntered) + + let label = STPLocalizedString("BSB number", "Placeholder for AU BECS BSB number") + let disallowedCharacters: CharacterSet = .stp_invertedAsciiDigit + func maxLength(for text: String) -> Int { + return 6 + } + let defaultValue: String? + func subLabel(text: String) -> String? { + return BSBNumberProvider.shared.bsbName(for: text) + } + + public func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + + let bsbNumber = BSBNumber(number: text) + return bsbNumber.isComplete ? .valid : + .invalid(Account.BSBConfiguration.incompleteError) + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .numberPad, textContentType: .none, autocapitalization: .none) + } + + public func makeDisplayText(for text: String) -> NSAttributedString { + let bsbNumber = BSBNumber(number: text) + return NSAttributedString(string: bsbNumber.formattedNumber()) + } + } + + public static func makeBSB(defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: BSBConfiguration(defaultValue: defaultValue), theme: theme) + } + + // MARK: - AUBECS Account Number + struct AUBECSAccountNumberConfiguration: TextFieldElementConfiguration { + static let incompleteError = Error.incomplete(localizedDescription: + String.Localized.incompleteAccountNumber) + let label = String.Localized.accountNumber + let disallowedCharacters: CharacterSet = .stp_invertedAsciiDigit + let minimumNumberOfDigits = 4 + let maximumNumberofDigits = 9 + + func maxLength(for text: String) -> Int { + return maximumNumberofDigits + } + let defaultValue: String? + + public func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + return text.count >= minimumNumberOfDigits && text.count <= maximumNumberofDigits ? .valid : .invalid(AUBECSAccountNumberConfiguration.incompleteError) + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .numberPad, textContentType: .none, autocapitalization: .none) + } + } + + public static func makeAUBECSAccountNumber(defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: AUBECSAccountNumberConfiguration(defaultValue: defaultValue), theme: theme) + } + + // MARK: - Bacs Sort Code + struct SortCodeConfiguration: TextFieldElementConfiguration { + static let invalidError = Error.incomplete(localizedDescription: String.Localized.invalidSortCodeEntered) + + let label = STPLocalizedString("Sort code", "Placeholder for Bacs sort code (a bank routing number used in the UK and Ireland)") + let disallowedCharacters: CharacterSet = .stp_invertedAsciiDigit + func maxLength(for text: String) -> Int { + return 6 + } + let defaultValue: String? + + public func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + + let sortCode = SortCode(number: text) + return sortCode.isComplete ? .valid : + .invalid(Account.SortCodeConfiguration.invalidError) + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .numberPad, textContentType: .none, autocapitalization: .none) + } + + public func makeDisplayText(for text: String) -> NSAttributedString { + let sortCode = SortCode(number: text) + return NSAttributedString(string: sortCode.formattedNumber()) + } + } + + public static func makeSortCode(defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: SortCodeConfiguration(defaultValue: defaultValue), theme: theme) + } + + // MARK: - Bacs Account Number + struct BacsAccountNumberConfiguration: TextFieldElementConfiguration { + static let incompleteError = Error.incomplete(localizedDescription: String.Localized.incompleteAccountNumber) + let label = String.Localized.accountNumber + let disallowedCharacters: CharacterSet = .stp_invertedAsciiDigit + let numberOfDigitsRequired = 8 + + func maxLength(for text: String) -> Int { + return numberOfDigitsRequired + } + let defaultValue: String? + + public func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + return text.count == numberOfDigitsRequired ? .valid : .invalid(BacsAccountNumberConfiguration.incompleteError) + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .numberPad, textContentType: .none, autocapitalization: .none) + } + } + + public static func makeBacsAccountNumber(defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: BacsAccountNumberConfiguration(defaultValue: defaultValue), theme: theme) + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+AddressFactory.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+AddressFactory.swift new file mode 100644 index 00000000..3e28e1a5 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+AddressFactory.swift @@ -0,0 +1,147 @@ +// +// TextFieldElement+AddressFactory.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public extension TextFieldElement { + enum Address { + + // MARK: - Line1, Line2 + + struct LineConfiguration: TextFieldElementConfiguration { + + enum LineType { + case line1 + case line2 + // Label is "Address" and shows a clear button + case autoComplete + // Same as .line1, but shows a 􀊫 autocomplete button accessory view + case line1Autocompletable(didTapAutocomplete: () -> Void) + } + let lineType: LineType + var label: String { + switch lineType { + case .line1, .line1Autocompletable: + return String.Localized.address_line1 + case .line2: + return String.Localized.address_line2 + case .autoComplete: + return String.Localized.address + } + } + let defaultValue: String? + var shouldShowClearButton: Bool { + if case .autoComplete = lineType { return true } + return false + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + switch lineType { + case .line1, .line1Autocompletable, .autoComplete: + return .init(type: .default, textContentType: .streetAddressLine1, autocapitalization: .words) + case .line2: + return .init(type: .default, textContentType: .streetAddressLine2, autocapitalization: .words) + } + } + + var isOptional: Bool { + if case .line2 = lineType { return true } // Hardcode all line2 as optional + return false + } + + func accessoryView(for text: String, theme: ElementsAppearance) -> UIView? { + if case .line1Autocompletable(let didTapAutocomplete) = lineType { + let autocompleteIconButton = UIButton.make(type: .system, didTap: didTapAutocomplete) + let configuration = UIImage.SymbolConfiguration(pointSize: CGFloat(10), weight: .bold) + let image = UIImage(systemName: "magnifyingglass", withConfiguration: configuration)? + .withTintColor(theme.colors.primary, renderingMode: .alwaysOriginal) + autocompleteIconButton.setImage(image, for: .normal) + autocompleteIconButton.accessibilityLabel = String.Localized.search + autocompleteIconButton.accessibilityIdentifier = "autocomplete_affordance" + return autocompleteIconButton + } + return nil + } + } + + public static func makeLine1(defaultValue: String?, theme: ElementsAppearance) -> TextFieldElement { + return TextFieldElement( + configuration: LineConfiguration(lineType: .line1, defaultValue: defaultValue), theme: theme + ) + } + + static func makeLine2(defaultValue: String?, theme: ElementsAppearance) -> TextFieldElement { + let line2 = TextFieldElement( + configuration: LineConfiguration(lineType: .line2, defaultValue: defaultValue), theme: theme + ) + return line2 + } + + public static func makeAutoCompleteLine(defaultValue: String?, theme: ElementsAppearance) -> TextFieldElement { + return TextFieldElement( + configuration: LineConfiguration(lineType: .autoComplete, defaultValue: defaultValue), theme: theme + ) + } + + // MARK: - City/Locality + + struct CityConfiguration: TextFieldElementConfiguration { + let label: String + let defaultValue: String? + let isOptional: Bool + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .default, textContentType: .addressCity, autocapitalization: .words) + } + } + + // MARK: - State/Province/Administrative area/etc. + + struct StateConfiguration: TextFieldElementConfiguration { + let label: String + let defaultValue: String? + let isOptional: Bool + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .default, textContentType: .addressState, autocapitalization: .words) + } + } + + // MARK: - Postal code/Zip code + + struct PostalCodeConfiguration: TextFieldElementConfiguration { + let countryCode: String + let label: String + let defaultValue: String? + let isOptional: Bool + public var disallowedCharacters: CharacterSet { + return countryCode == "US" ? .decimalDigits.inverted : .newlines + } + + func maxLength(for text: String) -> Int { + return countryCode == "US" ? 5 : .max + } + + func validate(text: String, isOptional: Bool) -> ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + if countryCode == "US", text.count < maxLength(for: text) { + return .invalid(Error.incomplete(localizedDescription: String.Localized.your_zip_is_incomplete)) + } + return .valid + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: countryCode == "US" ? .numberPad : .default, textContentType: .postalCode, autocapitalization: .allCharacters) + } + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift new file mode 100644 index 00000000..2628dada --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift @@ -0,0 +1,247 @@ +// +// TextFieldElement+Factory.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/17/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public extension TextFieldElement { + + // MARK: - Name + struct NameConfiguration: TextFieldElementConfiguration { + @frozen public enum NameType { + case given, family, full, onAccount + } + + let type: NameType + public let defaultValue: String? + public let label: String + public let isOptional: Bool + public let isEditable: Bool + private var textContentType: UITextContentType { + switch type { + case .given: + return .givenName + case .family: + return .familyName + case .full, .onAccount: + return .name + } + } + + /// - Parameter label: If `nil`, defaults to a string on the `type` e.g. "Name" + public init(type: NameType = .full, defaultValue: String?, label: String? = nil, isOptional: Bool = false, isEditable: Bool = true) { + self.type = type + self.defaultValue = defaultValue + if let label = label { + self.label = label + } else { + self.label = Self.label(for: type) + } + self.isOptional = isOptional + self.isEditable = isEditable + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .default, textContentType: textContentType, autocapitalization: .words) + } + + private static func label(for type: NameType) -> String { + switch type { + case .given: + return String.Localized.given_name + case .family: + return String.Localized.family_name + case .full: + return String.Localized.full_name + case .onAccount: + return String.Localized.nameOnAccount + } + } + } + + static func makeName(label: String? = nil, defaultValue: String?, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: NameConfiguration(type: .full, defaultValue: defaultValue, label: label), theme: theme) + } + + // MARK: - Email + + struct EmailConfiguration: TextFieldElementConfiguration { + public let label = String.Localized.email + public let defaultValue: String? + public let isOptional: Bool + public let isEditable: Bool + public let disallowedCharacters: CharacterSet = .whitespacesAndNewlines + let invalidError = Error.invalid( + localizedDescription: String.Localized.invalid_email + ) + + public init(defaultValue: String? = nil, isOptional: Bool = false, isEditable: Bool = true) { + self.defaultValue = defaultValue + self.isOptional = isOptional + self.isEditable = isEditable + } + + public func validate(text: String, isOptional: Bool) -> ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + if STPEmailAddressValidator.stringIsValidEmailAddress(text) { + return .valid + } else { + return .invalid(invalidError) + } + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .emailAddress, textContentType: .emailAddress, autocapitalization: .none) + } + } + + static func makeEmail(defaultValue: String?, isOptional: Bool = false, theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: EmailConfiguration(defaultValue: defaultValue, + isOptional: isOptional), theme: theme) + } + + // MARK: VPA + + struct VPAConfiguration: TextFieldElementConfiguration { + public let label = String.Localized.upi_id + public let disallowedCharacters: CharacterSet = .whitespacesAndNewlines + let invalidError = Error.invalid( + localizedDescription: .Localized.invalid_upi_id + ) + + public func validate(text: String, isOptional: Bool) -> ValidationState { + guard !text.isEmpty else { + return isOptional ? .valid : .invalid(Error.empty) + } + + return STPVPANumberValidator.stringIsValidVPANumber(text) ? .valid : .invalid(invalidError) + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .emailAddress, textContentType: .emailAddress, autocapitalization: .none) + } + + } + + static func makeVPA(theme: ElementsAppearance = .default) -> TextFieldElement { + return TextFieldElement(configuration: VPAConfiguration(), theme: theme) + } + + // MARK: - Blik code + struct BlikCodeConfiguration: TextFieldElementConfiguration { + public let label = String.Localized.blik_code + public let disallowedCharacters: CharacterSet = .decimalDigits.inverted + public let defaultValue: String? + let invalidError = Error.invalid( + localizedDescription: .Localized.invalid_blik_code + ) + + public func validate(text: String, isOptional: Bool) -> ValidationState { + guard !text.isEmpty else { + return isOptional ? .valid : .invalid(Error.empty) + } + return STPBlikCodeValidator.stringIsValidBlikCode(text) ?.valid: .invalid(invalidError) + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .numberPad, textContentType: .none, autocapitalization: .none) + } + + public func maxLength(for text: String) -> Int { + return 6 + } + } + + static func makeBlikCode(defaultValue: String?, theme: ElementsAppearance) -> TextFieldElement { + return TextFieldElement(configuration: BlikCodeConfiguration(defaultValue: defaultValue), theme: theme) + } + + // MARK: - Konbini confirmation/phone number + + /// An optional 10 to 11 digit numeric-only string determining the confirmation code at applicable convenience stores. This is typically a phone number, so we label it as such. + struct KonbiniPhoneNumberConfiguration: TextFieldElementConfiguration { + public let label = String.Localized.phoneNumber + public let disallowedCharacters: CharacterSet = .decimalDigits.inverted + public let isOptional: Bool = true + let incompleteError = Error.incomplete(localizedDescription: .Localized.incomplete_phone_number) + + public func validate(text: String, isOptional: Bool) -> ValidationState { + guard !text.isEmpty else { + return isOptional ? .valid : .invalid(Error.empty) + } + guard text.count > 9 else { + return .invalid(incompleteError) + } + return .valid + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .numberPad, textContentType: .telephoneNumber, autocapitalization: .none) + } + + public func maxLength(for text: String) -> Int { + return 11 + } + } + + static func makeKonbini(theme: ElementsAppearance) -> TextFieldElement { + return TextFieldElement(configuration: KonbiniPhoneNumberConfiguration(), theme: theme) + } + + // MARK: - Phone number + struct PhoneNumberConfiguration: TextFieldElementConfiguration { + static let incompleteError = Error.incomplete(localizedDescription: .Localized.incomplete_phone_number) + static let invalidError = Error.invalid(localizedDescription: .Localized.invalid_phone_number) + public let label: String = .Localized.phoneNumber + /// - Note: Country code helps us format the phone number + public let countryCodeProvider: () -> String + public let defaultValue: String? + public let isOptional: Bool + + public init(defaultValue: String? = nil, isOptional: Bool = false, countryCodeProvider: @escaping () -> String) { + self.countryCodeProvider = countryCodeProvider + self.defaultValue = defaultValue + self.isOptional = isOptional + } + + public func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + if text.isEmpty { + return isOptional ? .valid : .invalid(Error.empty) + } + + if let phoneNumber = PhoneNumber(number: text, countryCode: countryCodeProvider()) { + return phoneNumber.isComplete ? .valid : + .invalid(PhoneNumberConfiguration.incompleteError) + } else { + // Assume user has entered a format or for a region the SDK doesn't know about. + // Return valid as long as it's non-empty and let the server decide. + return .valid + } + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .phonePad, textContentType: .telephoneNumber, autocapitalization: .none) + } + + public var disallowedCharacters: CharacterSet { + return .stp_asciiDigit.inverted + } + + public func makeDisplayText(for text: String) -> NSAttributedString { + if let phoneNumber = PhoneNumber(number: text, countryCode: countryCodeProvider()) { + return NSAttributedString(string: phoneNumber.string(as: .national)) + } else { + return NSAttributedString(string: text) + } + } + } + +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Form/FormElement.swift b/StripeUICore/StripeUICore/Source/Elements/Form/FormElement.swift new file mode 100644 index 00000000..f2eb6237 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Form/FormElement.swift @@ -0,0 +1,84 @@ +// +// FormElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/7/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/** + The top-most, parent Element Element container. + Displays its views in a vertical stack. + Coordinates focus between its child Elements. + */ +@_spi(STP) public class FormElement: ContainerElement { + weak public var delegate: ElementDelegate? + lazy var formView: FormView = { + return FormView(viewModel: viewModel) + }() + + public let elements: [Element] + public let customSpacing: [(Element, CGFloat)] + public let style: Style + public let theme: ElementsAppearance + + // MARK: - Style + public enum Style { + /// Default element styling in stack view + case plain + /// Form draws borders around each Element + case bordered + } + + // MARK: - ViewModel + public struct ViewModel { + let elements: [UIView] + let bordered: Bool + let theme: ElementsAppearance + let customSpacing: [(UIView, CGFloat)] + public init(elements: [UIView], bordered: Bool, theme: ElementsAppearance = .default, customSpacing: [(UIView, CGFloat)] = []) { + self.elements = elements + self.bordered = bordered + self.theme = theme + self.customSpacing = customSpacing + } + } + + var viewModel: ViewModel { + return ViewModel(elements: elements.map({ $0.view }), bordered: style == .bordered, theme: theme, customSpacing: customSpacing.map({ ($0.0.view, $0.1) })) + } + + // MARK: - Initializer + + /// Initialize a FormElement. + /// - Parameters + /// - elements: The list of elements + /// - theme: The ElementsUITheme + /// - customSpacing: A list of Elements and a CGFloat of custom spacing to use after the element + public convenience init(elements: [Element?], theme: ElementsAppearance = .default, customSpacing: [(Element, CGFloat)] = []) { + self.init(elements: elements, style: .plain, theme: theme, customSpacing: customSpacing) + } + + public init(elements: [Element?], style: Style, theme: ElementsAppearance = .default, customSpacing: [(Element, CGFloat)] = []) { + self.elements = elements.compactMap { $0 } + self.style = style + self.theme = theme + self.customSpacing = customSpacing + self.elements.forEach { $0.delegate = self } + } + + public func toggleElements(_ elements: [Element], hidden: Bool, animated: Bool) { + formView.setViews(elements.map({ $0.view }), hidden: hidden, animated: animated) + } +} + +// MARK: - Element + +extension FormElement: Element { + public var view: UIView { + return formView + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Form/FormView.swift b/StripeUICore/StripeUICore/Source/Elements/Form/FormView.swift new file mode 100644 index 00000000..7faed6d4 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Form/FormView.swift @@ -0,0 +1,61 @@ +// +// FormView.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/7/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/** + A simple container view that displays its subviews in a vertical stack. + + For internal SDK use only + */ +@objc(STP_Internal_FormView) +@_spi(STP) public class FormView: UIView { + private let stackView: UIStackView + public init(viewModel: FormElement.ViewModel) { + if viewModel.bordered { + let stack = StackViewWithSeparator(arrangedSubviews: viewModel.elements) + self.stackView = stack + stack.drawBorder = true + stack.customBackgroundColor = viewModel.theme.colors.componentBackground + stack.separatorColor = viewModel.theme.colors.divider + stack.borderColor = viewModel.theme.colors.border + stack.borderCornerRadius = viewModel.theme.cornerRadius + stack.spacing = viewModel.theme.borderWidth + stack.hideShadow = true + stack.layer.applyShadow(shadow: viewModel.theme.shadow) + stack.axis = .vertical + } else { + let stack = UIStackView(arrangedSubviews: viewModel.elements) + self.stackView = stack + stack.axis = .vertical + stack.spacing = ElementsUI.formSpacing + } + for (view, spacing) in viewModel.customSpacing { + self.stackView.setCustomSpacing(spacing, after: view) + } + super.init(frame: .zero) + addAndPinSubview(self.stackView) + + // When the form is empty, set a height constraint of zero with the lowest possible priority. + // This provides a default height and avoids ambiguity in height constraints when there are no form elements present. + let zeroConstraint = self.stackView.heightAnchor.constraint(equalToConstant: 0) + zeroConstraint.priority = UILayoutPriority(rawValue: 1) // This sets the priority as low as possible, allowing other constraints to easily override it. + NSLayoutConstraint.activate([ + zeroConstraint + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setViews(_ views: [UIView], hidden: Bool, animated: Bool) { + stackView.toggleArrangedSubviews(views, shouldShow: !hidden, animated: animated) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/PhoneNumber/PhoneNumberElement.swift b/StripeUICore/StripeUICore/Source/Elements/PhoneNumber/PhoneNumberElement.swift new file mode 100644 index 00000000..d47be243 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/PhoneNumber/PhoneNumberElement.swift @@ -0,0 +1,190 @@ +// +// PhoneNumberElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/21/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +/** + A simple hstack of [🇺🇸 + 1] `DropdownElement` and [ Phone number ] `TextFieldElement` + */ +@_spi(STP) public class PhoneNumberElement: ContainerElement { + // MARK: - ContainerElement protocol + public lazy var elements: [Element] = { [countryDropdownElement, textFieldElement] }() + public var delegate: ElementDelegate? + public lazy var view: UIView = { + countryDropdownElement.view.directionalLayoutMargins.trailing = 0 + let hStackView = UIStackView(arrangedSubviews: elements.map { $0.view }) + if let infoView = infoView { + infoView.translatesAutoresizingMaskIntoConstraints = false + hStackView.addArrangedSubview(infoView) + // Add some extra padding to the right side + hStackView.isLayoutMarginsRelativeArrangement = true + hStackView.directionalLayoutMargins = .insets( + top: 0, + leading: 0, + bottom: 0, + trailing: ElementsUI.contentViewInsets.trailing + ) + } + return hStackView + }() + + // MARK: - sub-Elements + let countryDropdownElement: DropdownFieldElement + let textFieldElement: TextFieldElement + + var infoView: UIView? + + // MARK: - Public properties + public var phoneNumber: PhoneNumber? { + return PhoneNumber(number: textFieldElement.text, countryCode: countryDropdownElement.selectedItem.rawData) + } + + public var hasBeenModified: Bool { + return defaultPhoneNumber?.number != phoneNumber?.number || + defaultPhoneNumber?.countryCode != phoneNumber?.countryCode + } + + public var selectedCountryCode: String { + countryDropdownElement.selectedItem.rawData + } + + // MARK: - Private properties + private var defaultPhoneNumber: PhoneNumber? + + // MARK: - Initializer + /** + Creates an address section with a country dropdown populated from the given list of countryCodes. + + - Parameters: + - allowedCountryCodes: List of region codes to display in the country picker dropdown. If nil, defaults to ~all countries. + - defaultCountryCode: The country code that's initially selected in the dropdown. **This is ignored** if `defaultPhoneNumber` is in E.164 format in favor of the phone number's country code. + - defaultPhoneNumber:The initial value of the phone number text field. Note: If provided in E.164 format, the country prefix is removed. + - locale: Locale used to generate the display names for each country and as the default country if none is provided. + - theme: Theme used to stylize the phone number element + + - Note: The default parameters are not used as-is - we do extra logic! + */ + public init( + allowedCountryCodes: [String]? = nil, + defaultCountryCode: String? = nil, + defaultPhoneNumber: String? = nil, + isOptional: Bool = false, + infoView: UIView? = nil, + locale: Locale = .current, + theme: ElementsAppearance = .default + ) { + self.infoView = infoView + let defaults = Self.deriveDefaults(countryCode: defaultCountryCode, phoneNumber: defaultPhoneNumber) + let allowedCountryCodes = allowedCountryCodes ?? PhoneNumber.Metadata.allMetadata.map { $0.regionCode } + let countryDropdownElement = DropdownFieldElement.makeCountryCode( + countryCodes: allowedCountryCodes, + defaultCountry: defaults.countryCode, + locale: locale, + theme: theme + ) + self.countryDropdownElement = countryDropdownElement + self.textFieldElement = TextFieldElement.PhoneNumberConfiguration( + defaultValue: defaults.phoneNumber, + isOptional: isOptional, + countryCodeProvider: { + return countryDropdownElement.selectedItem.rawData + } + ).makeElement(theme: theme) + self.defaultPhoneNumber = phoneNumber + self.countryDropdownElement.delegate = self + self.textFieldElement.delegate = self + } + + public func setSelectedCountryCode(_ countryCode: String, shouldUpdateDefaultNumber: Bool = false) { + guard let index = countryDropdownElement.items.firstIndex(where: { $0.rawData == countryCode }) else { + return + } + selectCountry(index: index, shouldUpdateDefaultNumber: shouldUpdateDefaultNumber) + } + + public func clearPhoneNumber() { + textFieldElement.setText("") + } + + // MARK: - Element protocol + public let collectsUserInput: Bool = true + public func beginEditing() -> Bool { + return textFieldElement.beginEditing() + } + + // MARK: - ElementDelegate + public func didUpdate(element: Element) { + if element === textFieldElement && textFieldElement.didReceiveAutofill { + // Autofilled numbers may already include the country code, so check if that's the case. + // Note: We only validate against the currently selected country code, as an autofilled number _without_ a country code can trigger false positives, e.g. "2481234567" could be either "(248) 123-4567" (a phone number from Michigan, USA with no country code) or "+248 1 234 567" (a phone number from Seychelles with a country code). We can assume that generally, a user's autofilled phone number will match their phone's region setting. + // Autofilled numbers can include the + prefix indicating a country code, but we can't tell if they do here, as by the time we get here the input has already been sanitized and the "+" has been removed. + let countryCode = countryDropdownElement.selectedItem.rawData + if let prefix = PhoneNumber.Metadata.metadata(for: countryCode)?.prefix.dropFirst(), textFieldElement.text.hasPrefix(prefix) { + let unprefixedNumber = String(textFieldElement.text.dropFirst(prefix.count)) + // Double check that we actually have a valid phone number without the prefix. + if let phoneNumber = PhoneNumber(number: unprefixedNumber, countryCode: countryCode), phoneNumber.isComplete { + textFieldElement.setText(unprefixedNumber) + textFieldElement.endEditing() + // Setting the text directly triggers another update cycle, so short-circuit here to avoid double updating. + return + } + } + } + delegate?.didUpdate(element: self) + } + + // MARK: - Helpers + static func deriveDefaults(countryCode: String?, phoneNumber: String?) -> (countryCode: String?, phoneNumber: String?) { + // If the phone number is E164, derive defaults from that + if let phoneNumber = phoneNumber, let e164PhoneNumber = PhoneNumber.fromE164(phoneNumber) { + return (e164PhoneNumber.countryCode, e164PhoneNumber.number) + } else { + return (countryCode, phoneNumber) + } + } + + func selectCountry(index: Int, shouldUpdateDefaultNumber: Bool = false) { + countryDropdownElement.select(index: index) + + if shouldUpdateDefaultNumber { + self.defaultPhoneNumber = phoneNumber + } + } +} + +// MARK: - DropdownFieldElement helper +extension DropdownFieldElement { + static func makeCountryCode( + countryCodes: [String], + defaultCountry: String? = nil, + locale: Locale, + theme: ElementsAppearance + ) -> DropdownFieldElement { + let countryCodes = locale.sortedByTheirLocalizedNames(countryCodes) + let countryDisplayStrings: [DropdownFieldElement.DropdownItem] = countryCodes.map { + let flagEmoji = String.countryFlagEmoji(for: $0) ?? "" // 🇺🇸 + let name = locale.localizedString(forRegionCode: $0) ?? $0 // United States + let prefix = PhoneNumber.Metadata.metadata(for: $0)?.prefix ?? "" // +1 + return .init( + pickerDisplayName: "\(flagEmoji) \(name) \(prefix)", // 🇺🇸 United States +1 + labelDisplayName: "\(flagEmoji) \(prefix)", + accessibilityValue: "\(name) \(prefix)", + rawData: $0 + ) + } + let defaultCountry = defaultCountry ?? locale.stp_regionCode ?? "" + let defaultCountryIndex = countryCodes.firstIndex(of: defaultCountry) ?? 0 + return DropdownFieldElement( + items: countryDisplayStrings, + defaultIndex: defaultCountryIndex, + label: nil, + theme: theme + ) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/PickerField/PickerFieldView.swift b/StripeUICore/StripeUICore/Source/Elements/PickerField/PickerFieldView.swift new file mode 100644 index 00000000..9c2f0ef1 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/PickerField/PickerFieldView.swift @@ -0,0 +1,272 @@ +// +// PickerFieldView.swift +// StripeUICore +// +// Created by Mel Ludowise on 10/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +protocol PickerFieldViewDelegate: AnyObject { + func didBeginEditing(_ pickerFieldView: PickerFieldView) + func didFinish(_ pickerFieldView: PickerFieldView, shouldAutoAdvance: Bool) + func didCancel(_ pickerFieldView: PickerFieldView) +} + +/** + An input field that looks like TextFieldView but whose input is another view. + + - Note: This view has padding according to `directionalLayoutMargins`. + For internal SDK use only + */ +@objc(STP_Internal_PickerFieldView) +final class PickerFieldView: UIView { + + // MARK: - Views + private lazy var toolbar = DoneButtonToolbar(delegate: self, showCancelButton: true, theme: theme) + private lazy var textField: PickerTextField = { + let textField = PickerTextField() + // Input views are not supported on Catalyst (and are non-optimal on visionOS) +#if !targetEnvironment(macCatalyst) && !canImport(CompositorServices) + textField.inputView = pickerView +#endif + textField.adjustsFontForContentSizeCategory = true + textField.font = theme.fonts.subheadline +#if !canImport(CompositorServices) + textField.inputAccessoryView = toolbar +#endif + textField.delegate = self + return textField + }() + private lazy var floatingPlaceholderTextFieldView: FloatingPlaceholderTextFieldView? = { + guard let label = label else { + return nil + } + let floatingPlaceholderView = FloatingPlaceholderTextFieldView(textField: textField, theme: theme) + floatingPlaceholderView.placeholder = label + return floatingPlaceholderView + }() + private lazy var chevronImageView: UIImageView? = { + guard shouldShowChevron else { + return nil + } + let imageView = UIImageView(image: Image.icon_chevron_down.makeImage().withRenderingMode(.alwaysTemplate)) + imageView.setContentHuggingPriority(.required, for: .horizontal) + if isOptional { + imageView.image = imageView.image?.resized(to: 0.75)?.withRenderingMode(.alwaysTemplate) + } + imageView.tintColor = isOptional ? theme.colors.placeholderText : theme.colors.textFieldText + return imageView + }() + private lazy var hStackView: UIStackView = { + let hStackView = UIStackView( + arrangedSubviews: [floatingPlaceholderTextFieldView ?? textField, chevronImageView].compactMap { $0 } + ) + hStackView.alignment = .center + if isOptional { + hStackView.spacing = 3 + } else { + hStackView.spacing = 6 + } + return hStackView + }() + private let pickerView: UIView + + // MARK: - Other private properties + private let label: String? + private let shouldShowChevron: Bool + private weak var delegate: PickerFieldViewDelegate? + private let theme: ElementsAppearance + // When a PickerFieldView is optional it's chevron is smaller and takes the color of placeholder text + private let isOptional: Bool + private var _canBecomeFirstResponder = true + + // MARK: - Public properties + var displayText: NSAttributedString? { + get { + return textField.attributedText + } + set { + if newValue != textField.attributedPlaceholder { + invalidateIntrinsicContentSize() + } + textField.attributedText = newValue + // Unfortunate hack for card brand choice to show card brand logos + // UITextField doesn't render attributed text with text attachments for some reason + // But it works when setting it's placeholder text + // https://stackoverflow.com/questions/54804809/cant-add-image-as-nstextattachment-to-uitextfield + if (newValue?.hasTextAttachment ?? false) && newValue?.length == 1 { + textField.attributedPlaceholder = newValue + } + } + } + + var displayTextAccessibilityValue: String? { + get { + return textField.accessibilityValue + } + set { + textField.accessibilityValue = newValue + } + } + + // MARK: - Initializers + + /** + - Parameter label: The label of this picker + - Parameter shouldShowChevron: Whether a downward chevron should be displayed in this field + - Parameter pickerView: A `UIPicker` or `UIDatePicker` view that opens when this field becomes first responder + - Parameter delegate: Delegate for this view + - Parameter theme: Theme for the picker field + */ + init( + label: String?, + shouldShowChevron: Bool, + pickerView: UIView, + delegate: PickerFieldViewDelegate, + theme: ElementsAppearance, + hasPadding: Bool = true, + isOptional: Bool = false + ) { + self.label = label + self.shouldShowChevron = shouldShowChevron + self.pickerView = pickerView + self.delegate = delegate + self.theme = theme + self.isOptional = isOptional + super.init(frame: .zero) + addAndPinSubview(hStackView, directionalLayoutMargins: hasPadding ? ElementsUI.contentViewInsets : .zero) +// On Catalyst/visionOS, add the picker view as a subview instead of an input view. + #if targetEnvironment(macCatalyst) || canImport(CompositorServices) + addAndPinSubview(pickerView, directionalLayoutMargins: ElementsUI.contentViewInsets) + #endif + layer.borderColor = theme.colors.border.cgColor + isUserInteractionEnabled = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override func layoutSubviews() { + super.layoutSubviews() + floatingPlaceholderTextFieldView?.updatePlaceholder(animated: false) + } + + override var isUserInteractionEnabled: Bool { + didSet { + textField.textColor = theme.colors.textFieldText.disabled(!isUserInteractionEnabled) + if frame.size != .zero { + textField.layoutIfNeeded() // Fixes an issue on iOS 15 where setting textField properties causes it to lay out from zero size. + } + } + } + +#if !canImport(CompositorServices) + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + layer.borderColor = theme.colors.border.cgColor + // Update the text attachment images for the attributed placeholder + textField.attributedPlaceholder = textField.attributedPlaceholder?.switchAttachments(for: .current) + } +#endif + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard isUserInteractionEnabled, !isHidden, self.point(inside: point, with: event) else { + return nil + } + #if targetEnvironment(macCatalyst) || canImport(CompositorServices) + // Forward all events within our bounds to the button + return pickerView + #else + // Forward all events within our bounds to the textview + return textField + #endif + } + + override var intrinsicContentSize: CGSize { + // I'm implementing this to disambiguate layout of a horizontal stack view containing this view + let hStackViewSize = hStackView.systemLayoutSizeFitting(.zero) + return CGSize( + width: hStackViewSize.width + directionalLayoutMargins.leading + directionalLayoutMargins.trailing, + height: hStackViewSize.height + directionalLayoutMargins.top + directionalLayoutMargins.bottom + ) + } + + override func becomeFirstResponder() -> Bool { + // Prevents unwanted invocation of the picker's `becomeFirstResponder` method. + // Sometimes, when the picker is added as a subview or when its `isUserInteractionEnabled` is toggled, + // the operating system mistakenly calls `becomeFirstResponder`, causing the drop-down to display unintentionally. + guard _canBecomeFirstResponder, isUserInteractionEnabled else { + return false + } + + if super.becomeFirstResponder() { + return true + } + return textField.becomeFirstResponder() + } + + func setCanBecomeFirstResponder(_ value: Bool) { + _canBecomeFirstResponder = value + } +} + +// MARK: - EventHandler + +extension PickerFieldView: EventHandler { + func handleEvent(_ event: STPEvent) { + switch event { + case .shouldEnableUserInteraction: + isUserInteractionEnabled = true + case .shouldDisableUserInteraction: + isUserInteractionEnabled = false + default: + break + } + } +} + +// MARK: - UITextFieldDelegate + +extension PickerFieldView: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + UIAccessibility.post(notification: .layoutChanged, argument: pickerView) + floatingPlaceholderTextFieldView?.updatePlaceholder() + delegate?.didBeginEditing(self) + } + + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + return false + } + + func textFieldDidEndEditing(_ textField: UITextField) { + floatingPlaceholderTextFieldView?.updatePlaceholder() + delegate?.didFinish(self, shouldAutoAdvance: true) + } + + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return _canBecomeFirstResponder + } +} + +// MARK: - DoneButtonToolbarDelegate + +extension PickerFieldView: DoneButtonToolbarDelegate { + func didTapDone(_ toolbar: DoneButtonToolbar) { + _ = textField.resignFirstResponder() + } + + func didTapCancel(_ toolbar: DoneButtonToolbar) { + delegate?.didCancel(self) + _ = textField.resignFirstResponder() + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/PickerField/PickerTextField.swift b/StripeUICore/StripeUICore/Source/Elements/PickerField/PickerTextField.swift new file mode 100644 index 00000000..7793cc47 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/PickerField/PickerTextField.swift @@ -0,0 +1,39 @@ +// +// PickerTextField.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/17/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +// MARK: - PickerTextField + +/** + A subclass of `UITextField` that disables manual text entry. + + For internal SDK use only + */ +@objc(STP_Internal_PickerTextField) +class PickerTextField: UITextField { + + // MARK: Overrides + + override func caretRect(for position: UITextPosition) -> CGRect { + // Disallow selection + return .zero + } + + override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { + // Disallow selection + return [] + } + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if action == #selector(UIResponderStandardEditActions.paste(_:)) { + return false + } + return super.canPerformAction(action, withSender: sender) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Section/SectionContainerView.swift b/StripeUICore/StripeUICore/Source/Elements/Section/SectionContainerView.swift new file mode 100644 index 00000000..0c602ceb --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Section/SectionContainerView.swift @@ -0,0 +1,228 @@ +// +// SectionContainerView.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/** + A rounded, lightly shadowed container view with a thin border. + You can put views like TextFieldView inside it. + + - Note: This class sets the borderWidth, color, cornerRadius, etc. of its subviews. + + For internal SDK use only + */ +@objc(STP_Internal_SectionContainerView) +class SectionContainerView: UIView { + + // MARK: - Views + + lazy var bottomPinningContainerView: DynamicHeightContainerView = { + let view = DynamicHeightContainerView(pinnedDirection: .top) + view.directionalLayoutMargins = .zero + view.addPinnedSubview(stackView) + view.updateHeight() + return view + }() + + lazy var stackView: StackViewWithSeparator = { + let view = buildStackView(views: views, theme: theme) + return view + }() + + private(set) var views: [UIView] + private let theme: ElementsAppearance + + // MARK: - Initializers + + convenience init(view: UIView, theme: ElementsAppearance = .default) { + self.init(views: [view], theme: theme) + } + + /** + - Parameter views: A list of views to display in a row. To display multiple elements in a single row, put them inside a `MultiElementRowView`. + */ + init(views: [UIView], theme: ElementsAppearance = .default) { + self.views = views + self.theme = theme + super.init(frame: .zero) + addAndPinSubview(bottomPinningContainerView) + updateUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override var isUserInteractionEnabled: Bool { + didSet { + updateUI() + } + } + + override func layoutSubviews() { + super.layoutSubviews() + // Set up each subviews border corners + // Do this in layoutSubviews to update when views appear or disappear + let visibleRows = stackView.arrangedSubviews.filter { !$0.isHidden } + // 1. Reset all border corners to be square + for row in visibleRows { + // Pull out any Element views nested inside a MultiElementRowView + for view in (row as? MultiElementRowView)?.views ?? [row] { + view.layer.cornerRadius = theme.cornerRadius + view.layer.maskedCorners = [] + view.layer.shadowOpacity = 0.0 + view.layer.borderWidth = 0 + } + } + // 2. Round the top-most view's top corners + if let multiElementRowView = visibleRows.first as? MultiElementRowView { + multiElementRowView.views.first?.layer.maskedCorners.insert([.layerMinXMinYCorner]) + multiElementRowView.views.last?.layer.maskedCorners.insert([.layerMaxXMinYCorner]) + } else { + visibleRows.first?.layer.maskedCorners.insert([.layerMinXMinYCorner, .layerMaxXMinYCorner]) + } + // 3. Round the bottom-most view's bottom corners + if let multiElementRowView = visibleRows.last as? MultiElementRowView { + multiElementRowView.views.first?.layer.maskedCorners.insert([.layerMinXMaxYCorner]) + multiElementRowView.views.last?.layer.maskedCorners.insert([.layerMaxXMaxYCorner]) + } else { + visibleRows.last?.layer.maskedCorners.insert([.layerMaxXMaxYCorner, .layerMinXMaxYCorner]) + } + + // Improve shadow performance + layer.shadowPath = CGPath( + roundedRect: bounds, + cornerWidth: layer.cornerRadius, + cornerHeight: layer.cornerRadius, + transform: nil + ) + } + +#if !canImport(CompositorServices) + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateUI() + } +#endif + + // MARK: - Internal methods + func updateUI(newViews: [UIView]? = nil) { + layer.applyShadow(shadow: theme.shadow) + layer.cornerRadius = theme.cornerRadius + + if isUserInteractionEnabled || UITraitCollection.current.isDarkMode { + backgroundColor = theme.colors.componentBackground + } else { + backgroundColor = .tertiarySystemGroupedBackground + } + + guard let newViews = newViews, views != newViews else { + return + } + // Add new views in a new stack view + let dummyFirstView: UIView? // A hack to preserve the first view during the transition + let newStackViews: [UIView] + if let first = newViews.first, first == views.first { + // Hack: Give the new stack view a dummy view with the same height as the current stack view's first view + let dummy = UIView(frame: first.frame) + dummy.heightAnchor.constraint(equalToConstant: dummy.bounds.height).isActive = true + newStackViews = [dummy] + newViews.dropFirst() + dummyFirstView = dummy + } else { + dummyFirstView = nil + newStackViews = newViews + } + + let oldStackHeight = self.stackView.frame.size.height + let newStack = buildStackView(views: newStackViews, theme: theme) + newStack.arrangedSubviews.forEach { $0.alpha = 0 } + bottomPinningContainerView.addPinnedSubview(newStack) + bottomPinningContainerView.layoutIfNeeded() + let transition = { + // Hack: Swap the dummy first view and real first view + if let dummyFirstView = dummyFirstView, + let firstView = self.views.first + { + self.stackView.insertArrangedSubview(dummyFirstView, at: 0) + newStack.insertArrangedSubview(firstView, at: 0) + } + + // Fade old out + self.stackView.arrangedSubviews.forEach { $0.alpha = 0 } + self.stackView.alpha = 0.0 + // Change height to accommodate new views + self.bottomPinningContainerView.updateHeight() + // Fade new in + newStack.arrangedSubviews.forEach { $0.alpha = 1 } + let oldStackView = self.stackView + self.stackView = newStack + self.views = newViews + self.setNeedsLayout() + self.layoutIfNeeded() + oldStackView.removeFromSuperview() + } + guard let viewController = window?.rootViewController?.presentedViewController else { + transition() + return + } + let shouldAnimate = Int(newStack.frame.size.height) != Int(oldStackHeight) + viewController.animateHeightChange(duration: shouldAnimate ? 0.5 : 0.0, transition) + } +} + +// MARK: - EventHandler + +extension SectionContainerView: EventHandler { + func handleEvent(_ event: STPEvent) { + switch event { + case .shouldEnableUserInteraction: + isUserInteractionEnabled = true + case .shouldDisableUserInteraction: + isUserInteractionEnabled = false + default: + break + } + } +} + +// MARK: - MultiElementRowView + +extension SectionContainerView { + class MultiElementRowView: UIView { + let views: [UIView] + + init(views: [UIView], theme: ElementsAppearance = .default) { + self.views = views + super.init(frame: .zero) + let stackView = buildStackView(views: views, theme: theme) + stackView.axis = .horizontal + stackView.drawBorder = false + stackView.distribution = .fillEqually + addAndPinSubview(stackView) + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + } +} + +// MARK: - StackViewWithSeparator + +private func buildStackView(views: [UIView], theme: ElementsAppearance = .default) -> StackViewWithSeparator { + let stackView = StackViewWithSeparator(arrangedSubviews: views) + stackView.axis = .vertical + stackView.spacing = theme.borderWidth + stackView.separatorColor = theme.colors.divider + stackView.borderColor = theme.colors.border + stackView.borderCornerRadius = theme.cornerRadius + stackView.customBackgroundColor = theme.colors.componentBackground + stackView.drawBorder = true + stackView.hideShadow = true // Shadow is handled by `SectionContainerView` + return stackView +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Section/SectionElement+MultiElementRow.swift b/StripeUICore/StripeUICore/Source/Elements/Section/SectionElement+MultiElementRow.swift new file mode 100644 index 00000000..062e7689 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Section/SectionElement+MultiElementRow.swift @@ -0,0 +1,30 @@ +// +// SectionElement+MultiElementRow.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 3/18/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +public extension SectionElement { + /// A simple container element that displays its child elements in a horizontal stackview + @_spi(STP) final class MultiElementRow: ContainerElement { + weak public var delegate: ElementDelegate? + public lazy var view: UIView = { + return SectionContainerView.MultiElementRowView(views: elements.map { $0.view }, theme: theme) + }() + public let elements: [Element] + public let theme: ElementsAppearance + + public init(_ elements: [Element], theme: ElementsAppearance = .default) { + self.elements = elements + self.theme = theme + elements.forEach { + $0.delegate = self + } + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Section/SectionElement.swift b/StripeUICore/StripeUICore/Source/Elements/Section/SectionElement.swift new file mode 100644 index 00000000..e08896e2 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Section/SectionElement.swift @@ -0,0 +1,137 @@ +// +// SectionElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/6/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/** + A simple container element with an optional title and an error, and draws a border around its elements. + Chooses which of its sub-elements' errors to display. + */ +@_spi(STP) public final class SectionElement: ContainerElement { + weak public var delegate: ElementDelegate? + lazy var sectionView: SectionView = { + isViewInitialized = true + return SectionView(viewModel: viewModel) + }() + var isViewInitialized: Bool = false + var errorText: String? { + // Find the first element that's 1. invalid and 2. has a displayable error + for element in elements { + if case let .invalid(error, shouldDisplay) = element.validationState, shouldDisplay { + return error.localizedDescription + } + } + return nil + } + var viewModel: SectionViewModel { + return ViewModel( + views: elements.filter { !($0.view is HiddenElement.HiddenView) }.map({ $0.view }), // filter out hidden views to prevent showing the separator + title: title, + errorText: errorText, + subLabel: subLabel, + theme: theme + ) + } + public var elements: [Element] { + didSet { + elements.forEach { + $0.delegate = self + } + if isViewInitialized { + sectionView.update(with: viewModel) + } + delegate?.didUpdate(element: self) + } + } + let title: String? + + var subLabel: String? { + elements.compactMap({ $0.subLabelText }).first + } + + let theme: ElementsAppearance + + // MARK: - ViewModel + + struct ViewModel { + let views: [UIView] + let title: String? + let errorText: String? + var subLabel: String? + let theme: ElementsAppearance + } + + // MARK: - Initializers + + public init(title: String? = nil, elements: [Element], theme: ElementsAppearance = .default) { + self.title = title + self.elements = elements + self.theme = theme + elements.forEach { + $0.delegate = self + } + } + + public convenience init(_ element: Element, theme: ElementsAppearance = .default) { + self.init(title: nil, elements: [element], theme: theme) + } +} + +// MARK: - Element + +extension SectionElement: Element { + public var view: UIView { + return sectionView + } +} + +// MARK: - ElementDelegate + +extension SectionElement: ElementDelegate { + public func didUpdate(element: Element) { + // Glue: Update the view and our delegate + if isViewInitialized { + sectionView.update(with: viewModel) + } + delegate?.didUpdate(element: self) + } +} + +// MARK: HiddenElement + +extension SectionElement { + /// A simple container element where the element's view is hidden + /// Useful when an element is a part of a section but it's view is embeded into another element + /// E.g. card brand drop down embedded into the PAN textfield + /// - Note: `HiddenElement`'s are skipped by the `ContainerElement`'s auto advance logic + @_spi(STP) public final class HiddenElement: ContainerElement { + final class HiddenView: UIView {} + + weak public var delegate: ElementDelegate? + public lazy var view: UIView = { + return HiddenView(frame: .zero) // Hide the element's view + }() + public let elements: [Element] + + public init?(_ element: Element?) { + guard let element = element else { + return nil + } + self.elements = [element] + element.delegate = self + } + } +} + +// MARK: - DebugDescription +extension SectionElement { + public var debugDescription: String { + return "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())>; title = \(title ?? "nil")" + subElementDebugDescription + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/Section/SectionView.swift b/StripeUICore/StripeUICore/Source/Elements/Section/SectionView.swift new file mode 100644 index 00000000..821c0d8c --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/Section/SectionView.swift @@ -0,0 +1,72 @@ +// +// SectionView.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +typealias SectionViewModel = SectionElement.ViewModel + +/// For internal SDK use only +@objc(STP_Internal_SectionView) +final class SectionView: UIView { + + // MARK: - Views + + lazy var errorOrSubLabel: UILabel = { + return ElementsUI.makeErrorLabel(theme: viewModel.theme) + }() + let containerView: SectionContainerView + lazy var titleLabel: UILabel = { + return ElementsUI.makeSectionTitleLabel(theme: viewModel.theme) + }() + + let viewModel: SectionViewModel + + // MARK: - Initializers + + init(viewModel: SectionViewModel) { + self.viewModel = viewModel + self.containerView = SectionContainerView(views: viewModel.views, theme: viewModel.theme) + super.init(frame: .zero) + + let stack = UIStackView(arrangedSubviews: [titleLabel, containerView, errorOrSubLabel]) + stack.axis = .vertical + stack.spacing = 4 + addAndPinSubview(stack) + + update(with: viewModel) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Private methods + + func update(with viewModel: SectionViewModel) { + isHidden = viewModel.views.filter({ !$0.isHidden }).isEmpty + guard !isHidden else { + return + } + containerView.updateUI(newViews: viewModel.views) + titleLabel.text = viewModel.title + titleLabel.isHidden = viewModel.title == nil + if let errorText = viewModel.errorText, !errorText.isEmpty { + errorOrSubLabel.text = viewModel.errorText + errorOrSubLabel.isHidden = false + errorOrSubLabel.textColor = viewModel.theme.colors.danger + } else if let subLabel = viewModel.subLabel { + errorOrSubLabel.text = subLabel + errorOrSubLabel.isHidden = false + errorOrSubLabel.textColor = viewModel.theme.colors.secondaryText + } else { + errorOrSubLabel.text = nil + errorOrSubLabel.isHidden = true + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/StaticElement.swift b/StripeUICore/StripeUICore/Source/Elements/StaticElement.swift new file mode 100644 index 00000000..45ae0663 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/StaticElement.swift @@ -0,0 +1,27 @@ +// +// StaticElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/18/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +/** + A inert wrapper around a view. + */ +@_spi(STP) public class StaticElement: Element { + public let collectsUserInput: Bool = false + weak public var delegate: ElementDelegate? + public let view: UIView + public var isHidden: Bool = false { + didSet { + view.isHidden = isHidden + } + } + + public init(view: UIView) { + self.view = view + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextField/FloatingPlaceholderTextFieldView.swift b/StripeUICore/StripeUICore/Source/Elements/TextField/FloatingPlaceholderTextFieldView.swift new file mode 100644 index 00000000..6d9bb926 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextField/FloatingPlaceholderTextFieldView.swift @@ -0,0 +1,199 @@ +// +// FloatingPlaceholderTextFieldView.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 7/7/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +/** + A helper view that contains a floating placeholder and a user-provided text field + + For internal SDK use only + */ +@objc(STP_Internal_FloatingPlaceholderTextFieldView) +class FloatingPlaceholderTextFieldView: UIView { + + // MARK: - Views + + private let textField: UITextField + private let theme: ElementsAppearance + private lazy var placeholderLabel: UILabel = { + let label = UILabel() + label.textColor = theme.colors.placeholderText + label.font = theme.fonts.subheadline + return label + }() + + public var placeholder: String { + get { + return placeholderLabel.text ?? "" + } + set { + placeholderLabel.text = newValue + } + } + + // MARK: - Initializers + + public init(textField: UITextField, theme: ElementsAppearance = .default) { + self.textField = textField + self.theme = theme + super.init(frame: .zero) + isAccessibilityElement = true + installConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override var isUserInteractionEnabled: Bool { + didSet { + textField.isUserInteractionEnabled = isUserInteractionEnabled + } + } + + override var accessibilityValue: String? { + get { return textField.accessibilityValue } + set { assertionFailure() } // swiftlint:disable:this unused_setter_value + } + + override var accessibilityLabel: String? { + get { return textField.accessibilityLabel ?? placeholderLabel.text } + set { assertionFailure() } // swiftlint:disable:this unused_setter_value + } + + override var accessibilityTraits: UIAccessibilityTraits { + get { return textField.accessibilityTraits } + set { assertionFailure() } // swiftlint:disable:this unused_setter_value + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard isUserInteractionEnabled, !isHidden, self.point(inside: point, with: event) else { + return nil + } + // Forward all events within our bounds to the textfield + return textField + } + + override func becomeFirstResponder() -> Bool { + guard !isHidden else { + return false + } + return textField.becomeFirstResponder() + } + + // MARK: - Private methods + + fileprivate func installConstraints() { + textField.translatesAutoresizingMaskIntoConstraints = false + addSubview(textField) + + // Allow space for the minimized placeholder to sit above the textfield + let minimizedPlaceholderHeight = placeholderLabel.font.lineHeight * Constants.Placeholder.scale + NSLayoutConstraint.activate([ + textField.topAnchor.constraint(equalTo: topAnchor, constant: minimizedPlaceholderHeight + Constants.Placeholder.bottomPadding), + textField.bottomAnchor.constraint(equalTo: bottomAnchor), + textField.leadingAnchor.constraint(equalTo: leadingAnchor), + textField.trailingAnchor.constraint(equalTo: trailingAnchor), + ]) + + // Arrange placeholder + placeholderLabel.translatesAutoresizingMaskIntoConstraints = false + addSubview(placeholderLabel) + // Change anchorPoint so scale transforms occur from the leading edge instead of the center + placeholderLabel.layer.anchorPoint = effectiveUserInterfaceLayoutDirection == .leftToRight + ? CGPoint(x: 0, y: 0.5) + : CGPoint(x: 1, y: 0.5) + NSLayoutConstraint.activate([ + // Note placeholder's anchorPoint.x = 0 redefines its 'center' to the left + placeholderLabel.centerXAnchor.constraint(equalTo: textField.leadingAnchor), + placeholderCenterYConstraint, + ]) + } + + // MARK: - Animate placeholder + + fileprivate lazy var animator: UIViewPropertyAnimator = { + let params = UISpringTimingParameters( + mass: 1.0, + dampingRatio: 0.93, + frequencyResponse: 0.22 + ) + let animator = UIViewPropertyAnimator(duration: 0, timingParameters: params) + animator.isInterruptible = true + return animator + }() + + fileprivate lazy var placeholderCenterYConstraint: NSLayoutConstraint = { + placeholderLabel.centerYAnchor.constraint(equalTo: centerYAnchor) + }() + + fileprivate lazy var placeholderTopYConstraint: NSLayoutConstraint = { + placeholderLabel.topAnchor.constraint(equalTo: topAnchor) + }() + + public func updatePlaceholder(animated: Bool = true) { + enum Position { case up, down } + let isEmpty = textField.attributedText?.string.isEmpty ?? true + let position: Position = textField.isEditing || !isEmpty ? .up : .down + let scale = position == .up ? Constants.Placeholder.scale : 1.0 + let transform = CGAffineTransform.identity.scaledBy(x: scale, y: scale) + let updatePlaceholderLocation = { + self.placeholderLabel.transform = transform + self.placeholderCenterYConstraint.isActive = position != .up + self.placeholderTopYConstraint.isActive = position == .up + } + + // Don't update redundantly; this can cause animation issues + guard transform != self.placeholderLabel.transform else { + return + } + + // Note: Only animate if the view is inside of the window hierarchy, + // otherwise calling `layoutIfNeeded` inside the animation block causes + // autolayout errors + guard animated && window != nil else { + updatePlaceholderLocation() + return + } + + animator.stopAnimation(true) + animator.addAnimations { + updatePlaceholderLocation() + self.layoutIfNeeded() + } + animator.startAnimation() + } + +} + +// MARK: - EventHandler + +extension FloatingPlaceholderTextFieldView: EventHandler { + func handleEvent(_ event: STPEvent) { + switch event { + case .shouldEnableUserInteraction: + isUserInteractionEnabled = true + case .shouldDisableUserInteraction: + isUserInteractionEnabled = false + default: + break + } + } +} + +// MARK: - Constants + +private enum Constants { + enum Placeholder { + static let scale: CGFloat = 0.75 + /// The distance between the floating placeholder label and the textfield below it. + static let bottomPadding: CGFloat = 3.0 + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElement+Validation.swift b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElement+Validation.swift new file mode 100644 index 00000000..37f312f4 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElement+Validation.swift @@ -0,0 +1,87 @@ +// +// TextFieldElement+Validation.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public extension TextFieldElement { + @frozen enum ValidationState { + case valid + case invalid(_ error: TextFieldValidationError) + } + + /// A general-purpose TextFieldValidationError. + /// If it doesn't suit your text field's needs, create a new enum instead of modifying this one! + @frozen enum Error: TextFieldValidationError, Equatable { + /// An empty text field differs from incomplete in that it never displays an error. + case empty + case incomplete(localizedDescription: String) + case invalid(localizedDescription: String) + + public func shouldDisplay(isUserEditing: Bool) -> Bool { + switch self { + case .empty: + return false + case .incomplete, .invalid: + // By default, invalid errors aren't displayed. + // This makes sense when input that is invalid may become valid after further user input + return !isUserEditing + } + } + + public var localizedDescription: String { + switch self { + case .incomplete(let localizedDescription): + return localizedDescription + case .invalid(let localizedDescription): + return localizedDescription + case .empty: + return "" + } + } + } +} + +/** + - Seealso: `ElementValidation.swift` + - Seealso: `SectionElement` uses this to determine whether it should show the error or not. + */ +@_spi(STP) public protocol TextFieldValidationError: ElementValidationError { + /** + Some TextFieldElement validation errors should only be displayed to the user if they're finished typing, while others should + always be shown. + + For example, most fields in an "incomplete" state won't display an "incomplete" error until the user has finished typing. + + - Parameter isUserEditing: Whether or not the user is editing the field that is in error. + - Returns: Whether or not to display the error. + - Note: The default implementation always returns `true` + */ + func shouldDisplay(isUserEditing: Bool) -> Bool + + var localizedDescription: String { get } +} + +extension TextFieldValidationError { + func shouldDisplay(isUserEditing: Bool) -> Bool { + return true + } +} + +// MARK: - ElementValidationState +extension ElementValidationState { + /// Converts a `TextFieldElement.ValidationState` to an `ElementValidationState` + /// The only difference between the two is that the latter includes `isUserEditing` as part of its state so that it knows whether the error should display or not. + init(from validationState: TextFieldElement.ValidationState, isUserEditing: Bool) { + switch validationState { + case .valid: + self = .valid + case .invalid(let error): + self = .invalid(error: error, shouldDisplay: error.shouldDisplay(isUserEditing: isUserEditing)) + } + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElement.swift b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElement.swift new file mode 100644 index 00000000..8e7797e8 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElement.swift @@ -0,0 +1,191 @@ +// +// TextFieldElement.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/4/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/** + A generic text field whose logic is extracted into `TextFieldElementConfiguration`. + + - Seealso: `TextFieldElementConfiguration` + */ +@_spi(STP) public final class TextFieldElement { + + // MARK: - Properties + weak public var delegate: ElementDelegate? + lazy var textFieldView: TextFieldView = { + return TextFieldView(viewModel: viewModel, delegate: self) + }() + var configuration: TextFieldElementConfiguration { + didSet { + setText("") + } + } + public private(set) lazy var text: String = { + sanitize(text: configuration.defaultValue ?? "") + }() + public private(set) var isEditing: Bool = false + private(set) var didReceiveAutofill: Bool = false + public var validationState: ElementValidationState { + return .init( + from: configuration.validate(text: text, isOptional: configuration.isOptional), + isUserEditing: isEditing + ) + } + + private let theme: ElementsAppearance + +#if !canImport(CompositorServices) + public var inputAccessoryView: UIView? { + get { + return textFieldView.textField.inputAccessoryView + } + + set { + textFieldView.textField.inputAccessoryView = newValue + } + } +#endif + + // MARK: - ViewModel + public struct KeyboardProperties { + public init(type: UIKeyboardType, textContentType: UITextContentType?, autocapitalization: UITextAutocapitalizationType) { + self.type = type + self.textContentType = textContentType + self.autocapitalization = autocapitalization + } + + let type: UIKeyboardType + let textContentType: UITextContentType? + let autocapitalization: UITextAutocapitalizationType + } + + struct ViewModel { + let placeholder: String + let accessibilityLabel: String + let attributedText: NSAttributedString + let keyboardProperties: KeyboardProperties + let validationState: ValidationState + let accessoryView: UIView? + let shouldShowClearButton: Bool + let isEditable: Bool + let theme: ElementsAppearance + } + + var viewModel: ViewModel { + let placeholder: String = { + if !configuration.isOptional { + return configuration.label + } else { + let localized = String.Localized.optional_field + return String(format: localized, configuration.label) + } + }() + return ViewModel( + placeholder: placeholder, + accessibilityLabel: configuration.accessibilityLabel, + attributedText: configuration.makeDisplayText(for: text), + keyboardProperties: configuration.keyboardProperties(for: text), + validationState: configuration.validate(text: text, isOptional: configuration.isOptional), + accessoryView: configuration.accessoryView(for: text, theme: theme), + shouldShowClearButton: configuration.shouldShowClearButton, + isEditable: configuration.isEditable, + theme: theme + ) + } + + // MARK: - Initializer + + public required init(configuration: TextFieldElementConfiguration, theme: ElementsAppearance = .default) { + self.configuration = configuration + self.theme = theme + } + + /// Call this to manually set the text of the text field. + public func setText(_ text: String) { + self.text = sanitize(text: text) + + // Since we're setting the text manually, disable any previous autofill + didReceiveAutofill = false + + // Glue: Update the view and our delegate + textFieldView.updateUI(with: viewModel) + delegate?.didUpdate(element: self) + } + + // MARK: - Helpers + + func sanitize(text: String) -> String { + let sanitizedText = text.stp_stringByRemovingCharacters(from: configuration.disallowedCharacters) + return String(sanitizedText.prefix(configuration.maxLength(for: sanitizedText))) + } +} + +// MARK: - Element + +extension TextFieldElement: Element { + public var collectsUserInput: Bool { true } + public var view: UIView { + return textFieldView + } + + @discardableResult + public func beginEditing() -> Bool { + return textFieldView.textField.becomeFirstResponder() + } + + @discardableResult + public func endEditing(_ force: Bool = false, continueToNextField: Bool = true) -> Bool { + let didResign = textFieldView.endEditing(force) + isEditing = textFieldView.isEditing + if continueToNextField { + delegate?.continueToNextField(element: self) + } + return didResign + } + + public var subLabelText: String? { + return configuration.subLabel(text: text) + } +} + +// MARK: - TextFieldViewDelegate + +extension TextFieldElement: TextFieldViewDelegate { + func textFieldViewDidUpdate(view: TextFieldView) { + // Update our state + let newText = sanitize(text: view.text) + if text != newText { + text = newText + // Advance to the next field if text is maximum length and valid + if text.count == configuration.maxLength(for: text), case .valid = validationState { + delegate?.continueToNextField(element: self) + view.resignFirstResponder() + } + } + isEditing = view.isEditing + didReceiveAutofill = view.didReceiveAutofill + + // Glue: Update the view and our delegate + view.updateUI(with: viewModel) + delegate?.didUpdate(element: self) + } + + func textFieldViewContinueToNextField(view: TextFieldView) { + isEditing = view.isEditing + delegate?.continueToNextField(element: self) + } +} + +// MARK: - DebugDescription +extension TextFieldElement { + public var debugDescription: String { + return "; label = \(configuration.label); text = \(text); validationState = \(validationState)" + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElementConfiguration.swift b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElementConfiguration.swift new file mode 100644 index 00000000..3cf7c826 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldElementConfiguration.swift @@ -0,0 +1,144 @@ +// +// TextFieldElementConfiguration.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore +import UIKit + +/** + Contains the business logic for a TextField. + + - Seealso: `TextFieldElement+Factory.swift` + */ +@_spi(STP) public protocol TextFieldElementConfiguration { + var label: String { get } + + /** + Defaults to `label` + */ + var accessibilityLabel: String { get } + var shouldShowClearButton: Bool { get } + var disallowedCharacters: CharacterSet { get } + /** + If `true`, adds " (optional)" to the field's label . Defaults to `false`. + - Note: This value is passed to the `validate(text:isOptional:)` method. + */ + var isOptional: Bool { get } + + /** + - Note: The text field gets a sanitized version of this (i.e. after stripping disallowed characters, applying max length, etc.) + */ + var defaultValue: String? { get } + + /** + - Note: If false, this textfield is disabled, defaults to true. + */ + var isEditable: Bool { get } + + /** + Validate the text. + + - Parameter isOptional: Whether or not the text field's value is optional. + */ + func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState + + /** + A string to display under the field + */ + func subLabel(text: String) -> String? + + /** + - Parameter text: The user's sanitized input (i.e., removing `disallowedCharacters` and clipping to `maxLength(for:)`) + - Returns: A string as it should be displayed to the user. e.g., Apply kerning between every 4th and 5th number for PANs. + */ + func makeDisplayText(for text: String) -> NSAttributedString + + /** + - Returns: An assortment of properties to apply to the keyboard for the text field. + */ + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties + + /** + The maximum length of text allowed in the text field. + - Note: Text beyond this length is removed before its displayed in the UI or passed to other `TextFieldElementConfiguration` methods. + - Note: Return `Int.max` to indicate there is no maximum + */ + func maxLength(for text: String) -> Int + + /** + An optional accessory view displayed on the trailing side of the text field. + This could be the logo of a network, a bank, etc. + - Returns: a view. + */ + func accessoryView(for text: String, theme: ElementsAppearance) -> UIView? + + /** + Convenience method that creates a TextFieldElement using this Configuration + */ + func makeElement(theme: ElementsAppearance) -> TextFieldElement +} + +// MARK: - Default implementation + +public extension TextFieldElementConfiguration { + var accessibilityLabel: String { + return label + } + + var disallowedCharacters: CharacterSet { + return .newlines + } + + var isOptional: Bool { + return false + } + + var defaultValue: String? { + return nil + } + + // Hide clear button by default + var shouldShowClearButton: Bool { + return false + } + + var isEditable: Bool { + return true + } + + func makeDisplayText(for text: String) -> NSAttributedString { + return NSAttributedString(string: text) + } + + func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .default, textContentType: nil, autocapitalization: .words) + } + + func validate(text: String, isOptional: Bool) -> TextFieldElement.ValidationState { + if text.stp_stringByRemovingCharacters(from: .whitespacesAndNewlines).isEmpty { + return isOptional ? .valid : .invalid(TextFieldElement.Error.empty) + } + return .valid + } + + func subLabel(text: String) -> String? { + return nil + } + + func maxLength(for text: String) -> Int { + return .max + } + + func accessoryView(for text: String, theme: ElementsAppearance) -> UIView? { + return nil + } + + func makeElement(theme: ElementsAppearance) -> TextFieldElement { + return TextFieldElement(configuration: self, theme: theme) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldFormatter.swift b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldFormatter.swift new file mode 100644 index 00000000..d69fc776 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldFormatter.swift @@ -0,0 +1,108 @@ +// +// TextFieldFormatter.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/28/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +struct TextFieldFormatter { + + // NOTE(mludowise): If we ever have a case where we need to display `#` or `*` + // inside a formatted string, we should probably change `format` to something + // more structured other than a `String`. + + static let redactedNumberCharacter: Character = "•" + static let digitPatternCharacter: Character = "#" + static let letterPatternCharacter: Character = "*" + + private let format: String + + /** + - Parameters: + - format: Consists of a string using pound signs `#`for numeric placeholders, and asterisks `*` for letters. + + - Note: Returns nil if the given format is invalid and doesn't contain any `#` or `*` characters. + */ + init?(format: String) { + guard format.contains(TextFieldFormatter.letterPatternCharacter) || + format.contains(TextFieldFormatter.digitPatternCharacter) else { + return nil + } + self.format = format + } + + /** + Applies a format to `input`. + + - Note: + If `input` doesn't contain enough characters to fill-in the placeholders, a partially formatted string + will be returned. In the case of `input` containing more characters than expected, it will be truncated + to the max length allowed by the format. + + - Parameters: + - input: Content to be formatted. + - appendRemaining: Set to true if any remaining characters in input after filling the pattern should be added as a suffix + + - Returns: The resulting formatted string. + */ + func applyFormat(to input: String, shouldAppendRemaining: Bool = false) -> String { + var result: [Character] = [] + + var cursor = input.startIndex + + /* + Buffer of characters that will get appended to the result only if there + are more consumable characters (`*` or `#`). This prevents adding + formatted characters to the end of the string, which can break the + TextFieldView's backspace behavior when updating cursor position after + formatted characters. + */ + var resultBuffer: [Character] = [] + + for token in format { + guard cursor < input.endIndex else { + break + } + + repeat { + var consumeInput = false + if token == TextFieldFormatter.digitPatternCharacter, + (input[cursor].isNumber || input[cursor] == TextFieldFormatter.redactedNumberCharacter) { + consumeInput = true + resultBuffer.append(input[cursor]) + } else if token == TextFieldFormatter.letterPatternCharacter, + input[cursor].isLetter { + consumeInput = true + resultBuffer.append(input[cursor]) + } + + if consumeInput { + // Consume a character from the input + result += resultBuffer + resultBuffer = [] + cursor = input.index(after: cursor) + break + } + + if token == TextFieldFormatter.digitPatternCharacter || + token == TextFieldFormatter.letterPatternCharacter { + // Discard unmatched token + cursor = input.index(after: cursor) + } else { + resultBuffer.append(token) + break + } + } while cursor < input.endIndex + } + + if shouldAppendRemaining, + cursor < input.endIndex { + result += " " + String(input[cursor...]) + } + + return String(result) + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldView.swift b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldView.swift new file mode 100644 index 00000000..990cda00 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextField/TextFieldView.swift @@ -0,0 +1,310 @@ +// +// TextFieldView.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 6/3/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +protocol TextFieldViewDelegate: AnyObject { + func textFieldViewDidUpdate(view: TextFieldView) + func textFieldViewContinueToNextField(view: TextFieldView) +} + +/** + A text input field view with a floating placeholder and images. + - Seealso: `TextFieldElement.ViewModel` + + For internal SDK use only + */ +@objc(STP_Internal_TextFieldView) +class TextFieldView: UIView { + weak var delegate: TextFieldViewDelegate? + private lazy var toolbar = DoneButtonToolbar(delegate: self, theme: viewModel.theme) + var text: String { + return textField.text ?? "" + } + var isEditing: Bool { + return textField.isEditing + } + override var isUserInteractionEnabled: Bool { + didSet { + textField.isUserInteractionEnabled = isUserInteractionEnabled + updateUI(with: viewModel) + } + } + + var didReceiveAutofill = false + + // MARK: - Views + + private(set) lazy var textField: UITextField = { + let textField = UITextField() + textField.delegate = self + textField.addTarget(self, action: #selector(textDidChange), for: .editingChanged) + textField.autocorrectionType = .no + textField.spellCheckingType = .no + textField.adjustsFontForContentSizeCategory = true + textField.font = viewModel.theme.fonts.subheadline + return textField + }() + private lazy var textFieldView: FloatingPlaceholderTextFieldView = { + return FloatingPlaceholderTextFieldView(textField: textField, theme: viewModel.theme) + }() + + let accessoryContainerView = UIView() + + /// This could contain the logos of networks, banks, etc. + var accessoryView: UIView? { + didSet { + // For some reason, the stackview chooses to stretch accessoryContainerView if its + // content is nil instead of the text field, so we hide it. + accessoryContainerView.setHiddenIfNecessary(accessoryView == nil) + + guard oldValue != accessoryView else { + return + } + oldValue?.removeFromSuperview() + + if let accessoryView = accessoryView as? PickerFieldView { + // Hack, disable the ability for the picker to take focus while it's being added as a sub view + // Occasionally the OS will attempt to call `becomeFirstResponder` on it, causing it to take focus + accessoryView.setCanBecomeFirstResponder(false) + accessoryContainerView.addAndPinSubview(accessoryView) + accessoryView.setContentHuggingPriority(.required, for: .horizontal) + // Don't have trailing padding when showing a picker view in the accessory view + hStack.updateTrailingAnchor(constant: 0) + accessoryView.setCanBecomeFirstResponder(true) + } else if let accessoryView = accessoryView { + accessoryContainerView.addAndPinSubview(accessoryView) + accessoryView.setContentHuggingPriority(.required, for: .horizontal) + hStack.updateTrailingAnchor(constant: -ElementsUI.contentViewInsets.trailing) + } + } + } + + lazy var errorIconView: UIImageView = { + let imageView = UIImageView(image: Image.icon_error.makeImage(template: true)) + imageView.tintColor = viewModel.theme.colors.danger + imageView.contentMode = .scaleAspectFit + return imageView + }() + lazy var clearButton: UIButton = { + let button = UIButton(type: .custom) + button.tintColor = viewModel.theme.colors.placeholderText + button.setImage(Image.icon_clear.makeImage(template: true), for: .normal) + button.isHidden = true + button.addTarget(self, action: #selector(clearText), for: .touchUpInside) + + return button + }() + private var viewModel: TextFieldElement.ViewModel + private var hStack = UIStackView() + + // MARK: - Initializers + + init(viewModel: TextFieldElement.ViewModel, delegate: TextFieldViewDelegate) { + self.viewModel = viewModel + self.delegate = delegate + super.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = false + isAccessibilityElement = false // false b/c we use `accessibilityElements` + installConstraints() + updateUI(with: viewModel) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Overrides + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard isUserInteractionEnabled, !isHidden, self.point(inside: point, with: event) else { + return nil + } + // We override hitTest to forward all events within our bounds to the textfield + // ...except for these subviews: + for interactableSubview in [clearButton, accessoryView].compactMap({ $0 }) { + let convertedPoint = interactableSubview.convert(point, from: self) + if let hitView = interactableSubview.hitTest(convertedPoint, with: event) { + return hitView + } + } + return textField + } + + // MARK: - Private methods + + fileprivate func installConstraints() { + hStack = UIStackView(arrangedSubviews: [textFieldView, errorIconView, clearButton, accessoryContainerView]) + clearButton.setContentHuggingPriority(.required, for: .horizontal) + clearButton.setContentCompressionResistancePriority(textField.contentCompressionResistancePriority(for: .horizontal) + 1, + for: .horizontal) + errorIconView.setContentHuggingPriority(.required, for: .horizontal) + errorIconView.setContentCompressionResistancePriority(textField.contentCompressionResistancePriority(for: .horizontal) + 1, + for: .horizontal) + accessoryContainerView.setContentHuggingPriority(.required, for: .horizontal) + accessoryContainerView.setContentCompressionResistancePriority(textField.contentCompressionResistancePriority(for: .horizontal) + 1, + for: .horizontal) + hStack.alignment = .center + hStack.spacing = 6 + addAndPinSubview(hStack, insets: ElementsUI.contentViewInsets) + } + + @objc private func clearText() { + textField.text = nil + textField.sendActions(for: .editingChanged) + } + + private func setClearButton(hidden: Bool) { + UIView.performWithoutAnimation { + clearButton.isHidden = hidden + hStack.layoutIfNeeded() + } + } + + // MARK: - Internal methods + + func updateUI(with viewModel: TextFieldElement.ViewModel) { + self.viewModel = viewModel + + // Update accessibility + textField.accessibilityLabel = viewModel.accessibilityLabel + + // Update placeholder, text + textFieldView.placeholder = viewModel.placeholder + + // Setting attributedText moves the cursor to the end, so we grab the cursor position now + // Get the offset of the cursor from the end of the textField so it will keep + // the same relative position in case attributedText adds more characters + let cursorOffsetFromEnd = textField.selectedTextRange.map { textField.offset(from: textField.endOfDocument, to: $0.end) } + + textField.attributedText = viewModel.attributedText + if let cursorOffsetFromEnd = cursorOffsetFromEnd, + let cursor = textField.position(from: textField.endOfDocument, offset: cursorOffsetFromEnd) { + // Re-set the cursor back to where it was + textField.selectedTextRange = textField.textRange(from: cursor, to: cursor) + } + textFieldView.updatePlaceholder(animated: false) + + // Update keyboard + textField.autocapitalizationType = viewModel.keyboardProperties.autocapitalization + textField.textContentType = viewModel.keyboardProperties.textContentType + if viewModel.keyboardProperties.type != textField.keyboardType { + textField.keyboardType = viewModel.keyboardProperties.type +#if !canImport(CompositorServices) + textField.inputAccessoryView = textField.keyboardType.hasReturnKey ? nil : toolbar +#endif + textField.reloadInputViews() + } + + // Update text and border color + if case .invalid(let error) = viewModel.validationState, + error.shouldDisplay(isUserEditing: textField.isEditing) { + layer.borderColor = viewModel.theme.colors.danger.cgColor + textField.textColor = viewModel.theme.colors.danger + errorIconView.alpha = 1 + textField.accessibilityValue = viewModel.attributedText.string + ", " + error.localizedDescription + } else { + layer.borderColor = viewModel.theme.colors.border.cgColor + textField.textColor = viewModel.theme.colors.textFieldText.disabled(!isUserInteractionEnabled || !viewModel.isEditable) + errorIconView.alpha = 0 + textField.accessibilityValue = viewModel.attributedText.string + } + if frame != .zero { + textField.layoutIfNeeded() // Fixes an issue on iOS 15 where setting textField properties cause it to lay out from zero size. + } + + // Update accessory view + accessoryView = viewModel.accessoryView + + accessibilityElements = [textFieldView, accessoryView].compactMap { $0 } + // Manually call layoutIfNeeded to avoid unintentional animations + // in next layout pass + layoutIfNeeded() + } + +#if !canImport(CompositorServices) + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateUI(with: viewModel) + } +#endif +} + +// MARK: - UITextFieldDelegate + +extension TextFieldView: UITextFieldDelegate { + + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return viewModel.isEditable + } + + @objc func textDidChange() { + // If the text updates to non-empty, ensure the clear button is visible + if let text = textField.text, !text.isEmpty, viewModel.shouldShowClearButton { + setClearButton(hidden: false) + } else { + // Did update to empty text + setClearButton(hidden: true) + } + + delegate?.textFieldViewDidUpdate(view: self) + } + + func textFieldDidBeginEditing(_ textField: UITextField) { + // If text is already present in the text field we should show the clear button + if let text = textField.text, !text.isEmpty, viewModel.shouldShowClearButton { + setClearButton(hidden: false) + } + textFieldView.updatePlaceholder() + delegate?.textFieldViewDidUpdate(view: self) + } + + func textFieldDidEndEditing(_ textField: UITextField) { + setClearButton(hidden: true) // Hide clear button when not editing + textFieldView.updatePlaceholder() + textField.layoutIfNeeded() // Without this, the text jumps for some reason + delegate?.textFieldViewDidUpdate(view: self) + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + delegate?.textFieldViewContinueToNextField(view: self) + textField.resignFirstResponder() + return false + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + // This detects autofill specifically, which as of iOS 15 Apple only allows on empty text fields. This will also catch pastes into empty text fields. + // This is not a perfect heuristic, but is sufficient for the purposes of being able to process autofilled text specifically (e.g. a phone number with unpredictable formatting that we want to parse) + didReceiveAutofill = (text.isEmpty && range.length == 0 && range.location == 0 && string.count > 1) + return true + } +} + +// MARK: - EventHandler + +extension TextFieldView: EventHandler { + func handleEvent(_ event: STPEvent) { + switch event { + case .shouldEnableUserInteraction: + isUserInteractionEnabled = true + case .shouldDisableUserInteraction: + isUserInteractionEnabled = false + default: + break + } + } +} + +// MARK: - DoneButtonToolbarDelegate + +extension TextFieldView: DoneButtonToolbarDelegate { + func didTapDone(_ toolbar: DoneButtonToolbar) { + textField.resignFirstResponder() + } +} diff --git a/StripeUICore/StripeUICore/Source/Elements/TextOrDropdownElement.swift b/StripeUICore/StripeUICore/Source/Elements/TextOrDropdownElement.swift new file mode 100644 index 00000000..41571496 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Elements/TextOrDropdownElement.swift @@ -0,0 +1,47 @@ +// +// TextOrDropdownElement.swift +// StripeUICore +// +// Created by Nick Porter on 9/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Describes an element that is either a text or dropdown element +@_spi(STP) public protocol TextOrDropdownElement: Element { + + /// The raw data for the element, e.g. the text of a textfield or raw data of a dropdown item + var rawData: String { get } + + /// Sets the raw data for this element + func setRawData(_ rawData: String) +} + +// MARK: Conformance + +extension TextFieldElement: TextOrDropdownElement { + public var rawData: String { + return text + } + + public func setRawData(_ rawData: String) { + setText(rawData) + } + +} + +extension DropdownFieldElement: TextOrDropdownElement { + public var rawData: String { + return items[selectedIndex].rawData + } + + public func setRawData(_ rawData: String) { + guard let itemIndex = items.firstIndex(where: {$0.rawData.lowercased() == rawData.lowercased() + || $0.pickerDisplayName.string.lowercased() == rawData.lowercased()}) else { + return + } + + select(index: itemIndex) + } +} diff --git a/StripeUICore/StripeUICore/Source/Events.swift b/StripeUICore/StripeUICore/Source/Events.swift new file mode 100644 index 00000000..828e803a --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Events.swift @@ -0,0 +1,30 @@ +// +// Events.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 10/22/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/// Sends the event down the view hierarchy +@_spi(STP) public func sendEventToSubviews(_ event: STPEvent, from view: UIView) { + if let view = view as? EventHandler { + view.handleEvent(event) + } + for subview in view.subviews { + sendEventToSubviews(event, from: subview) + } +} + +@frozen @_spi(STP) public enum STPEvent { + case shouldEnableUserInteraction + case shouldDisableUserInteraction + case viewDidAppear +} + +@_spi(STP) public protocol EventHandler { + func handleEvent(_ event: STPEvent) +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/CompatibleColor.swift b/StripeUICore/StripeUICore/Source/Helpers/CompatibleColor.swift new file mode 100644 index 00000000..fedba2c2 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/CompatibleColor.swift @@ -0,0 +1,15 @@ +// +// CompatibleColor.swift +// StripeUICore +// +// Created by Ramon Torres on 10/26/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +// TODO(ramont): Remove. + +/// Alias to help migrating active feature branches. +@available(*, deprecated, renamed: "UIColor") +@_spi(STP) public typealias CompatibleColor = UIColor diff --git a/StripeUICore/StripeUICore/Source/Helpers/ImageMaker.swift b/StripeUICore/StripeUICore/Source/Helpers/ImageMaker.swift new file mode 100644 index 00000000..2aee2b16 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/ImageMaker.swift @@ -0,0 +1,64 @@ +// +// ImageMaker.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +@_spi(STP) import StripeCore + +@_spi(STP) public protocol ImageMaker { + associatedtype BundleLocator: BundleLocatorProtocol +} + +@_spi(STP) public extension ImageMaker { + private static func imageNamed( + _ imageName: String, + templateIfAvailable: Bool, + compatibleWith traitCollection: UITraitCollection? = nil + ) -> UIImage? { + + var image = UIImage( + named: imageName, in: BundleLocator.resourcesBundle, compatibleWith: traitCollection) + + if image == nil { + image = UIImage(named: imageName, in: nil, compatibleWith: traitCollection) + } + + if templateIfAvailable { + image = image?.withRenderingMode(.alwaysTemplate) + } + + return image + } + + static func safeImageNamed( + _ imageName: String, + templateIfAvailable: Bool = false, + overrideUserInterfaceStyle: UIUserInterfaceStyle? = nil + ) -> UIImage { + let image: UIImage + if let overrideUserInterfaceStyle = overrideUserInterfaceStyle { + let appearanceTrait = UITraitCollection(userInterfaceStyle: overrideUserInterfaceStyle) + image = imageNamed(imageName, templateIfAvailable: templateIfAvailable, compatibleWith: appearanceTrait) ?? UIImage() + } else { + image = imageNamed(imageName, templateIfAvailable: templateIfAvailable) ?? UIImage() + } + assert(image.size != .zero, "Failed to find an image named \(imageName)") + return image + } +} + +@_spi(STP) public extension ImageMaker where Self: RawRepresentable, RawValue == String { + func makeImage(template: Bool = false, overrideUserInterfaceStyle: UIUserInterfaceStyle? = nil) -> UIImage { + return Self.safeImageNamed( + self.rawValue, + templateIfAvailable: template, + overrideUserInterfaceStyle: overrideUserInterfaceStyle + ) + } +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/InputFormColors.swift b/StripeUICore/StripeUICore/Source/Helpers/InputFormColors.swift new file mode 100644 index 00000000..2d745f83 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/InputFormColors.swift @@ -0,0 +1,45 @@ +// +// InputFormColors.swift +// StripeUICore +// +// Created by Cameron Sabol on 9/22/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public enum InputFormColors { + + public static var textColor: UIColor { + return .label + } + + public static var disabledTextColor: UIColor { + return .dynamic( + light: UIColor(red: 60.0 / 255.0, green: 60.0 / 255.0, blue: 67.0 / 255.0, alpha: 0.6), + dark: UIColor(red: 235.0 / 255.0, green: 235.0 / 255.0, blue: 245.0 / 255.0, alpha: 0.6) + ) + } + + public static var errorColor: UIColor { + return .systemRed + } + + public static var outlineColor: UIColor { + return UIColor(red: 120.0 / 255.0, green: 120.0 / 255.0, blue: 128.0 / 255.0, alpha: 0.36) + } + + public static var backgroundColor: UIColor { + return .dynamic( + light: .systemBackground, + dark: UIColor(red: 116.0 / 255.0, green: 116.0 / 255.0, blue: 128.0 / 255.0, alpha: 0.18) + ) + } + + public static var disabledBackgroundColor: UIColor { + return .dynamic( + light: UIColor(red: 248.0 / 255.0, green: 248.0 / 255.0, blue: 248.0 / 255.0, alpha: 1), + dark: UIColor(red: 116.0 / 255.0, green: 116.0 / 255.0, blue: 128.0 / 255.0, alpha: 0.18) + ) + } +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/RegionCodeProvider.swift b/StripeUICore/StripeUICore/Source/Helpers/RegionCodeProvider.swift new file mode 100644 index 00000000..03a5f98c --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/RegionCodeProvider.swift @@ -0,0 +1,14 @@ +// +// RegionCodeProvider.swift +// StripeUICore +// +// Created by Cameron Sabol on 1/31/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Internal protocol to represent an object that provides a region code +@_spi(STP) public protocol RegionCodeProvider { + var regionCode: String { get } +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/STPLocalizedString.swift b/StripeUICore/StripeUICore/Source/Helpers/STPLocalizedString.swift new file mode 100644 index 00000000..969ed2c9 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/STPLocalizedString.swift @@ -0,0 +1,13 @@ +// +// STPLocalizedString.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore + +@inline(__always) func STPLocalizedString(_ key: String, _ comment: String?) -> String { + return STPLocalizationUtils.localizedStripeString(forKey: key, bundleLocator: StripeUICoreBundleLocator.self) +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/StackViewWithSeparator.swift b/StripeUICore/StripeUICore/Source/Helpers/StackViewWithSeparator.swift new file mode 100644 index 00000000..abc6f39f --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/StackViewWithSeparator.swift @@ -0,0 +1,215 @@ +// +// StackViewWithSeparator.swift +// StripeUICore +// +// Created by Cameron Sabol on 9/22/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +/// For internal SDK use only +@objc(STP_Internal_StackViewWithSeparator) +@_spi(STP) public class StackViewWithSeparator: UIStackView { + + public static let borderlessInset: CGFloat = 10 + + public enum SeparatoryStyle { + case full + case partial + } + + public var separatorStyle: SeparatoryStyle = .full { + didSet { + for view in arrangedSubviews { + if let substackView = view as? StackViewWithSeparator { + substackView.separatorStyle = separatorStyle + } + } + } + } + + public var separatorColor: UIColor = .clear { + didSet { + separatorLayer.strokeColor = separatorColor.cgColor + backgroundView.layer.borderColor = separatorColor.cgColor + } + } + + /// Commonly referred to as `borderWidth` + public override var spacing: CGFloat { + didSet { + backgroundView.layer.borderWidth = spacing + separatorLayer.lineWidth = spacing + layoutMargins = UIEdgeInsets( + top: spacing, left: spacing, bottom: spacing, right: spacing) + } + } + + public var drawBorder: Bool = false { + didSet { + isLayoutMarginsRelativeArrangement = drawBorder + if drawBorder { + addSubview(backgroundView) + sendSubviewToBack(backgroundView) + } else { + backgroundView.removeFromSuperview() + } + } + } + + public var borderCornerRadius: CGFloat { + get { + return backgroundView.layer.cornerRadius + } + set { + backgroundView.layer.cornerRadius = newValue + } + } + + public var borderColor: UIColor = .systemGray3 { + didSet { + backgroundView.layer.borderColor = borderColor.cgColor + } + } + + @objc + override public var isUserInteractionEnabled: Bool { + didSet { + if isUserInteractionEnabled { + backgroundView.backgroundColor = customBackgroundColor + } else { + backgroundView.backgroundColor = customBackgroundDisabledColor + } + } + } + + public var hideShadow: Bool = false { + didSet { + if hideShadow { + backgroundView.layer.shadowOffset = .zero + backgroundView.layer.shadowColor = UIColor.clear.cgColor + backgroundView.layer.shadowOpacity = 0 + backgroundView.layer.shadowRadius = 0 + backgroundView.layer.shadowOpacity = 0 + } else { + configureDefaultShadow() + } + } + } + + public var customBackgroundColor: UIColor? = InputFormColors.backgroundColor { + didSet { + if isUserInteractionEnabled { + backgroundView.backgroundColor = customBackgroundColor + } + } + } + + public var customBackgroundDisabledColor: UIColor? = InputFormColors.disabledBackgroundColor { + didSet { + if isUserInteractionEnabled { + backgroundView.backgroundColor = customBackgroundColor + } + } + } + + private let separatorLayer = CAShapeLayer() + let backgroundView: UIView = { + let view = UIView() + view.backgroundColor = InputFormColors.backgroundColor + view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + // copied from configureDefaultShadow to avoid recursion on init + view.layer.shadowOffset = CGSize(width: 0, height: 2) + view.layer.shadowColor = UIColor.black.cgColor + view.layer.shadowOpacity = 0.05 + view.layer.shadowRadius = 4 + return view + }() + + func configureDefaultShadow() { + backgroundView.layer.shadowOffset = CGSize(width: 0, height: 2) + backgroundView.layer.shadowColor = UIColor.black.cgColor + backgroundView.layer.shadowOpacity = 0.05 + backgroundView.layer.shadowRadius = 4 + } + + override public func layoutSubviews() { + if backgroundView.superview == self { + sendSubviewToBack(backgroundView) + } + super.layoutSubviews() + if separatorLayer.superlayer == nil { + layer.addSublayer(separatorLayer) + } + separatorLayer.strokeColor = separatorColor.cgColor + + let path = UIBezierPath() + path.lineWidth = spacing + + if spacing > 0 { + // inter-view separators + let nonHiddenArrangedSubviews = arrangedSubviews.filter({ !$0.isHidden }) + + let isRTL = traitCollection.layoutDirection == .rightToLeft + + for view in nonHiddenArrangedSubviews { + + if axis == .vertical { + + switch separatorStyle { + case .full: + if view == nonHiddenArrangedSubviews.last { + continue + } + path.move(to: CGPoint(x: view.frame.minX, y: view.frame.maxY + 0.5 * spacing)) + path.addLine( + to: CGPoint(x: view.frame.maxX, y: view.frame.maxY + 0.5 * spacing)) + case .partial: + // no-op in partial + break + } + + } else { // .horizontal + + switch separatorStyle { + case .full: + if (!isRTL && view == nonHiddenArrangedSubviews.first) + || (isRTL && view == nonHiddenArrangedSubviews.last) + { + continue + } + path.move(to: CGPoint(x: view.frame.minX - 0.5 * spacing, y: view.frame.minY)) + path.addLine( + to: CGPoint(x: view.frame.minX - 0.5 * spacing, y: view.frame.maxY)) + case .partial: + assert(!drawBorder, "Can't combine partial separator style in a horizontal stack with draw border") + if 2 * StackViewWithSeparator.borderlessInset * spacing >= view.frame.width { + continue + } + // These values are chosen to optimize for use in STPCardFormView with borderless style + path.move(to: CGPoint(x: view.frame.minX + StackViewWithSeparator.borderlessInset * spacing, y: view.frame.maxY)) + path.addLine( + to: CGPoint(x: view.frame.maxX - StackViewWithSeparator.borderlessInset * spacing, y: view.frame.maxY)) + } + + } + + } + } + + separatorLayer.path = path.cgPath + backgroundView.layer.shadowPath = hideShadow ? nil : + UIBezierPath(roundedRect: bounds, cornerRadius: borderCornerRadius).cgPath + } + +#if !canImport(CompositorServices) + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + // CGColor's must be manually updated when the trait collection changes + backgroundView.layer.borderColor = borderColor.cgColor + separatorLayer.strokeColor = separatorColor.cgColor + } +#endif + +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/String+CountryEmoji.swift b/StripeUICore/StripeUICore/Source/Helpers/String+CountryEmoji.swift new file mode 100644 index 00000000..b50399cc --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/String+CountryEmoji.swift @@ -0,0 +1,27 @@ +// +// String+CountryEmoji.swift +// StripeUICore +// +// Created by Cameron Sabol on 9/30/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +import UIKit + +@_spi(STP) public extension String { + static func countryFlagEmoji(for countryCode: String) -> String? { + let capitalized = countryCode.uppercased() + guard Locale.stp_isoRegionCodes.contains(capitalized) else { + return nil + } + + let unicodeScalars = capitalized.unicodeScalars.compactMap({ Unicode.Scalar($0.value + 127397) }) + guard unicodeScalars.count == 2 else { + return nil + } + + return String(String.UnicodeScalarView(unicodeScalars)) + + } +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift b/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift new file mode 100644 index 00000000..86fafb39 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift @@ -0,0 +1,351 @@ +// +// String+Localized.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +// Localized strings that are used in multiple contexts. Collected here to avoid re-translation +// We use snake case to make long names easier to read. +@_spi(STP) public extension String.Localized { + + static var address: String { + STPLocalizedString( + "Address", + """ + Caption for Address field on address form + Section header for address fields + """ + ) + } + + static var address_line1: String { + STPLocalizedString("Address line 1", "Address line 1 placeholder for billing address form.\nLabel for address line 1 field") + } + + static var address_line2: String { + STPLocalizedString("Address line 2", "Label for address line 2 field") + } + + static var country_or_region: String { + STPLocalizedString("Country or region", "Country selector and postal code entry form header title\nLabel of an address field") + } + + static var country: String { + STPLocalizedString("Country", "Caption for Country field on address form") + } + + static var email: String { + STPLocalizedString("Email", "Label for Email field on form") + } + + static var name: String { + STPLocalizedString("Name", "Label for Name field on form") + } + + static var full_name: String { + STPLocalizedString("Full name", "Label for Full name field on form") + } + + static var given_name: String { + STPLocalizedString("First", "Label for first (given) name field") + } + + static var family_name: String { + STPLocalizedString("Last", "Label for last (family) name field") + } + + static var nameOnAccount: String { + STPLocalizedString("Name on account", "Label for Name on account field on form") + } + + static var company: String { + STPLocalizedString("Company", "Label for Company field on form") + } + + static var invalid_email: String { + STPLocalizedString("Your email is invalid.", "Error message when email is invalid") + } + + static var billing_same_as_shipping: String { + STPLocalizedString("Billing address is same as shipping", "Label for a checkbox that makes customers billing address same as their shipping address") + } + + // MARK: - Phone number + + static var phoneNumber: String { + STPLocalizedString("Phone number", "Caption for Phone number field on address form") + } + + static var incomplete_phone_number: String { + STPLocalizedString("Incomplete phone number", "Error description for incomplete phone number") + } + + static var invalid_phone_number: String { + STPLocalizedString("Unable to parse phone number", "Error string when we can't parse a phone number") + } + + static var optional_field: String { + STPLocalizedString( + "%@ (optional)", + "The label of a text field that is optional. For example, 'Email (optional)' or 'Name (optional)" + ) + } + + static var other: String { + STPLocalizedString("Other", "An option in a dropdown selector indicating the customer's desired selection is not in the list. e.g., 'Choose your bank: Bank1, Bank2, Other'") + } + + // MARK: City field labels + + static var city: String { + STPLocalizedString("City", "Caption for City field on address form") + } + + static var district: String { + STPLocalizedString("District", "Label for the district field on an address form") + } + + static var suburb: String { + STPLocalizedString("Suburb", "Label of an address field") + } + + static var post_town: String { + STPLocalizedString("Town or city", "Label of an address field") + } + + static var suburb_or_city: String { + STPLocalizedString("Suburb or city", "Label of an address field") + } + + // MARK: Postal code field labels + + static var eircode: String { + STPLocalizedString("Eircode", "Label of an address field") + } + + static var postal_pin: String { + "PIN" // Intentionally left as-is + } + + static var postal_code: String { + STPLocalizedString( + "Postal code", + """ + Label of an address field + Short string for postal code (text used in non-US countries) + """ + ) + } + + static var zip: String { + STPLocalizedString( + "ZIP", + """ + Label of an address field + Short string for zip code (United States only) + Zip code placeholder US only + """ + ) + } + + static var your_zip_is_incomplete: String { + STPLocalizedString("Your ZIP is incomplete.", "Error message for when ZIP code in form is incomplete (US only)") + } + + static var your_postal_code_is_incomplete: String { + STPLocalizedString("Your postal code is incomplete.", "Error message for when postal code in form is incomplete") + } + + // MARK: State field labels + + static var area: String { + STPLocalizedString("Area", "Label of an address field") + } + + static var county: String { + STPLocalizedString( + "County", + """ + Caption for County field on address form (only countries that use county, like United Kingdom) + Label of an address field + """ + ) + } + + static var department: String { + STPLocalizedString("Department", "Label of an address field") + } + + static var do_si: String { + STPLocalizedString("Do Si", "Label of an address field") + } + + static var emirate: String { + STPLocalizedString("Emirate", "Label of an address field") + } + + static var island: String { + STPLocalizedString("Island", "Label of an address field") + } + + static var oblast: String { + STPLocalizedString("Oblast", "Label of an address field") + } + + static var parish: String { + STPLocalizedString("Parish", "Label of an address field") + } + + static var prefecture: String { + STPLocalizedString("Prefecture", "Label of an address field") + } + + static var province: String { + STPLocalizedString( + "Province", + """ + Caption for Province field on address form (only countries that use province, like Canada) + Label of an address field + """ + ) + } + + static var state: String { + STPLocalizedString( + "State", + """ + Caption for State field on address form (only countries that use state , like United States) + Label of an address field + """ + ) + } + + // MARK: - Account + static var accountNumber: String { + STPLocalizedString( + "Account number", + """ + Caption for account number + """ + ) + } + static var incompleteBSBEntered: String { + STPLocalizedString( + "The BSB you entered is incomplete.", + "Error string displayed to user when they have entered an incomplete BSB number.") + } + + static var invalidSortCodeEntered: String { + STPLocalizedString( + "The sort code you entered is invalid.", + "Error string displayed to user when they have entered an invalid 'sort code' (a bank routing number used in the UK and Ireland)") + } + + static var incompleteAccountNumber: String { + STPLocalizedString("The account number you entered is incomplete.", "Error description for incomplete account number") + } + + static var bank_account_xxxx: String { + STPLocalizedString( + "Bank account •••• %@", + "Content for alert popup prompting to confirm removing a saved bank account. e.g. 'Bank account •••• 4242'") + } + + static var removeBankAccount: String { + STPLocalizedString( + "Remove bank account?", + "Title for confirmation alert to remove a saved bank account payment method") + } + + // MARK: - Control strings + static var error: String { + return STPLocalizedString("Error", "Text for error labels") + } + + static var cancel: String { + STPLocalizedString("Cancel", "Button title to cancel action in an alert") + } + + static var closeFormTitle: String { + STPLocalizedString("Do you want to close this form?", + "Used as the title for prompting the user if they want to close the sheet") + } + + static var paymentInfoWontBeSaved: String { + STPLocalizedString("Your payment information will not be saved.", + "Used as the title for prompting the user if they want to close the sheet") + } + + static var ok: String { + STPLocalizedString("OK", "ok button") + } + + static var `continue`: String { + STPLocalizedString("Continue", "Text for continue button") + } + + static var remove: String { + STPLocalizedString("Remove", "Button title for confirmation alert to remove a saved payment method") + } + + static var search: String { + STPLocalizedString("Search", "Title of a button with a 🔍 (magnifying glass) icon that starts a search when tapped") + } + + static var useRotorToAccessLinks: String { + STPLocalizedString( + "Use rotor to access links", + "Accessibility hint indicating to use the accessibility rotor to open links. The word 'rotor' should be localized to match Apple's language here: https://support.apple.com/HT204783" + ) + } + + static var edit: String { + STPLocalizedString( + "Edit", + "Button title to enter editing mode" + ) + } + + // MARK: - UPI + + static var upi_id: String { + STPLocalizedString("UPI ID", "Label for UPI ID number field on form") + } + + static var invalid_upi_id: String { + STPLocalizedString("Invalid UPI ID", "Error message when UPI ID is invalid") + } + + // MARK: - Blik + + static var blik_code: String { + STPLocalizedString("BLIK code", "Label for BLIK code number field on form") + } + + static var incomplete_blik_code: String { + STPLocalizedString("Your BLIK code is incomplete.", "Error message when BLIK code is incomplete") + } + + static var invalid_blik_code: String { + STPLocalizedString("Your BLIK code is invalid.", "Error message when BLIK code is invalid") + } + + // MARK: - Card brand choice + + static var card_brand_dropdown_placeholder: String { + STPLocalizedString("Select card brand (optional)", "Message when a user is selecting a card brand in a dropdown") + } + + static var card_brand: String { + STPLocalizedString("Card brand", "Label an input field to update card brand") + } + + static var remove_card: String { + STPLocalizedString("Remove card", "Label on a button for removing a card") + } +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/String+RegionCodeProvider.swift b/StripeUICore/StripeUICore/Source/Helpers/String+RegionCodeProvider.swift new file mode 100644 index 00000000..a6c20262 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/String+RegionCodeProvider.swift @@ -0,0 +1,16 @@ +// +// String+RegionCodeProvider.swift +// StripeUICore +// +// Created by Cameron Sabol on 1/31/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// Default conformance of String to RegionCodeProvider +@_spi(STP) extension String: RegionCodeProvider { + public var regionCode: String { + return self + } +} diff --git a/StripeUICore/StripeUICore/Source/Helpers/StripeUICoreBundleLocator.swift b/StripeUICore/StripeUICore/Source/Helpers/StripeUICoreBundleLocator.swift new file mode 100644 index 00000000..80e2fcc8 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Helpers/StripeUICoreBundleLocator.swift @@ -0,0 +1,19 @@ +// +// StripeUICoreBundleLocator.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeCore + +@_spi(STP) public final class StripeUICoreBundleLocator: BundleLocatorProtocol { + public static let internalClass: AnyClass = StripeUICoreBundleLocator.self + public static let bundleName = "StripeUICoreBundle" + #if SWIFT_PACKAGE + public static let spmResourcesBundle = Bundle.module + #endif + public static let resourcesBundle = StripeUICoreBundleLocator.computeResourcesBundle() +} diff --git a/StripeUICore/StripeUICore/Source/Image.swift b/StripeUICore/StripeUICore/Source/Image.swift new file mode 100644 index 00000000..30cbec5c --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Image.swift @@ -0,0 +1,30 @@ +// +// Image.swift +// StripeUICore +// +// Created by Mel Ludowise on 9/10/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import Foundation + +/// The canonical set of all image files in the SDK. +/// This helps us avoid duplicates and automatically test that all images load properly +/// Raw value is the image file name. We use snake case to make long names easier to read. +@_spi(STP) public enum Image: String, ImageMaker, CaseIterable { + public typealias BundleLocator = StripeUICoreBundleLocator + + // Icons/symbols + case icon_chevron_down = "icon_chevron_down" + case icon_clear = "icon_clear" + + // Brand Icons + case brand_stripe = "brand_stripe" + case icon_error = "form_error_icon" +} + +@_spi(STP) public extension Image { + static func brandImage(named name: String) -> Image? { + return Image(rawValue: "brand_\(name)") + } +} diff --git a/StripeUICore/StripeUICore/Source/Validators/BankRoutingNumber.swift b/StripeUICore/StripeUICore/Source/Validators/BankRoutingNumber.swift new file mode 100644 index 00000000..7da5877b --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Validators/BankRoutingNumber.swift @@ -0,0 +1,61 @@ +// +// BankRoutingNumber.swift +// StripeUICore +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) import StripeCore + +@_spi(STP) public protocol BankRoutingNumber { + var number: String { get } + var pattern: String { get } +} + +@_spi(STP) public extension BankRoutingNumber { + var isComplete: Bool { + return formattedNumber().count >= pattern.count + } + + func formattedNumber() -> String { + guard let formatter = TextFieldFormatter(format: pattern) else { + return number + } + let allowedCharacterSet = CharacterSet.stp_asciiDigit + + let result = formatter.applyFormat( + to: number.stp_stringByRemovingCharacters(from: allowedCharacterSet.inverted), + shouldAppendRemaining: true + ) + guard !result.isEmpty else { + return "" + } + return result + } + + func bsbNumberText() -> String { + return number.filter { $0 != "-" } + } +} + +@_spi(STP) public struct BSBNumber: BankRoutingNumber { + public var number: String + + public init(number: String) { + self.number = number + } + + public let pattern: String = "###-###" +} + +@_spi(STP) public struct SortCode: BankRoutingNumber { + public var number: String + + public init(number: String) { + self.number = number + } + + public let pattern: String = "##-##-##" +} diff --git a/StripeUICore/StripeUICore/Source/Validators/PhoneNumber.swift b/StripeUICore/StripeUICore/Source/Validators/PhoneNumber.swift new file mode 100644 index 00000000..2a0beb4d --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Validators/PhoneNumber.swift @@ -0,0 +1,441 @@ +// +// PhoneNumber.swift +// StripeUICore +// +// Created by Cameron Sabol on 9/22/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) import StripeCore + +@_spi(STP) public struct PhoneNumber { + public enum Format { + /// Formatted according to e164 standard, e.g. +15555555555 + case e164 + /// Formatted for display as non-international number, e.g. (555) 555-555 + case national + /// Formatted for display an an international number with country code, e.g. +1 (555) 555-5555 + case international + + static let e164FormatMaxDigits = 15 + } + + public func string(as format: Format) -> String { + return metadata.formattedNumber(number, format: format) + } + + /// The country that matches this phone number, e.g. "US" + public var countryCode: String { + return metadata.regionCode + } + + /// The phone number prefix for the country of this phone number, e.g. "+1" + public var prefix: String { + return metadata.prefix + } + + /// Whether this represents a complete phone number + public var isComplete: Bool { + return string(as: .national).count >= metadata.pattern.count + } + + /// Whether the phone number is empty (it may have a country code, but it has no other digits) + public var isEmpty: Bool { + return number.isEmpty + } + + /// The phone number without the country prefix and containing only digits + public let number: String + private let metadata: Metadata + + public init?(number: String, countryCode: String?) { + guard let countryCode = countryCode, + let metadata = Metadata.metadata(for: countryCode) else { + return nil + } + + self.number = number + self.metadata = metadata + } + + init(number: String, metadata: Metadata) { + self.number = number + self.metadata = metadata + } + + /// Parses phone numbers in (*globalized*) E.164 format. + /// + /// - Note: Our metadata lacks of national destination code (area code) ranges, because of this we fallback to + /// the device's locale to disambiguate when a number can possibly belong to multiple regions. + /// + /// - Parameters: + /// - number: Phone number to parse. + /// - locale: User's locale. + /// - Returns: `PhoneNumber`, or `nil` if the number is not parsable. + public static func fromE164(_ number: String, locale: Locale = .current) -> PhoneNumber? { + let characters: [Character] = .init(number) + + // Matching regex: ^\+[1-9]\d{2,14}$ + guard + characters.count > 4, + characters.count <= Format.e164FormatMaxDigits + 1, + characters[0] == "+", + characters[1] != "0", + characters[1...].allSatisfy({ + $0.unicodeScalars.allSatisfy(CharacterSet.stp_asciiDigit.contains(_:)) + }) + else { + return nil + } + + let makePhoneNumber: (Metadata) -> PhoneNumber = { metadata in + return PhoneNumber( + number: String(characters[metadata.prefix.count...]), + metadata: metadata + ) + } + + // This filter should narrow down the metadata list to just 1 candidate in most cases, + // as very few countries share country calling codes. Country calling codes are also + // *Prefix codes*, which means that two codes will never overlap. + let candidates = Metadata.allMetadata.filter({ number.hasPrefix($0.prefix) }) + if candidates.count == 1 { + return candidates.first.flatMap(makePhoneNumber) + } + + // This second filter uses the device's locale to pick a winner out of N candidates. + if let winner = candidates.first(where: { $0.regionCode == locale.stp_regionCode }) { + return makePhoneNumber(winner) + } + + // If no winner, we simply return the first candidate. Our metadata is sorted in a way that the + // main country of a prefix is always first. + return candidates.first.flatMap(makePhoneNumber) + } + +} + +@_spi(STP) public extension PhoneNumber { + struct Metadata: RegionCodeProvider { + + private static var metadataByCountryCodeCache: [String: Metadata] = [:] + + public static func metadata(for countryCode: String) -> Metadata? { + if let cached = metadataByCountryCodeCache[countryCode] { + return cached + } + if let metadata = allMetadata.first(where: { $0.regionCode == countryCode }) { + metadataByCountryCodeCache[countryCode] = metadata + return metadata + } + return nil + } + + public let prefix: String + public let regionCode: String + internal let pattern: String + + public var sampleFilledPattern: String { + let numDigitsInPattern = pattern.filter({ $0 == "#" }).count + return formattedNumber(String(repeating: "5", count: numDigitsInPattern), format: .national) + } + + func formattedNumber(_ number: String, format: Format) -> String { + guard let formatter = TextFieldFormatter(format: pattern) else { + return number + } + + let allowedCharacterSet: CharacterSet = CharacterSet.stp_asciiDigit.union(CharacterSet(charactersIn: String(TextFieldFormatter.redactedNumberCharacter))) // allow '•' for redacted numbers + + let result = formatter.applyFormat( + to: number.stp_stringByRemovingCharacters(from: allowedCharacterSet.inverted), + shouldAppendRemaining: true + ) + + guard !result.isEmpty else { + return "" + } + + switch format { + case .e164: + var resultDigits = result.stp_stringByRemovingCharacters(from: allowedCharacterSet.inverted) + // e164 drops leading 0s + if resultDigits.hasPrefix("0") { + resultDigits = String(resultDigits.suffix(resultDigits.count - 1)) + } + + resultDigits = prefix.stp_stringByRemovingCharacters(from: allowedCharacterSet.inverted) + resultDigits + // e164 doesn't accept more than 15 digits + if resultDigits.count > Format.e164FormatMaxDigits { + resultDigits = String(resultDigits.prefix(Format.e164FormatMaxDigits)) + } + + return "+" + resultDigits + case .national: + return result + case .international: + return prefix + " " + result + } + } + + // Note: The patterns here are not complete in some cases, e.g. + // JP where the first group of numbers will sometimes have 3 digits + // for mobile but we only expect 2. In these cases the input should + // allow for entry passed the pattern length + public static let allMetadata: [Metadata] = [ + // NANP member countries and territories (Zone 1) + // https://en.wikipedia.org/wiki/North_American_Numbering_Plan#Countries_and_territories + Metadata(prefix: "+1", regionCode: "US", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "CA", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "AG", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "AS", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "AI", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "BB", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "BM", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "BS", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "DM", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "DO", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "GD", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "GU", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "JM", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "KN", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "KY", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "LC", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "MP", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "MS", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "PR", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "SX", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "TC", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "TT", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "VC", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "VG", pattern: "(###) ###-####"), + Metadata(prefix: "+1", regionCode: "VI", pattern: "(###) ###-####"), + // Rest of the world + Metadata(prefix: "+20", regionCode: "EG", pattern: "### ### ####"), + Metadata(prefix: "+211", regionCode: "SS", pattern: "### ### ###"), + Metadata(prefix: "+212", regionCode: "MA", pattern: "###-######"), + Metadata(prefix: "+212", regionCode: "EH", pattern: "###-######"), + Metadata(prefix: "+213", regionCode: "DZ", pattern: "### ## ## ##"), + Metadata(prefix: "+216", regionCode: "TN", pattern: "## ### ###"), + Metadata(prefix: "+218", regionCode: "LY", pattern: "##-#######"), + Metadata(prefix: "+220", regionCode: "GM", pattern: "### ####"), + Metadata(prefix: "+221", regionCode: "SN", pattern: "## ### ## ##"), + Metadata(prefix: "+222", regionCode: "MR", pattern: "## ## ## ##"), + Metadata(prefix: "+223", regionCode: "ML", pattern: "## ## ## ##"), + Metadata(prefix: "+224", regionCode: "GN", pattern: "### ## ## ##"), + Metadata(prefix: "+225", regionCode: "CI", pattern: "## ## ## ##"), + Metadata(prefix: "+226", regionCode: "BF", pattern: "## ## ## ##"), + Metadata(prefix: "+227", regionCode: "NE", pattern: "## ## ## ##"), + Metadata(prefix: "+228", regionCode: "TG", pattern: "## ## ## ##"), + Metadata(prefix: "+229", regionCode: "BJ", pattern: "## ## ## ##"), + Metadata(prefix: "+230", regionCode: "MU", pattern: "#### ####"), + Metadata(prefix: "+231", regionCode: "LR", pattern: "### ### ###"), + Metadata(prefix: "+232", regionCode: "SL", pattern: "## ######"), + Metadata(prefix: "+233", regionCode: "GH", pattern: "## ### ####"), + Metadata(prefix: "+234", regionCode: "NG", pattern: "### ### ####"), + Metadata(prefix: "+235", regionCode: "TD", pattern: "## ## ## ##"), + Metadata(prefix: "+236", regionCode: "CF", pattern: "## ## ## ##"), + Metadata(prefix: "+237", regionCode: "CM", pattern: "## ## ## ##"), + Metadata(prefix: "+238", regionCode: "CV", pattern: "### ## ##"), + Metadata(prefix: "+239", regionCode: "ST", pattern: "### ####"), + Metadata(prefix: "+240", regionCode: "GQ", pattern: "### ### ###"), + Metadata(prefix: "+241", regionCode: "GA", pattern: "## ## ## ##"), + Metadata(prefix: "+242", regionCode: "CG", pattern: "## ### ####"), + Metadata(prefix: "+243", regionCode: "CD", pattern: "### ### ###"), + Metadata(prefix: "+244", regionCode: "AO", pattern: "### ### ###"), + Metadata(prefix: "+245", regionCode: "GW", pattern: "### ####"), + Metadata(prefix: "+246", regionCode: "IO", pattern: "### ####"), + Metadata(prefix: "+247", regionCode: "AC", pattern: ""), + Metadata(prefix: "+248", regionCode: "SC", pattern: "# ### ###"), + Metadata(prefix: "+250", regionCode: "RW", pattern: "### ### ###"), + Metadata(prefix: "+251", regionCode: "ET", pattern: "## ### ####"), + Metadata(prefix: "+252", regionCode: "SO", pattern: "## #######"), + Metadata(prefix: "+253", regionCode: "DJ", pattern: "## ## ## ##"), + Metadata(prefix: "+254", regionCode: "KE", pattern: "## #######"), + Metadata(prefix: "+255", regionCode: "TZ", pattern: "### ### ###"), + Metadata(prefix: "+256", regionCode: "UG", pattern: "### ######"), + Metadata(prefix: "+257", regionCode: "BI", pattern: "## ## ## ##"), + Metadata(prefix: "+258", regionCode: "MZ", pattern: "## ### ####"), + Metadata(prefix: "+260", regionCode: "ZM", pattern: "## #######"), + Metadata(prefix: "+261", regionCode: "MG", pattern: "## ## ### ##"), + Metadata(prefix: "+262", regionCode: "RE", pattern: ""), + Metadata(prefix: "+262", regionCode: "TF", pattern: ""), + Metadata(prefix: "+262", regionCode: "YT", pattern: "### ## ## ##"), + Metadata(prefix: "+263", regionCode: "ZW", pattern: "## ### ####"), + Metadata(prefix: "+264", regionCode: "NA", pattern: "## ### ####"), + Metadata(prefix: "+265", regionCode: "MW", pattern: "### ## ## ##"), + Metadata(prefix: "+266", regionCode: "LS", pattern: "#### ####"), + Metadata(prefix: "+267", regionCode: "BW", pattern: "## ### ###"), + Metadata(prefix: "+268", regionCode: "SZ", pattern: "#### ####"), + Metadata(prefix: "+269", regionCode: "KM", pattern: "### ## ##"), + Metadata(prefix: "+27", regionCode: "ZA", pattern: "## ### ####"), + Metadata(prefix: "+290", regionCode: "SH", pattern: ""), + Metadata(prefix: "+290", regionCode: "TA", pattern: ""), + Metadata(prefix: "+291", regionCode: "ER", pattern: "# ### ###"), + Metadata(prefix: "+297", regionCode: "AW", pattern: "### ####"), + Metadata(prefix: "+298", regionCode: "FO", pattern: "######"), + Metadata(prefix: "+299", regionCode: "GL", pattern: "## ## ##"), + Metadata(prefix: "+30", regionCode: "GR", pattern: "### ### ####"), + Metadata(prefix: "+31", regionCode: "NL", pattern: "# ########"), + Metadata(prefix: "+32", regionCode: "BE", pattern: "### ## ## ##"), + Metadata(prefix: "+33", regionCode: "FR", pattern: "# ## ## ## ##"), + Metadata(prefix: "+34", regionCode: "ES", pattern: "### ## ## ##"), + Metadata(prefix: "+350", regionCode: "GI", pattern: "### #####"), + Metadata(prefix: "+351", regionCode: "PT", pattern: "### ### ###"), + Metadata(prefix: "+352", regionCode: "LU", pattern: "## ## ## ###"), + Metadata(prefix: "+353", regionCode: "IE", pattern: "## ### ####"), + Metadata(prefix: "+354", regionCode: "IS", pattern: "### ####"), + Metadata(prefix: "+355", regionCode: "AL", pattern: "## ### ####"), + Metadata(prefix: "+356", regionCode: "MT", pattern: "#### ####"), + Metadata(prefix: "+357", regionCode: "CY", pattern: "## ######"), + Metadata(prefix: "+358", regionCode: "FI", pattern: "## ### ## ##"), + Metadata(prefix: "+358", regionCode: "AX", pattern: ""), + Metadata(prefix: "+359", regionCode: "BG", pattern: "### ### ##"), + Metadata(prefix: "+36", regionCode: "HU", pattern: "## ### ####"), + Metadata(prefix: "+370", regionCode: "LT", pattern: "### #####"), + Metadata(prefix: "+371", regionCode: "LV", pattern: "## ### ###"), + Metadata(prefix: "+372", regionCode: "EE", pattern: "#### ####"), + Metadata(prefix: "+373", regionCode: "MD", pattern: "### ## ###"), + Metadata(prefix: "+374", regionCode: "AM", pattern: "## ######"), + Metadata(prefix: "+375", regionCode: "BY", pattern: "## ###-##-##"), + Metadata(prefix: "+376", regionCode: "AD", pattern: "### ###"), + Metadata(prefix: "+377", regionCode: "MC", pattern: "# ## ## ## ##"), + Metadata(prefix: "+378", regionCode: "SM", pattern: "## ## ## ##"), + Metadata(prefix: "+379", regionCode: "VA", pattern: ""), + Metadata(prefix: "+380", regionCode: "UA", pattern: "## ### ####"), + Metadata(prefix: "+381", regionCode: "RS", pattern: "## #######"), + Metadata(prefix: "+382", regionCode: "ME", pattern: "## ### ###"), + Metadata(prefix: "+383", regionCode: "XK", pattern: "## ### ###"), + Metadata(prefix: "+385", regionCode: "HR", pattern: "## ### ####"), + Metadata(prefix: "+386", regionCode: "SI", pattern: "## ### ###"), + Metadata(prefix: "+387", regionCode: "BA", pattern: "## ###-###"), + Metadata(prefix: "+389", regionCode: "MK", pattern: "## ### ###"), + Metadata(prefix: "+39", regionCode: "IT", pattern: "## #### ####"), + Metadata(prefix: "+40", regionCode: "RO", pattern: "## ### ####"), + Metadata(prefix: "+41", regionCode: "CH", pattern: "## ### ## ##"), + Metadata(prefix: "+420", regionCode: "CZ", pattern: "### ### ###"), + Metadata(prefix: "+421", regionCode: "SK", pattern: "### ### ###"), + Metadata(prefix: "+423", regionCode: "LI", pattern: "### ### ###"), + Metadata(prefix: "+43", regionCode: "AT", pattern: "### ######"), + Metadata(prefix: "+44", regionCode: "GB", pattern: "#### ######"), + Metadata(prefix: "+44", regionCode: "GG", pattern: "#### ######"), + Metadata(prefix: "+44", regionCode: "JE", pattern: "#### ######"), + Metadata(prefix: "+44", regionCode: "IM", pattern: "#### ######"), + Metadata(prefix: "+45", regionCode: "DK", pattern: "## ## ## ##"), + Metadata(prefix: "+46", regionCode: "SE", pattern: "##-### ## ##"), + Metadata(prefix: "+47", regionCode: "NO", pattern: "### ## ###"), + Metadata(prefix: "+47", regionCode: "BV", pattern: ""), + Metadata(prefix: "+47", regionCode: "SJ", pattern: "## ## ## ##"), + Metadata(prefix: "+48", regionCode: "PL", pattern: "## ### ## ##"), + Metadata(prefix: "+49", regionCode: "DE", pattern: "### #######"), + Metadata(prefix: "+500", regionCode: "FK", pattern: ""), + Metadata(prefix: "+500", regionCode: "GS", pattern: ""), + Metadata(prefix: "+501", regionCode: "BZ", pattern: "###-####"), + Metadata(prefix: "+502", regionCode: "GT", pattern: "#### ####"), + Metadata(prefix: "+503", regionCode: "SV", pattern: "#### ####"), + Metadata(prefix: "+504", regionCode: "HN", pattern: "####-####"), + Metadata(prefix: "+505", regionCode: "NI", pattern: "#### ####"), + Metadata(prefix: "+506", regionCode: "CR", pattern: "#### ####"), + Metadata(prefix: "+507", regionCode: "PA", pattern: "####-####"), + Metadata(prefix: "+508", regionCode: "PM", pattern: "## ## ##"), + Metadata(prefix: "+509", regionCode: "HT", pattern: "## ## ####"), + Metadata(prefix: "+51", regionCode: "PE", pattern: "### ### ###"), + Metadata(prefix: "+52", regionCode: "MX", pattern: "### ### ####"), + Metadata(prefix: "+537", regionCode: "CY", pattern: ""), + Metadata(prefix: "+54", regionCode: "AR", pattern: "## ##-####-####"), + Metadata(prefix: "+55", regionCode: "BR", pattern: "## #####-####"), + Metadata(prefix: "+56", regionCode: "CL", pattern: "# #### ####"), + Metadata(prefix: "+57", regionCode: "CO", pattern: "### #######"), + Metadata(prefix: "+58", regionCode: "VE", pattern: "###-#######"), + Metadata(prefix: "+590", regionCode: "BL", pattern: "### ## ## ##"), + Metadata(prefix: "+590", regionCode: "MF", pattern: ""), + Metadata(prefix: "+590", regionCode: "GP", pattern: "### ## ## ##"), + Metadata(prefix: "+591", regionCode: "BO", pattern: "########"), + Metadata(prefix: "+592", regionCode: "GY", pattern: "### ####"), + Metadata(prefix: "+593", regionCode: "EC", pattern: "## ### ####"), + Metadata(prefix: "+594", regionCode: "GF", pattern: "### ## ## ##"), + Metadata(prefix: "+595", regionCode: "PY", pattern: "## #######"), + Metadata(prefix: "+596", regionCode: "MQ", pattern: "### ## ## ##"), + Metadata(prefix: "+597", regionCode: "SR", pattern: "###-####"), + Metadata(prefix: "+598", regionCode: "UY", pattern: "#### ####"), + Metadata(prefix: "+599", regionCode: "CW", pattern: "# ### ####"), + Metadata(prefix: "+599", regionCode: "BQ", pattern: "### ####"), + Metadata(prefix: "+60", regionCode: "MY", pattern: "##-### ####"), + Metadata(prefix: "+61", regionCode: "AU", pattern: "### ### ###"), + Metadata(prefix: "+62", regionCode: "ID", pattern: "###-###-###"), + Metadata(prefix: "+63", regionCode: "PH", pattern: "#### ######"), + Metadata(prefix: "+64", regionCode: "NZ", pattern: "## ### ####"), + Metadata(prefix: "+65", regionCode: "SG", pattern: "#### ####"), + Metadata(prefix: "+66", regionCode: "TH", pattern: "## ### ####"), + Metadata(prefix: "+670", regionCode: "TL", pattern: "#### ####"), + Metadata(prefix: "+672", regionCode: "AQ", pattern: "## ####"), + Metadata(prefix: "+673", regionCode: "BN", pattern: "### ####"), + Metadata(prefix: "+674", regionCode: "NR", pattern: "### ####"), + Metadata(prefix: "+675", regionCode: "PG", pattern: "### ####"), + Metadata(prefix: "+676", regionCode: "TO", pattern: "### ####"), + Metadata(prefix: "+677", regionCode: "SB", pattern: "### ####"), + Metadata(prefix: "+678", regionCode: "VU", pattern: "### ####"), + Metadata(prefix: "+679", regionCode: "FJ", pattern: "### ####"), + Metadata(prefix: "+681", regionCode: "WF", pattern: "## ## ##"), + Metadata(prefix: "+682", regionCode: "CK", pattern: "## ###"), + Metadata(prefix: "+683", regionCode: "NU", pattern: ""), + Metadata(prefix: "+685", regionCode: "WS", pattern: ""), + Metadata(prefix: "+686", regionCode: "KI", pattern: ""), + Metadata(prefix: "+687", regionCode: "NC", pattern: "########"), + Metadata(prefix: "+688", regionCode: "TV", pattern: ""), + Metadata(prefix: "+689", regionCode: "PF", pattern: "## ## ##"), + Metadata(prefix: "+690", regionCode: "TK", pattern: ""), + Metadata(prefix: "+7", regionCode: "RU", pattern: "### ###-##-##"), + Metadata(prefix: "+7", regionCode: "KZ", pattern: ""), + Metadata(prefix: "+81", regionCode: "JP", pattern: "##-####-####"), + Metadata(prefix: "+82", regionCode: "KR", pattern: "##-####-####"), + Metadata(prefix: "+84", regionCode: "VN", pattern: "## ### ## ##"), + Metadata(prefix: "+852", regionCode: "HK", pattern: "#### ####"), + Metadata(prefix: "+853", regionCode: "MO", pattern: "#### ####"), + Metadata(prefix: "+855", regionCode: "KH", pattern: "## ### ###"), + Metadata(prefix: "+856", regionCode: "LA", pattern: "## ## ### ###"), + Metadata(prefix: "+86", regionCode: "CN", pattern: "### #### ####"), + Metadata(prefix: "+872", regionCode: "PN", pattern: ""), + Metadata(prefix: "+880", regionCode: "BD", pattern: "####-######"), + Metadata(prefix: "+886", regionCode: "TW", pattern: "### ### ###"), + Metadata(prefix: "+90", regionCode: "TR", pattern: "### ### ####"), + Metadata(prefix: "+91", regionCode: "IN", pattern: "## ## ######"), + Metadata(prefix: "+92", regionCode: "PK", pattern: "### #######"), + Metadata(prefix: "+93", regionCode: "AF", pattern: "## ### ####"), + Metadata(prefix: "+94", regionCode: "LK", pattern: "## # ######"), + Metadata(prefix: "+95", regionCode: "MM", pattern: "# ### ####"), + Metadata(prefix: "+960", regionCode: "MV", pattern: "###-####"), + Metadata(prefix: "+961", regionCode: "LB", pattern: "## ### ###"), + Metadata(prefix: "+962", regionCode: "JO", pattern: "# #### ####"), + Metadata(prefix: "+964", regionCode: "IQ", pattern: "### ### ####"), + Metadata(prefix: "+965", regionCode: "KW", pattern: "### #####"), + Metadata(prefix: "+966", regionCode: "SA", pattern: "## ### ####"), + Metadata(prefix: "+967", regionCode: "YE", pattern: "### ### ###"), + Metadata(prefix: "+968", regionCode: "OM", pattern: "#### ####"), + Metadata(prefix: "+970", regionCode: "PS", pattern: "### ### ###"), + Metadata(prefix: "+971", regionCode: "AE", pattern: "## ### ####"), + Metadata(prefix: "+972", regionCode: "IL", pattern: "##-###-####"), + Metadata(prefix: "+973", regionCode: "BH", pattern: "#### ####"), + Metadata(prefix: "+974", regionCode: "QA", pattern: "#### ####"), + Metadata(prefix: "+975", regionCode: "BT", pattern: "## ## ## ##"), + Metadata(prefix: "+976", regionCode: "MN", pattern: "#### ####"), + Metadata(prefix: "+977", regionCode: "NP", pattern: "###-#######"), + Metadata(prefix: "+992", regionCode: "TJ", pattern: "### ## ####"), + Metadata(prefix: "+993", regionCode: "TM", pattern: "## ##-##-##"), + Metadata(prefix: "+994", regionCode: "AZ", pattern: "## ### ## ##"), + Metadata(prefix: "+995", regionCode: "GE", pattern: "### ## ## ##"), + Metadata(prefix: "+996", regionCode: "KG", pattern: "### ### ###"), + Metadata(prefix: "+998", regionCode: "UZ", pattern: "## ### ## ##"), + ] + } +} + +// MARK: - Equatable + +extension PhoneNumber: Equatable { + public static func == (lhs: PhoneNumber, rhs: PhoneNumber) -> Bool { + return lhs.string(as: .e164) == rhs.string(as: .e164) + } +} diff --git a/StripeUICore/StripeUICore/Source/Validators/STPBlikCodeValidator.swift b/StripeUICore/StripeUICore/Source/Validators/STPBlikCodeValidator.swift new file mode 100644 index 00000000..8d1b94b4 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Validators/STPBlikCodeValidator.swift @@ -0,0 +1,19 @@ +// +// STPBlikCodeValidator.swift +// StripeUICoreTests +// +// Created by Fionn Barrett on 07/07/2023. +// + +import Foundation + +@_spi(STP) public class STPBlikCodeValidator { + public class func stringIsValidBlikCode(_ string: String?) -> Bool { + if string == nil || (string?.count ?? 0) > 6 { + return false + } + let pattern = "^[0-9]{6}$" + let predicate = NSPredicate(format: "SELF MATCHES %@", pattern) + return predicate.evaluate(with: string) + } +} diff --git a/StripeUICore/StripeUICore/Source/Validators/STPEmailAddressValidator.swift b/StripeUICore/StripeUICore/Source/Validators/STPEmailAddressValidator.swift new file mode 100644 index 00000000..d07d6209 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Validators/STPEmailAddressValidator.swift @@ -0,0 +1,29 @@ +// +// STPEmailAddressValidator.swift +// StripeUICore +// +// Created by Jack Flintermann on 3/23/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public class STPEmailAddressValidator: NSObject { + public class func stringIsValidPartialEmailAddress(_ string: String?) -> Bool { + guard let string = string else { + return true // an empty string isn't *invalid* + } + return (string.components(separatedBy: "@").count - 1) <= 1 + } + + public class func stringIsValidEmailAddress(_ string: String?) -> Bool { + if string == nil { + return false + } + // regex from http://www.regular-expressions.info/email.html + let pattern = + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" + let predicate = NSPredicate(format: "SELF MATCHES %@", pattern) + return predicate.evaluate(with: string?.lowercased()) + } +} diff --git a/StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift b/StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift new file mode 100644 index 00000000..f414b025 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift @@ -0,0 +1,28 @@ +// +// STPVPANumberValidator.swift +// StripeUICore +// +// Created by Nick Porter on 9/8/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +@_spi(STP) public class STPVPANumberValidator: NSObject { + public class func stringIsValidPartialVPANumber(_ string: String?) -> Bool { + guard let string = string else { + return true // an empty string isn't *invalid* + } + return (string.components(separatedBy: "@").count - 1) <= 1 + } + + public class func stringIsValidVPANumber(_ string: String?) -> Bool { + if string == nil || (string?.count ?? 0) > 30 { + return false + } + // regex from https://stackoverflow.com/questions/55143204/how-to-validate-a-upi-id-using-regex + let pattern = "[a-zA-Z0-9.\\-_]{2,256}@[a-zA-Z]{2,64}" + let predicate = NSPredicate(format: "SELF MATCHES %@", pattern) + return predicate.evaluate(with: string?.lowercased()) + } +} diff --git a/StripeUICore/StripeUICore/Source/Views/DoneButtonToolbar.swift b/StripeUICore/StripeUICore/Source/Views/DoneButtonToolbar.swift new file mode 100644 index 00000000..ff2a5657 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Views/DoneButtonToolbar.swift @@ -0,0 +1,74 @@ +// +// DoneButtonToolbar.swift +// StripeUICore +// +// Created by Mel Ludowise on 10/11/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) public protocol DoneButtonToolbarDelegate: AnyObject { + func didTapDone(_ toolbar: DoneButtonToolbar) + func didTapCancel(_ toolbar: DoneButtonToolbar) +} + +@_spi(STP) public extension DoneButtonToolbarDelegate { + func didTapCancel(_ toolbar: DoneButtonToolbar) { + // no-op, cancel button is hidden by default + } +} + +/// For internal SDK use only +@objc(STP_Internal_DoneButtonToolbar) +@_spi(STP) public final class DoneButtonToolbar: UIToolbar { + + public weak var doneButtonToolbarDelegate: DoneButtonToolbarDelegate? + + // MARK: - Initializers + + public init(delegate: DoneButtonToolbarDelegate?, showCancelButton: Bool = false, theme: ElementsAppearance = .default) { + // Initializing w/ an arbitrary frame stops autolayout from complaining on the first layout pass + super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 44)) + + self.doneButtonToolbarDelegate = delegate + + let doneButton = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(didTapDone) + ) + doneButton.tintColor = theme.colors.primary + let cancelButton = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(didTapCancel) + ) + cancelButton.tintColor = theme.colors.secondaryText + + var items = [.flexibleSpace(), doneButton] + if showCancelButton { + items = [cancelButton] + items + } + + setItems(items, animated: false) + sizeToFit() + setContentHuggingPriority(.defaultLow, for: .horizontal) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Internal Methods + + @objc + private func didTapDone() { + doneButtonToolbarDelegate?.didTapDone(self) + } + + @objc + private func didTapCancel() { + doneButtonToolbarDelegate?.didTapCancel(self) + } +} diff --git a/StripeUICore/StripeUICore/Source/Views/DynamicHeightContainerView.swift b/StripeUICore/StripeUICore/Source/Views/DynamicHeightContainerView.swift new file mode 100644 index 00000000..a69171aa --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Views/DynamicHeightContainerView.swift @@ -0,0 +1,73 @@ +// +// DynamicHeightContainerView.swift +// StripeUICore +// +// Created by Yuki Tokuhiro on 7/16/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +/// For internal SDK use only +@objc(STP_Internal_DynamicHeightContainerView) +@_spi(STP) public class DynamicHeightContainerView: UIView { + @frozen public enum PinnedDirection { + case top, bottom + } + let pinnedDirection: PinnedDirection + private var pinnedDirectionConstraint: NSLayoutConstraint? + + // MARK: - Initializers + + public required init(pinnedDirection: PinnedDirection = .bottom) { + self.pinnedDirection = pinnedDirection + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Internal methods + + /// Adds a subview and pins it to the top or bottom. It leaves the other end unpinned, thus not affecting the view's height. + public func addPinnedSubview(_ view: UIView) { + // Add new view + view.translatesAutoresizingMaskIntoConstraints = false + super.addSubview(view) + let pinnedDirectionAnchor: NSLayoutConstraint = { + switch pinnedDirection { + case .top: + return view.topAnchor.constraint(equalTo: topAnchor) + case .bottom: + return view.bottomAnchor.constraint(equalTo: bottomAnchor) + } + }() + + NSLayoutConstraint.activate([ + pinnedDirectionAnchor, + view.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + view.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + ]) + } + + /// Changes the view's height to be equal to the last added subview's height. + public func updateHeight() { + guard let mostRecentlyAddedView = subviews.last else { + return + } + // Deactivate old constraint + pinnedDirectionConstraint?.isActive = false + + // Activate the new constraint + pinnedDirectionConstraint = { + switch pinnedDirection { + case .top: + return bottomAnchor.constraint(equalTo: mostRecentlyAddedView.bottomAnchor) + case .bottom: + return topAnchor.constraint(equalTo: mostRecentlyAddedView.topAnchor) + } + }() + pinnedDirectionConstraint?.isActive = true + } +} diff --git a/StripeUICore/StripeUICore/Source/Views/DynamicImageView.swift b/StripeUICore/StripeUICore/Source/Views/DynamicImageView.swift new file mode 100644 index 00000000..f2477725 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Views/DynamicImageView.swift @@ -0,0 +1,52 @@ +// +// DynamicImageView.swift +// StripeUICore +// +// Created by Eduardo Urias on 11/10/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +/// A `UIImageView` that dynamically changes it's `image` according to the brightness of the +/// `pairedColor`. +@objc(STP_Internal_DynamicImageView) +@_spi(STP) public class DynamicImageView: UIImageView { + private let pairedColor: UIColor + private let dynamicImage: UIImage? + + private static func makeImage(for traitCollection: UITraitCollection, dynamicImage: UIImage?, pairedColor: UIColor) -> UIImage? { + let userInterfaceStyle: UIUserInterfaceStyle = pairedColor.resolvedColor(with: traitCollection).isDark ? .dark : .light + let traitCollection = UITraitCollection(userInterfaceStyle: userInterfaceStyle) + return dynamicImage?.withConfiguration(traitCollection.imageConfiguration) + } + + /// Initializes a `DynamicImageView`. + /// + /// - Parameters: + /// - image: A UIImage with light and dark variants. + /// - pairedColor: The color brightness to monitor. This should be a + /// `UIColor` initialized with `init(dynamicProvider:)`, otherwise the image will only be + /// choosen on initialization but won't change dynamically. + public init( + dynamicImage: UIImage? = nil, + pairedColor: UIColor + ) { + assert(dynamicImage != nil) + self.dynamicImage = dynamicImage + self.pairedColor = pairedColor + let image = Self.makeImage(for: UITraitCollection.current, dynamicImage: dynamicImage, pairedColor: pairedColor) + super.init(image: image) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + #if !canImport(CompositorServices) + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + image = Self.makeImage(for: traitCollection, dynamicImage: dynamicImage, pairedColor: pairedColor) + } + #endif +} diff --git a/StripeUICore/StripeUICore/Source/Views/LinkOpeningTextView.swift b/StripeUICore/StripeUICore/Source/Views/LinkOpeningTextView.swift new file mode 100644 index 00000000..e58d8894 --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Views/LinkOpeningTextView.swift @@ -0,0 +1,57 @@ +// +// LinkOpeningTextView.swift +// StripeUICore +// +// Created by Mel Ludowise on 5/11/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit + +/** + Subclass of UITextView that allows for links to be opened on tap when the text is un-selectable. + */ +@objc(STP_Internal_LinkOpeningTextView) +class LinkOpeningTextView: UITextView { + private var isTextSelectable = true + + /* + UITextView only allows links to be opened on tap if the text is + selectable. Override the `isSelectable` property such that + `super.isSelectable` is always true to enable the links to be tappable + but track internally whether the user should be able to select text in + the view using `isTextSelectable`. + */ + override var isSelectable: Bool { + get { + return isTextSelectable + } + set { + super.isSelectable = true + isTextSelectable = newValue + } + } + + /* + Override to only enable events if either: + - The text should be selectable. + - The user tapped on a link. + */ + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + // Only override the default behavior if the view should not be selectable + guard !isTextSelectable else { + return super.point(inside: point, with: event) + } + + guard let pos = closestPosition(to: point), + let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) + else { + return false + } + + let startIndex = offset(from: beginningOfDocument, to: range.start) + + return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil + } +} diff --git a/StripeUICore/StripeUICore/StripeUICore.h b/StripeUICore/StripeUICore/StripeUICore.h new file mode 100644 index 00000000..5f1f38a2 --- /dev/null +++ b/StripeUICore/StripeUICore/StripeUICore.h @@ -0,0 +1,18 @@ +// +// StripeUICore.h +// StripeUICore +// +// Created by Mel Ludowise on 9/8/21. +// + +#import + +//! Project version number for StripeUICore. +FOUNDATION_EXPORT double StripeUICoreVersionNumber; + +//! Project version string for StripeUICore. +FOUNDATION_EXPORT const unsigned char StripeUICoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/StripeUICore/StripeUICoreTests/Info.plist b/StripeUICore/StripeUICoreTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift b/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift new file mode 100644 index 00000000..e0db6c7c --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift @@ -0,0 +1,100 @@ +// +// ButtonSnapshotTest.swift +// StripeUICoreTests +// +// Created by Ramon Torres on 11/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +@_spi(STP) import StripeUICore +import UIKit + +final class ButtonSnapshotTest: STPSnapshotTestCase { + + func testPrimary() { + let button = Button(title: "Send") + verify(button) + + button.isHighlighted = true + verify(button, identifier: "Highlighted") + + button.isHighlighted = false + button.isEnabled = false + verify(button, identifier: "Disabled") + } + + func testSecondary() { + let button = Button(configuration: .secondary(), title: "Send") + verify(button) + + button.isHighlighted = true + verify(button, identifier: "Highlighted") + + button.isHighlighted = false + button.isEnabled = false + verify(button, identifier: "Disabled") + } + + func testPlain() { + let button = Button(configuration: .plain(), title: "Cancel") + verify(button) + + button.isHighlighted = true + verify(button, identifier: "Highlighted") + + button.isHighlighted = false + button.isEnabled = false + verify(button, identifier: "Disabled") + } + + func testIcon() { + let button = Button(title: "Add") + button.configuration.insets = .insets(top: 16, leading: 16, bottom: 16, trailing: 16) + + button.icon = .mockIcon() + verify(button, identifier: "Leading") + + button.iconPosition = .trailing + verify(button, identifier: "Trailing") + } + + func testColorCustomization() { + let primaryButton = Button(configuration: .primary(), title: "Delete") + primaryButton.tintColor = .red + verify(primaryButton, identifier: "Primary") + + let secondaryButton = Button(configuration: .secondary(), title: "Delete") + secondaryButton.tintColor = .red + verify(secondaryButton, identifier: "Secondary") + } + + func testDisabledColorCustomization() { + let button = Button(configuration: .primary(), title: "Delete") + button.configuration.disabledBackgroundColor = .black + button.isEnabled = false + verify(button) + } + + func testAttributedTitle() { + let button = Button(title: "Hello") + button.configuration.titleAttributes = [.underlineStyle: NSUnderlineStyle.single.rawValue] + verify(button) + } + + func testLoading() { + let button = Button(title: "Save") + button.isLoading = true + verify(button) + } + + func verify( + _ button: Button, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + button.autosizeHeight(width: 300) + STPSnapshotVerifyView(button, identifier: identifier, file: file, line: line) + } +} diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Elements/AddressSectionElementSnapshotTest.swift b/StripeUICore/StripeUICoreTests/Snapshot/Elements/AddressSectionElementSnapshotTest.swift new file mode 100644 index 00000000..80cc3ded --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Snapshot/Elements/AddressSectionElementSnapshotTest.swift @@ -0,0 +1,33 @@ +// +// AddressSectionElementSnapshotTest.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 7/28/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) @testable import StripeUICore + +class AddressSectionElementSnapshotTest: STPSnapshotTestCase { + let dummyAddressSpecProvider: AddressSpecProvider = { + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec(format: "ACSZP", require: "AZ", cityNameType: .post_town, stateNameType: .state, zip: "", zipNameType: .pin), + ] + return specProvider + }() + + func test_billing_address_same_as_shipping() throws { + let sut = AddressSectionElement( + addressSpecProvider: dummyAddressSpecProvider, + defaults: .init(address: .init(city: "San Francisco", country: "US", line1: "510 Townsend St.", line2: nil, postalCode: "94102", state: "California")), + additionalFields: .init( + billingSameAsShippingCheckbox: .enabled(isOptional: false) + ) + ) + sut.view.autosizeHeight(width: 300) + STPSnapshotVerifyView(sut.view) + } +} diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Elements/CheckboxButtonSnapshotTests.swift b/StripeUICore/StripeUICoreTests/Snapshot/Elements/CheckboxButtonSnapshotTests.swift new file mode 100644 index 00000000..90084da0 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Snapshot/Elements/CheckboxButtonSnapshotTests.swift @@ -0,0 +1,106 @@ +// +// CheckboxButtonSnapshotTests.swift +// StripeUICoreTests +// +// Created by Ramon Torres on 12/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +import UIKit + +@testable @_spi(STP) import StripeUICore + +class CheckboxButtonSnapshotTests: STPSnapshotTestCase { + + let attributedLinkText: NSAttributedString = { + let attributedText = NSMutableAttributedString(string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum auctor justo sit amet luctus egestas. Sed id urna dolor.") + attributedText.addAttributes([.link: URL(string: "https://stripe.com")!], range: NSRange(location: 0, length: 26)) + return attributedText + }() + + func testShortText() { + let checkbox = CheckboxButton(text: "Save this card for future [Merchant] payments") + verify(checkbox) + } + + func testLongText() { + let checkbox = CheckboxButton( + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum auctor justo sit amet luctus egestas. Sed id urna dolor." + ) + verify(checkbox) + } + + func testMultiline() { + let checkbox = CheckboxButton( + text: "Save my info for secure 1-click checkout", + description: "Pay faster at [Merchant] and thousands of businesses." + ) + + verify(checkbox) + } + + func testCustomFont() throws { + var theme = ElementsAppearance.default + theme.fonts.footnote = try XCTUnwrap(UIFont(name: "AmericanTypewriter", size: 13.0)) + theme.fonts.footnoteEmphasis = try XCTUnwrap(UIFont(name: "AmericanTypewriter-Semibold", size: 13.0)) + + let checkbox = CheckboxButton( + text: "Save my info for secure 1-click checkout", + description: "Pay faster at [Merchant] and thousands of businesses.", + theme: theme + ) + + verify(checkbox) + } + + func testLocalization_greek() { + let greekCheckbox = CheckboxButton(text: "Αποθηκεύστε αυτή την κάρτα για μελλοντικές [Merchant] πληρωμές") + verify(greekCheckbox, identifier: "Greek") + } + + func testLocalization_chinese() { + let chineseCheckbox = CheckboxButton( + text: "保存我的信息以便一键结账", + description: "在[Merchant]及千万商家使用快捷支付") + verify(chineseCheckbox, identifier: "Chinese") + } + + func testLocalization_hindi() { + let hindiCheckbox = CheckboxButton( + text: "सुरक्षित 1-क्लिक चेकआउट के लिए मेरी जानकारी सहेजें", + description: "[Merchant] और हज़ारों व्यापारियों पर तेज़ी से भुगतान करें।") + verify(hindiCheckbox, identifier: "Hindi") + } + + func testAttributedText() { + let checkbox = CheckboxButton( + attributedText: attributedLinkText + ) + verify(checkbox) + } + + func testAttributedTextCustomFont() throws { + var theme = ElementsAppearance.default + theme.fonts.footnote = try XCTUnwrap(UIFont(name: "AmericanTypewriter", size: 13.0)) + theme.fonts.footnoteEmphasis = try XCTUnwrap(UIFont(name: "AmericanTypewriter-Semibold", size: 13.0)) + let checkbox = CheckboxButton( + attributedText: attributedLinkText, + theme: theme + ) + verify(checkbox) + } + + func verify( + _ view: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + view.autosizeHeight(width: 340) + view.backgroundColor = .white + STPSnapshotVerifyView(view, identifier: identifier, file: file, line: line) + } + +} diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Elements/DateFieldElementSnapshotTest.swift b/StripeUICore/StripeUICoreTests/Snapshot/Elements/DateFieldElementSnapshotTest.swift new file mode 100644 index 00000000..51ba291c --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Snapshot/Elements/DateFieldElementSnapshotTest.swift @@ -0,0 +1,81 @@ +// +// DateFieldElementSnapshotTest.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 10/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) @testable import StripeUICore + +final class DateFieldElementSnapshotTest: STPSnapshotTestCase { + + // Use consistent locale and timezone for consistent test results + let locale_enUS = Locale(identifier: "en_US") + let timeZone_GMT = TimeZone(secondsFromGMT: 0)! + + // Mock dates + let oct1_2021 = Date(timeIntervalSince1970: 1633046400) + let oct3_2021 = Date(timeIntervalSince1970: 1633219200) + + func testNoDefaultUnfocused() { + let dateFieldElement = makeDateFieldElement() + verify(dateFieldElement) + } + + func testNoDefaultFocused() { + // Setting a max date to the past makes the UIDatePicker default to that + // date instead of the current date, giving us consistent UI to test. + let dateFieldElement = makeDateFieldElement( + maximumDate: oct1_2021 + ) + dateFieldElement.didBeginEditing(dateFieldElement.pickerFieldView) + verify(dateFieldElement) + } + + func testDefault() { + let dateFieldElement = makeDateFieldElement( + defaultDate: oct1_2021 + ) + verify(dateFieldElement) + } + + func testChangeInput() { + let dateFieldElement = makeDateFieldElement( + defaultDate: oct1_2021 + ) + dateFieldElement.datePickerView.date = oct3_2021 + + // Emulate a user changing the picker + dateFieldElement.didSelectDate() + + verify(dateFieldElement) + } +} + +// MARK: - Helpers + +private extension DateFieldElementSnapshotTest { + func makeDateFieldElement( + defaultDate: Date? = nil, + maximumDate: Date? = nil + ) -> DateFieldElement { + return DateFieldElement( + label: "Label", + defaultDate: defaultDate, + maximumDate: maximumDate, + locale: locale_enUS, + timeZone: timeZone_GMT + ) + } + + func verify(_ dateFieldElement: DateFieldElement, + file: StaticString = #filePath, + line: UInt = #line) { + let view = dateFieldElement.view + view.autosizeHeight(width: 200) + STPSnapshotVerifyView(view, file: file, line: line) + } +} diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Elements/DropdownFieldElementSnapshotTest.swift b/StripeUICore/StripeUICoreTests/Snapshot/Elements/DropdownFieldElementSnapshotTest.swift new file mode 100644 index 00000000..2a2813fa --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Snapshot/Elements/DropdownFieldElementSnapshotTest.swift @@ -0,0 +1,58 @@ +// +// DropdownFieldElementSnapshotTest.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 10/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) @testable import StripeUICore + +final class DropdownFieldElementSnapshotTest: STPSnapshotTestCase { + let items = ["A", "B", "C", "D"].map { DropdownFieldElement.DropdownItem(pickerDisplayName: $0, labelDisplayName: $0, accessibilityValue: $0, rawData: $0) } + + func testDefault0() { + let dropdownFieldElement = makeDropdownFieldElement( + defaultIndex: 0 + ) + verify(dropdownFieldElement) + } + + func testDefault3() { + let dropdownFieldElement = makeDropdownFieldElement( + defaultIndex: 3 + ) + verify(dropdownFieldElement) + } + + func testChangeInput() { + let dropdownFieldElement = makeDropdownFieldElement( + defaultIndex: 0 + ) + // Emulate a user changing the picker + dropdownFieldElement.pickerView(dropdownFieldElement.pickerView, didSelectRow: 3, inComponent: 0) + verify(dropdownFieldElement) + } +} + +private extension DropdownFieldElementSnapshotTest { + func makeDropdownFieldElement( + defaultIndex: Int + ) -> DropdownFieldElement { + return DropdownFieldElement( + items: items, + defaultIndex: defaultIndex, + label: "Label" + ) + } + + func verify(_ dropdownFieldElement: DropdownFieldElement, + file: StaticString = #filePath, + line: UInt = #line) { + let view = dropdownFieldElement.view + view.autosizeHeight(width: 200) + STPSnapshotVerifyView(view, file: file, line: line) + } +} diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Elements/PhoneNumberElementSnapshotTests.swift b/StripeUICore/StripeUICoreTests/Snapshot/Elements/PhoneNumberElementSnapshotTests.swift new file mode 100644 index 00000000..d1d8321a --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Snapshot/Elements/PhoneNumberElementSnapshotTests.swift @@ -0,0 +1,64 @@ +// +// PhoneNumberElementSnapshotTests.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 6/23/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import iOSSnapshotTestCase +import StripeCoreTestUtils +@_spi(STP) @testable import StripeUICore + +class PhoneNumberElementSnapshotTests: STPSnapshotTestCase { + + func testEmptyUS() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + defaultCountryCode: "US", + locale: Locale(identifier: "en_US") + ) + verify(sut) + } + + func testEmptyGB() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["GB"], + defaultCountryCode: "GB", + locale: Locale(identifier: "en_GB") + ) + verify(sut) + } + + func testFilledUS() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + defaultCountryCode: "US", + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "en_US") + ) + verify(sut) + } + + func testFilledGB() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["GB"], + defaultCountryCode: "GB", + defaultPhoneNumber: "442071838750", + locale: Locale(identifier: "en_GB") + ) + verify(sut) + } + + func verify( + _ sut: PhoneNumberElement, + file: StaticString = #filePath, + line: UInt = #line + ) { + let section = SectionElement(elements: [sut]) + let view = section.view + view.autosizeHeight(width: 320) + STPSnapshotVerifyView(view, file: file, line: line) + } + +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Categories/Locale+StripeUICoreTests.swift b/StripeUICore/StripeUICoreTests/Unit/Categories/Locale+StripeUICoreTests.swift new file mode 100644 index 00000000..0ff8836a --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Categories/Locale+StripeUICoreTests.swift @@ -0,0 +1,73 @@ +// +// Locale+StripeUICoreTests.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 9/28/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +final class LocaleStripeUICoreTests: XCTestCase { + // English, United States + let localeEN_US = Locale(identifier: "en_US") + + // Spanish, El Salvador + let localeES_SV = Locale(identifier: "es_SV") + + let regions = [ + "IT", // Italy (Italia) + "CA", // Canada (Canadá) + "US", // United States (Estados Unidos) + "DZ", // Algeria (Argelia) + "SV", // El Salvador (El Salvador) + ] + + // Sort countries by English localization + func testSortRegionsEnglish() { + let sorted = localeEN_US.sortedByTheirLocalizedNames( + regions, + thisRegionFirst: false + ) + XCTAssertEqual(sorted, ["DZ", "CA", "SV", "IT", "US"]) + } + + // Sort countries by Spanish localization + func testSortRegionsSpanish() { + let sorted = localeES_SV.sortedByTheirLocalizedNames( + regions, + thisRegionFirst: false + ) + XCTAssertEqual(sorted, ["DZ", "CA", "SV", "US", "IT"]) + } + + // Sort countries by English localization, with current country (US) first + func testSortCountriesUSFirst() { + let sorted = localeEN_US.sortedByTheirLocalizedNames( + regions, + thisRegionFirst: true + ) + XCTAssertEqual(sorted, ["US", "DZ", "CA", "SV", "IT"]) + } + + // Sort countries by English localization, with current country (SV) first + func testSortCountriesSVFirst() { + let sorted = localeES_SV.sortedByTheirLocalizedNames( + regions, + thisRegionFirst: true + ) + XCTAssertEqual(sorted, ["SV", "DZ", "CA", "US", "IT"]) + } + + // Ask for current country to be first when the list of countries doesn't contain it + func testSortCountriesMissingCurrent() { + var missingUS = regions + missingUS.removeAll(where: { $0 == "US" }) + let sorted = localeEN_US.sortedByTheirLocalizedNames( + missingUS, + thisRegionFirst: true + ) + XCTAssertEqual(sorted, ["DZ", "CA", "SV", "IT"]) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Categories/NSAttributedString+StripeUICoreTests.swift b/StripeUICore/StripeUICoreTests/Unit/Categories/NSAttributedString+StripeUICoreTests.swift new file mode 100644 index 00000000..1679c93b --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Categories/NSAttributedString+StripeUICoreTests.swift @@ -0,0 +1,25 @@ +// +// NSAttributedString+StripeUICoreTests.swift +// StripeUICoreTests +// +// Created by Nick Porter on 9/1/23. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +final class NSAttributedStringStripeUICoreTests: XCTestCase { + + func hasTextAttachment_shouldReturnTrue() { + let brandImageAttachment = NSTextAttachment() + brandImageAttachment.image = UIImage() + + let attrString = NSAttributedString(attachment: brandImageAttachment) + XCTAssertTrue(attrString.hasTextAttachment) + } + + func hasTextAttachment_shouldReturnFalse() { + let attrString = NSAttributedString(string: "no text attachments") + XCTAssertFalse(attrString.hasTextAttachment) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Categories/UIColor+StripeUICoreTests.swift b/StripeUICore/StripeUICoreTests/Unit/Categories/UIColor+StripeUICoreTests.swift new file mode 100644 index 00000000..979f72f2 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Categories/UIColor+StripeUICoreTests.swift @@ -0,0 +1,262 @@ +// +// UIColor+StripeUICoreTests.swift +// StripeUICoreTests +// +// Created by Ramon Torres on 11/9/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +final class UIColorStripeUICoreTests: XCTestCase { + + func testLighten() { + XCTAssertEqual( + UIColor.black.lighten(by: 0.5).cgColor, + UIColor(hue: 0, saturation: 0, brightness: 0.5, alpha: 1).cgColor + ) + + XCTAssertEqual( + UIColor.gray.lighten(by: 1).cgColor, + UIColor(hue: 0, saturation: 0, brightness: 1, alpha: 1).cgColor + ) + + XCTAssertEqual( + UIColor(hue: 0, saturation: 0.5, brightness: 0.5, alpha: 1).lighten(by: 0.3).cgColor, + UIColor(hue: 0, saturation: 0.5, brightness: 0.8, alpha: 1).cgColor + ) + } + + func testDarken() { + XCTAssertEqual( + UIColor.white.darken(by: 0.5).cgColor, + UIColor(hue: 0, saturation: 0, brightness: 0.5, alpha: 1).cgColor + ) + + XCTAssertEqual( + UIColor.gray.darken(by: 1).cgColor, + UIColor(hue: 0, saturation: 0, brightness: 0, alpha: 1).cgColor + ) + + XCTAssertEqual( + UIColor(hue: 0, saturation: 0.5, brightness: 0.5, alpha: 1).darken(by: 0.2).cgColor, + UIColor(hue: 0, saturation: 0.5, brightness: 0.3, alpha: 1).cgColor + ) + } + + func testLuminance() { + // Well-known color-luminance values + let testCases: [(UIColor, CGFloat)] = [ + // Grays + (UIColor(white: 0, alpha: 1), 0.0), + (UIColor(white: 0.25, alpha: 1), 0.05), + (UIColor(white: 0.5, alpha: 1), 0.21), + (UIColor(white: 0.75, alpha: 1), 0.52), + (UIColor(white: 1, alpha: 1), 1.0), + // Colors (Extract Rec. 709 coefficients) + (UIColor(red: 1, green: 0, blue: 0, alpha: 1), 0.2126), + (UIColor(red: 0, green: 1, blue: 0, alpha: 1), 0.7152), + (UIColor(red: 0, green: 0, blue: 1, alpha: 1), 0.0722), + ] + + for (color, expectedLuminance) in testCases { + XCTAssertEqual(color.luminance, expectedLuminance, accuracy: 0.01) + } + } + + func testContrastRatio() { + // Highest contrast ratio + XCTAssertEqual(UIColor.black.contrastRatio(to: .white), 21) + XCTAssertEqual(UIColor.white.contrastRatio(to: .black), 21) + + // Lowest contrast ratio (identical colors) + XCTAssertEqual(UIColor.red.contrastRatio(to: .red), 1) + + // Black to 50% gray + XCTAssertEqual(UIColor.black.contrastRatio(to: .gray), 5.28, accuracy: 0.01) + + // Red to black + XCTAssertEqual(UIColor.red.contrastRatio(to: .black), 5.25, accuracy: 0.01) + } + + func testGrayscaleColorsIsBright() { + let space = CGColorSpaceCreateDeviceGray() + var components: [CGFloat] = [0.0, 1.0] + + // Using 0.3 as the cutoff from bright/non-bright because that's what + // the current implementation does. + + var white: CGFloat = 0.0 + while white < 0.3 { + components[0] = white + let cgcolor = CGColor(colorSpace: space, components: &components)! + let color = UIColor(cgColor: cgcolor) + + XCTAssertFalse(color.isBright) + white += CGFloat(0.05) + } + + white = CGFloat(0.3001) + while white < 2 { + components[0] = white + let cgcolor = CGColor(colorSpace: space, components: &components)! + let color = UIColor(cgColor: cgcolor) + + XCTAssertTrue(color.isBright) + white += CGFloat(0.1) + } + } + + func testBuiltinColorsIsBright() { + // This is primarily to document what colors are considered bright/dark + let brightColors = [ + UIColor.brown, + UIColor.cyan, + UIColor.darkGray, + UIColor.gray, + UIColor.green, + UIColor.lightGray, + UIColor.magenta, + UIColor.orange, + UIColor.white, + UIColor.yellow, + ] + let darkColors = [ + UIColor.black, + UIColor.blue, + UIColor.clear, + UIColor.purple, + UIColor.red, + ] + + for color in brightColors { + XCTAssertTrue(color.isBright) + } + + for color in darkColors { + XCTAssertFalse(color.isBright) + } + } + + func testAllColorSpaces() { + // block to create & check brightness of color in a given color space + let testColorSpace: ((CFString, Bool) -> Void)? = { colorSpaceName, expectedToBeBright in + // this a bright color in almost all color spaces + let components: [CGFloat] = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + + var color: UIColor? + let colorSpace = CGColorSpace(name: colorSpaceName) + + if let colorSpace = colorSpace { + let cgcolor = CGColor(colorSpace: colorSpace, components: components) + + if let cgcolor = cgcolor { + color = UIColor(cgColor: cgcolor) + } + } + + if let color = color { + if expectedToBeBright { + XCTAssertTrue(color.isBright) + } else { + XCTAssertFalse(color.isBright) + } + } else { + XCTFail("Could not create color for \(colorSpaceName)") + } + } + + let colorSpaceNames = [ + CGColorSpace.sRGB, CGColorSpace.dcip3, CGColorSpace.rommrgb, CGColorSpace.itur_709, + CGColorSpace.displayP3, CGColorSpace.itur_2020, CGColorSpace.genericXYZ, + CGColorSpace.linearSRGB, CGColorSpace.genericCMYK, CGColorSpace.acescgLinear, + CGColorSpace.adobeRGB1998, CGColorSpace.extendedGray, CGColorSpace.extendedSRGB, + CGColorSpace.genericRGBLinear, CGColorSpace.extendedLinearSRGB, + CGColorSpace.genericGrayGamma2_2, + ] + + let colorSpaceCount = + MemoryLayout.size(ofValue: colorSpaceNames) + / MemoryLayout.size(ofValue: colorSpaceNames[0]) + for i in 0.. Bool { + let left = lhs.rgba + let right = rhs.rgba + + return left.red == right.red + && left.green == right.green + && left.blue == right.blue + && left.alpha == right.alpha + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/AddressSectionElementTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/AddressSectionElementTest.swift new file mode 100644 index 00000000..bd0c6992 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/AddressSectionElementTest.swift @@ -0,0 +1,255 @@ +// +// AddressSectionElementTest.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 7/20/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeCore +@_spi(STP) @testable import StripeUICore +import XCTest + +class AddressSectionElementTest: XCTestCase { + let locale_enUS = Locale(identifier: "us_EN") + let dummyAddressSpecProvider: AddressSpecProvider = { + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec(format: "ACSZP", require: "AZ", cityNameType: .post_town, stateNameType: .state, zip: "", zipNameType: .pin), + "CA": AddressSpec(format: "ACSZP", require: "AZ", cityNameType: .post_town, stateNameType: .province, zip: "", zipNameType: .pin), + ] + return specProvider + }() + + func testAddressFieldsMapsSpecs() throws { + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec(format: "ACSZP", require: "AZ", cityNameType: .post_town, stateNameType: .state, zip: "", zipNameType: .pin), + ] + let section = AddressSectionElement( + title: "", + locale: locale_enUS, + addressSpecProvider: specProvider + ).addressSection + XCTAssert(section.elements.first is DropdownFieldElement, + "'\(String(describing: section.elements.first.map { type(of: $0) }))' is not 'DropdownFieldElement')") + guard let fields = Array(section.elements.dropFirst()) as? [TextFieldElement] else { + return XCTFail("Expected `[TextFieldElement]`") + } + // Test ordering and label mapping + typealias Expected = (label: String, isOptional: Bool) + let expected = [ + Expected(label: "Address line 1", isOptional: false), + Expected(label: "Address line 2", isOptional: true), + Expected(label: "Town or city", isOptional: true), + Expected(label: "State", isOptional: true), + Expected(label: "PIN", isOptional: false), + ] + XCTAssertEqual(fields.map { $0.configuration.label }, expected.map { $0.label }) + XCTAssertEqual(fields.map { $0.configuration.isOptional }, expected.map { $0.isOptional }) + } + + func testAddressFieldsWithDefaults() { + // An address section with defaults... + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec(format: "NOACSZ", require: "ACSZ", cityNameType: .city, stateNameType: .state, zip: "", zipNameType: .zip), + ] + let defaultAddress = AddressSectionElement.AddressDetails(address: .init( + city: "San Francisco", country: "US", line1: "510 Townsend St.", line2: "Line 2", postalCode: "94102", state: "CA" + )) + let addressSection = AddressSectionElement( + title: "", + locale: locale_enUS, + addressSpecProvider: specProvider, + defaults: defaultAddress + ) + + XCTAssertEqual(addressSection.line1?.text, defaultAddress.address.line1) + XCTAssertEqual(addressSection.line2?.text, defaultAddress.address.line2) + XCTAssertEqual(addressSection.city?.text, defaultAddress.address.city) + XCTAssertEqual(addressSection.postalCode?.text, defaultAddress.address.postalCode) + XCTAssertEqual(addressSection.state?.rawData, defaultAddress.address.state) + XCTAssertEqual(addressSection.selectedCountryCode, defaultAddress.address.country) + } + + func testAddressFieldsChangeWithCountry() { + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec(format: "ACSZP", require: "AZ", cityNameType: .post_town, stateNameType: .state, zip: "", zipNameType: .pin), + "ZZ": AddressSpec(format: "PZSCA", require: "CS", cityNameType: .city, stateNameType: .province, zip: "", zipNameType: .postal_code), + ] + let sut = AddressSectionElement( + title: "", + locale: locale_enUS, + addressSpecProvider: specProvider + ) + let section = sut.addressSection + + // Test ordering and label mapping + typealias Expected = (label: String, isOptional: Bool) + let expectedUSFields = [ + Expected(label: "Address line 1", isOptional: false), + Expected(label: "Address line 2", isOptional: true), + Expected(label: "Town or city", isOptional: true), + Expected(label: "State", isOptional: true), + Expected(label: "PIN", isOptional: false), + ] + guard let country = section.elements.first as? DropdownFieldElement else { XCTFail(); return } + let USTextFields = section.elements.compactMap { $0 as? TextFieldElement } + XCTAssertEqual(country.selectedIndex, 0) + XCTAssertEqual(USTextFields.map { $0.configuration.label }, expectedUSFields.map { $0.label }) + XCTAssertEqual(USTextFields.map { $0.configuration.isOptional }, expectedUSFields.map { $0.isOptional }) + + // Hack to switch the country + country.pickerView(country.pickerView, didSelectRow: 1, inComponent: 0) + country.didFinish(country.pickerFieldView, shouldAutoAdvance: true) + let ZZTextFields = section.elements.compactMap { $0 as? TextFieldElement } + let expectedZZFields = [ + Expected(label: "Postal code", isOptional: true), + Expected(label: "Province", isOptional: false), + Expected(label: "City", isOptional: false), + Expected(label: "Address line 1", isOptional: false), + Expected(label: "Address line 2", isOptional: true), + ] + XCTAssertEqual(country.selectedIndex, 1) + XCTAssertEqual(ZZTextFields.map { $0.configuration.label }, expectedZZFields.map { $0.label }) + XCTAssertEqual(ZZTextFields.map { $0.configuration.isOptional }, expectedZZFields.map { $0.isOptional }) + } + + func testCountries() { + let specProvider = AddressSpecProvider() + specProvider.addressSpecs = [ + "US": AddressSpec(format: "ACSZP", require: "AZ", cityNameType: .post_town, stateNameType: .state, zip: "", zipNameType: .pin), + ] + + // Use spec provider's country codes if no countries explicitly specified + XCTAssertEqual(AddressSectionElement(title: "", countries: nil, addressSpecProvider: specProvider).countryCodes, ["US"]) + // Countries not in spec + XCTAssertEqual(AddressSectionElement(title: "", countries: ["UK"], addressSpecProvider: specProvider).countryCodes, ["UK"]) + // Countries not in spec + XCTAssertEqual(AddressSectionElement(title: "", countries: ["UK", "US"], addressSpecProvider: specProvider).countryCodes, ["UK", "US"]) + } + + func test_additionalFields() { + for isOptional in [true, false] { // Test when the field is optional and when it's required + // AddressSectionElement configured to collect a name and phone field... + let sut = AddressSectionElement( + addressSpecProvider: dummyAddressSpecProvider, + defaults: .init(name: "Default name", phone: "6505551234"), + additionalFields: .init( + name: .enabled(isOptional: isOptional), + phone: .enabled(isOptional: isOptional) + ) + ) + // ...has a name and phone field + guard + let name = sut.name, + let phone = sut.phone + else { + XCTFail(); return + } + // ...and sets the default + XCTAssertEqual(name.text, "Default name") + XCTAssertEqual(phone.phoneNumber?.string(as: .e164), "+16505551234") + // ...and isOptional matches + XCTAssertEqual(name.configuration.isOptional, isOptional) + XCTAssertEqual(phone.textFieldElement.configuration.isOptional, isOptional) + } + + } + + func test_additionalFields_hidden_by_default() { + // By default, the AddressSectionElement doesn't have additional fields + let sut = AddressSectionElement( + addressSpecProvider: dummyAddressSpecProvider + ) + XCTAssertNil(sut.name) + XCTAssertNil(sut.phone) + } + + func test_billing_same_as_shipping_checkbox_hidden_if_invalid_country() { + // AddressSectionElement with 'checkbox' field enabled but with an invalid default country... + let sut = AddressSectionElement( + addressSpecProvider: dummyAddressSpecProvider, + defaults: .init(address: .init(city: "San Francisco", country: "GB", line1: "510 Townsend St.", line2: nil, postalCode: "94102", state: "California")), + additionalFields: .init( + billingSameAsShippingCheckbox: .enabled(isOptional: false) + ) + ) + + // ...should not display the checkbox + XCTAssertTrue(sut.sameAsCheckbox.view.isHidden) + } + + func test_billing_same_as_shipping_checkbox_deselected_upon_edit() { + // AddressSectionElement with 'checkbox' field enabled... + let sut = AddressSectionElement( + addressSpecProvider: dummyAddressSpecProvider, + defaults: .init(address: .init(city: "San Francisco", country: "US", line1: "510 Townsend St.", line2: nil, postalCode: "94102", state: "California")), + additionalFields: .init( + billingSameAsShippingCheckbox: .enabled(isOptional: false) + ) + ) + + // ...should display the checkbox + guard !sut.sameAsCheckbox.view.isHidden else { + XCTFail("Missing checkbox element") + return + } + XCTAssertTrue(sut.sameAsCheckbox.isSelected) + // Editing a field... + sut.line1?.setText("123 Foo St.") + // ...should deselect the checkbox + XCTAssertFalse(sut.sameAsCheckbox.isSelected) + } + + func test_phone_country_updates_with_country_picker() { + let sut = AddressSectionElement( + addressSpecProvider: dummyAddressSpecProvider, + additionalFields: .init( + phone: .enabled(isOptional: false) + ) + ) + + // Country and phone should have same inital value + XCTAssertEqual(sut.country.selectedIndex, sut.phone?.countryDropdownElement.selectedIndex) + + // Phone field should default to empty + XCTAssertTrue(sut.phone?.textFieldElement.text.isEmpty ?? false) + + // Country and phone should update together when country changes and phone text is empty + sut.country.select(index: 0) + XCTAssertEqual(sut.country.selectedIndex, sut.phone?.countryDropdownElement.selectedIndex) + + // Country and phone should update together when country changes and phone text is empty + sut.country.select(index: 1) + XCTAssertEqual(sut.country.selectedIndex, sut.phone?.countryDropdownElement.selectedIndex) + + // Phone country should not change once it has text populated + sut.phone?.textFieldElement.setText("555") + sut.country.select(index: 0) + XCTAssertNotEqual(sut.country.selectedIndex, sut.phone?.countryDropdownElement.selectedIndex) + } + + func testConvertLinkBillingAddressToAddressDetails() { + let linkBillingDetails = BillingAddress( + name: "Test Testerson", + line1: "123 Main St", + line2: "Apt 4", + city: "San Francisco", + state: "CA", + postalCode: "94102", + countryCode: "US" + ) + let addressDetails = AddressSectionElement.AddressDetails(billingAddress: linkBillingDetails, phone: "+1231231234") + XCTAssertEqual(addressDetails.name, linkBillingDetails.name) + XCTAssertEqual(addressDetails.phone, "+1231231234") + XCTAssertEqual(addressDetails.address.city, linkBillingDetails.city) + XCTAssertEqual(addressDetails.address.country, linkBillingDetails.countryCode) + XCTAssertEqual(addressDetails.address.line1, linkBillingDetails.line1) + XCTAssertEqual(addressDetails.address.line2, linkBillingDetails.line2) + XCTAssertEqual(addressDetails.address.postalCode, linkBillingDetails.postalCode) + XCTAssertEqual(addressDetails.address.state, linkBillingDetails.state) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/AddressSpecProviderTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/AddressSpecProviderTest.swift new file mode 100644 index 00000000..1797c45f --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/AddressSpecProviderTest.swift @@ -0,0 +1,50 @@ +// +// AddressSpecProviderTest.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 7/20/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +class AddressSpecProviderTest: XCTestCase { + func testLoadsJSON() throws { + let e = expectation(description: "") + let sut = AddressSpecProvider.shared + sut.addressSpecs = [:] + XCTAssertTrue(sut.addressSpecs.isEmpty) + sut.loadAddressSpecs { + e.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + XCTAssertFalse(sut.addressSpecs.isEmpty) + + // Sanity check some spec properties + let us = sut.addressSpec(for: "US") + let jp = sut.addressSpec(for: "JP") + let gb = sut.addressSpec(for: "GB") + + XCTAssertEqual(us.zipNameType, .zip) + XCTAssertEqual(jp.zipNameType, .postal_code) + XCTAssertEqual(gb.zipNameType, .postal_code) + + XCTAssertEqual(us.stateNameType, .state) + XCTAssertEqual(jp.stateNameType, .prefecture) + XCTAssertEqual(gb.stateNameType, .province) + + XCTAssertEqual(us.cityNameType, .city) + XCTAssertEqual(jp.cityNameType, .city) + XCTAssertEqual(gb.cityNameType, .post_town) + + // Sanity check countries all exist + let unknownCountries = sut.countries.filter { !Locale.isoRegionCodes.contains($0) } + XCTAssertTrue(unknownCountries.count == 0) + + // Require that all countries collect at least line1 and line2 + for spec in sut.addressSpecs.values { + XCTAssertTrue(spec.fieldOrdering.contains(.line)) + } + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/BSBNumberProviderTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/BSBNumberProviderTest.swift new file mode 100644 index 00000000..feaafa32 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/BSBNumberProviderTest.swift @@ -0,0 +1,76 @@ +// +// BSBNumberProviderTest.swift +// StripeUICoreTests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +class BSBNumberProviderTest: XCTestCase { + var sut: BSBNumberProvider! = nil + + override func setUp() { + sut = BSBNumberProvider.shared + let e = expectation(description: "") + + XCTAssert(sut.bsbNumberToNameMapping.isEmpty) + sut.loadBSBData { + e.fulfill() + } + waitForExpectations(timeout: 5, handler: nil) + XCTAssertFalse(sut.bsbNumberToNameMapping.isEmpty) + } + + override func tearDown() { + sut.bsbNumberToNameMapping = [:] + sut = nil + } + + func testBankName_10() { + let bsbName = sut.bsbName(for: "10") + XCTAssertEqual(bsbName, "BankSA (division of Westpac Bank)") + } + func testLoads_fullBSB() { + let bsbName = sut.bsbName(for: "100-000") + XCTAssertEqual(bsbName, "BankSA (division of Westpac Bank)") + } + func testBankName_2char() { + let bsbName = sut.bsbName(for: "09") + XCTAssertEqual(bsbName, "Reserve Bank of Australia") + } + func testBankName_3char() { + let bsbName = sut.bsbName(for: "091") + XCTAssertEqual(bsbName, "Reserve Bank of Australia") + } + func testBankName_4char() { + let bsbName = sut.bsbName(for: "091-") + XCTAssertEqual(bsbName, "Reserve Bank of Australia") + } + func testBankName_5char() { + let bsbName = sut.bsbName(for: "091-0") + XCTAssertEqual(bsbName, "Reserve Bank of Australia") + } + func testBankName_6char() { + let bsbName = sut.bsbName(for: "091-01") + XCTAssertEqual(bsbName, "Reserve Bank of Australia") + } + func testBSBSingleChar() { + let bsbName = sut.bsbName(for: "0") + XCTAssertEqual(bsbName, "") + } + func testBSBEmpty() { + let bsbName = sut.bsbName(for: "") + XCTAssertEqual(bsbName, "") + } + func testInvalidBank1() { + let bsbName = sut.bsbName(for: "0") + XCTAssertEqual(bsbName, "") + } + func testInvalidBank2() { + let bsbName = sut.bsbName(for: "560-111") + XCTAssertEqual(bsbName, "") + } + +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/DateFieldElementTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/DateFieldElementTest.swift new file mode 100644 index 00000000..df618303 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/DateFieldElementTest.swift @@ -0,0 +1,85 @@ +// +// DateFieldElementTest.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 10/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP)@testable import StripeUICore +import XCTest + +final class DateFieldElementTest: XCTestCase { + // Mock dates + let oct1_2021 = Date(timeIntervalSince1970: 1633046400) + let oct3_2021 = Date(timeIntervalSince1970: 1633219200) + + func testNoDefault() { + let element = DateFieldElement(label: "") + XCTAssertNil(element.selectedDate) + XCTAssertFalse(element.validationState.isValid) + } + + func testWithDefault() { + let element = DateFieldElement(label: "", defaultDate: oct1_2021) + XCTAssertEqual(element.selectedDate, oct1_2021) + XCTAssertTrue(element.validationState.isValid) + } + + func testDefaultExceedsMax() { + let element = DateFieldElement(label: "", defaultDate: oct3_2021, maximumDate: oct1_2021) + XCTAssertNil(element.selectedDate) + } + + func testDefaultExceedsMin() { + let element = DateFieldElement(label: "", defaultDate: oct1_2021, minimumDate: oct3_2021) + XCTAssertNil(element.selectedDate) + } + + func testCustomDateformatter() { + let timeZone = TimeZone(secondsFromGMT: 0) + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "MMddyyyy" + + let element = DateFieldElement(timeZone: timeZone!, customDateFormatter: dateFormatter) + // Emulate a user changing the picker and hitting done button + element.datePickerView.date = oct3_2021 + element.didSelectDate() + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + + XCTAssertEqual(element.pickerFieldView.displayText?.string, "10032021") + } + + func testDidUpdate() { + var date: Date? + let element = DateFieldElement(label: "", didUpdate: { date = $0 }) + XCTAssertNil(date) + // Emulate a user changing the picker and hitting done button + element.datePickerView.date = oct3_2021 + element.didSelectDate() + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertEqual(date, oct3_2021) + } + + func testDidUpdateToDefault() { + // Ensure `didUpdate` is not called if the selection doesn't change + + var date: Date? + let element = DateFieldElement(label: "", defaultDate: oct1_2021, didUpdate: { date = $0 }) + XCTAssertNil(date) + + // Emulate a user changing the picker + element.datePickerView.date = oct3_2021 + element.didSelectDate() + XCTAssertNil(date) + + // Emulate the user changing the picker back + element.datePickerView.date = oct1_2021 + element.didSelectDate() + XCTAssertNil(date) + + // Emulate user hitting the done button + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertNil(date) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/DropdownFieldElementTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/DropdownFieldElementTest.swift new file mode 100644 index 00000000..b8538658 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/DropdownFieldElementTest.swift @@ -0,0 +1,96 @@ +// +// DropdownFieldElementTest.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 10/8/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +final class DropdownFieldElementTest: XCTestCase { + + let items = ["A", "B", "C", "D"].map { DropdownFieldElement.DropdownItem(pickerDisplayName: $0, labelDisplayName: $0, accessibilityValue: $0, rawData: $0) } + + func testNoDefault() { + let element = DropdownFieldElement(items: items, label: "") + XCTAssertEqual(element.selectedIndex, 0) + } + + func testWithDefault() { + let element = DropdownFieldElement(items: items, defaultIndex: 3, label: "") + XCTAssertEqual(element.selectedIndex, 3) + } + + func testDefaultExceedsMax() { + let element = DropdownFieldElement(items: items, defaultIndex: items.count, label: "") + XCTAssertEqual(element.selectedIndex, 0) + } + + func testDefaultExceedsMin() { + let element = DropdownFieldElement(items: items, defaultIndex: -1, label: "") + XCTAssertEqual(element.selectedIndex, 0) + } + + func testDisableDropdownWithSingleElement() { + let multipleElements = DropdownFieldElement(items: items, defaultIndex: -1, label: "", disableDropdownWithSingleElement: true) + + XCTAssertEqual(multipleElements.pickerFieldView.isUserInteractionEnabled, true) + + let singleElement = DropdownFieldElement(items: [DropdownFieldElement.DropdownItem(pickerDisplayName: "Item", labelDisplayName: "Item", accessibilityValue: "Item", rawData: "Item")], defaultIndex: -1, label: "", disableDropdownWithSingleElement: true) + + XCTAssertEqual(singleElement.pickerFieldView.isUserInteractionEnabled, false) + } + + func testDidUpdate() { + var index: Int? + let element = DropdownFieldElement(items: items, label: "", didUpdate: { index = $0 }) + XCTAssertNil(index) + // Emulate a user changing the picker and hitting done button + element.pickerView(element.pickerView, didSelectRow: 3, inComponent: 0) + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertEqual(index, 3) + } + + func testDidUpdateToDefault() { + // Ensure `didUpdate` is not called if the selection doesn't change + + var index: Int? + let element = DropdownFieldElement(items: items, defaultIndex: 0, label: "", didUpdate: { index = $0 }) + XCTAssertNil(index) + + // Emulate a user changing the picker + element.pickerView(element.pickerView, didSelectRow: 3, inComponent: 0) + XCTAssertNil(index) + + // Emulate a user changing the picker back + element.pickerView(element.pickerView, didSelectRow: 0, inComponent: 0) + XCTAssertNil(index) + + // Emulate user hitting the done button + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertNil(index) + } + + func testUpdate() { + var index: Int? + let element = DropdownFieldElement(items: items, label: "", didUpdate: { index = $0 }) + XCTAssertNil(index) + // Emulate a user changing the picker and hitting done button + element.pickerView(element.pickerView, didSelectRow: 3, inComponent: 0) + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertEqual(index, 3) + + // Update items with same list should keep original item selected + element.update(items: items) + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertEqual(index, 3) + + // Update items removing/replacing item at index 4, should select the first index + let items = ["A", "B", "C", "DD"].map { DropdownFieldElement.DropdownItem(pickerDisplayName: $0, labelDisplayName: $0, accessibilityValue: $0, rawData: $0) } + element.update(items: items) + element.didFinish(element.pickerFieldView, shouldAutoAdvance: true) + XCTAssertEqual(index, 0) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/IDNumberTextFieldConfigurationTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/IDNumberTextFieldConfigurationTest.swift new file mode 100644 index 00000000..a602451e --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/IDNumberTextFieldConfigurationTest.swift @@ -0,0 +1,108 @@ +// +// IDNumberTextFieldConfigurationTest.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 9/28/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +final class IDNumberTextFieldConfigurationTest: XCTestCase { + + func testValidationBR_CPF_CNPJ() { + let config = IDNumberTextFieldConfiguration(type: .BR_CPF_CNPJ, label: "", defaultValue: nil) + + // CPF is 11 digits + verifyValid(config.validate(text: "12345678901", isOptional: false)) + // CNPJ is 14 digits + verifyValid(config.validate(text: "12345678901234", isOptional: false)) + // Empty string is okay if optional + verifyValid(config.validate(text: "", isOptional: true)) + + // Anything else is invalid + + // Empty + verifyInvalidEmpty(config.validate(text: "", isOptional: false)) + // Too few digits + verifyInvalidIncomplete(config.validate(text: "1", isOptional: false)) + verifyInvalidIncomplete(config.validate(text: "1234567890", isOptional: false)) + // Between 11–14 digits + verifyInvalidIncomplete(config.validate(text: "123456789012", isOptional: false)) + // > 14 digits + verifyInvalidIncomplete(config.validate(text: "1234567890123456", isOptional: false)) + } + + func testValidationUnspecifiedType() { + let config = IDNumberTextFieldConfiguration(type: nil, label: "", defaultValue: nil) + // Anything but empty string is valid + verifyInvalidEmpty(config.validate(text: "", isOptional: false)) + verifyValid(config.validate(text: "a", isOptional: false)) + verifyValid(config.validate(text: "1", isOptional: false)) + verifyValid(config.validate(text: "/;'", isOptional: false)) + verifyValid(config.validate(text: "asdfghjklqwertyuiopzxcvbnm1234567890", isOptional: false)) + // Empty string is okay if optional + verifyValid(config.validate(text: "", isOptional: true)) + } + + func testDisplayTextBR_CPF_CNPJ() { + let config = IDNumberTextFieldConfiguration(type: .BR_CPF_CNPJ, label: "", defaultValue: nil) + + XCTAssertEqual(config.makeDisplayText(for: "").string, "") + + // Format as CPF if <= 11 characters + XCTAssertEqual(config.makeDisplayText(for: "123").string, "123") + XCTAssertEqual(config.makeDisplayText(for: "123456789").string, "123.456.789") + XCTAssertEqual(config.makeDisplayText(for: "12345678901").string, "123.456.789-01") + + // Format as CNPJ if > 11 characters + XCTAssertEqual(config.makeDisplayText(for: "123456789012").string, "12.345.678/9012") + XCTAssertEqual(config.makeDisplayText(for: "12345678901234").string, "12.345.678/9012-34") + XCTAssertEqual(config.makeDisplayText(for: "12345678901234567").string, "12.345.678/9012-34") + } +} + +private extension IDNumberTextFieldConfigurationTest { + func verifyValid(_ validationState: TextFieldElement.ValidationState, + file: StaticString = #filePath, + line: UInt = #line) { + XCTAssertEqual(validationState, .valid, file: file, line: line) + } + + func getTextFieldError(_ validationState: TextFieldElement.ValidationState, + file: StaticString = #filePath, + line: UInt = #line) -> TextFieldElement.Error? { + guard case let .invalid(error) = validationState else { + XCTFail("Expected `.invalid` but was `.valid`", file: file, line: line) + return nil + } + guard let textFieldError = error as? TextFieldElement.Error else { + XCTFail("Expected `TextFieldElement.Error` but was `\(type(of: error))`", file: file, line: line) + return nil + } + return textFieldError + } + + func verifyInvalidIncomplete(_ validationState: TextFieldElement.ValidationState, + file: StaticString = #filePath, + line: UInt = #line) { + guard let textFieldError = getTextFieldError(validationState, file: file, line: line) else { + return + } + guard case .incomplete = textFieldError else { + return XCTFail("Expected `.incomplete` but was `\(textFieldError)`", file: file, line: line) + } + } + + func verifyInvalidEmpty(_ validationState: TextFieldElement.ValidationState, + file: StaticString = #filePath, + line: UInt = #line) { + guard let textFieldError = getTextFieldError(validationState, file: file, line: line) else { + return + } + guard case .empty = textFieldError else { + return XCTFail("Expected `.empty` but was `\(textFieldError)`", file: file, line: line) + } + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/PhoneNumberElementTests.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/PhoneNumberElementTests.swift new file mode 100644 index 00000000..c9301e1c --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/PhoneNumberElementTests.swift @@ -0,0 +1,177 @@ +// +// PhoneNumberElementTests.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 6/23/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@testable @_spi(STP) import StripeUICore +import XCTest + +class PhoneNumberElementTests: XCTestCase { + + func test_init_with_defaults() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["PR"], + defaultCountryCode: "PR", + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "en_US") + ) + // + XCTAssertEqual(sut.textFieldElement.text, "3105551234") + XCTAssertEqual(sut.phoneNumber?.countryCode, "PR") + XCTAssertEqual(sut.phoneNumber?.number, "3105551234") + } + + func test_init_with_default_e164_phone_number() { + // Initializing a PhoneNumberElement.... + let sut = PhoneNumberElement( + allowedCountryCodes: ["US", "PR"], + defaultCountryCode: "PR", // ...with a default country code... + defaultPhoneNumber: "+13105551234", // ...and a phone number that also contains a country code... + locale: Locale(identifier: "en_US") + ) + // ...should favor the phone number's country code... + XCTAssertEqual(sut.countryDropdownElement.selectedItem.rawData, "US") + // ...and remove the country prefix from the number + XCTAssertEqual(sut.textFieldElement.text, "3105551234") + XCTAssertEqual(sut.phoneNumber?.countryCode, "US") + XCTAssertEqual(sut.phoneNumber?.number, "3105551234") + } + + func test_no_default_country_and_locale_in_allowed_countries() { + // A PhoneNumberElement initialized without a default country... + // ...where the user's locale is in `allowedCountryCodes`... + let sut = PhoneNumberElement( + allowedCountryCodes: ["PR"], + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "es_PR") + ) + // ...should default to the locale + XCTAssertEqual(sut.textFieldElement.text, "3105551234") + XCTAssertEqual(sut.phoneNumber?.countryCode, "PR") + XCTAssertEqual(sut.phoneNumber?.number, "3105551234") + } + + func test_no_default_country_and_locale_not_in_allowed_countries() { + // A PhoneNumberElement initialized without a default country... + // ...where the user's locale is **not** in `allowedCountryCodes`... + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "es_PR") + ) + // ...should default to the first country in the list + XCTAssertEqual(sut.textFieldElement.text, "3105551234") + XCTAssertEqual(sut.phoneNumber?.countryCode, "US") + XCTAssertEqual(sut.phoneNumber?.number, "3105551234") + } + + func test_autofill_removesMatchingCountryCode() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + locale: Locale(identifier: "en_US") + ) + simulateAutofill(sut, autofilledPhoneNumber: "+1 (310) 555-1234") + XCTAssertEqual(sut.textFieldElement.text, "3105551234") + XCTAssertEqual(sut.phoneNumber?.number, "3105551234") + } + + func test_autofill_preservesNonMatchingCountryCode() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + locale: Locale(identifier: "en_US") + ) + simulateAutofill(sut, autofilledPhoneNumber: "+44 12 3456 7890") + XCTAssertEqual(sut.textFieldElement.text, "441234567890") + XCTAssertEqual(sut.phoneNumber?.number, "441234567890") + } + + func test_hasBeenModified_noDefaults_noModification() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + locale: Locale(identifier: "en_US") + ) + XCTAssertFalse(sut.hasBeenModified) + } + + func test_hasBeenModified_defaultNumber() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "en_US") + ) + XCTAssertFalse(sut.hasBeenModified) + } + + func test_hasBeenModified_isModified() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + locale: Locale(identifier: "en_US") + ) + simulateAutofill(sut, autofilledPhoneNumber: "3") + XCTAssertTrue(sut.hasBeenModified) + } + + func test_hasBeenModified_defaultNumber_isModified() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "en_US") + ) + simulateAutofill(sut, autofilledPhoneNumber: "3") + XCTAssertTrue(sut.hasBeenModified) + } + + func test_hasBeenModified_isNotModified() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + locale: Locale(identifier: "en_US") + ) + simulateAutofill(sut, autofilledPhoneNumber: "3") + simulateAutofill(sut, autofilledPhoneNumber: "") + XCTAssertFalse(sut.hasBeenModified) + } + + func test_hasBeenModified_defaultNumber_isNotModified() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US"], + defaultPhoneNumber: "3105551234", + locale: Locale(identifier: "en_US") + ) + simulateAutofill(sut, autofilledPhoneNumber: "3") + simulateAutofill(sut, autofilledPhoneNumber: "3105551234") + XCTAssertFalse(sut.hasBeenModified) + } + + func test_selectCountry_dontUpdateDefault() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US", "CA"], + locale: Locale(identifier: "en_US") + ) + + sut.selectCountry(index: 0, shouldUpdateDefaultNumber: false) // select CA + XCTAssertEqual(sut.countryDropdownElement.selectedIndex, 0) + XCTAssert(sut.hasBeenModified) + } + + func test_selectCountry_updateDefault() { + let sut = PhoneNumberElement( + allowedCountryCodes: ["US", "CA"], + locale: Locale(identifier: "en_US") + ) + + sut.selectCountry(index: 0, shouldUpdateDefaultNumber: true) // select CA + XCTAssertEqual(sut.countryDropdownElement.selectedIndex, 0) + XCTAssertFalse(sut.hasBeenModified) + } + + private func simulateAutofill(_ sut: PhoneNumberElement, autofilledPhoneNumber: String) { + let textField = sut.textFieldElement.textFieldView.textField + _ = sut.textFieldElement.textFieldView.textField(textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: autofilledPhoneNumber) + textField.text = autofilledPhoneNumber + sut.textFieldElement.textFieldView.textDidChange() + + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/SectionElementTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/SectionElementTest.swift new file mode 100644 index 00000000..3ea4e8f7 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/SectionElementTest.swift @@ -0,0 +1,61 @@ +// +// SectionElementTest.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 6/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@testable @_spi(STP) import StripeUICore +import XCTest + +class SectionElementTest: XCTestCase { + struct DummyTextFieldElementConfiguration: TextFieldElementConfiguration { + let validationState: ValidationState + let label = "foo" + func validate(text: String, isOptional: Bool) -> ValidationState { + return validationState + } + } + + enum Error: TextFieldValidationError { + case undisplayableError + case displayableError + + var localizedDescription: String { + switch self { + case .undisplayableError: + return "undisplayable error" + case .displayableError: + return "displayable error" + } + } + + func shouldDisplay(isUserEditing: Bool) -> Bool { + switch self { + case .undisplayableError: + return false + case .displayableError: + return true + } + } + } + + func testValidationStateAndError() { + // Given an invalid element whose error shouldn't be displayed... + let element1 = TextFieldElement( + configuration: DummyTextFieldElementConfiguration(validationState: .invalid(Error.undisplayableError)) + ) + + // ...and an invalid element whose error *should* be displayed... + let element2 = TextFieldElement( + configuration: DummyTextFieldElementConfiguration(validationState: .invalid(Error.displayableError)) + ) + + // ...a section with these two elements.... + let section = SectionElement(title: "Foo", elements: [element1, element2]) + + // ...should display the first invalid element with a *displayable* error + XCTAssertEqual(section.errorText, "displayable error") + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/TestFieldElement+AccountFactoryTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/TestFieldElement+AccountFactoryTest.swift new file mode 100644 index 00000000..f3acd8af --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/TestFieldElement+AccountFactoryTest.swift @@ -0,0 +1,62 @@ +// +// TestFieldElement+AccountFactoryTest.swift +// StripeUICoreTests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +class TextFieldElementAccountFactoryTest: XCTestCase { + // MARK: - BSB + func testBSBConfiguration_validBSB() { + let bsb = TextFieldElement.Account.BSBConfiguration(defaultValue: nil) + + bsb.test(text: "000000", isOptional: false, matches: .valid) + bsb.test(text: "082902", isOptional: false, matches: .valid) + } + + func testBSBConfiguration_empty() { + let bsb = TextFieldElement.Account.BSBConfiguration(defaultValue: nil) + + bsb.test(text: "", isOptional: false, matches: .invalid(TextFieldElement.Error.empty)) + } + + func testBSBConfiguration_incomplete() { + let bsb = TextFieldElement.Account.BSBConfiguration(defaultValue: nil) + + bsb.test(text: "0", isOptional: false, matches: .invalid(TextFieldElement.Account.BSBConfiguration.incompleteError)) + bsb.test(text: "00", isOptional: false, matches: .invalid(TextFieldElement.Account.BSBConfiguration.incompleteError)) + bsb.test(text: "000", isOptional: false, matches: .invalid(TextFieldElement.Account.BSBConfiguration.incompleteError)) + bsb.test(text: "0000", isOptional: false, matches: .invalid(TextFieldElement.Account.BSBConfiguration.incompleteError)) + bsb.test(text: "00000", isOptional: false, matches: .invalid(TextFieldElement.Account.BSBConfiguration.incompleteError)) + } + + // MARK: - AU BECS Account Number + func testAUBECSAccountNumberConfiguration_validAccountNumber() { + let bsb = TextFieldElement.Account.AUBECSAccountNumberConfiguration(defaultValue: nil) + + bsb.test(text: "1234", isOptional: false, matches: .valid) + bsb.test(text: "12345", isOptional: false, matches: .valid) + bsb.test(text: "123456", isOptional: false, matches: .valid) + bsb.test(text: "1234567", isOptional: false, matches: .valid) + bsb.test(text: "12345678", isOptional: false, matches: .valid) + bsb.test(text: "000123456", isOptional: false, matches: .valid) + } + + func testAUBECSAccountNumberConfiguration_empty() { + let bsb = TextFieldElement.Account.AUBECSAccountNumberConfiguration(defaultValue: nil) + + bsb.test(text: "", isOptional: false, matches: .invalid(TextFieldElement.Error.empty)) + } + + func testAUBECSAccountNumberConfiguration_incomplete() { + let bsb = TextFieldElement.Account.AUBECSAccountNumberConfiguration(defaultValue: nil) + + bsb.test(text: "0", isOptional: false, matches: .invalid(TextFieldElement.Account.AUBECSAccountNumberConfiguration.incompleteError)) + bsb.test(text: "00", isOptional: false, matches: .invalid(TextFieldElement.Account.AUBECSAccountNumberConfiguration.incompleteError)) + bsb.test(text: "000", isOptional: false, matches: .invalid(TextFieldElement.Account.AUBECSAccountNumberConfiguration.incompleteError)) + } + +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldElement+AddressFactoryTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldElement+AddressFactoryTest.swift new file mode 100644 index 00000000..e3d20481 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldElement+AddressFactoryTest.swift @@ -0,0 +1,137 @@ +// +// TextFieldElement+AddressFactoryTest.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 6/14/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) @testable import StripeUICore +import XCTest + +typealias ValidationState = TextFieldElement.ValidationState + +class TextFieldElementAddressFactoryTest: XCTestCase { + // MARK: - Name + + func testNameConfigurationValidation() { + let name = TextFieldElement.NameConfiguration(type: .full, defaultValue: nil) + + // MARK: Required + let requiredTestcases: [String: ValidationState] = [ + "": .invalid(TextFieldElement.Error.empty), + "0": .valid, + "A": .valid, + "; foo": .valid, + ] + requiredTestcases.forEach { testcase, expected in + name.test(text: testcase, isOptional: false, matches: expected) + } + + // MARK: Optional + // Overwrite the required test cases with the ones whose expected value differs when the field is optional + let optionalTestcases: [String: ValidationState] = requiredTestcases.merging([ + "": .valid, + ]) { _, new in new } + for (testcase, expected) in optionalTestcases { + name.test(text: testcase, isOptional: true, matches: expected) + } + } + + // MARK: - Email + + func testEmailConfigurationValidation() { + let email = TextFieldElement.EmailConfiguration(defaultValue: nil) + + // MARK: Required + let requiredTestcases: [String: ValidationState] = [ + "": .invalid(TextFieldElement.Error.empty), + "f": .invalid(email.invalidError), + "f@": .invalid(email.invalidError), + "f@z": .invalid(email.invalidError), + "f@z.c": .valid, + ] + for (testcase, expected) in requiredTestcases { + email.test(text: testcase, isOptional: false, matches: expected) + } + + // MARK: Optional + // Overwrite the required test cases with the ones whose expected value differs when the field is optional + let optionalTestcases: [String: ValidationState] = requiredTestcases.merging([ + "": .valid, + ]) { _, new in new } + for (testcase, expected) in optionalTestcases { + email.test(text: testcase, isOptional: true, matches: expected) + } + } + + // MARK: - Postal Code + + func testPostalCodeConfigurationValidation() { + let US_config = TextFieldElement.Address.PostalCodeConfiguration(countryCode: "US", label: "ZIP", defaultValue: nil, isOptional: false) + XCTAssertEqual(US_config.keyboardProperties(for: "").type, .numberPad) + US_config.test(text: "9411", isOptional: false, matches: .invalid(TextFieldElement.Error.incomplete(localizedDescription: String.Localized.your_zip_is_incomplete))) + US_config.test(text: "94115", isOptional: false, matches: .valid) + + // PostalCodeConfiguration only special cases US, so we can test any other country for full code coverage + let UK_config = TextFieldElement.Address.PostalCodeConfiguration(countryCode: "UK", label: "Postal", defaultValue: nil, isOptional: false) + XCTAssertEqual(UK_config.keyboardProperties(for: "").type, .default) + UK_config.test(text: "SW1A 1AA", isOptional: false, matches: .valid) + } + + // MARK: - Phone Number + func testPhoneNumberConfigurationValidation() { + // US formatting + let usConfiguration = TextFieldElement.PhoneNumberConfiguration { + return "US" + } + + // valid numbers + for number in [ + "555-555-5555", + "5555555555", + "(555) 555-5555", + ] { + usConfiguration.test(text: number, isOptional: false, matches: .valid) + } + + // incomplete + for number in [ + "555-555-555", + "555-555-A555", // the formatter should remove the A here + ] { + usConfiguration.test(text: number, + isOptional: false, + matches: .invalid(TextFieldElement.PhoneNumberConfiguration.incompleteError)) + } + } +} + +// MARK: - Helpers + +// TODO(mludowise): These should get migrated to a shared StripeUICoreTestUtils target + +extension TextFieldElementConfiguration { + func test(text: String, isOptional: Bool, matches expected: ValidationState, file: StaticString = #filePath, line: UInt = #line) { + let actual = validate(text: text, isOptional: isOptional) + XCTAssertEqual(actual, expected, "\(text), \(isOptional): Expected \(expected) but got \(actual)", file: file, line: line) + } +} + +extension TextFieldElement.ValidationState: Equatable { + public static func == (lhs: TextFieldElement.ValidationState, rhs: TextFieldElement.ValidationState) -> Bool { + switch (lhs, rhs) { + case (.valid, .valid): + return true + case let (.invalid(lhsError), .invalid(rhsError)): + return lhsError == rhsError + default: + return false + } + } +} + +func == (lhs: TextFieldValidationError, rhs: TextFieldValidationError) -> Bool { + return (lhs as NSError).isEqual(rhs as NSError) +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldElementTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldElementTest.swift new file mode 100644 index 00000000..5d926cb0 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldElementTest.swift @@ -0,0 +1,78 @@ +// +// TextFieldElementTest.swift +// StripeUICoreTests +// +// Created by Yuki Tokuhiro on 8/23/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@testable @_spi(STP) import StripeUICore +import XCTest + +class TextFieldElementTest: XCTestCase { + struct Configuration: TextFieldElementConfiguration { + var defaultValue: String? + var label: String = "label" + func maxLength(for text: String) -> Int { "default value".count } + } + + func testNoDefaultValue() { + let element = TextFieldElement(configuration: Configuration(defaultValue: nil)) + XCTAssertTrue(element.textFieldView.text.isEmpty) + XCTAssertTrue(element.text.isEmpty) + } + + func testDefaultValue() { + let element = TextFieldElement(configuration: Configuration(defaultValue: "default value")) + XCTAssertEqual(element.textFieldView.text, "default value") + XCTAssertEqual(element.text, "default value") + } + + func testInvalidDefaultValueIsSanitized() { + let element = TextFieldElement(configuration: Configuration( + defaultValue: "\ndefault\n value that is too long and contains disallowed characters") + ) + XCTAssertEqual(element.textFieldView.text, "default value") + XCTAssertEqual(element.text, "default value") + } + + func testEmptyStringsFailDefaultConfigurationValidation() { + let sut = Configuration() + XCTAssertEqual(sut.validate(text: "", isOptional: false), .invalid(TextFieldElement.Error.empty)) + XCTAssertEqual(sut.validate(text: " ", isOptional: false), .invalid(TextFieldElement.Error.empty)) + XCTAssertEqual(sut.validate(text: " \n", isOptional: false), .invalid(TextFieldElement.Error.empty)) + + } + + func testMultipleCharacterChangeInEmptyFieldIsAutofill() { + let element = TextFieldElement(configuration: Configuration(defaultValue: nil)) + XCTAssertEqual(element.didReceiveAutofill, false) + _ = element.textFieldView.textField(element.textFieldView.textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "This is autofill") + element.textFieldView.textDidChange() + XCTAssertEqual(element.didReceiveAutofill, true) + } + + func testSingleCharacterChangeInEmptyFieldIsNotAutofill() { + let element = TextFieldElement(configuration: Configuration(defaultValue: nil)) + XCTAssertEqual(element.didReceiveAutofill, false) + _ = element.textFieldView.textField(element.textFieldView.textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "T") + element.textFieldView.textDidChange() + XCTAssertEqual(element.didReceiveAutofill, false) + } + + func testMultipleCharacterChangeInPopulatedFieldIsNotAutofill() { + let element = TextFieldElement(configuration: Configuration(defaultValue: "default value")) + XCTAssertEqual(element.didReceiveAutofill, false) + _ = element.textFieldView.textField(element.textFieldView.textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "This is autofill") + element.textFieldView.textDidChange() + XCTAssertEqual(element.didReceiveAutofill, false) + } + + func testSingleCharacterChangeInPopulatedFieldIsNotAutofill() { + let element = TextFieldElement(configuration: Configuration(defaultValue: "default value")) + XCTAssertEqual(element.didReceiveAutofill, false) + _ = element.textFieldView.textField(element.textFieldView.textField, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "T") + element.textFieldView.textDidChange() + XCTAssertEqual(element.didReceiveAutofill, false) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldFormatterTest.swift b/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldFormatterTest.swift new file mode 100644 index 00000000..b0a80914 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Elements/TextFieldFormatterTest.swift @@ -0,0 +1,70 @@ +// +// TextFieldFormatterTest.swift +// StripeUICoreTests +// +// Created by Mel Ludowise on 9/28/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) @testable import StripeUICore +import XCTest + +final class TextFieldFormatterTest: XCTestCase { + // TODO: Test that we don't get lagging characters (e.g. `###-##-` should always drop the last `-` when formatted + + func testInvalidFormat() { + // Formats are required to contain at least one `#` or `*` + XCTAssertNil(TextFieldFormatter(format: "")) + XCTAssertNil(TextFieldFormatter(format: "•••")) + } + + func testApplyFormat() { + // Don't format empty string + verifyFormat(format: "###-###-###", input: "", expectedOutput: "") + // Discard unwanted characters + verifyFormat(format: "###-###-###", input: "12ab3", expectedOutput: "123") + // Trim to size + verifyFormat(format: "###-###-###", input: "1234567890000", expectedOutput: "123-456-789") + // Partial format + verifyFormat(format: "###-###-###", input: "12345", expectedOutput: "123-45") + // Don't display lagging format characters + verifyFormat(format: "###-###-###", input: "123456", expectedOutput: "123-456") + // Already formatted + verifyFormat(format: "###-###-###", input: "123-456-789", expectedOutput: "123-456-789") + + // Letters + verifyFormat(format: "**####-###", input: "", expectedOutput: "") + verifyFormat(format: "**####-###", input: "12ab3", expectedOutput: "ab3") + verifyFormat(format: "**####-###", input: "ab123456789", expectedOutput: "ab1234-567") + verifyFormat(format: "**####-###", input: "ab12345", expectedOutput: "ab1234-5") + verifyFormat(format: "**####-###", input: "ab1234", expectedOutput: "ab1234") + + // Leading formatting + verifyFormat(format: "••• - •• - ####", input: "", expectedOutput: "") + verifyFormat(format: "••• - •• - ####", input: "abc123", expectedOutput: "••• - •• - 123") + verifyFormat(format: "••• - •• - ####", input: "123456", expectedOutput: "••• - •• - 1234") + } + + func testNoLaggingFormatCharacters() { + // Note: If a format has non `*` or `#` characters at the end, they will + // never be displayed. This is by design since it makes it impossible to + // use the backspace key because TextFieldView will keep reformatting + // and moving the cursor. We should never use a format like this, but + // if we inadvertantly do, we want to ensure we don't break the + // backspace key behavior. + verifyFormat(format: "### - ## - •••", input: "12345", expectedOutput: "123 - 45") + verifyFormat(format: "### - ## - •••", input: "123456789", expectedOutput: "123 - 45") + } +} + +// MARK: - Helpers + +private extension TextFieldFormatterTest { + func verifyFormat(format: String, + input: String, + expectedOutput: String, + file: StaticString = #filePath, + line: UInt = #line) { + XCTAssertEqual(TextFieldFormatter(format: format)?.applyFormat(to: input), expectedOutput, file: file, line: line) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Validators/BSBNumberTests.swift b/StripeUICore/StripeUICoreTests/Unit/Validators/BSBNumberTests.swift new file mode 100644 index 00000000..f48ca973 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Validators/BSBNumberTests.swift @@ -0,0 +1,81 @@ +// +// BSBNumberTests.swift +// StripeUICoreTests +// +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import XCTest + +@_spi(STP) @testable import StripeUICore + +class BSBNumberTests: XCTestCase { + func testBSBNumber_0() { + let bsbNumber = BSBNumber(number: "0") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "0") + XCTAssertEqual(bsbNumber.bsbNumberText(), "0") + } + func testBSBNumber_01() { + let bsbNumber = BSBNumber(number: "00") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "00") + XCTAssertEqual(bsbNumber.bsbNumberText(), "00") + } + + func testBSBNumber_012() { + let bsbNumber = BSBNumber(number: "012") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012") + XCTAssertEqual(bsbNumber.bsbNumberText(), "012") + } + + func testBSBNumber_0123() { + let bsbNumber = BSBNumber(number: "0123") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012-3") + XCTAssertEqual(bsbNumber.bsbNumberText(), "0123") + } + + func testBSBNumber_01234() { + let bsbNumber = BSBNumber(number: "01234") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012-34") + XCTAssertEqual(bsbNumber.bsbNumberText(), "01234") + } + + func testBSBNumber_012345() { + let bsbNumber = BSBNumber(number: "012345") + XCTAssert(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012-345") + XCTAssertEqual(bsbNumber.bsbNumberText(), "012345") + } + + func testBSBNumber_012_withdash() { + let bsbNumber = BSBNumber(number: "012-") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012") + XCTAssertEqual(bsbNumber.bsbNumberText(), "012") + } + + func testBSBNumber_0123_withdash() { + let bsbNumber = BSBNumber(number: "012-3") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012-3") + XCTAssertEqual(bsbNumber.bsbNumberText(), "0123") + } + + func testBSBNumber_01234_withdash() { + let bsbNumber = BSBNumber(number: "012-34") + XCTAssertFalse(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012-34") + XCTAssertEqual(bsbNumber.bsbNumberText(), "01234") + } + + func testBSBNumber_012345_withdash() { + let bsbNumber = BSBNumber(number: "012-345") + XCTAssert(bsbNumber.isComplete) + XCTAssertEqual(bsbNumber.formattedNumber(), "012-345") + XCTAssertEqual(bsbNumber.bsbNumberText(), "012345") + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Validators/PhoneNumberTests.swift b/StripeUICore/StripeUICoreTests/Unit/Validators/PhoneNumberTests.swift new file mode 100644 index 00000000..ac0b6b93 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Validators/PhoneNumberTests.swift @@ -0,0 +1,199 @@ +// +// PhoneNumberTests.swift +// StripeUICoreTests +// +// Created by Cameron Sabol on 10/11/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import XCTest + +@_spi(STP) @testable import StripeUICore + +class PhoneNumberTests: XCTestCase { + + func testFormats() { + let cases: [(number: String, country: String, format: PhoneNumber.Format, formattedNumber: String)] = [ + ( + number: "", + country: "US", + format: .national, + formattedNumber: "" + ), + ( + number: "4", + country: "US", + format: .national, + formattedNumber: "(4" + ), + ( + number: "+", + country: "US", + format: .national, + formattedNumber: "" // doesn't include + in national format + ), + ( + number: "+", + country: "US", + format: .international, + formattedNumber: "" // empty input shouldn't get formatted + ), + ( + number: "a", + country: "US", + format: .national, + formattedNumber: "" + ), + ( + number: "(", // PhoneNumberFormat only formats digits, +, and • + country: "US", + format: .national, + formattedNumber: "" + ), + ( + number: "+49", + country: "DE", + format: .international, + formattedNumber: "+49 49" // never treats input as country code + ), + ( + number: "160 1234567", + country: "DE", + format: .international, + formattedNumber: "+49 160 1234567" + ), + ( + number: "5551231234", + country: "US", + format: .international, + formattedNumber: "+1 (555) 123-1234" + ), + ( + number: "(555) 123-1234", + country: "US", + format: .international, + formattedNumber: "+1 (555) 123-1234" + ), + ( + number: "555", + country: "US", + format: .international, + formattedNumber: "+1 (555" + ), + ( + number: "(555) a", + country: "US", + format: .international, + formattedNumber: "+1 (555" + ), + ( + number: "(403) 123-1234", + country: "CA", + format: .international, + formattedNumber: "+1 (403) 123-1234" + ), + ( + number: "(403) 123-1234", + country: "CA", + format: .national, + formattedNumber: "(403) 123-1234" + ), + ( + number: "4031231234", + country: "CA", + format: .national, + formattedNumber: "(403) 123-1234" + ), + ( + number: "6711231234", + country: "GU", + format: .international, + formattedNumber: "+1 (671) 123-1234" + ), + ( + number: "6711231234", + country: "GU", + format: .national, + formattedNumber: "(671) 123-1234" + ), + ( + number: "1234567890", + country: "MX", + format: .international, + formattedNumber: "+52 123 456 7890" + ), + ] + + for c in cases { + guard let phoneNumber = PhoneNumber(number: c.number, countryCode: c.country) else { + XCTFail("Could not create phone number for \(c.country), \(c.number)") + continue + } + XCTAssertEqual(phoneNumber.string(as: c.format), c.formattedNumber) + } + } + + func teste164FormatDropsLeadingZeros() { + guard let phoneNumber = PhoneNumber(number: "08022223333", countryCode: "JP") else { + XCTFail("Could not create phone number") + return + } + XCTAssertEqual(phoneNumber.string(as: .e164), "+818022223333") + } + + func teste164MaxLength() { + guard let phoneNumber = PhoneNumber(number: "123456789123456789", countryCode: "US") else { + XCTFail("Could not create phone number") + return + } + XCTAssertEqual(phoneNumber.string(as: .e164), "+112345678912345") + } + + func testFromE164() { + let gbPhone = PhoneNumber.fromE164("+445555555555") + XCTAssertEqual(gbPhone?.countryCode, "GB") + XCTAssertEqual(gbPhone?.number, "5555555555") + + let brPhone = PhoneNumber.fromE164("+5591155256325") + XCTAssertEqual(brPhone?.countryCode, "BR") + XCTAssertEqual(brPhone?.number, "91155256325") + } + + func testFromE164_shouldHandleInvalidInput() { + XCTAssertNil(PhoneNumber.fromE164("")) + XCTAssertNil(PhoneNumber.fromE164("++")) + XCTAssertNil(PhoneNumber.fromE164("+13")) + XCTAssertNil(PhoneNumber.fromE164("1 (555) 555 5555")) + XCTAssertNil(PhoneNumber.fromE164("+1555555555555555")) // too long + } + + func testFromE164_shouldDisambiguateUsingLocale() { + // This test number is very ambiguous, it can belong to ~25 countries/territories due to + // the "+1" calling code/prefix being shared by many countries. + let number = "+15555555555" + + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "en_US"))?.countryCode, "US") + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "en_CA"))?.countryCode, "CA") + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "es_DO"))?.countryCode, "DO") + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "en_PR"))?.countryCode, "PR") + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "en_JM"))?.countryCode, "JM") + + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "ja_JP"))?.countryCode, "US") + XCTAssertEqual(PhoneNumber.fromE164(number, locale: .init(identifier: "ar_LB"))?.countryCode, "US") + } + + func testEquals() { + XCTAssertEqual( + PhoneNumber(number: "08022223333", countryCode: "JP"), + PhoneNumber(number: "08022223333", countryCode: "JP") + ) + XCTAssertNotEqual( + PhoneNumber(number: "08022223333", countryCode: "JP"), + PhoneNumber(number: "08022223333", countryCode: "US") + ) + XCTAssertEqual( + PhoneNumber(number: "+08022223333", countryCode: "US"), + PhoneNumber(number: "08022223333", countryCode: "US") + ) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Validators/STPBlikCodeValidatorTest.swift b/StripeUICore/StripeUICoreTests/Unit/Validators/STPBlikCodeValidatorTest.swift new file mode 100644 index 00000000..2656c69d --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Validators/STPBlikCodeValidatorTest.swift @@ -0,0 +1,37 @@ +// +// STPBlikCodeValidator.swift +// StripeUICoreTests +// +// Created by Fionn Barrett on 07/07/2023. +// + +import Foundation +@_spi(STP) import StripeUICore +import XCTest + +class STPBlikCodeValidatorTest: XCTestCase { + func testBlikCode_0() { + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode("0")) + } + func testBlikCode_valid() { + XCTAssertTrue(STPBlikCodeValidator.stringIsValidBlikCode("123456")) + } + + func testBlikCode_lessThanSixDigits() { + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode("1234")) + } + + func testBlikCode_moreThanSixDigits() { + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode("1234567")) + } + + func testBlikCode_nil() { + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode(nil)) + } + + func testBlikCode_nonNumeric() { + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode("12a456")) + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode("abcdef")) + XCTAssertFalse(STPBlikCodeValidator.stringIsValidBlikCode("stripe.com")) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Validators/STPEmailAddressValidatorTest.swift b/StripeUICore/StripeUICoreTests/Unit/Validators/STPEmailAddressValidatorTest.swift new file mode 100644 index 00000000..da01621d --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Validators/STPEmailAddressValidatorTest.swift @@ -0,0 +1,26 @@ +// +// STPEmailAddressValidatorTest.swift +// StripeUICoreTests +// +// Created by Jack Flintermann on 3/23/16. +// Copyright © 2016 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import XCTest + +class STPEmailAddressValidatorTest: XCTestCase { + func testValidEmails() { + let validEmails = ["test@test.com", "test+thing@test.com.nz", "a@b.c", "A@b.c"] + for email in validEmails { + XCTAssert(STPEmailAddressValidator.stringIsValidEmailAddress(email)) + } + } + + func testInvalidEmails() { + let invalidEmails = ["", "google.com", "asdf", "asdg@c"] + for email in invalidEmails { + XCTAssertFalse(STPEmailAddressValidator.stringIsValidEmailAddress(email)) + } + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift b/StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift new file mode 100644 index 00000000..43f0dd77 --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift @@ -0,0 +1,31 @@ +// +// STPVPANumberValidatorTest.swift +// StripeUICoreTests +// +// Created by Nick Porter on 9/15/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +@_spi(STP) import StripeUICore +import XCTest + +class STPVPANumberValidatorTest: XCTestCase { + func testValidVPAs() { + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("stripe@icici")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("stripe@okaxis")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("stripe.9897605011@paytm")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("payment.pending@stripeupi")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("test30c_123@numberofcharacters")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("test29c_12@numberofcharacters")) + } + + func testInvalidVPAs() { + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("test@stripe.com")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("stripe")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("stripe@gmail.com")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("this-vpa-id-is-too-long-30-chars")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("test31c_1234@numberofcharacters")) + } +} diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..366f3f54 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +24.1.2 \ No newline at end of file